Compare commits

..

276 Commits

Author SHA1 Message Date
kolaente 551ec184bf
chore: add <time> html attribute
continuous-integration/drone/pr Build is passing Details
2022-02-06 16:44:17 +01:00
kolaente c5e869c7ad
chore: re-add spaces instead of tabs 2022-02-06 16:42:34 +01:00
kolaente 448afd8df2
chore: use BaseButton 2022-02-06 16:42:25 +01:00
kolaente b93ad15c10
fix: reactivity for prop 2022-02-06 16:42:25 +01:00
kolaente 8ba693a446
fix: related tasks add button and task dates in read only view 2022-02-06 16:42:23 +01:00
konrad 2ea3499bf7 feat: add timezone setting (#1379)
continuous-integration/drone/push Build is running Details
Reviewed-on: #1379
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2022-02-06 15:23:02 +00:00
kolaente a812793ead
feat: add setting for time zone to user settings
continuous-integration/drone/pr Build is passing Details
2022-02-06 16:04:00 +01:00
kolaente 59da6686d0
feat: add authenticated http factory to create an axios instance with bearer header 2022-02-06 16:01:56 +01:00
renovate 7c954e9168 chore(deps): update dependency vue-tsc to v0.31.2 (#1488)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1488
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-06 14:09:34 +00:00
konrad 3d3ccf629a feat: add remember me style login (#1339)
continuous-integration/drone/push Build is passing Details
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: #1339
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2022-02-06 13:17:55 +00:00
kolaente ba20ac3b89
fix: don't try to load a langauge if there's none provided
continuous-integration/drone/push Build is passing Details
(cherry picked from commit 210a78be86)
2022-02-06 12:28:15 +01:00
Dominik Pschenitschni c1978e2f6c [skip ci] Updated translations via Crowdin 2022-02-06 00:12:30 +00:00
Dominik Pschenitschni 8058790f9a Merge pull request 'feat: improve login and register ux' (#1104) from feature/login-improvements into main
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1104
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2022-02-05 17:22:43 +00:00
Dominik Pschenitschni a8ac2fc2dd
Merge branch 'main' into feature/login-improvements
continuous-integration/drone/pr Build is passing Details
2022-02-05 18:04:33 +01:00
konrad a57676bf54 feat: implement modals with vue router 4 (#816)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #816
Reviewed-by: konrad <k@knt.li>
2022-02-05 16:49:04 +00:00
Dominik Pschenitschni 9a5b5c688d
Merge branch 'fix/app-ready' into feature/vue3-modals-with-router-4
continuous-integration/drone/pr Build is passing Details
2022-02-05 17:38:28 +01:00
Dominik Pschenitschni 6827390b77
feat: merge TaskDetailViewModal with modal
continuous-integration/drone/pr Build is passing Details
2022-02-05 17:29:15 +01:00
renovate bc2ac3e3d8 chore(deps): update dependency eslint-plugin-vue to v8.4.1 (#1486)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1486
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-04 14:36:42 +00:00
renovate 42b2cded82 chore(deps): update dependency caniuse-lite to v1.0.30001307 (#1484)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1484
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-04 07:16:07 +00:00
renovate 2a431507dc chore(deps): update dependency vitest to v0.2.7 (#1485)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1485
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-04 07:15:49 +00:00
renovate fdcb6b31cb chore(deps): update dependency postcss-preset-env to v7.3.1 (#1483)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1483
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-03 10:20:37 +00:00
renovate a68ebff8df chore(deps): update dependency caniuse-lite to v1.0.30001306 (#1482)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1482
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-03 08:43:04 +00:00
renovate 7f7126b8be chore(deps): update dependency vitest to v0.2.6 (#1481)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1481
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-03 06:46:49 +00:00
renovate 39094ec968 chore(deps): update dependency esbuild to v0.14.18 (#1480)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1480
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-02 20:11:05 +00:00
renovate 64425b5c6e fix(deps): update sentry-javascript monorepo to v6.17.4 (#1479)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1479
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-02 18:34:45 +00:00
renovate 6629cdd009 chore(deps): update dependency rollup to v2.67.0 (#1478)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1478
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-02 07:41:15 +00:00
renovate 6a833bd8c4 chore(deps): update dependency esbuild to v0.14.17 (#1477)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1477
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-02 07:40:45 +00:00
renovate fed852baec chore(deps): update dependency caniuse-lite to v1.0.30001305 (#1476)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1476
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-02 06:58:15 +00:00
renovate 25011e57d6 chore(deps): update dependency sass to v1.49.7 (#1475)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1475
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-02 06:57:55 +00:00
renovate 766a6e1fce chore(deps): update dependency sass to v1.49.6 (#1474)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1474
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-01 22:19:02 +00:00
Dominik Pschenitschni 24a154422d
chore: remove vikunjaReady from store
continuous-integration/drone/pr Build is passing Details
2022-02-01 23:09:41 +01:00
renovate bf239849ba chore(deps): update dependency sass to v1.49.5
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-02-01 21:02:40 +00:00
kolaente dfa30258aa
chore: rename function
continuous-integration/drone/pr Build is passing Details
2022-02-01 21:25:42 +01:00
renovate 1269cfeb5b chore(deps): update dependency postcss to v8.4.6
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-02-01 12:02:39 +00:00
renovate 95a560584f chore(deps): update dependency sass to v1.49.4 (#1470)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1470
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-01 06:58:03 +00:00
renovate 782e45eb8f chore(deps): update dependency esbuild to v0.14.16 (#1469)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1469
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-01 06:56:12 +00:00
renovate 1e19cf7f8b fix(deps): update dependency @github/hotkey to v2 (#1471)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1471
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-02-01 06:55:24 +00:00
renovate 7150e20d45 chore(deps): update dependency cypress to v9.4.1
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-01-31 22:02:49 +00:00
renovate 0dedbfa901 chore(deps): update typescript-eslint monorepo to v5.10.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-01-31 19:02:56 +00:00
renovate 749eb2e6a7 fix(deps): update dependency ufo to v0.7.10 (#1466)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1466
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-31 13:05:03 +00:00
renovate aea910e1e4 chore(deps): update dependency happy-dom to v2.31.1 (#1465)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1465
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-31 10:35:08 +00:00
renovate 110f48ebd7 chore(deps): update dependency postcss-preset-env to v7.3.0 (#1464)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1464
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-31 09:20:00 +00:00
renovate 8242527d0f chore(deps): update dependency netlify-cli to v8.15.0 (#1463)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1463
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-31 06:52:45 +00:00
Dominik Pschenitschni de626eab31
feat: don't open task detail in modal for list and table view
continuous-integration/drone/pr Build is passing Details
2022-01-31 01:31:41 +01:00
kolaente 3d420c3770
fix: make isButton prop optional
continuous-integration/drone/pr Build is passing Details
2022-01-30 23:27:22 +01:00
kolaente 3e311e07cd
fix: undefined prop subscription
continuous-integration/drone/pr Build is passing Details
2022-01-30 23:18:13 +01:00
kolaente dd518ce643
Merge branch 'main' into feature/vue3-modals-with-router-4 2022-01-30 23:13:17 +01:00
kolaente 20a9ad2c9e
fix: related task within the same namespace
continuous-integration/drone/push Build is passing Details
2022-01-30 23:12:42 +01:00
kolaente 00ffe17eb8
fix: related task with the same namespace 2022-01-30 23:07:31 +01:00
kolaente a5b0a834bc
fix(tests): make sure the namespace exists before trying to run the history tests
continuous-integration/drone/pr Build is passing Details
If there's no namespace, there is no lists in state to show in the view. The CI runs all tests from a blank state which isn't the case when running the tests locally. Therefore, if the test doesn't create a new namespace, there won't be any to test for.
2022-01-30 22:51:29 +01:00
kolaente b7c8138ad5
fix(tests): make sure to create all lists before doing anything
continuous-integration/drone/pr Build was killed Details
2022-01-30 22:47:59 +01:00
kolaente 92864fa5c1
chore(tests): remove test result upload to s3 since we now have cypress dashboard
continuous-integration/drone/push Build is passing Details
2022-01-30 22:35:17 +01:00
kolaente 4fc9384acc
Merge branch 'main' into feature/vue3-modals-with-router-4
continuous-integration/drone/pr Build is failing Details
2022-01-30 22:33:44 +01:00
konrad c21f236249 feat: add cypress dashboard record (#1462)
continuous-integration/drone/push Build is passing Details
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: #1462
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2022-01-30 21:33:27 +00:00
kolaente 9995abf64c
fix: mark broken test as skipped
continuous-integration/drone/pr Build is failing Details
2022-01-30 22:11:26 +01:00
kolaente 56fca21320
trigger ci
continuous-integration/drone/pr Build is failing Details
2022-01-30 21:54:02 +01:00
kolaente a16e8fea61
Merge branch 'fix/app-ready' into feature/vue3-modals-with-router-4
continuous-integration/drone/pr Build was killed Details
2022-01-30 21:53:24 +01:00
kolaente 2a819eccb4
revert: model properties
continuous-integration/drone/pr Build is failing Details
partially reverts de3c47dc69
2022-01-30 20:18:34 +01:00
kolaente 8f04b10e08
Revert "fix: improve ListModel typing"
This reverts commit 98b41a22c6.
2022-01-30 20:15:53 +01:00
kolaente ec65a37dcb
Merge branch 'main' into feature/vue3-modals-with-router-4 2022-01-30 19:59:11 +01:00
renovate ea710e227a chore(deps): update dependency happy-dom to v2.31.0 (#1461)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1461
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-30 18:13:34 +00:00
Dominik Pschenitschni 0bd235cea3
fix: expose configureCompat types
continuous-integration/drone/pr Build is failing Details
see: https://github.com/vuejs/docs/pull/1475
2022-01-30 17:24:38 +01:00
konrad 81cf8f2f29 feat(tests): replace cypress-file-upload with .selectFile() (#1460)
continuous-integration/drone/push Build is passing Details
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: #1460
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2022-01-30 16:17:03 +00:00
Dominik Pschenitschni a325e4b721
feat: use es2022 for @typescript-eslint/parser
continuous-integration/drone/pr Build was killed Details
2022-01-30 16:59:47 +01:00
Dominik Pschenitschni 6ff621ada1
chore: ignore wrong second argument argument for cause
see: https://github.com/tc39/proposal-error-cause
2022-01-30 16:58:59 +01:00
Dominik Pschenitschni 8937b42321
feat: improve Sort component
continuous-integration/drone/pr Build is failing Details
2022-01-30 16:53:46 +01:00
Dominik Pschenitschni 6894024ad4
fix: use to.hash for returned element 2022-01-30 16:53:46 +01:00
Dominik Pschenitschni de3c47dc69
fix ts errors in various files 2022-01-30 16:53:46 +01:00
Dominik Pschenitschni 98b41a22c6
fix: improve ListModel typing 2022-01-30 16:53:45 +01:00
Dominik Pschenitschni a9fb24aa35
fix: currentList typing 2022-01-30 16:53:45 +01:00
Dominik Pschenitschni 57965b1ea3
fix: keyboard-shortcuts typing 2022-01-30 16:53:45 +01:00
Dominik Pschenitschni 187e62a7ec
feat: make subscription a BaseButton 2022-01-30 16:53:45 +01:00
Dominik Pschenitschni 24b7821c50
fix: ts errors in subscription 2022-01-30 16:53:44 +01:00
kolaente 3f893fb16d
fix(tests): don't visit / directly but use navigation instead
continuous-integration/drone/pr Build is failing Details
2022-01-30 15:25:34 +01:00
kolaente 05350affad
fix(tests): don't assert for h3 anymore
continuous-integration/drone/pr Build is failing Details
2022-01-30 14:37:41 +01:00
kolaente 6ab7aac5ce
fix(tests): wait until lists are loaded
continuous-integration/drone/pr Build is failing Details
2022-01-30 14:10:46 +01:00
kolaente 6c1857b133
fix(tests): assert absence of last viewed headline more precisely
continuous-integration/drone/pr Build is failing Details
2022-01-30 14:03:50 +01:00
kolaente 3212bc8e86
fix(tests): add more waits for namespaces loaded
continuous-integration/drone/pr Build was killed Details
2022-01-30 14:00:14 +01:00
kolaente e6eb48b5af
fix(tests): wait until namespaces are loaded before checking if the history is present
continuous-integration/drone/pr Build is failing Details
2022-01-30 13:07:08 +01:00
kolaente 423195155e
Merge branch 'main' into feature/vue3-modals-with-router-4
continuous-integration/drone/pr Build is failing Details
2022-01-30 12:40:36 +01:00
kolaente d913fa1745
fix: edge cases for dates where the next month had fewer days than the current one
continuous-integration/drone/push Build is passing Details
2022-01-30 12:38:17 +01:00
kolaente 931941359b
fix: don't try to parse date numbers with letters around them 2022-01-30 12:37:14 +01:00
kolaente b3697cb9bf
fix: subscription icon not rendered correctly
continuous-integration/drone/push Build is passing Details
The computed property "icon" which holds the icon string to be rendered was overriding the <icon> component. Therefore, the component wasn't rendered at all, instead vue would render a html tag with the icon name.
2022-01-29 22:30:47 +01:00
konrad c98ab42e75 feat: move lists between namespaces (#1430)
continuous-integration/drone/push Build is passing Details
Frontend implementation of vikunja/api#1096

I've implemented this re-using the drag and drop part of rearranging the list position in the menu. The only problem we have is there's no way to check if a namespace is read-only and disable dropping a list into it. I hope to solve this in the future on the api but for now you can drop a list into it and get an error message. I felt like properly handling the error and restoring the list and its position given this will change anyway.

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: #1430
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2022-01-29 21:12:46 +00:00
renovate f5a92d1aec chore(deps): update dependency caniuse-lite to v1.0.30001304 (#1459)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1459
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-29 20:54:49 +00:00
renovate fb17adcfe3 chore(deps): update dependency eslint to v8.8.0 (#1458)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1458
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-28 22:45:24 +00:00
renovate 198c97af57 fix(deps): update sentry-javascript monorepo to v6.17.3 (#1457)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1457
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-28 20:05:01 +00:00
renovate 3ff4f3516f chore(deps): update dependency vitest to v0.2.5 (#1456)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1456
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-28 14:02:34 +00:00
renovate ab5d768895 fix(deps): update dependency vue-i18n to v9.2.0-beta.30 (#1454)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1454
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-27 20:04:32 +00:00
renovate 0d2ff42673 chore(deps): update dependency caniuse-lite to v1.0.30001303 (#1453)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1453
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-27 09:29:26 +00:00
renovate aeb548ee09 fix(deps): update dependency marked to v4.0.12 (#1452)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1452
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-27 09:29:08 +00:00
renovate f168e36f68 chore(deps): update dependency eslint-plugin-vue to v8.4.0 (#1451)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1451
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-27 09:28:49 +00:00
renovate 691644a3a3 chore(deps): update dependency vitest to v0.2.4 (#1450)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1450
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-27 09:28:11 +00:00
renovate 0075ca0c99 fix(deps): update dependency marked to v4.0.11 (#1449)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1449
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-26 22:14:59 +00:00
renovate 83676f51b5 fix(deps): update dependency dompurify to v2.3.5 (#1448)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1448
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-26 19:26:16 +00:00
kolaente fdd2e7e538
fix: vuex store mutation violation when archiving a namespace
continuous-integration/drone/push Build is passing Details
2022-01-26 14:36:32 +01:00
renovate 9e4ea9e597 chore(deps): update dependency caniuse-lite to v1.0.30001302 (#1447)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1447
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-26 08:27:25 +00:00
renovate e0141f4a38 chore(deps): update dependency esbuild to v0.14.14 (#1446)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1446
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-26 07:12:23 +00:00
renovate d3e95adc32 chore(deps): update dependency happy-dom to v2.30.1 (#1445)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1445
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-25 22:11:13 +00:00
renovate 07ac1245a9 fix(deps): update sentry-javascript monorepo to v6.17.2 (#1444)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1444
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-25 21:18:23 +00:00
renovate 075a7875bf fix(deps): update dependency @vueuse/core to v7.5.5 (#1442)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1442
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-25 17:28:08 +00:00
renovate 43a537ef5b fix(deps): update dependency @vueuse/router to v7.5.5 (#1443)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1443
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-25 16:25:34 +00:00
renovate 52a613bf7f chore(deps): update dependency @faker-js/faker to v6.0.0-alpha.5 (#1436)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1436
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-25 16:00:36 +00:00
renovate 8e806f9308 chore(deps): update dependency vitest to v0.2.3 (#1441)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1441
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-25 13:29:11 +00:00
renovate df5300fa02 chore(deps): update dependency vitest to v0.2.2 (#1440)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1440
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-25 10:40:43 +00:00
renovate f469cac4d1 chore(deps): update dependency rollup to v2.66.1 (#1439)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1439
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-25 09:21:08 +00:00
renovate 7ad200179b fix(deps): update dependency vue-i18n to v9.2.0-beta.29 (#1438)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1438
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-25 08:08:40 +00:00
renovate f9eceeb6b6 chore(deps): update dependency happy-dom to v2.30.0 (#1437)
continuous-integration/drone/push Build was killed Details
Reviewed-on: #1437
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-25 08:07:43 +00:00
renovate 3a82fd4ad9 fix(deps): update sentry-javascript monorepo to v6.17.1 (#1434)
continuous-integration/drone/push Build was killed Details
Reviewed-on: #1434
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-24 19:47:51 +00:00
renovate 733e3c20c6 chore(deps): update typescript-eslint monorepo to v5.10.1 (#1435)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1435
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-24 19:47:34 +00:00
renovate 842249eb2c chore(deps): update dependency vitest to v0.2.1 (#1433)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1433
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-24 12:28:34 +00:00
renovate 7458130ee3 fix(deps): update sentry-javascript monorepo to v6.17.0 (#1432)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1432
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-24 08:19:48 +00:00
renovate 19fd2d603b chore(deps): update dependency netlify-cli to v8.13.0 (#1431)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1431
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-24 07:26:48 +00:00
konrad cd92d224a2 Merge branch 'main' into feature/login-improvements
continuous-integration/drone/pr Build is passing Details
2022-01-23 17:06:49 +00:00
renovate fc1f3966e6 fix(deps): update dependency vue to v3.2.29 (#1429)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1429
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-23 15:57:32 +00:00
renovate 5886577108 fix(deps): update dependency @vue/compat to v3.2.29 (#1428)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1428
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-23 15:57:20 +00:00
renovate e87311c746 fix(deps): update dependency vue-advanced-cropper to v2.8.0 (#1425)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1425
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-23 13:00:10 +00:00
renovate 7c1c6b7dfc chore(deps): update dependency vitest to v0.2.0 (#1427)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1427
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-23 13:00:01 +00:00
renovate 55b77725df chore(deps): update dependency rollup to v2.66.0 (#1424)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1424
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-23 11:34:17 +00:00
renovate 55b710dbfa chore(deps): update dependency esbuild to v0.14.13 (#1426)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1426
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-23 11:34:08 +00:00
renovate af6c48fd2e chore(deps): update dependency vue-tsc to v0.31.1 (#1423)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1423
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-23 11:33:50 +00:00
renovate 692722d826 chore(deps): update dependency vitest to v0.1.27 (#1422)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1422
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-22 06:42:01 +00:00
renovate 3f7fd3b9c0 chore(deps): update dependency @vitejs/plugin-vue to v2.1.0 (#1421)
continuous-integration/drone/push Build is running Details
Reviewed-on: #1421
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-22 06:41:09 +00:00
renovate 2f18d742e0 fix(deps): update dependency @vueuse/core to v7.5.4 (#1419)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1419
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-21 13:00:05 +00:00
renovate aa182d1517 fix(deps): update dependency @vueuse/router to v7.5.4 (#1420)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1420
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-21 11:33:51 +00:00
renovate 3ad133c5d6 chore(deps): update dependency vitest to v0.1.26 (#1418)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1418
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-21 10:22:40 +00:00
renovate 3d1f2d8a77 fix(deps): update dependency vue to v3.2.28 (#1417)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1417
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-21 10:08:00 +00:00
renovate 3e415cd7c9 fix(deps): update dependency @vue/compat to v3.2.28 (#1416)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1416
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-21 10:07:29 +00:00
renovate 0215b4bcf9 chore(deps): update dependency rollup to v2.65.0 (#1415)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1415
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-21 08:35:27 +00:00
renovate 9236a7cec8 chore(deps): update dependency vitest to v0.1.25 (#1411)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1411
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-21 08:35:05 +00:00
renovate 8a2694a2f0 chore(deps): update dependency caniuse-lite to v1.0.30001301 (#1414)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1414
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-21 08:34:41 +00:00
renovate 240ab2a72f chore(deps): update dependency happy-dom to v2.28.0 (#1412)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1412
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-21 07:12:22 +00:00
renovate 9f5754ee42 chore(deps): update dependency esbuild to v0.14.12 (#1413)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1413
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-21 07:11:56 +00:00
renovate f10152718c chore(deps): update dependency typescript to v4.5.5 (#1410)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1410
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-20 19:35:54 +00:00
renovate 365a017efe fix(deps): update dependency codemirror to v5.65.1 (#1409)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1409
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-20 13:33:38 +00:00
Dominik Pschenitschni 6db0559b81 fix: replace faker with community fork faker-js/faker (#1408)
continuous-integration/drone/push Build is passing Details
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: #1408
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2022-01-20 07:54:39 +00:00
Dominik Pschenitschni 5867f79735
fix: use AsyncEditor again in comments and description
continuous-integration/drone/pr Build is failing Details
2022-01-19 23:26:34 +01:00
Dominik Pschenitschni 959b53b3a6
chore: remove console.log
continuous-integration/drone/pr Build is failing Details
2022-01-19 23:16:44 +01:00
Dominik Pschenitschni c896ad5883
fix: subscription prop validation linting
continuous-integration/drone/pr Build is failing Details
2022-01-19 23:07:02 +01:00
renovate cae351d9bd chore(deps): update dependency vitest to v0.1.24
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-01-19 20:02:33 +00:00
renovate faa34ee692 chore(deps): update dependency happy-dom to v2.27.2 (#1406)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1406
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-19 19:27:00 +00:00
renovate 136ded9f73 chore(deps): update dependency sass to v1.49.0 (#1403)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1403
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-19 08:53:05 +00:00
renovate 5661b352c0 chore(deps): update dependency vitest to v0.1.23 (#1405)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1405
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-19 08:52:45 +00:00
renovate 02c551fdbc chore(deps): update dependency vue-tsc to v0.30.6 (#1404)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1404
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-19 07:00:25 +00:00
renovate 2d7cafd329 chore(deps): update dependency cypress to v9.3.1 (#1402)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1402
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-19 06:59:47 +00:00
renovate 7107044cc6 chore(deps): update dependency vite to v2.7.13 (#1401)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1401
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-19 06:58:13 +00:00
Dominik Pschenitschni 375c3adfb1 fix: show namespace count for long titles (#1057)
continuous-integration/drone/push Build is failing Details
When the namespace title is to long the count was only shown in the toolip. With this pull request the count is separated from the title and always shown.

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-authored-by: kolaente <k@knt.li>
Co-authored-by: konrad <k@knt.li>
Reviewed-on: #1057
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2022-01-19 06:57:45 +00:00
drone 637f2e9742 [skip ci] Updated translations via Crowdin 2022-01-18 22:22:48 +00:00
kolaente 0548649257
fix: lint
continuous-integration/drone/push Build is passing Details
2022-01-18 23:12:28 +01:00
renovate 4a290214c0 chore(deps): update dependency vitest to v0.1.21 (#1400)
continuous-integration/drone/push Build was killed Details
Reviewed-on: #1400
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-18 21:54:50 +00:00
kolaente 8555ffcd17
Merge branch 'main' into feature/vue3-modals-with-router-4
continuous-integration/drone/pr Build is failing Details
2022-01-18 22:27:32 +01:00
kolaente ca938b8615
fix: subscription prop validation
continuous-integration/drone/push Build is failing Details
2022-01-18 22:26:27 +01:00
kolaente 6a6203f553
fix: label edit spacing
continuous-integration/drone/push Build is passing Details
2022-01-18 22:22:32 +01:00
kolaente ff9e1b3fca
fix: vuex store manipulation warning when modifying task labels
continuous-integration/drone/push Build is passing Details
2022-01-18 22:12:08 +01:00
kolaente 1818ed3648
fix: scrolling to heading if it wasn't available 2022-01-18 22:00:13 +01:00
kolaente 8233c8c953
fix: check if a shortcut has an available function before trying to invoke it
continuous-integration/drone/pr Build is failing Details
2022-01-18 21:51:24 +01:00
kolaente e2d9aa3d7f
Merge branch 'main' into feature/vue3-modals-with-router-4
continuous-integration/drone/pr Build is failing Details
# Conflicts:
#	src/router/index.ts
#	src/views/tasks/TaskDetailView.vue
2022-01-18 21:47:17 +01:00
kolaente 50c3bcd793 fix: don't try to format invalid dates as ISO
continuous-integration/drone/push Build is passing Details
2022-01-18 20:43:52 +00:00
kolaente 2e77df2db2 fix(a11y): remove wrong aria-label 2022-01-18 20:43:52 +00:00
kolaente 56d321e513 feat(a11y): make sure the contrast for the primary color works with dark and light themes 2022-01-18 20:43:52 +00:00
kolaente cb776872aa feat(a11y): honor prefer-reduced-motion 2022-01-18 20:43:52 +00:00
kolaente c255e3f025 feat(a11y): add aria-expanded 2022-01-18 20:43:52 +00:00
kolaente b96af50338 feat(a11y): use better markup tags everywhere 2022-01-18 20:43:52 +00:00
kolaente 0e47cebed5 feat(a11y): use <time> tag for dates everywhere 2022-01-18 20:43:52 +00:00
kolaente 443a9c14b9
fix: attachment meta data not aligned properly
continuous-integration/drone/push Build is passing Details
2022-01-18 21:42:43 +01:00
kolaente cc3fcdf1c3
fix: keyboard shortcut message bottom margin
continuous-integration/drone/push Build is passing Details
2022-01-18 21:33:10 +01:00
kolaente b73165fce4
fix: update available text color in dark mode
continuous-integration/drone/push Build is passing Details
2022-01-18 21:31:40 +01:00
drone 729432bceb [skip ci] Updated translations via Crowdin 2022-01-18 18:18:15 +00:00
drone eddccc709d [skip ci] Updated translations via Crowdin 2022-01-18 14:18:13 +00:00
renovate 559ff5f7f7 chore(deps): update typescript-eslint monorepo to v5.10.0 (#1396)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1396
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-18 09:04:07 +00:00
renovate b5661de152 chore(deps): update dependency happy-dom to v2.27.0 (#1397)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1397
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-18 09:03:20 +00:00
renovate 3a1ce7458b chore(deps): update dependency vitest to v0.1.20 (#1398)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1398
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-18 09:02:59 +00:00
renovate 2f938fe695 chore(deps): update dependency axios to v0.25.0 (#1399)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1399
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-18 09:02:28 +00:00
renovate f2d7ee2be0 chore(deps): update dependency vitest to v0.1.19
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-01-17 16:05:17 +00:00
renovate d673150d8a chore(deps): update dependency vitest to v0.1.18 (#1393)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1393
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-17 10:50:27 +00:00
renovate 2ca5b3c817 chore(deps): update dependency caniuse-lite to v1.0.30001300 (#1391)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1391
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-17 08:42:06 +00:00
renovate 017f82dad7 chore(deps): update dependency vue-tsc to v0.30.5 (#1392)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1392
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-17 08:41:49 +00:00
kolaente 26a94c7e8c
fix: some typechecks
continuous-integration/drone/push Build is passing Details
Most of what's still left now is related to models not exporting visible properties for typescript, that's a problem for another day.
2022-01-16 22:24:51 +01:00
renovate 654f5f8f57 chore(deps): update dependency vue-tsc to v0.30.4 (#1389)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1389
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-16 16:16:12 +00:00
renovate e3ed0b2f1f fix(deps): update vue monorepo to v3.2.27 (#1387)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1387
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-16 15:24:20 +00:00
renovate 2e99a0ed29 chore(deps): update dependency vue-tsc to v0.30.3 (#1386)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1386
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-16 14:16:17 +00:00
renovate d450096303 chore(deps): update dependency vitest to v0.1.17 (#1385)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1385
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-16 10:49:54 +00:00
renovate 315a50e5b2 chore(deps): update dependency eslint to v8.7.0 (#1384)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1384
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-16 10:49:36 +00:00
renovate 5e4a9cca0c fix(deps): update dependency easymde to v2.16.1
continuous-integration/drone/push Build is passing Details
2022-01-15 22:02:00 +00:00
renovate 864669fd3f chore(deps): update dependency vitest to v0.1.16 (#1382)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1382
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-15 21:09:55 +00:00
renovate 010c4a2a3d chore(deps): update dependency rollup-plugin-visualizer to v5.5.4 (#1381)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1381
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-15 21:09:48 +00:00
kolaente 9446550ce9
chore: update netlify-cli only weekly
continuous-integration/drone/push Build is passing Details
2022-01-14 22:20:32 +01:00
kolaente c7ac81a99f
fix: save user language when it wasn't saved previously
continuous-integration/drone/push Build encountered an error Details
2022-01-14 22:11:13 +01:00
renovate c8558e6b25 chore(deps): update dependency rollup to v2.64.0 (#1377)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1377
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-14 15:09:06 +00:00
renovate 97ef78f1b0 chore(deps): update dependency netlify-cli to v8.8.2 (#1376)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1376
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-14 15:08:51 +00:00
renovate 8a97483dc2 chore(deps): update dependency vitest to v0.1.13 (#1375)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1375
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-14 10:08:50 +00:00
renovate 47b9878846 chore(deps): update dependency netlify-cli to v8.8.1 (#1374)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1374
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-13 21:16:19 +00:00
renovate e8086aac39 chore(deps): update dependency vite to v2.7.12 (#1373)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1373
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-13 20:23:21 +00:00
renovate b0ac968fbf chore(deps): update dependency netlify-cli to v8.8.0 (#1372)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1372
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-13 15:15:48 +00:00
renovate ea8a5df0b5 chore(deps): update dependency vite to v2.7.11
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-01-13 12:02:46 +00:00
renovate a03b7be194 fix(deps): update dependency marked to v4.0.10
continuous-integration/drone/push Build is passing Details
2022-01-13 11:12:34 +00:00
renovate 3128e76e7a chore(deps): update dependency happy-dom to v2.25.2
continuous-integration/drone/push Build is passing Details
2022-01-13 11:12:11 +00:00
renovate b17b7ec296 chore(deps): update dependency sass to v1.48.0
continuous-integration/drone/push Build is passing Details
2022-01-13 11:11:43 +00:00
renovate ecd61cca63 chore(deps): update dependency vitest to v0.1.12
continuous-integration/drone/push Build is failing Details
2022-01-13 11:11:19 +00:00
renovate 411847cd79 fix(deps): update dependency vue-i18n to v9.2.0-beta.28
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-01-13 10:03:07 +00:00
renovate 1fe5e4bd8e chore(deps): update dependency vitest to v0.0.142 (#1365)
continuous-integration/drone/push Build encountered an error Details
Reviewed-on: #1365
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-12 21:29:09 +00:00
drone b6788d9367 [skip ci] Updated translations via Crowdin 2022-01-12 21:10:32 +00:00
kolaente 796a56d5d8
fix: translation typo
continuous-integration/drone/push Build encountered an error Details
2022-01-12 19:53:02 +01:00
renovate 9d1ebd675d chore(deps): update dependency netlify-cli to v8.6.23 (#1363)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1363
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-12 18:15:26 +00:00
renovate 33cefd2c3e chore(deps): update dependency vite-plugin-pwa to v0.11.13 (#1364)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1364
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-12 18:13:32 +00:00
renovate 1484c282eb chore(deps): update dependency postcss-preset-env to v7.2.3 (#1361)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1361
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-12 16:39:59 +00:00
renovate f2c0460fdb fix(deps): update dependency v-tooltip to v4.0.0-beta.17 (#1362)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1362
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-12 16:39:43 +00:00
renovate 6d6843fe96 chore(deps): update dependency netlify-cli to v8.6.22 (#1359)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1359
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-12 14:27:00 +00:00
renovate e278bb4840 chore(deps): update dependency eslint-plugin-vue to v8.3.0 (#1360)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1360
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-12 14:01:29 +00:00
renovate 14f999ba55 chore(deps): update dependency postcss-preset-env to v7.2.2 (#1358)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1358
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-12 14:00:41 +00:00
renovate c85d1c241e chore(deps): update dependency caniuse-lite to v1.0.30001299 (#1357)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1357
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-12 14:00:05 +00:00
renovate fa4560d4af fix(deps): update dependency easymde to v2.16.0 (#1356)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1356
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-11 22:20:19 +00:00
renovate bd5483f100 chore(deps): update dependency netlify-cli to v8.6.21 (#1353)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1353
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-11 20:41:12 +00:00
renovate 9c50fb3f4e fix(deps): update dependency v-tooltip to v4.0.0-beta.16 (#1354)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1354
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-11 20:40:47 +00:00
renovate 23dc74b373 chore(deps): update dependency vitest to v0.0.141 (#1355)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1355
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-11 20:40:15 +00:00
renovate 7ec5023c45 chore(deps): update dependency netlify-cli to v8.6.19 (#1352)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1352
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-11 15:28:20 +00:00
renovate 9dbec1e11d chore(deps): update dependency vite-svg-loader to v3.1.2 (#1351)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #1351
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-11 13:14:31 +00:00
renovate 31ddc6cc5a chore(deps): update dependency netlify-cli to v8.6.18 (#1350)
continuous-integration/drone/push Build is running Details
Reviewed-on: #1350
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-11 06:59:09 +00:00
renovate c4bd5702ad chore(deps): update dependency cypress to v9.2.1 (#1349)
continuous-integration/drone/push Build was killed Details
Reviewed-on: #1349
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-11 06:58:49 +00:00
renovate 9bb6360e05 chore(deps): update typescript-eslint monorepo to v5.9.1 (#1347)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1347
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-10 21:25:43 +00:00
renovate 1570cccc36 chore(deps): update dependency vitest to v0.0.140 (#1348)
continuous-integration/drone/push Build is passing Details
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [vitest](https://github.com/vitest-dev/vitest) | devDependencies | patch | [`0.0.139` -> `0.0.140`](https://renovatebot.com/diffs/npm/vitest/0.0.139/0.0.140) |

---

### Release Notes

<details>
<summary>vitest-dev/vitest</summary>

### [`v0.0.140`](https://github.com/vitest-dev/vitest/releases/v0.0.140)

[Compare Source](https://github.com/vitest-dev/vitest/compare/v0.0.139...v0.0.140)

##### Bug Fixes

-   inline snapshot if not called inside suite ([6d743c5](https://github.com/vitest-dev/vitest/commit/6d743c5)), closes [#&#8203;484](https://github.com/vitest-dev/vitest/issues/484)
-   **ui:** flex / percentage based layout ([#&#8203;492](https://github.com/vitest-dev/vitest/issues/492)) ([c43ebaf](https://github.com/vitest-dev/vitest/commit/c43ebaf))
-   correctly inline shapshot with properties ([4603ffd](https://github.com/vitest-dev/vitest/commit/4603ffd))
-   mocking is lost with threads: false ([28b97d8](https://github.com/vitest-dev/vitest/commit/28b97d8)), closes [#&#8203;482](https://github.com/vitest-dev/vitest/issues/482)
-   Reflect.get called on non-object ([3c9073a](https://github.com/vitest-dev/vitest/commit/3c9073a)), closes [#&#8203;479](https://github.com/vitest-dev/vitest/issues/479)
-   snapshot ignores indentation ([aff1481](https://github.com/vitest-dev/vitest/commit/aff1481))
-   **ui:** reduce graph container size ([#&#8203;478](https://github.com/vitest-dev/vitest/issues/478)) ([23e1e62](https://github.com/vitest-dev/vitest/commit/23e1e62))

##### Features

-   global setup ([#&#8203;372](https://github.com/vitest-dev/vitest/issues/372)) ([eaa119f](https://github.com/vitest-dev/vitest/commit/eaa119f))
-   **ui:** tasks state group ([7782e7d](https://github.com/vitest-dev/vitest/commit/7782e7d))

</details>

---

### Configuration

📅 **Schedule**: At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box.

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).

Reviewed-on: #1348
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-10 21:23:42 +00:00
renovate b31008fe20 fix(deps): update dependency v-tooltip to v4.0.0-beta.15 (#1346)
continuous-integration/drone/push Build is passing Details
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [v-tooltip](https://github.com/Akryum/vue-tooltip) | dependencies | patch | [`4.0.0-beta.14` -> `4.0.0-beta.15`](https://renovatebot.com/diffs/npm/v-tooltip/4.0.0-beta.14/4.0.0-beta.15) |

---

### Release Notes

<details>
<summary>Akryum/vue-tooltip</summary>

### [`v4.0.0-beta.15`](https://github.com/Akryum/vue-tooltip/compare/v4.0.0-beta.14...v4.0.0-beta.15)

[Compare Source](https://github.com/Akryum/vue-tooltip/compare/v4.0.0-beta.14...v4.0.0-beta.15)

</details>

---

### Configuration

📅 **Schedule**: At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box.

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).

Reviewed-on: #1346
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-10 18:45:24 +00:00
renovate d3efcb499f chore(deps): update dependency netlify-cli to v8.6.17 (#1345)
continuous-integration/drone/push Build is passing Details
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [netlify-cli](https://github.com/netlify/cli) | devDependencies | patch | [`8.6.16` -> `8.6.17`](https://renovatebot.com/diffs/npm/netlify-cli/8.6.16/8.6.17) |

---

### Release Notes

<details>
<summary>netlify/cli</summary>

### [`v8.6.17`](https://github.com/netlify/cli/blob/master/CHANGELOG.md#&#8203;8617-httpsgithubcomnetlifyclicomparev8616v8617-2022-01-10)

[Compare Source](https://github.com/netlify/cli/compare/v8.6.16...v8.6.17)

</details>

---

### Configuration

📅 **Schedule**: At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box.

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).

Reviewed-on: #1345
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-10 13:22:00 +00:00
renovate 23e2550534 fix(deps): update dependency v-tooltip to v4.0.0-beta.14 (#1344)
continuous-integration/drone/push Build is passing Details
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [v-tooltip](https://github.com/Akryum/vue-tooltip) | dependencies | patch | [`4.0.0-beta.13` -> `4.0.0-beta.14`](https://renovatebot.com/diffs/npm/v-tooltip/4.0.0-beta.13/4.0.0-beta.14) |

---

### Release Notes

<details>
<summary>Akryum/vue-tooltip</summary>

### [`v4.0.0-beta.14`](https://github.com/Akryum/vue-tooltip/compare/v4.0.0-beta.13...v4.0.0-beta.14)

[Compare Source](https://github.com/Akryum/vue-tooltip/compare/v4.0.0-beta.13...v4.0.0-beta.14)

</details>

---

### Configuration

📅 **Schedule**: At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box.

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).

Reviewed-on: #1344
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-10 13:07:52 +00:00
renovate c3bc2e470c chore(deps): update dependency netlify-cli to v8.6.16 (#1343)
continuous-integration/drone/push Build is passing Details
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [netlify-cli](https://github.com/netlify/cli) | devDependencies | patch | [`8.6.15` -> `8.6.16`](https://renovatebot.com/diffs/npm/netlify-cli/8.6.15/8.6.16) |

---

### Release Notes

<details>
<summary>netlify/cli</summary>

### [`v8.6.16`](https://github.com/netlify/cli/blob/master/CHANGELOG.md#&#8203;8616-httpsgithubcomnetlifyclicomparev8615v8616-2022-01-10)

[Compare Source](https://github.com/netlify/cli/compare/v8.6.15...v8.6.16)

</details>

---

### Configuration

📅 **Schedule**: At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this PR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box.

---

This PR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).

Reviewed-on: #1343
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-10 12:07:57 +00:00
drone 8d811fcf19 [skip ci] Updated translations via Crowdin 2022-01-09 10:29:59 +00:00
konrad 6080e49f26 fix: flatpickr date not updating (#1336)
continuous-integration/drone/push Build is passing Details
Fixes https://github.com/go-vikunja/api/issues/16

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: #1336
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2022-01-09 10:17:18 +00:00
konrad ed88fb91bc fix: don't recognize emails in quick add magic (#1335)
continuous-integration/drone/push Build was killed Details
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: #1335
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2022-01-09 10:17:01 +00:00
konrad 76fe2ceac6 fix: editor color in dark mode (#1338)
continuous-integration/drone/push Build was killed Details
This PR fixes the editor colors in dark mode. It bothered me enough to finally fix it :)
The implementation is probably only "good enough" as we'll replace the editor anyway in the future.

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: #1338
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2022-01-09 10:16:13 +00:00
renovate 99da6bb19a chore(deps): update dependency esbuild to v0.14.11 (#1341)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #1341
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-09 08:01:25 +00:00
drone c2e4fc1e3c [skip ci] Updated translations via Crowdin 2022-01-08 22:31:19 +00:00
dmorlitz 044f2b927d feat: changed green "Done" button to read "Mark task done" (#1340)
continuous-integration/drone/push Build is failing Details
The green button can be confusing because the wording "Done" can be interpreted as "done editing the task" or "the task should be marked as done". Changing the text in the button makes the description more precise.

Co-authored-by: David Morlitz <david@morlitz.com>
Reviewed-on: #1340
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dmorlitz <david@morlitz.com>
Co-committed-by: dmorlitz <david@morlitz.com>
2022-01-08 21:18:06 +00:00
kolaente 0473c385d6
fix: editor cursor color
continuous-integration/drone/push Build is passing Details
2022-01-08 17:34:13 +01:00
kolaente 55826bb8c9
fix: make sure the app is fully ready before trying to redirect to the login page
continuous-integration/drone/pr Build is passing Details
2022-01-08 15:44:33 +01:00
kolaente 68a76faacc
fix: don't reset active fields when saving
continuous-integration/drone/push Build is passing Details
Resolves #590
2022-01-08 15:13:49 +01:00
kolaente 4579dd3ce7
fix: button size on task detail view
continuous-integration/drone/push Build is passing Details
2022-01-08 15:11:55 +01:00
kolaente 19a161ff78
fix: password validation field in test
continuous-integration/drone/pr Build is passing Details
2022-01-08 13:49:07 +01:00
kolaente 310578d349
Merge branch 'main' into feature/login-improvements
continuous-integration/drone/pr Build was killed Details
# Conflicts:
#	src/components/misc/no-auth-wrapper.vue
#	src/styles/components/_index.scss
#	src/views/user/Login.vue
#	src/views/user/Register.vue
2022-01-08 13:44:35 +01:00
Dominik Pschenitschni e6e8a98514
fix: don't set defined values for search and page
continuous-integration/drone/pr Build is failing Details
2022-01-04 21:55:33 +01:00
Dominik Pschenitschni 76f4cca5fe
fix: move local storage list view to router 2022-01-04 21:55:33 +01:00
Dominik Pschenitschni 5916a44724
feat: provide listId prop via router 2022-01-04 21:55:32 +01:00
Dominik Pschenitschni 6d62ca1ada
fix: check now just once 2022-01-04 21:55:32 +01:00
Dominik Pschenitschni e54d95802b
fix: closing modal 2022-01-04 21:55:32 +01:00
Dominik Pschenitschni e837621ef8
Try to cache list views 2022-01-04 21:55:32 +01:00
Dominik Pschenitschni 2db820d926
feat: review changes 2022-01-04 21:55:32 +01:00
Dominik Pschenitschni f3358269e5
fix task remove label test 2022-01-04 21:55:32 +01:00
Dominik Pschenitschni 700fce3c2c
fix: sharing components 2022-01-04 21:55:32 +01:00
Dominik Pschenitschni 58207db6c3
fix: kanban tests 2022-01-04 21:55:31 +01:00
Dominik Pschenitschni da8cf13619
fix: task done label test 2022-01-04 21:55:31 +01:00
Dominik Pschenitschni e78d47fdcf
fix: list specs 2022-01-04 21:55:30 +01:00
Dominik Pschenitschni 5937f01cc5
fix: list loading 2022-01-04 21:55:30 +01:00
Dominik Pschenitschni 29a9335844
feat: save current list view just once 2022-01-04 21:55:30 +01:00
Dominik Pschenitschni 7eed0628d0
feat: mount list views as route-views 2022-01-04 21:55:30 +01:00
Dominik Pschenitschni 16b0d03601
fix: readd modal transitions 2022-01-04 21:55:30 +01:00
Dominik Pschenitschni c70211ad32
feat: unify modal view
fix: List.vue
2022-01-04 21:55:30 +01:00
Dominik Pschenitschni 281c922de1
feat: make taskList a composable 2022-01-04 21:55:29 +01:00
Dominik Pschenitschni 5a0c0eff9f
feat: implement modals with vue router 4
This is an implementation of the modals with the new possibilities of vue router 3.

See: https://github.com/vuejs/vue-router/issues/703#issuecomment-865066913 for a better explanation
and the linked example implementation: https://github.com/vuejs/vue-router-next/blob/master/e2e/modal/index.ts
2022-01-04 21:55:29 +01:00
kolaente 9c5613ad98
fix: lint
continuous-integration/drone/pr Build is failing Details
2021-12-26 13:42:21 +01:00
kolaente 0322daf4d4
feat: move password to separate component
continuous-integration/drone/pr Build is failing Details
2021-12-26 13:37:33 +01:00
kolaente 6041ad1482
Merge branch 'main' into feature/login-improvements 2021-12-26 12:19:44 +01:00
Dominik Pschenitschni 9a3069c20d
fix: propType validation in message.vue
continuous-integration/drone/pr Build is failing Details
2021-12-21 16:07:38 +01:00
Dominik Pschenitschni 27cd9535bf
fix: remove @ts-ignore
continuous-integration/drone/pr Build is failing Details
2021-12-21 15:47:57 +01:00
Dominik Pschenitschni c46273ca34
fix: remove unused var 2021-12-21 15:47:40 +01:00
kolaente a4ec41e937
fix: motd on mobile
continuous-integration/drone/pr Build is failing Details
2021-12-21 15:21:23 +01:00
kolaente 3eb0d58f79
fix: add .vue suffix to fix typescript warning 2021-12-21 15:21:23 +01:00
kolaente 5558d91f44
feat: change links to login / register pages 2021-12-21 15:21:23 +01:00
kolaente 9c04fb4e40
fix: disable login button 2021-12-21 15:21:23 +01:00
kolaente 1fc1c20c87
feat: add extra prop for message center text 2021-12-21 15:21:23 +01:00
kolaente a1814ea29d
fix: message spacing 2021-12-21 15:21:23 +01:00
kolaente fda0b81d9c
feat: add tooltip and aria-label 2021-12-21 15:21:22 +01:00
kolaente 8397608fef
chore: move password field toggle to scss file 2021-12-21 15:21:22 +01:00
kolaente 66d5e851e8
feat: improve error handling of login fields 2021-12-21 15:21:22 +01:00
kolaente 1d916e7e03
feat: change wording 2021-12-21 15:21:22 +01:00
kolaente aa12bffcbc
feat: replace password comparison with password toggle 2021-12-21 15:21:22 +01:00
kolaente 05e054f501
feat: improve input validation for register form 2021-12-21 15:21:20 +01:00
kolaente f7eb160509
fix: move forgot password link next to password label
In some languages, the texts on the "log in" and "register" buttons were so long they wrapped underneath each other. Moving the "forgot password" link next to the password label leaves these two buttons enough space to always stay next to each other.
2021-12-21 14:34:16 +01:00
134 changed files with 4852 additions and 3457 deletions

View File

@ -116,36 +116,16 @@ steps:
YARN_CACHE_FOLDER: .cache/yarn/
CYPRESS_CACHE_FOLDER: .cache/cypress/
CYPRESS_DEFAULT_COMMAND_TIMEOUT: 60000
CYPRESS_RECORD_KEY:
from_secret: cypress_project_key
commands:
- sed -i 's/localhost/api/g' dist/index.html
- yarn serve:dist & npx wait-on http://localhost:5000
- yarn test:frontend --browser chrome
- yarn test:frontend --browser chrome --record
depends_on:
- dependencies
- build-prod
- name: upload-test-results
image: plugins/s3
pull: true
settings:
bucket: drone-test-results
access_key:
from_secret: test_results_aws_access_key_id
secret_key:
from_secret: test_results_aws_secret_access_key
endpoint: https://s3.fr-par.scw.cloud
region: fr-par
path_style: true
source: cypress/screenshots/**/**/*
strip_prefix: cypress/screenshots/
target: /${DRONE_REPO}/${DRONE_PULL_REQUEST}_${DRONE_BRANCH}/${DRONE_BUILD_NUMBER}/
depends_on:
- test-frontend
when:
status:
- failure
- success
- name: deploy-preview
image: node:16
pull: true
@ -665,6 +645,6 @@ steps:
from_secret: crowdin_key
---
kind: signature
hmac: 188ee90100c5fc5922a445e531e7a47453121edddb2a64a182eb23ed2bf602de
hmac: 997e1badebe484ac29557c4af356e63db4d3d57f3d32e92d482f117f8cec64da
...

View File

@ -7,5 +7,6 @@
"video": false,
"retries": {
"runMode": 2
}
},
"projectId": "181c7x"
}

View File

@ -1,4 +1,4 @@
import faker from 'faker'
import faker from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'

View File

@ -1,4 +1,4 @@
import faker from 'faker'
import faker from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'

View File

@ -1,6 +1,6 @@
import {Factory} from '../support/factory'
import {formatISO} from "date-fns"
import faker from 'faker'
import faker from '@faker-js/faker'
export class LinkShareFactory extends Factory {
static table = 'link_shares'

View File

@ -1,6 +1,6 @@
import {Factory} from '../support/factory'
import {formatISO} from "date-fns"
import faker from 'faker'
import faker from '@faker-js/faker'
export class ListFactory extends Factory {
static table = 'lists'

View File

@ -1,4 +1,4 @@
import faker from 'faker'
import faker from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'

View File

@ -1,4 +1,4 @@
import faker from 'faker'
import faker from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'

View File

@ -1,4 +1,4 @@
import faker from 'faker'
import faker from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from "date-fns"

View File

@ -1,4 +1,4 @@
import faker from 'faker'
import faker from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from 'date-fns'

View File

@ -1,4 +1,4 @@
import faker from 'faker'
import faker from '@faker-js/faker'
import {Factory} from '../support/factory'
import {formatISO} from "date-fns"

View File

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

View File

@ -0,0 +1,76 @@
import {formatISO, format} from 'date-fns'
import {TaskFactory} from '../../factories/task'
import {prepareLists} from './prepareLists'
import '../../support/authenticateUser'
describe('List View Gantt', () => {
prepareLists()
it('Hides tasks with no dates', () => {
const tasks = TaskFactory.create(1)
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart .tasks')
.should('not.contain', tasks[0].title)
})
it('Shows tasks from the current and next month', () => {
const now = new Date()
const nextMonth = now
nextMonth.setDate(1)
nextMonth.setMonth(now.getMonth() + 1)
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart .months')
.should('contain', format(now, 'MMMM'))
.should('contain', format(nextMonth, 'MMMM'))
})
it('Shows tasks with dates', () => {
const now = new Date()
const tasks = TaskFactory.create(1, {
start_date: formatISO(now),
end_date: formatISO(now.setDate(now.getDate() + 4))
})
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart .tasks')
.should('not.be.empty')
cy.get('.gantt-chart .tasks')
.should('contain', tasks[0].title)
})
it('Shows tasks with no dates after enabling them', () => {
TaskFactory.create(1, {
start_date: null,
end_date: null,
})
cy.visit('/lists/1/gantt')
cy.get('.gantt-options .fancycheckbox')
.contains('Show tasks which don\'t have dates set')
.click()
cy.get('.gantt-chart .tasks')
.should('not.be.empty')
cy.get('.gantt-chart .tasks .task.nodate')
.should('exist')
})
it('Drags a task around', () => {
const now = new Date()
TaskFactory.create(1, {
start_date: formatISO(now),
end_date: formatISO(now.setDate(now.getDate() + 4))
})
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart .tasks .task')
.first()
.trigger('mousedown', {which: 1})
.trigger('mousemove', {clientX: 500, clientY: 0})
.trigger('mouseup', {force: true})
})
})

View File

@ -0,0 +1,196 @@
import {BucketFactory} from '../../factories/bucket'
import {ListFactory} from '../../factories/list'
import {TaskFactory} from '../../factories/task'
import {prepareLists} from './prepareLists'
import '../../support/authenticateUser'
describe('List View Kanban', () => {
let buckets
prepareLists()
beforeEach(() => {
buckets = BucketFactory.create(2)
})
it('Shows all buckets with their tasks', () => {
const data = TaskFactory.create(10, {
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.get('.kanban .bucket .title')
.contains(buckets[0].title)
.should('exist')
cy.get('.kanban .bucket .title')
.contains(buckets[1].title)
.should('exist')
cy.get('.kanban .bucket')
.first()
.should('contain', data[0].title)
})
it('Can add a new task to a bucket', () => {
TaskFactory.create(2, {
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket')
.contains(buckets[0].title)
.get('.bucket-footer .button')
.contains('Add another task')
.click()
cy.get('.kanban .bucket')
.contains(buckets[0].title)
.get('.bucket-footer .field .control input.input')
.type('New Task{enter}')
cy.get('.kanban .bucket')
.first()
.should('contain', 'New Task')
})
it('Can create a new bucket', () => {
cy.visit('/lists/1/kanban')
cy.get('.kanban .bucket.new-bucket .button')
.click()
cy.get('.kanban .bucket.new-bucket input.input')
.type('New Bucket{enter}')
cy.wait(1000) // Wait for the request to finish
cy.get('.kanban .bucket .title')
.contains('New Bucket')
.should('exist')
})
it('Can set a bucket limit', () => {
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
.first()
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
.contains('Limit: Not Set')
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
.first()
.type(3)
cy.get('[data-cy="setBucketLimit"]')
.first()
.click()
cy.get('.kanban .bucket .bucket-header span.limit')
.contains('0/3')
.should('exist')
})
it('Can rename a bucket', () => {
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .bucket-header .title')
.first()
.type('{selectall}New Bucket Title{enter}')
cy.get('.kanban .bucket .bucket-header .title')
.first()
.should('contain', 'New Bucket Title')
})
it('Can delete a bucket', () => {
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
.first()
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
.contains('Delete')
.click()
cy.get('.modal-mask .modal-container .modal-content .header')
.should('contain', 'Delete the bucket')
cy.get('.modal-mask .modal-container .modal-content .actions .button')
.contains('Do it!')
.click()
cy.get('.kanban .bucket .title')
.contains(buckets[0].title)
.should('not.exist')
cy.get('.kanban .bucket .title')
.contains(buckets[1].title)
.should('exist')
})
it('Can drag tasks around', () => {
const tasks = TaskFactory.create(2, {
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
.first()
.drag('.kanban .bucket:nth-child(2) .tasks .dropper')
cy.get('.kanban .bucket:nth-child(2) .tasks')
.should('contain', tasks[0].title)
cy.get('.kanban .bucket:nth-child(1) .tasks')
.should('not.contain', tasks[0].title)
})
it('Should navigate to the task when the task card is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
.should('be.visible')
.click()
cy.url()
.should('contain', `/tasks/${tasks[0].id}`, { timeout: 1000 })
})
it('Should remove a task from the kanban board when moving it to another list', () => {
const lists = ListFactory.create(2)
BucketFactory.create(2, {
list_id: '{increment}',
})
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
bucket_id: 1,
})
const task = tasks[0]
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .tasks .task')
.contains(task.title)
.should('be.visible')
.click()
cy.get('.task-view .action-buttons .button', { timeout: 3000 })
.contains('Move task')
.click()
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
.type(`${lists[1].title}{enter}`)
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
// presses enter and we can't simulate pressing on enter to select the item.
cy.get('.task-view .content.details .field .multiselect.control .search-results')
.children()
.first()
.click()
cy.get('.global-notification', { timeout: 1000 })
.should('contain', 'Success')
cy.go('back')
cy.get('.kanban .bucket')
.should('not.contain', task.title)
})
})

View File

@ -0,0 +1,97 @@
import {UserListFactory} from '../../factories/users_list'
import {TaskFactory} from '../../factories/task'
import {UserFactory} from '../../factories/user'
import {ListFactory} from '../../factories/list'
import {prepareLists} from './prepareLists'
import '../../support/authenticateUser'
describe('List View List', () => {
prepareLists()
it('Should be an empty list', () => {
cy.visit('/lists/1')
cy.url()
.should('contain', '/lists/1/list')
cy.get('.list-title h1')
.should('contain', 'First List')
cy.get('.list-title .dropdown')
.should('exist')
cy.get('p')
.contains('This list is currently empty.')
.should('exist')
})
it('Should navigate to the task when the title is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
})
cy.visit('/lists/1/list')
cy.get('.tasks .task .tasktext')
.contains(tasks[0].title)
.first()
.click()
cy.url()
.should('contain', `/tasks/${tasks[0].id}`)
})
it('Should not see any elements for a list which is shared read only', () => {
UserFactory.create(2)
UserListFactory.create(1, {
list_id: 2,
user_id: 1,
right: 0,
})
const lists = ListFactory.create(2, {
owner_id: '{increment}',
namespace_id: '{increment}',
})
cy.visit(`/lists/${lists[1].id}/`)
cy.get('.list-title a.icon')
.should('not.exist')
cy.get('input.input[placeholder="Add a new task..."')
.should('not.exist')
})
it('Should only show the color of a list in the navigation and not in the list view', () => {
const lists = ListFactory.create(1, {
hex_color: '00db60',
})
TaskFactory.create(10, {
list_id: lists[0].id,
})
cy.visit(`/lists/${lists[0].id}/`)
cy.get('.menu-list li .list-menu-link .color-bubble')
.should('have.css', 'background-color', 'rgb(0, 219, 96)')
cy.get('.tasks-container .tasks .color-bubble')
.should('not.exist')
})
it('Should paginate for > 50 tasks', () => {
const tasks = TaskFactory.create(100, {
id: '{increment}',
title: i => `task${i}`,
list_id: 1,
})
cy.visit('/lists/1/list')
cy.get('.tasks-container .tasks')
.should('contain', tasks[99].title)
cy.get('.card-content .pagination .pagination-link')
.contains('2')
.click()
cy.url()
.should('contain', '?page=2')
cy.get('.tasks-container .tasks')
.should('contain', tasks[1].title)
cy.get('.tasks-container .tasks')
.should('not.contain', tasks[99].title)
})
})

View File

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

View File

@ -1,25 +1,11 @@
import {formatISO, format} from 'date-fns'
import {TaskFactory} from '../../factories/task'
import {ListFactory} from '../../factories/list'
import {UserListFactory} from '../../factories/users_list'
import {UserFactory} from '../../factories/user'
import {NamespaceFactory} from '../../factories/namespace'
import {BucketFactory} from '../../factories/bucket'
import {prepareLists} from './prepareLists'
import '../../support/authenticateUser'
describe('Lists', () => {
let lists
beforeEach(() => {
UserFactory.create(1)
NamespaceFactory.create(1)
lists = ListFactory.create(1, {
title: 'First List'
})
TaskFactory.truncate()
})
prepareLists((newLists) => (lists = newLists))
it('Should create a new list', () => {
cy.visit('/')
@ -29,7 +15,7 @@ describe('Lists', () => {
.contains('New list')
.click()
cy.url()
.should('contain', '/namespaces/1/list')
.should('contain', '/lists/new/1')
cy.get('.card-header-title')
.contains('New list')
cy.get('input.input')
@ -56,7 +42,7 @@ describe('Lists', () => {
})
it('Should rename the list in all places', () => {
const tasks = TaskFactory.create(5, {
TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
})
@ -112,429 +98,4 @@ describe('Lists', () => {
cy.location('pathname')
.should('equal', '/')
})
describe('List View', () => {
it('Should be an empty list', () => {
cy.visit('/lists/1')
cy.url()
.should('contain', '/lists/1/list')
cy.get('.list-title h1')
.should('contain', 'First List')
cy.get('.list-title .dropdown')
.should('exist')
cy.get('p')
.contains('This list is currently empty.')
.should('exist')
})
it('Should navigate to the task when the title is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
})
cy.visit('/lists/1/list')
cy.get('.tasks .task .tasktext')
.contains(tasks[0].title)
.first()
.click()
cy.url()
.should('contain', `/tasks/${tasks[0].id}`)
})
it('Should not see any elements for a list which is shared read only', () => {
UserFactory.create(2)
UserListFactory.create(1, {
list_id: 2,
user_id: 1,
right: 0,
})
const lists = ListFactory.create(2, {
owner_id: '{increment}',
namespace_id: '{increment}',
})
cy.visit(`/lists/${lists[1].id}/`)
cy.get('.list-title a.icon')
.should('not.exist')
cy.get('input.input[placeholder="Add a new task..."')
.should('not.exist')
})
it('Should only show the color of a list in the navigation and not in the list view', () => {
const lists = ListFactory.create(1, {
hex_color: '00db60',
})
TaskFactory.create(10, {
list_id: lists[0].id,
})
cy.visit(`/lists/${lists[0].id}/`)
cy.get('.menu-list li .list-menu-link .color-bubble')
.should('have.css', 'background-color', 'rgb(0, 219, 96)')
cy.get('.tasks-container .tasks .color-bubble')
.should('not.exist')
})
it('Should paginate for > 50 tasks', () => {
const tasks = TaskFactory.create(100, {
id: '{increment}',
title: i => `task${i}`,
list_id: 1,
})
cy.visit('/lists/1/list')
cy.get('.tasks-container .tasks')
.should('contain', tasks[99].title)
cy.get('.card-content .pagination .pagination-link')
.contains('2')
.click()
cy.url()
.should('contain', '?page=2')
cy.get('.tasks-container .tasks')
.should('contain', tasks[1].title)
cy.get('.tasks-container .tasks')
.should('not.contain', tasks[99].title)
})
})
describe('Table View', () => {
it('Should show a table with tasks', () => {
const tasks = TaskFactory.create(1)
cy.visit('/lists/1/table')
cy.get('.table-view table.table')
.should('exist')
cy.get('.table-view table.table')
.should('contain', tasks[0].title)
})
it('Should have working column switches', () => {
TaskFactory.create(1)
cy.visit('/lists/1/table')
cy.get('.table-view .filter-container .items .button')
.contains('Columns')
.click()
cy.get('.table-view .filter-container .card.columns-filter .card-content .fancycheckbox .check')
.contains('Priority')
.click()
cy.get('.table-view .filter-container .card.columns-filter .card-content .fancycheckbox .check')
.contains('Done')
.click()
cy.get('.table-view table.table th')
.contains('Priority')
.should('exist')
cy.get('.table-view table.table th')
.contains('Done')
.should('not.exist')
})
it('Should navigate to the task when the title is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
})
cy.visit('/lists/1/table')
cy.get('.table-view table.table')
.contains(tasks[0].title)
.click()
cy.url()
.should('contain', `/tasks/${tasks[0].id}`)
})
})
describe('Gantt View', () => {
it('Hides tasks with no dates', () => {
const tasks = TaskFactory.create(1)
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart-container .gantt-chart .tasks')
.should('not.contain', tasks[0].title)
})
it('Shows tasks from the current and next month', () => {
const now = new Date()
const nextMonth = now
nextMonth.setDate(1)
nextMonth.setMonth(now.getMonth() + 1)
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart-container .gantt-chart .months')
.should('contain', format(now, 'MMMM'))
.should('contain', format(nextMonth, 'MMMM'))
})
it('Shows tasks with dates', () => {
const now = new Date()
const tasks = TaskFactory.create(1, {
start_date: formatISO(now),
end_date: formatISO(now.setDate(now.getDate() + 4))
})
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart-container .gantt-chart .tasks')
.should('not.be.empty')
cy.get('.gantt-chart-container .gantt-chart .tasks')
.should('contain', tasks[0].title)
})
it('Shows tasks with no dates after enabling them', () => {
TaskFactory.create(1, {
start_date: null,
end_date: null,
})
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart-container .gantt-options .fancycheckbox')
.contains('Show tasks which don\'t have dates set')
.click()
cy.get('.gantt-chart-container .gantt-chart .tasks')
.should('not.be.empty')
cy.get('.gantt-chart-container .gantt-chart .tasks .task.nodate')
.should('exist')
})
it('Drags a task around', () => {
const now = new Date()
TaskFactory.create(1, {
start_date: formatISO(now),
end_date: formatISO(now.setDate(now.getDate() + 4))
})
cy.visit('/lists/1/gantt')
cy.get('.gantt-chart-container .gantt-chart .tasks .task')
.first()
.trigger('mousedown', {which: 1})
.trigger('mousemove', {clientX: 500, clientY: 0})
.trigger('mouseup', {force: true})
})
})
describe('Kanban', () => {
let buckets
beforeEach(() => {
buckets = BucketFactory.create(2)
})
it('Shows all buckets with their tasks', () => {
const data = TaskFactory.create(10, {
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.get('.kanban .bucket .title')
.contains(buckets[0].title)
.should('exist')
cy.get('.kanban .bucket .title')
.contains(buckets[1].title)
.should('exist')
cy.get('.kanban .bucket')
.first()
.should('contain', data[0].title)
})
it('Can add a new task to a bucket', () => {
const data = TaskFactory.create(2, {
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.get('.kanban .bucket')
.contains(buckets[0].title)
.get('.bucket-footer .button')
.contains('Add another task')
.click()
cy.get('.kanban .bucket')
.contains(buckets[0].title)
.get('.bucket-footer .field .control input.input')
.type('New Task{enter}')
cy.get('.kanban .bucket')
.first()
.should('contain', 'New Task')
})
it('Can create a new bucket', () => {
cy.visit('/lists/1/kanban')
cy.get('.kanban .bucket.new-bucket .button')
.click()
cy.get('.kanban .bucket.new-bucket input.input')
.type('New Bucket{enter}')
cy.wait(1000) // Wait for the request to finish
cy.get('.kanban .bucket .title')
.contains('New Bucket')
.should('exist')
})
it('Can set a bucket limit', () => {
cy.visit('/lists/1/kanban')
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
.first()
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
.contains('Limit: Not Set')
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
.first()
.type(3)
cy.get('[data-cy="setBucketLimit"]')
.first()
.click()
cy.get('.kanban .bucket .bucket-header span.limit')
.contains('0/3')
.should('exist')
})
it('Can rename a bucket', () => {
cy.visit('/lists/1/kanban')
cy.get('.kanban .bucket .bucket-header .title')
.first()
.type('{selectall}New Bucket Title{enter}')
cy.get('.kanban .bucket .bucket-header .title')
.first()
.should('contain', 'New Bucket Title')
})
it('Can delete a bucket', () => {
cy.visit('/lists/1/kanban')
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-trigger')
.first()
.click()
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item')
.contains('Delete')
.click()
cy.get('.modal-mask .modal-container .modal-content .header')
.should('contain', 'Delete the bucket')
cy.get('.modal-mask .modal-container .modal-content .actions .button')
.contains('Do it!')
.click()
cy.get('.kanban .bucket .title')
.contains(buckets[0].title)
.should('not.exist')
cy.get('.kanban .bucket .title')
.contains(buckets[1].title)
.should('exist')
})
it('Can drag tasks around', () => {
const tasks = TaskFactory.create(2, {
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.get('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
.first()
.drag('.kanban .bucket:nth-child(2) .tasks .dropper')
cy.get('.kanban .bucket:nth-child(2) .tasks')
.should('contain', tasks[0].title)
cy.get('.kanban .bucket:nth-child(1) .tasks')
.should('not.contain', tasks[0].title)
})
it('Should navigate to the task when the task card is clicked', () => {
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
bucket_id: 1,
})
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
.should('be.visible')
.click()
cy.url()
.should('contain', `/tasks/${tasks[0].id}`)
})
it('Should remove a task from the kanban board when moving it to another list', () => {
const lists = ListFactory.create(2)
BucketFactory.create(2, {
list_id: '{increment}',
})
const tasks = TaskFactory.create(5, {
id: '{increment}',
list_id: 1,
bucket_id: 1,
})
const task = tasks[0]
cy.visit('/lists/1/kanban')
cy.getSettled('.kanban .bucket .tasks .task')
.contains(task.title)
.should('be.visible')
.click()
cy.get('.task-view .action-buttons .button')
.contains('Move task')
.click()
cy.get('.task-view .content.details .field .multiselect.control .input-wrapper input')
.type(`${lists[1].title}{enter}`)
// The requests happen with a 200ms timeout. Because of that, the results are not yet there when cypress
// presses enter and we can't simulate pressing on enter to select the item.
cy.get('.task-view .content.details .field .multiselect.control .search-results')
.children()
.first()
.click()
cy.get('.global-notification', { timeout: 1000 })
.should('contain', 'Success')
cy.go('back')
cy.get('.kanban .bucket')
.should('not.contain', task.title)
})
})
describe('List history', () => {
it('should show a list history on the home page', () => {
const lists = ListFactory.create(6)
cy.visit('/')
cy.get('h3')
.contains('Last viewed')
.should('not.exist')
cy.visit(`/lists/${lists[0].id}`)
cy.visit(`/lists/${lists[1].id}`)
cy.visit(`/lists/${lists[2].id}`)
cy.visit(`/lists/${lists[3].id}`)
cy.visit(`/lists/${lists[4].id}`)
cy.visit(`/lists/${lists[5].id}`)
cy.visit('/')
cy.get('h3')
.contains('Last viewed')
.should('exist')
cy.get('.list-cards-wrapper-2-rows')
.should('not.contain', lists[0].title)
.should('contain', lists[1].title)
.should('contain', lists[2].title)
.should('contain', lists[3].title)
.should('contain', lists[4].title)
.should('contain', lists[5].title)
})
})
})

View File

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

View File

@ -116,6 +116,7 @@ describe('Task', () => {
.should('be.visible')
.should('contain', 'Done')
cy.get('.task-view .action-buttons p.created')
.scrollIntoView()
.should('be.visible')
.should('contain', 'Done')
})
@ -128,7 +129,7 @@ describe('Task', () => {
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Done!')
.contains('Mark task done!')
.click()
cy.get('.task-view .heading .is-done')
@ -372,13 +373,13 @@ describe('Task', () => {
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .details.labels-list .multiselect .input-wrapper')
cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
.should('be.visible')
.should('contain', labels[0].title)
cy.get('.task-view .details.labels-list .multiselect .input-wrapper')
cy.getSettled('.task-view .details.labels-list .multiselect .input-wrapper')
.children()
.first()
.get('a.delete')
.get('[data-cy="taskDetail.removeLabel"]')
.click()
cy.get('.global-notification')

View File

@ -25,7 +25,6 @@ context('Registration', () => {
cy.get('#username').type(fixture.username)
cy.get('#email').type(fixture.email)
cy.get('#password').type(fixture.password)
cy.get('#passwordValidation').type(fixture.password)
cy.get('#register-submit').click()
cy.url().should('include', '/')
cy.clock(1625656161057) // 13:00
@ -43,7 +42,6 @@ context('Registration', () => {
cy.get('#username').type(fixture.username)
cy.get('#email').type(fixture.email)
cy.get('#password').type(fixture.password)
cy.get('#passwordValidation').type(fixture.password)
cy.get('#register-submit').click()
cy.get('div.message.danger').contains('A user with this username already exists.')
})

View File

@ -8,12 +8,14 @@ describe('User Settings', () => {
})
it('Changes the user avatar', () => {
cy.intercept(`${Cypress.env('API_URL')}/user/settings/avatar/upload`).as('uploadAvatar')
cy.visit('/user/settings/avatar')
cy.get('input[name=avatarProvider][value=upload]')
.click()
cy.get('input[type=file]', { timeout: 1000 })
.attachFile('image.jpg')
cy.get('input[type=file]', {timeout: 1000})
.selectFile('cypress/fixtures/image.jpg', {force: true}) // The input is not visible, but on purpose
cy.get('.vue-handler-wrapper.vue-handler-wrapper--south .vue-simple-handler.vue-simple-handler--south')
.trigger('mousedown', {which: 1})
.trigger('mousemove', {clientY: 100})
@ -22,7 +24,7 @@ describe('User Settings', () => {
.contains('Upload Avatar')
.click()
cy.wait(3000) // Wait for the request to finish
cy.wait('@uploadAvatar')
cy.get('.global-notification')
.should('contain', 'Success')
})

View File

@ -1,6 +1,5 @@
import './commands'
import 'cypress-file-upload'
import '@4tw/cypress-drag-drop'
// see https://github.com/cypress-io/cypress/issues/702#issuecomment-587127275

View File

@ -13,41 +13,42 @@
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
"cypress:open": "cypress open",
"test:unit": "vitest run",
"test:unit-watch": "vitest watch",
"test:frontend": "cypress run",
"browserslist:update": "npx browserslist@latest --update-db"
},
"dependencies": {
"@github/hotkey": "1.6.1",
"@github/hotkey": "2.0.0",
"@kyvg/vue3-notification": "2.3.4",
"@sentry/tracing": "6.16.1",
"@sentry/vue": "6.16.1",
"@sentry/tracing": "6.17.4",
"@sentry/vue": "6.17.4",
"@types/is-touch-device": "1.0.0",
"@vue/compat": "3.2.26",
"@vueuse/core": "7.5.2",
"@vueuse/router": "7.5.3",
"@vue/compat": "3.2.29",
"@vueuse/core": "7.5.5",
"@vueuse/router": "7.5.5",
"bulma-css-variables": "0.9.33",
"camel-case": "4.1.2",
"codemirror": "5.65.0",
"codemirror": "5.65.1",
"copy-to-clipboard": "3.3.1",
"date-fns": "2.28.0",
"dompurify": "2.3.4",
"easymde": "2.15.0",
"dompurify": "2.3.5",
"easymde": "2.16.1",
"flatpickr": "4.6.9",
"flexsearch": "0.7.21",
"highlight.js": "11.4.0",
"is-touch-device": "1.0.1",
"lodash.clonedeep": "4.5.0",
"lodash.debounce": "4.0.8",
"marked": "4.0.9",
"marked": "4.0.12",
"register-service-worker": "1.7.2",
"snake-case": "3.0.4",
"ufo": "0.7.9",
"v-tooltip": "4.0.0-beta.13",
"vue": "3.2.26",
"vue-advanced-cropper": "2.7.1",
"ufo": "0.7.10",
"v-tooltip": "4.0.0-beta.17",
"vue": "3.2.29",
"vue-advanced-cropper": "2.8.0",
"vue-drag-resize": "2.0.3",
"vue-flatpickr-component": "9.0.5",
"vue-i18n": "9.2.0-beta.26",
"vue-i18n": "9.2.0-beta.30",
"vue-router": "4.0.12",
"vuedraggable": "4.1.0",
"vuex": "4.0.2",
@ -55,41 +56,40 @@
},
"devDependencies": {
"@4tw/cypress-drag-drop": "2.1.0",
"@faker-js/faker": "6.0.0-alpha.5",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/vue-fontawesome": "3.0.0-5",
"@types/flexsearch": "0.7.2",
"@typescript-eslint/eslint-plugin": "5.9.0",
"@typescript-eslint/parser": "5.9.0",
"@typescript-eslint/eslint-plugin": "5.10.2",
"@typescript-eslint/parser": "5.10.2",
"@vitejs/plugin-legacy": "1.6.4",
"@vitejs/plugin-vue": "2.0.1",
"@vitejs/plugin-vue": "2.1.0",
"@vue/eslint-config-typescript": "10.0.0",
"autoprefixer": "10.4.2",
"axios": "0.24.0",
"axios": "0.25.0",
"browserslist": "4.19.1",
"caniuse-lite": "1.0.30001298",
"cypress": "9.2.0",
"cypress-file-upload": "5.0.8",
"esbuild": "0.14.10",
"eslint": "8.6.0",
"eslint-plugin-vue": "8.2.0",
"caniuse-lite": "1.0.30001307",
"cypress": "9.4.1",
"esbuild": "0.14.18",
"eslint": "8.8.0",
"eslint-plugin-vue": "8.4.1",
"express": "4.17.2",
"faker": "5.5.3",
"netlify-cli": "8.6.15",
"happy-dom": "2.25.1",
"postcss": "8.4.5",
"postcss-preset-env": "7.2.0",
"rollup": "2.63.0",
"rollup-plugin-visualizer": "5.5.2",
"sass": "1.47.0",
"happy-dom": "2.31.1",
"netlify-cli": "8.15.0",
"postcss": "8.4.6",
"postcss-preset-env": "7.3.1",
"rollup": "2.67.0",
"rollup-plugin-visualizer": "5.5.4",
"sass": "1.49.7",
"slugify": "1.6.5",
"typescript": "4.5.4",
"vite": "2.7.10",
"vite-plugin-pwa": "0.11.12",
"vite-svg-loader": "3.1.1",
"vitest": "0.0.139",
"vue-tsc": "0.30.2",
"typescript": "4.5.5",
"vite": "2.7.13",
"vite-plugin-pwa": "0.11.13",
"vite-svg-loader": "3.1.2",
"vitest": "0.2.7",
"vue-tsc": "0.31.2",
"wait-on": "6.0.0",
"workbox-cli": "6.4.2"
},
@ -129,7 +129,7 @@
"parser": "vue-eslint-parser",
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": 2021
"ecmaVersion": 2022
},
"ignorePatterns": [
"*.test.*",

View File

@ -3,5 +3,11 @@
"labels": ["dependencies"],
"extends": [
"config:base"
],
"packageRules": [
{
"matchPackageNames": ["netlify-cli"],
"extends": ["schedule:weekly"]
}
]
}

View File

@ -42,7 +42,7 @@ import {useBodyClass} from '@/composables/useBodyClass'
const store = useStore()
const router = useRouter()
useBodyClass('is-touch', isTouchDevice)
useBodyClass('is-touch', isTouchDevice())
const keyboardShortcutsActive = computed(() => store.state.keyboardShortcutsActive)
const authUser = computed(() => store.getters['auth/authUser'])

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 5.9 KiB

After

Width:  |  Height:  |  Size: 5.9 KiB

View File

@ -1,33 +1,51 @@
<template>
<div>
<a @click="$store.commit('menuActive', false)" class="menu-hide-button" v-if="menuActive">
<BaseButton
v-if="menuActive"
@click="$store.commit('menuActive', false)"
class="menu-hide-button"
>
<icon icon="times" />
</a>
</BaseButton>
<div
:class="{'has-background': background}"
:style="{'background-image': background && `url(${background})`}"
class="app-container"
>
<navigation/>
<div
<main
:class="[
{ 'is-menu-enabled': menuActive },
$route.name,
]"
class="app-content"
>
<a @click="$store.commit('menuActive', false)" class="mobile-overlay" v-if="menuActive"></a>
<BaseButton
v-if="menuActive"
@click="$store.commit('menuActive', false)"
class="mobile-overlay"
/>
<quick-actions/>
<router-view/>
<router-view name="popup" v-slot="{ Component }">
<transition name="modal">
<router-view :route="routeWithModal" v-slot="{ Component }">
<keep-alive :include="['list.list', 'list.gantt', 'list.table', 'list.kanban']">
<component :is="Component" />
</transition>
</keep-alive>
</router-view>
<transition name="modal">
<modal
v-if="currentModal"
@close="closeModal()"
variant="scrolling"
class="task-detail-view-modal"
>
<component :is="currentModal" />
</modal>
</transition>
<a
class="keyboard-shortcuts-button"
@click="showKeyboardShortcuts()"
@ -35,13 +53,13 @@
>
<icon icon="keyboard"/>
</a>
</div>
</main>
</div>
</div>
</template>
<script lang="ts" setup>
import {watch, computed} from 'vue'
import {watch, computed, shallowRef, watchEffect, VNode, h} from 'vue'
import {useStore} from 'vuex'
import {useRoute, useRouter} from 'vue-router'
import {useEventListener} from '@vueuse/core'
@ -49,6 +67,59 @@ import {useEventListener} from '@vueuse/core'
import {CURRENT_LIST, KEYBOARD_SHORTCUTS_ACTIVE, MENU_ACTIVE} from '@/store/mutation-types'
import Navigation from '@/components/home/navigation.vue'
import QuickActions from '@/components/quick-actions/quick-actions.vue'
import BaseButton from '@/components/base/BaseButton.vue'
function useRouteWithModal() {
const router = useRouter()
const route = useRoute()
const backdropView = computed(() => route.fullPath && window.history.state.backdropView)
const routeWithModal = computed(() => {
return backdropView.value
? router.resolve(backdropView.value)
: route
})
const currentModal = shallowRef<VNode>()
watchEffect(() => {
if (!backdropView.value) {
currentModal.value = undefined
return
}
// logic from vue-router
// https://github.com/vuejs/vue-router-next/blob/798cab0d1e21f9b4d45a2bd12b840d2c7415f38a/src/RouterView.ts#L125
const routePropsOption = route.matched[0]?.props.default
const routeProps = routePropsOption
? routePropsOption === true
? route.params
: typeof routePropsOption === 'function'
? routePropsOption(route)
: routePropsOption
: null
currentModal.value = h(
route.matched[0]?.components.default,
routeProps,
)
})
function closeModal() {
const historyState = computed(() => route.fullPath && window.history.state)
if (historyState.value) {
router.back()
} else {
const backdropRoute = historyState.value?.backdropView && router.resolve(historyState.value.backdropView)
router.push(backdropRoute)
}
}
return { routeWithModal, currentModal, closeModal }
}
const { routeWithModal, currentModal, closeModal } = useRouteWithModal()
const store = useStore()
@ -223,4 +294,6 @@ store.dispatch('labels/loadAllLabels')
display: none;
}
}
@include modal-transition();
</style>

View File

@ -1,8 +1,8 @@
<template>
<div :class="{'is-active': menuActive}" class="namespace-container">
<div class="menu top-menu">
<aside :class="{'is-active': menuActive}" class="namespace-container">
<nav class="menu top-menu">
<router-link :to="{name: 'home'}" class="logo">
<Logo width="164" height="48" />
<Logo width="164" height="48"/>
</router-link>
<ul class="menu-list">
<li>
@ -46,31 +46,35 @@
</router-link>
</li>
</ul>
</div>
</nav>
<aside class="menu namespaces-lists loader-container is-loading-small" :class="{'is-loading': loading}">
<template v-for="(n, nk) in namespaces" :key="n.id" >
<nav class="menu namespaces-lists loader-container is-loading-small" :class="{'is-loading': loading}">
<template v-for="(n, nk) in namespaces" :key="n.id">
<div class="namespace-title" :class="{'has-menu': n.id > 0}">
<span
@click="toggleLists(n.id)"
class="menu-label"
v-tooltip="namespaceTitles[nk]">
v-tooltip="namespaceTitles[nk]"
>
<span
v-if="n.hexColor !== ''"
:style="{ backgroundColor: n.hexColor }"
class="color-bubble"
/>
<span class="name">
<span
:style="{ backgroundColor: n.hexColor }"
class="color-bubble"
v-if="n.hexColor !== ''">
</span>
{{ namespaceTitles[nk] }}
</span>
<a
class="icon is-small toggle-lists-icon pl-2"
:class="{'active': typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true}"
@click="toggleLists(n.id)"
>
<icon icon="chevron-down"/>
</a>
<span class="count" :class="{'ml-2 mr-0': n.id > 0}">
({{ namespaceListsCount[nk] }})
</span>
</span>
<a
class="icon is-small toggle-lists-icon"
:class="{'active': typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true}"
@click="toggleLists(n.id)"
>
<icon icon="chevron-down"/>
</a>
<namespace-settings-dropdown :namespace="n" v-if="n.id > 0"/>
</div>
<div
@ -81,18 +85,20 @@
<!--
NOTE: a v-model / computed setter is not possible, since the updateActiveLists function
triggered by the change needs to have access to the current namespace
-->
-->
<draggable
v-bind="dragOptions"
:modelValue="activeLists[nk]"
@update:modelValue="(lists) => updateActiveLists(n, lists)"
:group="`namespace-${n.id}-lists`"
group="namespace-lists"
@start="() => drag = true"
@end="e => saveListPosition(e, nk)"
@end="saveListPosition"
handle=".handle"
:disabled="n.id < 0 || null"
tag="transition-group"
item-key="id"
:data-namespace-id="n.id"
:data-namespace-index="nk"
:component-data="{
type: 'transition',
tag: 'ul',
@ -134,7 +140,7 @@
:class="{'is-favorite': l.isFavorite}"
@click.prevent.stop="toggleFavoriteList(l)"
class="favorite">
<icon :icon="l.isFavorite ? 'star' : ['far', 'star']" />
<icon :icon="l.isFavorite ? 'star' : ['far', 'star']"/>
</span>
</a>
</router-link>
@ -145,9 +151,9 @@
</draggable>
</div>
</template>
</aside>
<PoweredByLink />
</div>
</nav>
<PoweredByLink/>
</aside>
</template>
<script>
@ -194,13 +200,13 @@ export default {
loading: state => state[LOADING] && state[LOADING_MODULE] === 'namespaces',
}),
activeLists() {
return this.namespaces.map(({lists}) => lists?.filter(item => !item.isArchived))
return this.namespaces.map(({lists}) => lists?.filter(item => typeof item !== 'undefined' && !item.isArchived))
},
namespaceTitles() {
return this.namespaces.map((namespace, index) => {
const title = this.getNamespaceTitle(namespace)
return `${title} (${this.activeLists[index]?.length ?? 0})`
})
return this.namespaces.map((namespace) => this.getNamespaceTitle(namespace))
},
namespaceListsCount() {
return this.namespaces.map((_, index) => this.activeLists[index]?.length ?? 0)
},
},
beforeCreate() {
@ -237,15 +243,15 @@ export default {
this.listsVisible[namespaceId] = !this.listsVisible[namespaceId]
},
updateActiveLists(namespace, activeLists) {
// this is a bit hacky: since we do have to filter out the archived items from the list
// This is a bit hacky: since we do have to filter out the archived items from the list
// for vue draggable updating it is not as simple as replacing it.
// instead we iterate over the non archived items in the old list and replace them with the ones in their new order
const lists = namespace.lists.map((item) => {
if (item.isArchived) {
return item
}
return activeLists.shift()
})
// To work around this, we merge the active lists with the archived ones. Doing so breaks the order
// because now all archived lists are sorted after the active ones. This is fine because they are sorted
// later when showing them anyway, and it makes the merging happening here a lot easier.
const lists = [
...activeLists,
...namespace.lists.filter(l => l.isArchived),
]
const newNamespace = {
...namespace,
@ -255,8 +261,11 @@ export default {
this.$store.commit('namespaces/setNamespaceById', newNamespace)
},
async saveListPosition(e, namespaceIndex) {
const listsActive = this.activeLists[namespaceIndex]
async saveListPosition(e) {
const namespaceId = parseInt(e.to.dataset.namespaceId)
const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex)
const listsActive = this.activeLists[newNamespaceIndex]
const list = listsActive[e.newIndex]
const listBefore = listsActive[e.newIndex - 1] ?? null
const listAfter = listsActive[e.newIndex + 1] ?? null
@ -269,6 +278,7 @@ export default {
await this.$store.dispatch('lists/updateList', {
...list,
position,
namespaceId,
})
} finally {
this.listUpdating[list.id] = false
@ -365,8 +375,9 @@ $vikunja-nav-selected-width: 0.4rem;
.menu-label {
.color-bubble {
width: 14px !important;
height: 14px !important;
width: 14px;
height: 14px;
flex-basis: auto;
}
.is-archived {
@ -387,6 +398,12 @@ $vikunja-nav-selected-width: 0.4rem;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-right: auto;
}
.count {
color: var(--grey-500);
margin-right: .5rem;
}
}
@ -482,7 +499,7 @@ $vikunja-nav-selected-width: 0.4rem;
height: 1rem;
vertical-align: middle;
padding-right: 0.5rem;
&.handle {
opacity: 0;
transition: opacity $transition;
@ -490,7 +507,7 @@ $vikunja-nav-selected-width: 0.4rem;
cursor: grab;
}
}
&:hover .icon.handle {
opacity: 1;
}
@ -542,7 +559,7 @@ $vikunja-nav-selected-width: 0.4rem;
span.list-menu-link, li > a {
padding-left: 2rem;
display: inline-block;
.icon {
padding-bottom: .25rem;
}

View File

@ -1,9 +1,8 @@
<template>
<nav
<header
:class="{'has-background': background}"
aria-label="main navigation"
class="navbar main-theme is-fixed-top"
role="navigation"
>
<router-link :to="{name: 'home'}" class="logo-link">
<Logo width="164" height="48"/>
@ -77,7 +76,7 @@
</dropdown>
</div>
</div>
</nav>
</header>
</template>
<script>

View File

@ -85,4 +85,8 @@ export default {
margin-left: .5rem;
}
}
.dark .update-notification {
color: var(--grey-200);
}
</style>

View File

@ -128,10 +128,6 @@ export default {
date: null,
show: false,
changed: false,
// Since flatpickr dates are strings, we need to convert them to native date objects.
// To make that work, we need a separate variable since flatpickr does not have a change event.
flatPickrDate: null,
}
},
components: {
@ -164,10 +160,6 @@ export default {
handler: 'setDateValue',
immediate: true,
},
flatPickrDate(newVal) {
this.date = createDateFromString(newVal)
this.updateData()
},
},
computed: {
flatPickerConfig() {
@ -183,6 +175,17 @@ export default {
},
}
},
// Since flatpickr dates are strings, we need to convert them to native date objects.
// To make that work, we need a separate variable since flatpickr does not have a change event.
flatPickrDate: {
set(newValue) {
this.date = createDateFromString(newValue)
this.updateData()
},
get() {
return format(this.date, 'yyy-LL-dd H:mm')
},
},
},
methods: {
setDateValue(newVal) {

View File

@ -312,8 +312,6 @@ export default {
@import './vue-easymde/vue-easymde.css';
@import 'highlight.js/scss/base16/equilibrium-gray-light';
$editor-border-color: #ddd;
.editor {
.clear {
clear: both;
@ -337,7 +335,7 @@ $editor-border-color: #ddd;
.CodeMirror {
padding: .5rem;
border: 1px solid $editor-border-color;
border: 1px solid var(--grey-200) !important;
background: var(--white);
&-lines pre {
@ -348,6 +346,10 @@ $editor-border-color: #ddd;
color: var(--grey-400) !important;
font-style: italic;
}
&-cursor {
border-color: var(--grey-700);
}
}
.editor-preview {
@ -359,12 +361,13 @@ $editor-border-color: #ddd;
}
.editor-toolbar {
background: #ffffff;
border-top: 1px solid $editor-border-color;
border-left: 1px solid $editor-border-color;
border-right: 1px solid $editor-border-color;
background: var(--grey-50);
border: 1px solid var(--grey-200);
border-bottom: none;
button {
color: var(--grey-700);
svg {
vertical-align: middle;
@ -379,6 +382,15 @@ $editor-border-color: #ddd;
top: 24px;
margin-left: -3px;
}
&:hover {
background: var(--grey-200);
border-color: var(--grey-300);
}
}
i.separator {
border-color: var(--grey-200) !important;
}
}

View File

@ -14,112 +14,112 @@ export function createEasyMDEConfig({ placeholder, uploadImage, imageUploadFunct
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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-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>',
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',
@ -127,7 +127,7 @@ export function createEasyMDEConfig({ placeholder, uploadImage, imageUploadFunct
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-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>',
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

@ -0,0 +1,85 @@
<template>
<div class="password-field">
<input
class="input"
id="password"
name="password"
:placeholder="$t('user.auth.passwordPlaceholder')"
required
:type="passwordFieldType"
autocomplete="current-password"
@keyup.enter="e => $emit('submit', e)"
:tabindex="props.tabindex"
@focusout="validate"
@input="handleInput"
/>
<a
@click="togglePasswordFieldType"
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'"/>
</a>
</div>
<p class="help is-danger" v-if="!isValid">
{{ $t('user.auth.passwordRequired') }}
</p>
</template>
<script lang="ts" setup>
import {ref, watch} from 'vue'
import {useDebounceFn} from '@vueuse/core'
const props = defineProps({
tabindex: String,
modelValue: String,
// This prop is a workaround to trigger validation from the outside when the user never had focus in the input.
validateInitially: Boolean,
})
const emit = defineEmits(['submit', 'update:modelValue'])
const passwordFieldType = ref<String>('password')
const password = ref<String>('')
const isValid = ref<Boolean>(!props.validateInitially)
watch(
() => props.validateInitially,
(doValidate: Boolean) => {
if (doValidate) {
validate()
}
},
)
function validate() {
useDebounceFn(() => {
isValid.value = password.value !== ''
}, 100)()
}
function togglePasswordFieldType() {
passwordFieldType.value = passwordFieldType.value === 'password'
? 'text'
: 'password'
}
function handleInput(e) {
password.value = e.target.value
emit('update:modelValue', e.target.value)
}
</script>
<style scoped>
.password-field {
position: relative;
}
.password-field-type-toggle {
position: absolute;
color: var(--grey-400);
top: 50%;
right: 1rem;
transform: translateY(-50%);
}
</style>

View File

@ -2,21 +2,22 @@
<dropdown>
<template v-if="isSavedFilter">
<dropdown-item
:to="{ name: `${listRoutePrefix}.edit`, params: { listId: list.id } }"
:to="{ name: 'filter.settings.edit', params: { listId: list.id } }"
icon="pen"
>
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.delete`, params: { listId: list.id } }"
:to="{ name: 'filter.settings.delete', params: { listId: list.id } }"
icon="trash-alt"
>
{{ $t('misc.delete') }}
</dropdown-item>
</template>
<template v-else-if="list.isArchived">
<dropdown-item
:to="{ name: `${listRoutePrefix}.archive`, params: { listId: list.id } }"
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
icon="archive"
>
{{ $t('menu.unarchive') }}
@ -24,37 +25,38 @@
</template>
<template v-else>
<dropdown-item
:to="{ name: `${listRoutePrefix}.edit`, params: { listId: list.id } }"
:to="{ name: 'list.settings.edit', params: { listId: list.id } }"
icon="pen"
>
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.background`, params: { listId: list.id } }"
v-if="backgroundsEnabled"
:to="{ name: 'list.settings.background', params: { listId: list.id } }"
icon="image"
>
{{ $t('menu.setBackground') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.share`, params: { listId: list.id } }"
:to="{ name: 'list.settings.share', params: { listId: list.id } }"
icon="share-alt"
>
{{ $t('menu.share') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.duplicate`, params: { listId: list.id } }"
:to="{ name: 'list.settings.duplicate', params: { listId: list.id } }"
icon="paste"
>
{{ $t('menu.duplicate') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.archive`, params: { listId: list.id } }"
:to="{ name: 'list.settings.archive', params: { listId: list.id } }"
icon="archive"
>
{{ $t('menu.archive') }}
</dropdown-item>
<task-subscription
v-if="subscription"
class="dropdown-item has-no-shadow"
:is-button="false"
entity="list"
@ -63,7 +65,7 @@
@change="sub => subscription = sub"
/>
<dropdown-item
:to="{ name: `${listRoutePrefix}.delete`, params: { listId: list.id } }"
:to="{ name: 'list.settings.delete', params: { listId: list.id } }"
icon="trash-alt"
class="has-text-danger"
>
@ -73,56 +75,32 @@
</dropdown>
</template>
<script>
<script setup lang="ts">
import {ref, computed, watchEffect} from 'vue'
import {useStore} from 'vuex'
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue'
import TaskSubscription from '@/components/misc/subscription.vue'
import ListModel from '@/models/list'
import SubscriptionModel from '@/models/subscription'
export default {
name: 'list-settings-dropdown',
data() {
return {
subscription: null,
}
const props = defineProps({
list: {
type: ListModel,
required: true,
},
components: {
TaskSubscription,
DropdownItem,
Dropdown,
},
props: {
list: {
required: true,
},
},
mounted() {
this.subscription = this.list.subscription
},
computed: {
backgroundsEnabled() {
return this.$store.state.config.enabledBackgroundProviders !== null && this.$store.state.config.enabledBackgroundProviders.length > 0
},
listRoutePrefix() {
let name = 'list'
})
const subscription = ref<SubscriptionModel>()
watchEffect(() => {
if (props.list.subscription) {
subscription.value = props.list.subscription
}
})
if (this.$route.name !== null && this.$route.name.startsWith('list.')) {
// HACK: we should implement a better routing for the modals
const settingsRoutes = ['edit', 'delete', 'archive', 'background', 'share', 'duplicate']
const suffix = settingsRoutes.find((route) => this.$route.name.endsWith(`.settings.${route}`))
name = this.$route.name.replace(`.settings.${suffix}`,'')
}
if (this.isSavedFilter) {
name = name.replace('list.', 'filter.')
}
return `${name}.settings`
},
isSavedFilter() {
return getSavedFilterIdFromListId(this.list.id) > 0
},
},
}
const store = useStore()
const backgroundsEnabled = computed(() => store.state.config.enabledBackgroundProviders?.length > 0)
const isSavedFilter = computed(() => getSavedFilterIdFromListId(props.list.id) > 0)
</script>

View File

@ -29,9 +29,10 @@
<script>
import Filters from '@/components/list/partials/filters'
import {getDefaultParams} from '@/components/tasks/mixins/taskList'
import Popup from '@/components/misc/popup'
import {getDefaultParams} from '@/composables/taskList'
export default {
name: 'filter-popup',
components: {

View File

@ -191,7 +191,7 @@ import NamespaceService from '@/services/namespace'
import EditLabels from '@/components/tasks/partials/editLabels.vue'
import {objectToSnakeCase} from '@/helpers/case'
import {getDefaultParams} from '@/components/tasks/mixins/taskList'
import {getDefaultParams} from '@/composables/taskList'
// FIXME: merge with DEFAULT_PARAMS in taskList.js
const DEFAULT_PARAMS = {

View File

@ -28,19 +28,20 @@
</template>
<script lang="ts" setup>
import {ref, watch} from 'vue'
import {PropType, ref, watch} from 'vue'
import {useStore} from 'vuex'
import ListService from '@/services/list'
import {colorIsDark} from '@/helpers/color/colorIsDark'
import ListModel from '@/models/list'
const background = ref<string | null>(null)
const backgroundLoading = ref(false)
const props = defineProps({
list: {
type: Object,
type: Object as PropType<ListModel>,
required: true,
},
showArchived: {
@ -68,7 +69,7 @@ async function loadBackground() {
const store = useStore()
function toggleFavoriteList(list) {
function toggleFavoriteList(list: ListModel) {
// The favorites pseudo list is always favorite
// Archived lists cannot be marked favorite
if (list.id === -1 || list.isArchived) {

View File

@ -4,9 +4,11 @@
<template v-for="(s, i) in shortcuts" :key="i">
<h3>{{ $t(s.title) }}</h3>
<message>
<message class="mb-4" v-if="s.available">
{{
s.available($route) ? $t('keyboardShortcuts.currentPageOnly') : $t('keyboardShortcuts.allPages')
s.available($route)
? $t('keyboardShortcuts.currentPageOnly')
: $t('keyboardShortcuts.allPages')
}}
</message>
@ -17,7 +19,8 @@
class="shortcut-keys"
is="dd"
:keys="sc.keys"
:combination="typeof sc.combination !== 'undefined' ? $t(`keyboardShortcuts.${sc.combination}`) : null"/>
:combination="sc.combination && $t(`keyboardShortcuts.${sc.combination}`)"
/>
</template>
</dl>
</template>
@ -25,28 +28,18 @@
</modal>
</template>
<script>
import {KEYBOARD_SHORTCUTS_ACTIVE} from '@/store/mutation-types'
import Shortcut from '@/components/misc/shortcut.vue'
import Message from '@/components/misc/message'
import {KEYBOARD_SHORTCUTS} from './shortcuts'
<script lang="ts" setup>
import {useStore} from 'vuex'
export default {
name: 'keyboard-shortcuts',
components: {
Message,
Shortcut,
},
data() {
return {
shortcuts: KEYBOARD_SHORTCUTS,
}
},
methods: {
close() {
this.$store.commit(KEYBOARD_SHORTCUTS_ACTIVE, false)
},
},
import Shortcut from '@/components/misc/shortcut.vue'
import Message from '@/components/misc/message.vue'
import {KEYBOARD_SHORTCUTS_ACTIVE} from '@/store/mutation-types'
import {KEYBOARD_SHORTCUTS as shortcuts} from './shortcuts'
const store = useStore()
function close() {
store.commit(KEYBOARD_SHORTCUTS_ACTIVE, false)
}
</script>

View File

@ -1,11 +1,24 @@
import {RouteLocation} from 'vue-router'
import {isAppleDevice} from '@/helpers/isAppleDevice'
const ctrl = isAppleDevice() ? '⌘' : 'ctrl'
export const KEYBOARD_SHORTCUTS = [
interface Shortcut {
title: string
keys: string[]
combination?: 'then'
}
interface ShortcutGroup {
title: string
available?: (route: RouteLocation) => boolean
shortcuts: Shortcut[]
}
export const KEYBOARD_SHORTCUTS : ShortcutGroup[] = [
{
title: 'keyboardShortcuts.general',
available: () => null,
shortcuts: [
{
title: 'keyboardShortcuts.toggleMenu',
@ -29,7 +42,7 @@ export const KEYBOARD_SHORTCUTS = [
},
{
title: 'keyboardShortcuts.list.title',
available: (route) => route.name.startsWith('list.'),
available: (route) => (route.name as string)?.startsWith('list.'),
shortcuts: [
{
title: 'keyboardShortcuts.list.switchToListView',
@ -55,13 +68,7 @@ export const KEYBOARD_SHORTCUTS = [
},
{
title: 'keyboardShortcuts.task.title',
available: (route) => [
'task.detail',
'task.list.detail',
'task.gantt.detail',
'task.kanban.detail',
'task.detail',
].includes(route.name),
available: (route) => route.name === 'task.detail',
shortcuts: [
{
title: 'keyboardShortcuts.task.assign',

View File

@ -1,18 +1,35 @@
<template>
<div class="message-wrapper">
<div class="message" :class="variant">
<div class="message" :class="[variant, textAlignClass]">
<slot/>
</div>
</div>
</template>
<script lang="ts" setup>
defineProps({
import {computed, 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,
default: 'info',
},
textAlign: {
type: String as PropType<textAlignVariants>,
default: 'left',
},
})
const textAlignClass = computed(() => TEXT_ALIGN_MAP[props.textAlign])
</script>
<style lang="scss" scoped>

View File

@ -14,6 +14,9 @@
<div>
<h2 class="title" v-if="title">{{ title }}</h2>
<api-config/>
<Message v-if="motd !== ''" class="is-hidden-tablet mb-4">
{{ motd }}
</Message>
<slot/>
</div>
<legal/>
@ -38,8 +41,8 @@ const store = useStore()
const {t} = useI18n()
const motd = computed(() => store.state.config.motd)
// @ts-ignore
const title = computed(() => t(route.meta.title ?? ''))
const title = computed(() => t(route.meta?.title as string || ''))
useTitle(() => title.value)
</script>

View File

@ -52,9 +52,15 @@ import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl'
import {useOnline} from '@/composables/useOnline'
import {useRouter, useRoute} from 'vue-router'
import {getAuthForRoute} from '@/router'
const router = useRouter()
const route = useRoute()
const store = useStore()
const ready = computed(() => store.state.vikunjaReady)
const ready = ref(false)
const online = useOnline()
const error = ref('')
@ -63,7 +69,12 @@ const showLoading = computed(() => !ready.value && error.value === '')
async function load() {
try {
await store.dispatch('loadApp')
} catch(e: any) {
const redirectTo = getAuthForRoute(route)
if (typeof redirectTo !== 'undefined') {
await router.push(redirectTo)
}
ready.value = true
} catch (e: any) {
error.value = e
}
}

View File

@ -1,53 +1,51 @@
<template>
<x-button
v-if="isButton"
variant="secondary"
:icon="icon"
:icon="iconName"
v-tooltip="tooltipText"
@click="changeSubscription"
:disabled="disabled || null"
v-if="isButton"
>
{{ buttonText }}
</x-button>
<a
<BaseButton
v-else
v-tooltip="tooltipText"
@click="changeSubscription"
:class="{'is-disabled': disabled}"
v-else
>
<span class="icon">
<icon :icon="icon"/>
<icon :icon="iconName"/>
</span>
{{ buttonText }}
</a>
</BaseButton>
</template>
<script lang="ts" setup>
import {computed, shallowRef} from 'vue'
import {useI18n} from 'vue-i18n'
import BaseButton from '@/components/base/BaseButton.vue'
import SubscriptionService from '@/services/subscription'
import SubscriptionModel from '@/models/subscription'
import {success} from '@/message'
const props = defineProps({
entity: {
required: true,
type: String,
},
subscription: {
required: true,
},
entityId: {
required: true,
},
isButton: {
type: Boolean,
default: true,
},
interface Props {
entity: string
entityId: number
subscription: SubscriptionModel
isButton?: boolean
}
const props = withDefaults(defineProps<Props>(), {
isButton: true,
})
const subscriptionEntity = computed<string>(() => props.subscription.entity)
const emit = defineEmits(['change'])
const subscriptionService = shallowRef(new SubscriptionService())
@ -57,7 +55,7 @@ const tooltipText = computed(() => {
if (disabled.value) {
return t('task.subscription.subscribedThroughParent', {
entity: props.entity,
parent: props.subscription.entity,
parent: subscriptionEntity.value,
})
}
@ -67,13 +65,13 @@ const tooltipText = computed(() => {
})
const buttonText = computed(() => props.subscription !== null ? t('task.subscription.unsubscribe') : t('task.subscription.subscribe'))
const icon = computed(() => props.subscription !== null ? ['far', 'bell-slash'] : 'bell')
const iconName = computed(() => props.subscription !== null ? ['far', 'bell-slash'] : 'bell')
const disabled = computed(() => {
if (props.subscription === null) {
return false
}
return props.subscription.entity !== props.entity
return subscriptionEntity.value !== props.entity
})
function changeSubscription() {

View File

@ -1,4 +1,5 @@
<template>
<!-- FIXME: transition should not be included in the modal -->
<transition name="modal">
<section
v-if="enabled"
@ -21,6 +22,13 @@
'is-wide': wide
}"
>
<BaseButton
@click="emit('close')"
class="close"
>
<icon icon="times"/>
</BaseButton>
<slot>
<div class="header">
<slot name="header"></slot>
@ -53,6 +61,8 @@
</template>
<script>
import BaseButton from '@/components/base/BaseButton.vue'
export const TRANSITION_NAMES = {
MODAL: 'modal',
FADE: 'fade',
@ -70,6 +80,11 @@ function validValue(values) {
export default {
name: 'modal',
components: {
BaseButton,
},
mounted() {
document.addEventListener('keydown', (e) => {
// Close the model when escape is pressed
@ -197,17 +212,22 @@ export default {
}
}
.close {
position: fixed;
top: 5px;
right: 26px;
color: var(--white);
font-size: 2rem;
/* Transitions */
.modal-enter,
.modal-leave-active {
opacity: 0;
@media screen and (max-width: $desktop) {
color: var(--dark);
}
}
</style>
.modal-enter .modal-container,
.modal-leave-active .modal-container {
transform: scale(0.9);
<style lang="scss">
// Close icon SVG uses currentColor, change the color to keep it visible
.dark .task-detail-view-modal .close {
color: var(--grey-900);
}
</style>

View File

@ -13,6 +13,7 @@
import {ref, computed} from 'vue'
import {useStore} from 'vuex'
import Multiselect from '@/components/input/multiselect.vue'
import NamespaceModel from '@/models/namespace'
const emit = defineEmits(['selected'])
@ -25,7 +26,7 @@ function findNamespaces(newQuery: string) {
query.value = newQuery
}
function select(namespace) {
function select(namespace: NamespaceModel) {
emit('selected', namespace)
}
</script>

View File

@ -16,13 +16,13 @@
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'namespace.settings.share', params: { id: namespace.id } }"
:to="{ name: 'namespace.settings.share', params: { namespaceId: namespace.id } }"
icon="share-alt"
>
{{ $t('menu.share') }}
</dropdown-item>
<dropdown-item
:to="{ name: 'list.create', params: { id: namespace.id } }"
:to="{ name: 'list.create', params: { namespaceId: namespace.id } }"
icon="plus"
>
{{ $t('menu.newList') }}
@ -34,6 +34,7 @@
{{ $t('menu.archive') }}
</dropdown-item>
<task-subscription
v-if="subscription"
class="dropdown-item has-no-shadow"
:is-button="false"
entity="namespace"

View File

@ -264,4 +264,6 @@ export default {
.sharables-list:not(.card-content) {
overflow-y: auto
}
@include modal-transition();
</style>

View File

@ -365,3 +365,7 @@ export default {
},
}
</script>
<style lang="scss" scoped>
@include modal-transition();
</style>

View File

@ -67,7 +67,7 @@
<router-link
class="mt-2 has-text-centered is-block"
:to="{name: 'task.detail', params: {id: taskEditTask.id}}"
:to="taskDetailRoute"
>
{{ $t('task.openDetail') }}
</router-link>
@ -97,6 +97,15 @@ export default {
taskEditTask: TaskModel,
}
},
computed: {
taskDetailRoute() {
return {
name: 'task.detail',
params: { id: this.taskEditTask.id },
state: { backdropView: this.$router.currentRoute.value.fullPath },
}
},
},
components: {
ColorPicker,
Reminders,

View File

@ -1,101 +0,0 @@
import TaskCollectionService from '@/services/taskCollection'
// FIXME: merge with DEFAULT_PARAMS in filters.vue
export const getDefaultParams = () => ({
sort_by: ['position', 'id'],
order_by: ['asc', 'desc'],
filter_by: ['done'],
filter_value: ['false'],
filter_comparator: ['equals'],
filter_concat: 'and',
})
/**
* This mixin provides a base set of methods and properties to get tasks on a list.
*/
export default {
data() {
return {
taskCollectionService: new TaskCollectionService(),
tasks: [],
currentPage: 0,
loadedList: null,
searchTerm: '',
showTaskFilter: false,
params: {...getDefaultParams()},
}
},
watch: {
// Only listen for query path changes
'$route.query': {
handler: 'loadTasksForPage',
immediate: true,
},
'$route.path': 'loadTasksOnSavedFilter',
},
methods: {
async loadTasks(
page,
search = '',
params = null,
forceLoading = false,
) {
// Because this function is triggered every time on topNavigation, we're putting a condition here to only load it when we actually want to show tasks
// FIXME: This is a bit hacky -> Cleanup.
if (
this.$route.name !== 'list.list' &&
this.$route.name !== 'list.table' &&
!forceLoading
) {
return
}
if (params === null) {
params = this.params
}
if (search !== '') {
params.s = search
}
const list = {listId: parseInt(this.$route.params.listId)}
const currentList = {
id: list.listId,
params,
search,
page,
}
if (JSON.stringify(currentList) === JSON.stringify(this.loadedList) && !forceLoading) {
return
}
this.tasks = []
this.tasks = await this.taskCollectionService.getAll(list, params, page)
this.currentPage = page
this.loadedList = JSON.parse(JSON.stringify(currentList))
},
loadTasksForPage(e) {
// The page parameter can be undefined, in the case where the user loads a new list from the side bar menu
let page = Number(e.page)
if (typeof e.page === 'undefined') {
page = 1
}
let search = e.search
if (typeof e.search === 'undefined') {
search = ''
}
this.initTasks(page, search)
},
loadTasksOnSavedFilter() {
if (typeof this.$route.params.listId !== 'undefined' && parseInt(this.$route.params.listId) < 0) {
this.loadTasks(1, '', null, true)
}
},
},
}

View File

@ -34,7 +34,7 @@
>
<div class="filename">{{ a.file.name }}</div>
<div class="info">
<p class="collapses">
<p class="attachment-info-meta">
<i18n-t keypath="task.attachment.createdBy">
<span v-tooltip="formatDate(a.created)">
{{ formatDateSince(a.created) }}
@ -289,21 +289,6 @@ export default {
content: '·';
padding: 0 .25rem;
}
@media screen and (max-width: $mobile) {
&.collapses {
flex-direction: column;
> span:not(:last-child):after,
> a:not(:last-child):after {
display: none;
}
.user .username {
display: none;
}
}
}
}
}
}
@ -341,6 +326,10 @@ export default {
height: auto;
text-shadow: var(--shadow-md);
animation: bounce 2s infinite;
@media (prefers-reduced-motion: reduce) {
animation: none;
}
}
.hint {
@ -357,6 +346,35 @@ export default {
}
}
.attachment-info-meta {
display: flex;
align-items: center;
:deep(.user) {
display: flex !important;
align-items: center;
margin: 0 .5rem;
}
@media screen and (max-width: $mobile) {
flex-direction: column;
align-items: flex-start;
:deep(.user) {
margin: .5rem 0;
}
> span:not(:last-child):after,
> a:not(:last-child):after {
display: none;
}
.user .username {
display: none;
}
}
}
@keyframes bounce {
from,
20%,
@ -382,4 +400,6 @@ export default {
transform: translate3d(0, -4px, 0);
}
}
@include modal-transition();
</style>

View File

@ -162,7 +162,7 @@ import {mapState} from 'vuex'
export default {
name: 'comments',
components: {
editor: AsyncEditor,
Editor: AsyncEditor,
},
props: {
taskId: {
@ -339,4 +339,6 @@ export default {
.media-content {
width: calc(100% - 48px - 2rem);
}
@include modal-transition();
</style>

View File

@ -1,21 +1,27 @@
<template>
<p class="created">
<i18n-t keypath="task.detail.created">
<span v-tooltip="formatDate(task.created)">{{ formatDateSince(task.created) }}</span>
{{ task.createdBy.getDisplayName() }}
</i18n-t>
<time :datetime="formatISO(task.created)" v-tooltip="formatDate(task.created)">
<i18n-t keypath="task.detail.created">
<span>{{ formatDateSince(task.created) }}</span>
{{ task.createdBy.getDisplayName() }}
</i18n-t>
</time>
<template v-if="+new Date(task.created) !== +new Date(task.updated)">
<br/>
<!-- Computed properties to show the actual date every time it gets updated -->
<i18n-t keypath="task.detail.updated">
<span v-tooltip="updatedFormatted">{{ updatedSince }}</span>
</i18n-t>
<time :datetime="formatISO(task.updated)" v-tooltip="updatedFormatted">
<i18n-t keypath="task.detail.updated">
<span>{{ updatedSince }}</span>
</i18n-t>
</time>
</template>
<template v-if="task.done">
<br/>
<i18n-t keypath="task.detail.doneAt">
<span v-tooltip="doneFormatted">{{ doneSince }}</span>
</i18n-t>
<time :datetime="formatISO(task.doneAt)" v-tooltip="doneFormatted">
<i18n-t keypath="task.detail.doneAt">
<span>{{ doneSince }}</span>
</i18n-t>
</time>
</template>
</p>
</template>

View File

@ -1,6 +1,8 @@
<template>
<td v-tooltip="+date === 0 ? '' : formatDate(date)">
{{ +date === 0 ? '-' : formatDateSince(date) }}
<time :datetime="date ? formatISO(date) : null">
{{ +date === 0 ? '-' : formatDateSince(date) }}
</time>
</td>
</template>

View File

@ -38,7 +38,7 @@ import {mapState} from 'vuex'
export default {
name: 'description',
components: {
editor: AsyncEditor,
Editor: AsyncEditor,
},
data() {
return {

View File

@ -19,19 +19,19 @@
:style="{'background': props.item.hexColor, 'color': props.item.textColor}"
class="tag">
<span>{{ props.item.title }}</span>
<a @click="removeLabel(props.item)" class="delete is-small"></a>
<button type="button" v-cy="'taskDetail.removeLabel'" @click="removeLabel(props.item)" class="delete is-small" />
</span>
</template>
<template #searchResult="props">
<span
v-if="typeof props.option === 'string'"
class="tag">
class="tag search-result">
<span>{{ props.option }}</span>
</span>
<span
v-else
:style="{'background': props.option.hexColor, 'color': props.option.textColor}"
class="tag">
class="tag search-result">
<span>{{ props.option.title }}</span>
</span>
</template>
@ -114,23 +114,17 @@ export default {
},
async removeLabel(label) {
const removeFromState = () => {
for (const l in this.labels) {
if (this.labels[l].id === label.id) {
this.labels.splice(l, 1)
}
if (!this.taskId === 0) {
await this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
}
for (const l in this.labels) {
if (this.labels[l].id === label.id) {
this.labels.splice(l, 1)
}
this.$emit('update:modelValue', this.labels)
this.$emit('change', this.labels)
}
if (this.taskId === 0) {
removeFromState()
return
}
await this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
removeFromState()
this.$emit('update:modelValue', this.labels)
this.$emit('change', this.labels)
this.$message.success({message: this.$t('task.label.removeSuccess')})
},
@ -152,6 +146,18 @@ export default {
<style lang="scss" scoped>
.tag {
margin: .5rem 0 0 .5rem;
margin: .25rem !important;
}
.tag.search-result {
margin: 0 !important;
}
:deep(.input-wrapper) {
padding: .25rem !important;
}
:deep(input.input) {
padding: 0 .5rem;
}
</style>

View File

@ -7,8 +7,8 @@
'has-light-text': !colorIsDark(task.hexColor) && task.hexColor !== `#${task.defaultColor}` && task.hexColor !== task.defaultColor,
}"
:style="{'background-color': task.hexColor !== '#' && task.hexColor !== `#${task.defaultColor}` ? task.hexColor : false}"
@click.exact="openTaskDetail()"
@click.ctrl="() => toggleTaskDone(task)"
@click.exact="() => $router.push({ name: 'task.kanban.detail', params: { id: task.id } })"
@click.meta="() => toggleTaskDone(task)"
>
<span class="task-id">
@ -28,9 +28,9 @@
<span class="icon">
<icon :icon="['far', 'calendar-alt']"/>
</span>
<span>
<time :datetime="formatISO(task.dueDate)">
{{ formatDateSince(task.dueDate) }}
</span>
</time>
</span>
<h3>{{ task.title }}</h3>
<progress
@ -115,6 +115,13 @@ export default {
this.loadingInternal = false
}
},
openTaskDetail() {
this.$router.push({
name: 'task.detail',
params: { id: this.task.id },
state: { backdropView: this.$router.currentRoute.value.fullPath },
})
},
},
}
</script>

View File

@ -274,10 +274,11 @@ export default {
return tasks
.map(task => {
// by doing this here once we can save a lot of duplicate calls in the template
const listAndNamespace = this.$store.getters['namespaces/getListAndNamespaceById'](task.listId, true)
const {
list,
namespace,
} = this.$store.getters['namespaces/getListAndNamespaceById'](task.listId, true)
} = listAndNamespace === null ? {list: null, namespace: null} : listAndNamespace
return {
...task,
@ -364,4 +365,6 @@ export default {
:deep(.multiselect .search-results button) {
padding: 0.5rem;
}
@include modal-transition();
</style>

View File

@ -8,7 +8,7 @@
>
</span>
<router-link
:to="{ name: taskDetailRoute, params: { id: task.id } }"
:to="taskDetailRoute"
:class="{ 'done': task.done}"
class="tasktext">
<span>
@ -39,14 +39,17 @@
:user="a"
v-for="(a, i) in task.assignees"
/>
<i
<time
:datetime="formatISO(task.dueDate)"
:class="{'overdue': task.dueDate <= new Date() && !task.done}"
class="is-italic"
@click.prevent.stop="showDefer = !showDefer"
v-if="+new Date(task.dueDate) > 0"
v-tooltip="formatDate(task.dueDate)"
:aria-expanded="showDefer ? 'true' : 'false'"
>
- {{ $t('task.detail.due', {at: formatDateSince(task.dueDate)}) }}
</i>
</time>
<transition name="fade">
<defer-task v-if="+new Date(task.dueDate) > 0 && showDefer" v-model="task" ref="deferDueDate"/>
</transition>
@ -126,10 +129,6 @@ export default {
type: Boolean,
default: false,
},
taskDetailRoute: {
type: String,
default: 'task.list.detail',
},
showList: {
type: Boolean,
default: false,
@ -167,6 +166,14 @@ export default {
title: '',
} : this.$store.state.currentList
},
taskDetailRoute() {
return {
name: 'task.detail',
params: { id: this.task.id },
// TODO: re-enable opening task detail in modal
// state: { backdropView: this.$router.currentRoute.value.fullPath },
}
},
},
methods: {
async markAsDone(checked) {

View File

@ -1,20 +1,21 @@
<template>
<a @click="$emit('click')">
<BaseButton>
<icon icon="sort-up" v-if="order === 'asc'"/>
<icon icon="sort-up" rotation="180" v-else-if="order === 'desc'"/>
<icon icon="sort-up" v-else-if="order === 'desc'" rotation="180"/>
<icon icon="sort" v-else/>
</a>
</BaseButton>
</template>
<script>
export default {
name: 'sort',
props: {
order: {
type: String,
default: 'none',
},
<script setup lang="ts">
import {PropType} from 'vue'
import BaseButton from '@/components/base/BaseButton.vue'
type Order = 'asc' | 'desc' | 'none'
defineProps({
order: {
type: String as PropType<Order>,
default: 'none',
},
emits: ['click'],
}
})
</script>

111
src/composables/taskList.js Normal file
View File

@ -0,0 +1,111 @@
import { ref, shallowReactive, watch, computed } from 'vue'
import {useRoute} from 'vue-router'
import TaskCollectionService from '@/services/taskCollection'
// FIXME: merge with DEFAULT_PARAMS in filters.vue
export const getDefaultParams = () => ({
sort_by: ['position', 'id'],
order_by: ['asc', 'desc'],
filter_by: ['done'],
filter_value: ['false'],
filter_comparator: ['equals'],
filter_concat: 'and',
})
const SORT_BY_DEFAULT = {
id: 'desc',
}
/**
* This mixin provides a base set of methods and properties to get tasks on a list.
*/
export function useTaskList(listId) {
const params = ref({...getDefaultParams()})
const search = ref('')
const page = ref(1)
const sortBy = ref({ ...SORT_BY_DEFAULT })
// This makes sure an id sort order is always sorted last.
// When tasks would be sorted first by id and then by whatever else was specified, the id sort takes
// precedence over everything else, making any other sort columns pretty useless.
function formatSortOrder(params) {
let hasIdFilter = false
const sortKeys = Object.keys(sortBy.value)
for (const s of sortKeys) {
if (s === 'id') {
sortKeys.splice(s, 1)
hasIdFilter = true
break
}
}
if (hasIdFilter) {
sortKeys.push('id')
}
params.sort_by = sortKeys
params.order_by = sortKeys.map(s => sortBy.value[s])
return params
}
const getAllTasksParams = computed(() => {
let loadParams = {...params.value}
if (search.value !== '') {
loadParams.s = search.value
}
loadParams = formatSortOrder(loadParams)
return [
{listId: listId.value},
loadParams,
page.value || 1,
]
})
const taskCollectionService = shallowReactive(new TaskCollectionService())
const loading = computed(() => taskCollectionService.loading)
const totalPages = computed(() => taskCollectionService.totalPages)
const tasks = ref([])
async function loadTasks() {
tasks.value = await taskCollectionService.getAll(...getAllTasksParams.value)
return tasks.value
}
const route = useRoute()
watch(() => route.query, (query) => {
const { page: pageQueryValue, search: searchQuery } = query
if (searchQuery !== undefined) {
search.value = searchQuery
}
if (pageQueryValue !== undefined) {
page.value = parseInt(pageQueryValue)
}
}, { immediate: true })
// Only listen for query path changes
watch(() => JSON.stringify(getAllTasksParams.value), (newParams, oldParams) => {
if (oldParams === newParams) {
return
}
loadTasks()
}, { immediate: true })
return {
tasks,
loading,
totalPages,
currentPage: page,
loadTasks,
searchTerm: search,
params,
}
}

View File

@ -1,9 +1,9 @@
import {computed, watch, readonly} from 'vue'
import {useStorage, createSharedComposable, ColorSchema, usePreferredColorScheme, tryOnMounted} from '@vueuse/core'
import {useStorage, createSharedComposable, BasicColorSchema, usePreferredColorScheme, tryOnMounted} from '@vueuse/core'
const STORAGE_KEY = 'color-scheme'
const DEFAULT_COLOR_SCHEME_SETTING: ColorSchema = 'light'
const DEFAULT_COLOR_SCHEME_SETTING: BasicColorSchema = 'light'
const CLASS_DARK = 'dark'
const CLASS_LIGHT = 'light'
@ -16,7 +16,7 @@ const CLASS_LIGHT = 'light'
// - value is synced via `createSharedComposable`
// https://github.com/vueuse/vueuse/blob/main/packages/core/useDark/index.ts
export const useColorScheme = createSharedComposable(() => {
const store = useStorage<ColorSchema>(STORAGE_KEY, DEFAULT_COLOR_SCHEME_SETTING)
const store = useStorage<BasicColorSchema>(STORAGE_KEY, DEFAULT_COLOR_SCHEME_SETTING)
const preferredColorScheme = usePreferredColorScheme()

View File

@ -1,9 +1,9 @@
import { computed, watchEffect } from 'vue'
import { setTitle } from '@/helpers/setTitle'
import { ComputedGetter, ComputedRef } from '@vue/reactivity'
import { ComputedGetter } from '@vue/reactivity'
export function useTitle<T>(titleGetter: ComputedGetter<T>) : ComputedRef<T> {
export function useTitle(titleGetter: ComputedGetter<string>) {
const titleRef = computed(titleGetter)
watchEffect(() => setTitle(titleRef.value))

View File

@ -53,6 +53,7 @@ export async function refreshToken(persist: boolean): Promise<AxiosResponse> {
return response
} catch(e) {
// @ts-ignore
throw new Error('Error renewing token: ', { cause: e })
}
}

6
src/helpers/isEmail.ts Normal file
View File

@ -0,0 +1,6 @@
export function isEmail(email: string): Boolean {
const format = /^.+@.+$/
const match = email.match(format)
return match === null ? false : match.length > 0
}

View File

@ -1,3 +1,5 @@
// Save the current list view to local storage
// We use local storage and not vuex here to make it persistent across reloads.
export const saveListView = (listId, routeName) => {
if (routeName.includes('settings.')) {
return

View File

@ -1,4 +1,4 @@
export function setTitle(title) {
export function setTitle(title : undefined | string) {
document.title = (typeof title === 'undefined' || title === '')
? 'Vikunja'
: `${title} | Vikunja`

View File

@ -1,5 +1,5 @@
import {createDateFromString} from '@/helpers/time/createDateFromString'
import {format, formatDistanceToNow} from 'date-fns'
import {format, formatDistanceToNow, formatISO as formatISOfns} from 'date-fns'
import {enGB, de, fr, ru} from 'date-fns/locale'
import {i18n} from '@/i18n'
@ -44,3 +44,7 @@ export const formatDateSince = (date) => {
addSuffix: true,
})
}
export function formatISO(date) {
return date ? formatISOfns(date) : ''
}

View File

@ -288,7 +288,7 @@ const getDateFromWeekday = (text: string): dateFoundResult => {
}
const getDayFromText = (text: string) => {
const matcher = /(([1-2][0-9])|(3[01])|(0?[1-9]))(st|nd|rd|th|\.)/ig
const matcher = /($| )(([1-2][0-9])|(3[01])|(0?[1-9]))(st|nd|rd|th|\.)($| )/ig
const results = matcher.exec(text)
if (results === null) {
return {
@ -302,17 +302,17 @@ const getDayFromText = (text: string) => {
const day = parseInt(results[0])
date.setDate(day)
// If the parsed day is the 31st but the next month only has 30 days, setting the day to 31 will "overflow" the
// date to the next month, but the first.
// If the parsed day is the 31st (or 29+ and the next month is february) but the next month only has 30 days,
// setting the day to 31 will "overflow" the date to the next month, but the first.
// This would look like a very weired bug. Now, to prevent that, we check if the day is the same as parsed after
// setting it for the first time and set it again if it isn't - that would mean the month overflowed.
if (day === 31 && date.getDate() !== day) {
date.setDate(day)
}
if (date < now) {
while (date < now) {
date.setMonth(date.getMonth() + 1)
}
if (date.getDate() !== day) {
date.setDate(day)
}
return {
foundText: results[0],

View File

@ -1,7 +1,18 @@
import axios from 'axios'
import {getToken} from '@/helpers/auth'
export const HTTPFactory = () => {
export function HTTPFactory() {
return axios.create({
baseURL: window.API_URL,
})
}
export function AuthenticatedHTTPFactory(token = getToken()) {
return axios.create({
baseURL: window.API_URL,
headers: {
Authorization: `Bearer ${token}`,
'Content-Type': 'application/json',
},
})
}

View File

@ -30,6 +30,10 @@ const setI18nLanguage = lang => {
}
export const loadLanguageAsync = lang => {
if(!lang) {
return
}
if (
// If the same language
i18n.global.locale === lang ||

View File

@ -7,7 +7,7 @@
"lastViewed": "Naposledy zobrazeno",
"list": {
"newText": "Můžete vytvořit nový seznam pro své nové úkoly:",
"new": "New list",
"new": "Nový seznam",
"importText": "Nebo importujte své seznamy a úkoly z jiných služeb:",
"import": "Importujte svá data do Vikunja"
}
@ -157,7 +157,7 @@
"searchSelect": "Klikněte nebo stiskněte Enter pro výběr tohoto seznamu",
"shared": "Sdílené seznamy",
"create": {
"header": "New list",
"header": "Nový seznam",
"titlePlaceholder": "Název seznamu přijde sem…",
"addTitleRequired": "Uveďte prosím název.",
"createdSuccess": "Seznam byl úspěšně vytvořen.",
@ -315,7 +315,7 @@
"namespaces": "Prostory",
"search": "Začni psát pro vyhledání prostoru…",
"create": {
"title": "New namespace",
"title": "Nový prostor",
"titleRequired": "Uveďte prosím název.",
"explanation": "Prostor je kolekce seznamů, které můžete sdílet a používat k organizaci seznamů. Každý seznam patří do nějakého prostoru.",
"tooltip": "Co je prostor?",
@ -383,7 +383,7 @@
"reminderRange": "Připomínky - období"
},
"create": {
"title": "New Saved Filter",
"title": "Nový uložený filtr",
"description": "Uložený filtr je virtuální seznam, který se počítá ze sady filtrů pokaždé, když je přístupný. Jakmile bude vytvořen, objeví se ve speciálním prostoru.",
"action": "Vytvořit uložený filtr"
},
@ -545,7 +545,7 @@
"chooseStartDate": "Klikněte zde pro nastavení počátečního data",
"chooseEndDate": "Klikněte zde pro nastavení data ukončení",
"move": "Přesunout úkol do jiného seznamu",
"done": "Hotovo!",
"done": "Označit úkol jako hotový!",
"undone": "Označit jako znovu otevřené",
"created": "Vytvořeno {0} uživatelem {1}",
"updated": "Aktualizováno {0}",
@ -781,7 +781,7 @@
"then": "potom",
"task": {
"title": "Stránka úkolů",
"done": "Označit úkol jako hotový",
"done": "Hotovo",
"assign": "Přiřadit uživateli",
"labels": "Přidat štítky k tomuto úkolu",
"dueDate": "Změnit termín tohoto úkolu",
@ -899,7 +899,7 @@
"4015": "Komentář k úkolu neexistuje.",
"4016": "Neplatné pole úkolu.",
"4017": "Neplatný komparátor filtru úkolů.",
"4018": "Neplatný koncatinátor filtru úkolů.",
"4018": "Invalid task filter concatenator.",
"4019": "Neplatná hodnota filtru úkolů.",
"5001": "Prostor neexistuje.",
"5003": "Nemáte přístup ke zvolenému prostoru.",

View File

@ -7,7 +7,7 @@
"lastViewed": "Zuletzt angesehen",
"list": {
"newText": "Du kannst eine neue Liste für deine neuen Aufgaben erstellen:",
"new": "New list",
"new": "Neue Liste",
"importText": "Oder importiere deine Listen und Aufgaben aus anderen Diensten in Vikunja:",
"import": "Deine Daten in Vikunja importieren"
}
@ -31,7 +31,7 @@
"username": "Anmeldename",
"usernameEmail": "Anmeldename oder E-Mail-Adresse",
"usernamePlaceholder": "z.B. frederick",
"email": "E-Mail-Adresse",
"email": "Email address",
"emailPlaceholder": "z.B. frederic{'@'}vikunja.io",
"password": "Passwort",
"passwordRepeat": "Gib dein Passwort erneut ein",
@ -157,7 +157,7 @@
"searchSelect": "Klicke auf oder drücke die Eingabetaste, um diese Liste auszuwählen",
"shared": "Geteilte Listen",
"create": {
"header": "New list",
"header": "Neue Liste",
"titlePlaceholder": "Der Titel der Liste steht hier…",
"addTitleRequired": "Bitte gebe einen Namen an.",
"createdSuccess": "Die Liste wurde erfolgreich erstellt.",
@ -315,7 +315,7 @@
"namespaces": "Namespaces",
"search": "Beginne zu schreiben, um einen Namespace zu suchen…",
"create": {
"title": "New namespace",
"title": "Neuer Namespace",
"titleRequired": "Bitte gebe einen Titel an.",
"explanation": "Ein Namespace ist eine Sammlung von Listen, die du teilen und zur Organisation verwenden kannst. Jede Liste zu einem Namespace.",
"tooltip": "Was ist ein Namespace?",
@ -383,7 +383,7 @@
"reminderRange": "Erinnerungs-Datumsbereich"
},
"create": {
"title": "New Saved Filter",
"title": "Neuer gespeicherter Filter",
"description": "Ein gespeicherter Filter ist eine virtuelle Liste, die bei jedem Zugriff aus einem Satz von Filtern errechnet wird. Einmal erstellt, erscheint diese in einem speziellen Namespace.",
"action": "Neuen gespeicherten Filter erstellen"
},
@ -545,7 +545,7 @@
"chooseStartDate": "Klicke hier, um ein Startdatum zu setzen",
"chooseEndDate": "Klicke hier, um ein Enddatum zu setzen",
"move": "Aufgabe in eine andere Liste verschieben",
"done": "Fertig!",
"done": "Als erledigt markieren!",
"undone": "Als nicht erledigt markieren",
"created": "Erstellt {0} von {1}",
"updated": "Aktualisiert {0}",
@ -781,7 +781,7 @@
"then": "dann",
"task": {
"title": "Aufgabenseite",
"done": "Eine Aufgabe als erledigt markieren",
"done": "Fertig",
"assign": "Benutzer:in zuweisen",
"labels": "Dieser Aufgabe ein Label hinzufügen",
"dueDate": "Ändere das Fälligkeitsdatum dieser Aufgabe",
@ -899,7 +899,7 @@
"4015": "Dieser Aufgabenkommentar existiert nicht.",
"4016": "Ungültiges Aufgabenfeld.",
"4017": "Ungültiger Aufgabenfilter (Vergleichskriterium).",
"4018": "Ungültiger Aufgabenfilter (Kombination).",
"4018": "Ungültige Verkettung von Aufgabenfiltern.",
"4019": "Ungültiger Aufgabenfilter (Wert).",
"5001": "Dieser Namespace existiert nicht.",
"5003": "Du hast keinen Zugriff auf den Namespace.",

View File

@ -7,7 +7,7 @@
"lastViewed": "Zletscht ahglueget",
"list": {
"newText": "Du chasch e Liste für dini neue Uufgabe erstelle:",
"new": "New list",
"new": "Neue Liste",
"importText": "Oder importier dini Liste und Uufgabe us anderne Dienst nach Vikunja:",
"import": "Dini Date in Vikunja importiere"
}
@ -31,10 +31,9 @@
"username": "Benutzernamä",
"usernameEmail": "Benutzernamä oder E-Mail Adrässe",
"usernamePlaceholder": "z.B. Hansruedi",
"email": "E-Mail Adrässe",
"email": "Email address",
"emailPlaceholder": "z.B. frederic{'@'}vikunja.io",
"password": "Passwort",
"passwordRepeat": "Gib dis Passwort nomal iih",
"passwordPlaceholder": "z.B. •••••••••••",
"forgotPassword": "Passwort vergessen?",
"resetPassword": "Setz diis Passwort zrugg",
@ -45,12 +44,19 @@
"totpTitle": "Zweifaktor Authentifizierigs Ziffere",
"totpPlaceholder": "z.B. 123456",
"login": "Iihlogge",
"register": "Registriere",
"createAccount": "Create account",
"loginWith": "Iihlogge mit {provider}",
"authenticating": "Authentifiziere…",
"openIdStateError": "Status stimmt nid überiih, ich verweigerä wiiter zmache!",
"openIdGeneralError": "Es ist ein Fehler bei der externen Authentisierung aufgetreten.",
"logout": "Uuslogge"
"logout": "Uuslogge",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
"passwordRequired": "Please provide a password.",
"showPassword": "Show the password",
"hidePassword": "Hide the password",
"noAccountYet": "Don't have an account yet?",
"alreadyHaveAnAccount": "Already have an account?"
},
"settings": {
"title": "Iihstellige",
@ -61,7 +67,7 @@
"currentPasswordPlaceholder": "Diis jetzige Passwort",
"passwordsDontMatch": "Dis neue Passwort und siini Bestätigung stimmed nid überiih.",
"passwordUpdateSuccess": "Dis Passwort isch erfolgriich aktualisiert wordä.",
"updateEmailTitle": "Dini E-Mail Adrässä änderä",
"updateEmailTitle": "Update Your Email Address",
"updateEmailNew": "Neui E-Mail Adrässä",
"updateEmailSuccess": "Dini E-Mail Adrässä isch erfolgriich gänderet worde. Mir hend dir en Link gschickt, um si zu bestätigä.",
"general": {
@ -157,7 +163,7 @@
"searchSelect": "Druck uf Enter um die Liste uuszwähle",
"shared": "Teilti Liste",
"create": {
"header": "New list",
"header": "Neue Liste",
"titlePlaceholder": "Listetitl da ahgeh…",
"addTitleRequired": "Bitte gib en Titl ah.",
"createdSuccess": "Liste erfolgriich erstellt.",
@ -315,7 +321,7 @@
"namespaces": "Namensrüüm",
"search": "Schriib, um nachemne Namensruum z'sueche…",
"create": {
"title": "New namespace",
"title": "Neuer Namespace",
"titleRequired": "Bitte gib en Titl ah.",
"explanation": "En Namensruum isch e Gruppe vo Liste, wo du chasch zur Organisation benutze. Tatsächlich sind alli Listene emne Namensruum zuegwise.",
"tooltip": "Was isch en Namensruum?",
@ -383,7 +389,7 @@
"reminderRange": "Errinnerigs Datumbereich"
},
"create": {
"title": "New Saved Filter",
"title": "Neuer gespeicherter Filter",
"description": "En gspeicherete Filter isch e virtuelli Liste, welche vomene Satz a Filter zemmegsetzt wird, sobald me uf sie zuegriift. Wenn sie mal erstellt worde isch, erhaltet si ihren eigene Namensruum.",
"action": "Neue gspeicherete Filter erstelle"
},
@ -545,7 +551,7 @@
"chooseStartDate": "Druck dah, um es Startdatum z'setze",
"chooseEndDate": "Druck da, um es Enddatum z'setze",
"move": "Schieb die Uufgab in e anderi Liste",
"done": "Fertig!",
"done": "Als erledigt markieren!",
"undone": "Als unerledigt markierä",
"created": "Erstellt am {0} vo {1}",
"updated": "{0} g'updatet",
@ -781,7 +787,7 @@
"then": "dann",
"task": {
"title": "Uufgabesiite",
"done": "Uufgab als erledigt markiere",
"done": "Fertig",
"assign": "Benutzer:in zuweisen",
"labels": "Labels ennere Uufgab hinzuefüege",
"dueDate": "S'Fälligkeitsdatum für die Uufgab ändere",
@ -899,7 +905,7 @@
"4015": "De Uufgabe Kommentar giz nid.",
"4016": "Ungültigs Uufgabefeld.",
"4017": "Ungültige Uufgabefilter vergliich.",
"4018": "Ungültige Uufgabefilter Zemmezug.",
"4018": "Ungültige Verkettung von Aufgabenfiltern.",
"4019": "Ungültigi Uufgabe Filter Wert.",
"5001": "De Namensruum existiert nid.",
"5003": "Du hesch kei Zuegriff zu dem Namensruum.",

View File

@ -31,10 +31,9 @@
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "E-mail address",
"email": "Email address",
"emailPlaceholder": "e.g. frederic{'@'}vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"forgotPassword": "Forgot your password?",
"resetPassword": "Reset your password",
@ -45,12 +44,20 @@
"totpTitle": "Two Factor Authentication Code",
"totpPlaceholder": "e.g. 123456",
"login": "Login",
"register": "Register",
"createAccount": "Create account",
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"logout": "Logout"
"logout": "Logout",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
"passwordRequired": "Please provide a password.",
"showPassword": "Show the password",
"hidePassword": "Hide the password",
"noAccountYet": "Don't have an account yet?",
"alreadyHaveAnAccount": "Already have an account?",
"remember": "Stay logged in"
},
"settings": {
"title": "Settings",
@ -61,7 +68,7 @@
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Update Your E-Mail Address",
"updateEmailTitle": "Update Your Email Address",
"updateEmailNew": "New Email Address",
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
"general": {
@ -78,7 +85,8 @@
"weekStartSunday": "Sunday",
"weekStartMonday": "Monday",
"language": "Language",
"defaultList": "Default List"
"defaultList": "Default List",
"timezone": "Time Zone"
},
"totp": {
"title": "Two Factor Authentication",
@ -545,7 +553,7 @@
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"done": "Done!",
"done": "Mark task done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Updated {0}",
@ -781,7 +789,7 @@
"then": "then",
"task": {
"title": "Task Page",
"done": "Mark a task as done",
"done": "Done",
"assign": "Assign to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
@ -899,7 +907,7 @@
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.",
"4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.",
"5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.",

View File

@ -31,10 +31,9 @@
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "E-mail address",
"email": "Email address",
"emailPlaceholder": "e.g. frederic{'@'}vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"forgotPassword": "Forgot your password?",
"resetPassword": "Reset your password",
@ -45,12 +44,19 @@
"totpTitle": "Two Factor Authentication Code",
"totpPlaceholder": "e.g. 123456",
"login": "Login",
"register": "Register",
"createAccount": "Create account",
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"logout": "Logout"
"logout": "Logout",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
"passwordRequired": "Please provide a password.",
"showPassword": "Show the password",
"hidePassword": "Hide the password",
"noAccountYet": "Don't have an account yet?",
"alreadyHaveAnAccount": "Already have an account?"
},
"settings": {
"title": "Settings",
@ -61,7 +67,7 @@
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Update Your E-Mail Address",
"updateEmailTitle": "Update Your Email Address",
"updateEmailNew": "New Email Address",
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
"general": {
@ -545,7 +551,7 @@
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"done": "Done!",
"done": "Mark task done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Updated {0}",
@ -781,7 +787,7 @@
"then": "then",
"task": {
"title": "Task Page",
"done": "Mark a task as done",
"done": "Done",
"assign": "Assign to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
@ -899,7 +905,7 @@
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.",
"4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.",
"5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.",

View File

@ -31,7 +31,7 @@
"username": "Nom dutilisateur·rice",
"usernameEmail": "Nom dutilisateur·rice ou adresse courriel",
"usernamePlaceholder": "p. ex. frederick",
"email": "Adresse courriel",
"email": "Email address",
"emailPlaceholder": "p. ex. frederic{'@'}vikunja.io",
"password": "Mot de passe",
"passwordRepeat": "Retape ton mot de passe",
@ -116,12 +116,12 @@
"vikunja": "Vikunja"
},
"appearance": {
"title": "Color Scheme",
"setSuccess": "Saved change of color scheme to {colorScheme}",
"title": "Jeu de couleurs",
"setSuccess": "Changement du jeu de couleurs enregistré vers {colorScheme}",
"colorScheme": {
"light": "Light",
"system": "System",
"dark": "Dark"
"light": "Clair",
"system": "Système",
"dark": "Sombre"
}
}
},
@ -475,7 +475,7 @@
"download": "Télécharger",
"showMenu": "Afficher le menu",
"hideMenu": "Masquer le menu",
"forExample": "For example:",
"forExample": "Par exemple :",
"welcomeBack": "Welcome Back!"
},
"input": {
@ -545,7 +545,7 @@
"chooseStartDate": "Clique ici pour fixer une date de début",
"chooseEndDate": "Clique ici pour fixer une date de fin",
"move": "Déplacer une tâche vers une autre liste",
"done": "Terminé !",
"done": "Mark task done!",
"undone": "Marquer comme inachevé",
"created": "Créé {0} par {1}",
"updated": "Mis à jour {0}",
@ -561,7 +561,7 @@
"text2": "Ceci supprimera également toutes les pièces jointes, les rappels et les relations associés à cette tâche et ne pourra pas être annulé !"
},
"actions": {
"assign": "Assign to a user",
"assign": "Attribuer à un utilisateur",
"label": "Ajouter des étiquettes",
"priority": "Définir la priorité",
"dueDate": "Définir léchéance",
@ -726,8 +726,8 @@
"dateCurrentYear": "utilisera lannée en cours",
"dateNth": "utilisera le {day}e du mois en cours",
"dateTime": "Combinez nimporte lequel des formats de date avec « {time} » (ou {timePM}) pour définir une heure.",
"repeats": "Repeating tasks",
"repeatsDescription": "To set a task as repeating in an interval, simply add '{suffix}' to the task text. The amount needs to be a number and can be omitted to use just the type (see examples)."
"repeats": "Tâches répétitives",
"repeatsDescription": "Pour définir une tâche comme répétitive dans un intervalle, il suffit d'ajouter « {suffix} » au texte de la tâche. Le montant doit être un nombre et peut être omis pour utiliser uniquement le type (voir exemples)."
}
},
"team": {
@ -781,8 +781,8 @@
"then": "puis",
"task": {
"title": "Page de tâche",
"done": "Marquer une tâche comme terminée",
"assign": "Assign to a user",
"done": "Done",
"assign": "Attribuer à un utilisateur",
"labels": "Ajouter des étiquettes à cette tâche",
"dueDate": "Modifier la date déchéance de cette tâche",
"attachment": "Ajouter une pièce jointe à cette tâche",
@ -899,7 +899,7 @@
"4015": "Le commentaire de la tâche nexiste pas.",
"4016": "Champ de tâche invalide.",
"4017": "Comparateur de filtre de tâche invalide.",
"4018": "Concaténateur de filtre de tâche invalide.",
"4018": "Invalid task filter concatenator.",
"4019": "Valeur de filtre de tâche invalide.",
"5001": "Lespace de noms nexiste pas.",
"5003": "Tu nas pas accès à lespace de noms indiqué.",
@ -908,7 +908,7 @@
"5010": "Cette équipe na pas accès à cet espace de noms.",
"5011": "Cet·e utilisateur·rice a déjà accès à cet espace de noms.",
"5012": "Lespace de noms est archivé et ne peut donc être consulté quen lecture seule.",
"6001": "The team name cannot be empty.",
"6001": "Le nom de l'équipe ne peut pas être vide.",
"6002": "Léquipe nexiste pas.",
"6004": "Léquipe a déjà accès à cet espace de noms ou à cette liste.",
"6005": "Lutilisateur·rice est déjà membre de cette équipe.",

View File

@ -7,7 +7,7 @@
"lastViewed": "Ultima visualizzazione",
"list": {
"newText": "È possibile creare una nuova lista per le nuove attività:",
"new": "New list",
"new": "Nuova lista",
"importText": "O importare le liste e le attività da altri servizi in Vikunja:",
"import": "Importa i tuoi dati in Vikunja"
}
@ -17,26 +17,26 @@
"text": "La pagina richiesta non esiste."
},
"ready": {
"loading": "Vikunja is loading…",
"errorOccured": "An error occured:",
"checkApiUrl": "Please check if the api url is correct.",
"noApiUrlConfigured": "No API url was configured. Please set one below:"
"loading": "Vikunja sta caricando…",
"errorOccured": "Si è verificato un errore:",
"checkApiUrl": "Controlla se l'URL API è corretto.",
"noApiUrlConfigured": "Nessun URL API configurato. Impostane uno qui sotto:"
},
"offline": {
"title": "You are offline.",
"text": "Please check your network connection and try again."
"title": "Sei offline.",
"text": "Controlla la connessione di rete e riprova."
},
"user": {
"auth": {
"username": "Nome utente",
"usernameEmail": "Nome utente o indirizzo e-mail",
"usernamePlaceholder": "es. frederick",
"email": "Indirizzo e-mail",
"email": "Email address",
"emailPlaceholder": "per es. frederic{'@'}vikunja.io",
"password": "Password",
"passwordRepeat": "Digita di nuovo la tua password",
"passwordPlaceholder": "es. ••••••••••••",
"forgotPassword": "Forgot your password?",
"forgotPassword": "Password dimenticata?",
"resetPassword": "Reimposta la tua password",
"resetPasswordAction": "Inviami il link per reimpostare la password",
"resetPasswordSuccess": "Controlla la tua casella di posta! Dovresti avere un'e-mail con le istruzioni su come reimpostare la password.",
@ -48,7 +48,7 @@
"register": "Registrati",
"loginWith": "Accedi con {provider}",
"authenticating": "Autenticazione…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdStateError": "Stato non corrispondente, impossibile continuare!",
"openIdGeneralError": "Si è verificato un errore durante l'autenticazione con terze parti.",
"logout": "Esci"
},
@ -103,31 +103,31 @@
"title": "Avatar",
"initials": "Iniziali",
"gravatar": "Gravatar",
"marble": "Marble",
"marble": "Marmo",
"upload": "Carica",
"uploadAvatar": "Carica Avatar",
"statusUpdateSuccess": "Avatar status was updated successfully!",
"statusUpdateSuccess": "Avatar aggiornato!",
"setSuccess": "L'avatar è stato impostato con successo!"
},
"quickAddMagic": {
"title": "Quick Add Magic Mode",
"title": "Modalità Aggiunta Rapida Magica",
"disabled": "Disabilitato",
"todoist": "Todoist",
"vikunja": "Vikunja"
},
"appearance": {
"title": "Color Scheme",
"setSuccess": "Saved change of color scheme to {colorScheme}",
"title": "Tema",
"setSuccess": "Tema cambiato in {colorScheme}",
"colorScheme": {
"light": "Light",
"system": "System",
"dark": "Dark"
"light": "Chiaro",
"system": "Sistema",
"dark": "Scuro"
}
}
},
"deletion": {
"title": "Delete your Vikunja Account",
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, lists, tasks and everything associated with it.",
"title": "Elimina il tuo Account Vikunja",
"text1": "La cancellazione del tuo account è permanente e non può essere annullata. Elimineremo tutti i tuoi namespace, liste, attività e tutto ciò che è ad esso associato.",
"text2": "Per continuare, inserisci la tua password. Riceverai un'e-mail con ulteriori istruzioni.",
"confirm": "Elimina il mio profilo",
"requestSuccess": "Richiesta riuscita. Riceverai un'e-mail con ulteriori istruzioni.",
@ -141,7 +141,7 @@
},
"export": {
"title": "Esporta i tuoi dati Vikunja",
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Lists, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
"description": "Puoi richiedere una copia di tutti i tuoi dati all'interno di Vikunja. Questo include i Namespace, le Liste, le Attività e tutto ciò che è loro associato. È possibile importare questi dati in qualsiasi istanza Vikunja attraverso la funzione di migrazione.",
"descriptionPasswordRequired": "Inserisci la tua password per procedere:",
"request": "Richiedi una copia dei miei dati Vikunja",
"success": "Hai richiesto con successo i tuoi dati Vikunja! Ti invieremo un'e-mail una volta che saranno pronti da scaricare.",
@ -157,7 +157,7 @@
"searchSelect": "Fare clic o premere invio per selezionare questa lista",
"shared": "Liste Condivise",
"create": {
"header": "New list",
"header": "Nuova lista",
"titlePlaceholder": "Il titolo della lista va qui…",
"addTitleRequired": "Specifica un titolo.",
"createdSuccess": "La lista è stata creata correttamente.",
@ -191,7 +191,7 @@
"duplicate": {
"title": "Duplica questa lista",
"label": "Duplica",
"text": "Select a namespace which should hold the duplicated list:",
"text": "Seleziona un namespace che dovrebbe contenere l'elenco duplicato:",
"success": "Lista duplicata."
},
"edit": {
@ -279,23 +279,23 @@
"title": "Kanban",
"limit": "Limite: {limit}",
"noLimit": "Non Impostato",
"doneBucket": "Done bucket",
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
"doneBucketHintExtended": "All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.",
"doneBucketSavedSuccess": "The done bucket has been saved successfully.",
"deleteLast": "You cannot remove the last bucket.",
"addTaskPlaceholder": "Enter the new task title…",
"doneBucket": "Colonna attività completate",
"doneBucketHint": "Tutte le attività spostate in questa colonna verranno automaticamente contrassegnate come completate.",
"doneBucketHintExtended": "Tutte le attività spostate nella colonna attività completate saranno contrassegnate automaticamente come completate. Tutte le attività contrassegnate come completate altrove verranno anche spostate.",
"doneBucketSavedSuccess": "Colonna attività completate salvata.",
"deleteLast": "Impossibile eliminare l'ultima colonna.",
"addTaskPlaceholder": "Inserisci il nuovo titolo dell'attività…",
"addTask": "Aggiungi un'attività",
"addAnotherTask": "Aggiungi un'altra attività",
"addBucket": "Create a new bucket",
"addBucketPlaceholder": "Enter the new bucket title…",
"deleteHeaderBucket": "Delete the bucket",
"deleteBucketText1": "Are you sure you want to delete this bucket?",
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
"deleteBucketSuccess": "The bucket has been deleted successfully.",
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
"bucketLimitSavedSuccess": "The bucket limit been saved successfully.",
"collapse": "Collapse this bucket"
"addBucket": "Crea una nuova colonna",
"addBucketPlaceholder": "Inserisci il titolo della nuova colonna…",
"deleteHeaderBucket": "Elimina la colonna",
"deleteBucketText1": "Confermi di voler eliminare questa colonna?",
"deleteBucketText2": "Questo non eliminerà nessuna attività, ma la sposterà nel bucket predefinito.",
"deleteBucketSuccess": "Colonna eliminata.",
"bucketTitleSavedSuccess": "Titolo della colonna salvato.",
"bucketLimitSavedSuccess": "Limite della colonna salvato.",
"collapse": "Comprimi questa colonna"
},
"pseudo": {
"favorites": {
@ -304,52 +304,52 @@
}
},
"namespace": {
"title": "Namespaces & Lists",
"title": "Namespace e Liste",
"namespace": "Namespace",
"showArchived": "Show Archived",
"noneAvailable": "You don't have any namespaces right now.",
"unarchive": "Un-Archive",
"archived": "Archived",
"noLists": "This namespace does not contain any lists.",
"createList": "Create a new list in this namespace.",
"namespaces": "Namespaces",
"search": "Type to search for a namespace…",
"showArchived": "Mostra Archiviati",
"noneAvailable": "Non hai alcun namespace in questo momento.",
"unarchive": "De-Archivia",
"archived": "Archiviato",
"noLists": "Questo namespace non contiene alcuna lista.",
"createList": "Crea una nuova lista in questo namespace.",
"namespaces": "Namespace",
"search": "Digita per cercare un namespace…",
"create": {
"title": "New namespace",
"titleRequired": "Please specify a title.",
"explanation": "A namespace is a collection of lists you can share and use to organize your lists with. In fact, every list belongs to a namepace.",
"tooltip": "What's a namespace?",
"success": "The namespace was successfully created."
"title": "Nuovo namespace",
"titleRequired": "Specifica un titolo.",
"explanation": "Un namespace è una raccolta di liste che puoi condividere e che puoi usare per organizzare le tue liste. Infatti, ogni lista appartiene a un namespace.",
"tooltip": "Che cos'è un namespace?",
"success": "Namespace creato."
},
"archive": {
"titleArchive": "Archivia \"{namespace}\"",
"titleUnarchive": "Un-Archive \"{namespace}\"",
"archiveText": "You won't be able to edit this namespace or create new lists until you un-archive it. This will also archive all lists in this namespace.",
"unarchiveText": "You will be able to create new lists or edit it.",
"success": "The namespace was successfully archived.",
"description": "If a namespace is archived, you cannot create new lists or edit it."
"titleUnarchive": "Disarchivia \"{namespace}\"",
"archiveText": "Non sarà possibile modificare questo namespace o creare nuove liste fino a quando non verrà disarchiviato. Questo archivierà anche tutte le liste in questo namespace.",
"unarchiveText": "Potrai creare nuove liste o modificarle.",
"success": "Namespace creato.",
"description": "Se un namespace è archiviato, non è possibile creare nuove liste o modificarlo."
},
"delete": {
"title": "Delete \"{namespace}\"",
"text1": "Are you sure you want to delete this namespace and all of its contents?",
"title": "Elimina \"{namespace}\"",
"text1": "Sei sicuro di voler rimuovere questo namespace e tutto il relativo contenuto?",
"text2": "Questo include tutte le liste e le attività e NON PUÒ ESSERE RIPRISTINATO!",
"success": "The namespace was successfully deleted."
"success": "Namespace eliminato."
},
"edit": {
"title": "Modifica \"{namespace}\"",
"success": "The namespace was successfully updated."
"success": "Namespace aggiornato."
},
"share": {
"title": "Condividi \"{namespace}\""
},
"attributes": {
"title": "Namespace Title",
"titlePlaceholder": "The namespace title goes here…",
"title": "Titolo del Namespace",
"titlePlaceholder": "Il titolo del namespace va qui…",
"description": "Descrizione",
"descriptionPlaceholder": "The namespaces description goes here…",
"descriptionPlaceholder": "La descrizione del namespace va qui…",
"color": "Colore",
"archived": "Is Archived",
"isArchived": "This namespace is archived"
"archived": "Archiviato",
"isArchived": "Questo namespace è archiviato"
},
"pseudo": {
"sharedLists": {
@ -365,7 +365,7 @@
},
"filters": {
"title": "Filtri",
"clear": "Clear Filters",
"clear": "Pulisci Filtri",
"attributes": {
"title": "Titolo",
"titlePlaceholder": "Il titolo del filtro salvato va qui…",
@ -374,17 +374,17 @@
"includeNulls": "Includi attività che non hanno un valore impostato",
"requireAll": "Tutti i filtri devono essere veri affinché l'attività venga mostrata",
"showDoneTasks": "Mostra Attività Fatte",
"sortAlphabetically": "Sort Alphabetically",
"sortAlphabetically": "Ordine alfabetico",
"enablePriority": "Abilita Filtro Per Priorità",
"enablePercentDone": "Abilitare Filtro Per Percentuale Fatta",
"dueDateRange": "Intervallo Data Di Scadenza",
"startDateRange": "Intervallo Data Iniziale",
"endDateRange": "Intervallo Data Finale",
"reminderRange": "Reminder Date Range"
"reminderRange": "Intervallo date dei promemoria"
},
"create": {
"title": "New Saved Filter",
"description": "A saved filter is a virtual list which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"title": "Nuovo Filtro Salvato",
"description": "Un filtro salvato è una lista virtuale che viene calcolata da un insieme di filtri di volta in volta. Una volta creato, apparirà in un namespace speciale.",
"action": "Crea nuovo filtro salvato"
},
"delete": {
@ -446,9 +446,9 @@
},
"navigation": {
"overview": "Panoramica",
"upcoming": "Upcoming",
"upcoming": "Prossimamente",
"settings": "Impostazioni",
"imprint": "Imprint",
"imprint": "Informazioni legali",
"privacy": "Politica sulla Privacy"
},
"misc": {
@ -464,19 +464,19 @@
"searchPlaceholder": "Digita per cercare…",
"previous": "Precedente",
"next": "Successivo",
"poweredBy": "Powered by Vikunja",
"poweredBy": "Creato con Vikunja",
"info": "Info",
"create": "Create",
"create": "Crea",
"doit": "Fallo!",
"saving": "Salvataggio…",
"saved": "Salvato!",
"default": "Predefinito",
"close": "Chiudi",
"download": "Scarica",
"showMenu": "Show the menu",
"hideMenu": "Hide the menu",
"forExample": "For example:",
"welcomeBack": "Welcome Back!"
"showMenu": "Mostra il menu",
"hideMenu": "Nascondi il menù",
"forExample": "Ad esempio:",
"welcomeBack": "Bentornato!"
},
"input": {
"resetColor": "Ripristina Colore",
@ -485,9 +485,9 @@
"tomorrow": "Domani",
"nextMonday": "Lunedì Prossimo",
"thisWeekend": "Questo fine settimana",
"laterThisWeek": "Later This Week",
"laterThisWeek": "Alla fine di questa settimana",
"nextWeek": "Prossima Settimana",
"chooseDate": "Choose a date"
"chooseDate": "Seleziona una data"
},
"editor": {
"edit": "Modifica",
@ -504,16 +504,16 @@
"quote": "Citazione",
"unorderedList": "Elenco puntato",
"orderedList": "Elenco numerato",
"cleanBlock": "Clean Block",
"cleanBlock": "Pulisci Blocco",
"link": "Link",
"image": "Immagine",
"table": "Tabella",
"horizontalRule": "Horizontal Rule",
"sideBySide": "Side By Side",
"guide": "Guide"
"horizontalRule": "Divisore Orizzontale",
"sideBySide": "Affianca",
"guide": "Guida"
},
"multiselect": {
"createPlaceholder": "Create new",
"createPlaceholder": "Crea nuovo",
"selectPlaceholder": "Clicca o premere invio per selezionare"
}
},
@ -533,19 +533,19 @@
"titleDates": "Attività dal {from} al {to}",
"noDates": "Mostra attività senza date",
"current": "Attività attuali",
"from": "Tasks from",
"until": "until",
"from": "Attività dal",
"until": "fino al",
"today": "Oggi",
"nextWeek": "Settimana Prossima",
"nextMonth": "Prossimo Mese",
"noTasks": "Nothing to do — Have a nice day!"
"noTasks": "Nessuna attività — Buona giornata!"
},
"detail": {
"chooseDueDate": "Clicca qui per impostare una data di scadenza",
"chooseStartDate": "Clicca qui per impostare una data di inizio",
"chooseEndDate": "Clicca qui per impostare una data di fine",
"move": "Sposta attività in un'altra lista",
"done": "Fatto!",
"done": "Segna attività fatta!",
"undone": "Segna come non completato",
"created": "Creato {0} da {1}",
"updated": "Aggiornato {0}",
@ -554,21 +554,21 @@
"deleteSuccess": "L'attività è stata eliminata con successo.",
"belongsToList": "Questa attività appartiene alla lista '{list}'",
"due": "Scadenza {at}",
"closePopup": "Close popup",
"closePopup": "Chiudi popup",
"delete": {
"header": "Elimina questa attività",
"text1": "Sei sicuro di voler eliminare questa attività?",
"text2": "Questo rimuoverà anche tutti gli allegati, i promemoria e le relazioni associati a questa attività e non può essere ripristinato!"
},
"actions": {
"assign": "Assign to a user",
"assign": "Assegna ad un utente",
"label": "Aggiungi etichette",
"priority": "Imposta Priorità",
"dueDate": "Imposta data di scadenza",
"startDate": "Imposta una data di inizio",
"endDate": "Imposta una data di fine",
"reminders": "Imposta promemoria",
"repeatAfter": "Set a repeating interval",
"repeatAfter": "Imposta ricorrenza",
"percentDone": "Imposta Percentuale Completata",
"attachments": "Aggiungi allegati",
"relatedTasks": "Aggiungi attività collegate",
@ -599,13 +599,13 @@
"updated": "Aggiornato"
},
"subscription": {
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribe": "Subscribe",
"unsubscribe": "Unsubscribe",
"subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
"subscribedThroughParent": "Non puoi annullare l'iscrizione qui perché sei iscritto a questo {entity} attraverso il suo {parent}.",
"subscribed": "Sei attualmente iscritto a questo {entity} e riceverai notifiche per le modifiche.",
"notSubscribed": "Non sei iscritto a questo {entity} e non riceverai notifiche per le modifiche.",
"subscribe": "Iscriviti",
"unsubscribe": "Disiscriviti",
"subscribeSuccess": "Ti sei iscritto a questo {entity}",
"unsubscribeSuccess": "Ti sei disiscritto a questo {entity}"
},
"attachment": {
"title": "Allegati",
@ -623,41 +623,41 @@
"comment": {
"title": "Commenti",
"loading": "Caricamento commenti…",
"edited": "edited {date}",
"edited": "modificato il {date}",
"creating": "Creazione del commento…",
"placeholder": "Aggiungi un commento…",
"comment": "Comment",
"comment": "Commenta",
"delete": "Elimina questo commento",
"deleteText1": "Sei sicuro di voler eliminare questo commento?",
"deleteText2": "Questa azione non può essere annullata!",
"addedSuccess": "Il commento è stato aggiunto correttamente."
},
"deferDueDate": {
"title": "Defer due date",
"title": "Rinvia data di scadenza",
"1day": "1 giorno",
"3days": "3 giorni",
"1week": "1 settimana"
},
"description": {
"placeholder": "Click here to enter a description…",
"empty": "No description available yet."
"placeholder": "Clicca qui per inserire una descrizione…",
"empty": "Nessuna descrizione."
},
"assignee": {
"placeholder": "Type to assign a user…",
"placeholder": "Digita per assegnare un utente…",
"selectPlaceholder": "Assegna questo utente",
"assignSuccess": "The user has been assigned successfully.",
"unassignSuccess": "The user has been unassigned successfully."
"assignSuccess": "Utente assegnato.",
"unassignSuccess": "Utente disassegnato."
},
"label": {
"placeholder": "Type to add a new label…",
"createPlaceholder": "Add this as new label",
"placeholder": "Digita per aggiungere una nuova etichetta…",
"createPlaceholder": "Aggiungila come nuova etichetta",
"addSuccess": "Etichetta aggiunta.",
"createSuccess": "Etichetta creata.",
"removeSuccess": "Etichetta eliminata.",
"addCreateSuccess": "Etichetta creata e aggiunta."
},
"priority": {
"unset": "Unset",
"unset": "Azzera",
"low": "Bassa",
"medium": "Media",
"high": "Alta",
@ -665,38 +665,38 @@
"doNow": "FARE ORA"
},
"relation": {
"add": "Add a New Task Relation",
"new": "New Task Relation",
"searchPlaceholder": "Type search for a new task to add as related…",
"createPlaceholder": "Add this as new related task",
"differentList": "This task belongs to a different list.",
"differentNamespace": "This task belongs to a different namespace.",
"noneYet": "No task relations yet.",
"delete": "Delete Task Relation",
"deleteText1": "Are you sure you want to delete this task relation?",
"add": "Aggiungi Attività Collegata",
"new": "Nuova Attività Collegata",
"searchPlaceholder": "Digita per cercare un'attività da aggiungere come collegata…",
"createPlaceholder": "Aggiungi come attività collegata",
"differentList": "Questa attività è di una lista diversa.",
"differentNamespace": "Questa attività appartiene ad un namespace diverso.",
"noneYet": "Nessuna attività collegata.",
"delete": "Elimina Collegamento Attività",
"deleteText1": "Confermi di voler eliminare questo collegamento attività?",
"deleteText2": "Questa azione non può essere annullata!",
"select": "Select a relation kind",
"select": "Seleziona un tipo di collegamento",
"kinds": {
"subtask": "Subtask | Subtasks",
"parenttask": "Parent Task | Parent Tasks",
"related": "Related Task | Related Tasks",
"subtask": "Sotto-attività | Sotto-attività",
"parenttask": "Attività Principale | Attività Principale",
"related": "Attività Correlata | Attività Correlata",
"duplicateof": "Duplicato Di | Duplicati Di",
"duplicates": "Duplicates | Duplicates",
"blocking": "Blocking | Blocking",
"blocked": "Blocked By | Blocked By",
"precedes": "Precedes | Precedes",
"follows": "Follows | Follows",
"copiedfrom": "Copied From | Copied From",
"copiedto": "Copied To | Copied To"
"duplicates": "Duplicato | Duplicati",
"blocking": "Bloccante | Bloccanti",
"blocked": "Bloccato Da | Bloccati Da",
"precedes": "Precede | Precede",
"follows": "Segue | Segue",
"copiedfrom": "Copiata Da | Copiate Da",
"copiedto": "Copiata In | Copiate In"
}
},
"repeat": {
"everyDay": "Ogni Giorno",
"everyWeek": "Ogni Settimana",
"everyMonth": "Ogni Mese",
"mode": "Repeat mode",
"mode": "Modalità Ripetizione",
"monthly": "Mensilmente",
"fromCurrentDate": "From Current Date",
"fromCurrentDate": "Dalla Data Attuale",
"each": "Ogni",
"specifyAmount": "Specifica una quantità…",
"hours": "Ore",
@ -706,32 +706,32 @@
"years": "Anni"
},
"quickAddMagic": {
"hint": "You can use Quick Add Magic",
"hint": "Puoi usare l'Aggiunta Rapida Magica",
"what": "Cosa?",
"title": "Quick Add Magic",
"intro": "When creating a task, you can use special keywords to directly add attributes to the newly created task. This allows to add commonly used attributes to tasks much faster.",
"title": "Aggiunta Rapida Magica",
"intro": "Quando si crea un'attività, è possibile utilizzare parole chiave speciali per aggiungere direttamente attributi all'attività appena creata. Questo permette di aggiungere gli attributi comuni molto più velocemente.",
"multiple": "Puoi usarlo più volte.",
"label1": "To add a label, simply prefix the name of the label with {prefix}.",
"label2": "Vikunja will first check if the label already exist and create it if not.",
"label3": "To use spaces, simply add a \" around the label name.",
"label4": "For example: {prefix}\"Label with spaces\".",
"priority1": "To set a task's priority, add a number 1-5, prefixed with a {prefix}.",
"priority2": "The higher the number, the higher the priority.",
"assignees": "To directly assign the task to a user, add their username prefixed with {prefix} to the task.",
"list1": "To set a list for the task to appear in, enter its name prefixed with {prefix}.",
"list2": "This will return an error if the list does not exist.",
"label1": "Per aggiungere un'etichetta, basta aggiungere il nome dell'etichetta preceduto da {prefix}.",
"label2": "Vikunja controllerà prima se l'etichetta esiste già e nel caso la creerà.",
"label3": "Per usare gli spazi, basta \" prima e dopo del nome dell'etichetta.",
"label4": "Per esempio: {prefix}\"Etichetta con spazi\".",
"priority1": "Per impostare la priorità di un'attività, aggiungi un numero 1-5, preceduto da {prefix}.",
"priority2": "Più alto è il numero, più alta è la priorità.",
"assignees": "Per assegnare direttamente l'attività a un utente, aggiungere il suo nome utente preceduto da {prefix} all'attività.",
"list1": "Per impostare una lista di appartenenza all'attività, inserisci il suo nome prefisso con {prefix}.",
"list2": "Ciò restituirà un errore se la lista non esiste.",
"dateAndTime": "Data e ora",
"date": "Any date will be used as the due date of the new task. You can use dates in any of these formats:",
"dateWeekday": "any weekday, will use the next date with that date",
"dateCurrentYear": "will use the current year",
"dateNth": "will use the {day}th of the current month",
"dateTime": "Combine any of the date formats with \"{time}\" (or {timePM}) to set a time.",
"repeats": "Repeating tasks",
"repeatsDescription": "To set a task as repeating in an interval, simply add '{suffix}' to the task text. The amount needs to be a number and can be omitted to use just the type (see examples)."
"date": "Qualsiasi data verrà utilizzata come data di scadenza della nuova attività. È possibile utilizzare le date in uno qualsiasi di questi formati:",
"dateWeekday": "qualsiasi giorno della settimana, userà la data più vicina",
"dateCurrentYear": "userà lanno corrente",
"dateNth": "userà il {day} del mese corrente",
"dateTime": "Combina uno qualsiasi dei formati di data con \"{time}\" (o {timePM}) per impostare un orario.",
"repeats": "Attività ricorrenti",
"repeatsDescription": "Per impostare un'attività come ricorrente in un intervallo, basta aggiungere '{suffix}' al testo dell'attività. La quantità deve essere un numero e può essere omesso per usare solo il tipo (vedi esempi)."
}
},
"team": {
"title": "Teams",
"title": "Gruppi",
"noTeams": "Non fai parte di nessun gruppo.",
"create": {
"title": "Crea un nuovo gruppo",
@ -746,23 +746,23 @@
"makeAdmin": "Rendi Amministratore",
"success": "Gruppo aggiornato.",
"userAddedSuccess": "Membro del gruppo aggiunto.",
"madeMember": "The team member was successfully made member.",
"madeAdmin": "The team member was successfully made admin.",
"madeMember": "Membro del gruppo reso membro.",
"madeAdmin": "Membro del gruppo reso amministratore.",
"delete": {
"header": "Elimina il gruppo",
"text1": "Sei sicuro di voler eliminare questo gruppo e tutti i suoi membri?",
"text2": "All team members will lose access to lists and namespaces shared with this team. This CANNOT BE UNDONE!",
"text2": "Tutti i membri del gruppo perderanno l'accesso alle liste e ai namespace condivisi con questo gruppo. NON PUÒ ESSERE RIPRISTINATO!",
"success": "Gruppo eliminato."
},
"deleteUser": {
"header": "Rimuovi un utente dal gruppo",
"text1": "Confermi di voler rimuovere questo utente dal gruppo?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"text2": "Perderanno l'accesso a tutte le liste e i namespace a cui questo gruppo ha accesso. NON PUÒ ESSERE RIPRISTINATO!",
"success": "Utente rimosso dal gruppo."
}
},
"attributes": {
"name": "Team Name",
"name": "Nome Gruppo",
"namePlaceholder": "Il nome del gruppo va qui…",
"nameRequired": "Specifica un nome.",
"description": "Descrizione",
@ -772,32 +772,32 @@
}
},
"keyboardShortcuts": {
"title": "Keyboard Shortcuts",
"general": "General",
"title": "Tasti Rapidi",
"general": "Generali",
"allPages": "Queste scorciatoie funzionano in tutte le pagine.",
"currentPageOnly": "Queste scorciatoie funzionano solo nella pagina attuale.",
"toggleMenu": "Attiva/Disattiva Menu",
"quickSearch": "Apri la barra di ricerca/azione rapida",
"then": "then",
"then": "e dopo",
"task": {
"title": "Task Page",
"done": "Mark a task as done",
"assign": "Assign to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
"attachment": "Add an attachment to this task",
"related": "Modify related tasks of this task"
"title": "Pagina Attività",
"done": "Fatto",
"assign": "Assegna a un utente",
"labels": "Aggiungi etichette a questa attività",
"dueDate": "Modifica la data di scadenza di questa attività",
"attachment": "Aggiungi un allegato a questa attività",
"related": "Modifica le attività collegate a questa"
},
"list": {
"title": "List Views",
"switchToListView": "Switch to list view",
"switchToGanttView": "Switch to gantt view",
"switchToKanbanView": "Switch to kanban view",
"switchToTableView": "Switch to table view"
"title": "Viste Liste",
"switchToListView": "Passa alla vista Lista",
"switchToGanttView": "Passa alla vista Gantt",
"switchToKanbanView": "Passa alla vista Kanban",
"switchToTableView": "Passa alla vista Tabella"
}
},
"update": {
"available": "There is an update for Vikunja available!",
"available": "È disponibile un aggiornamento per Vikunja!",
"do": "Aggiorna Adesso"
},
"menu": {
@ -805,136 +805,136 @@
"archive": "Archivia",
"duplicate": "Duplica",
"delete": "Elimina",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"unarchive": "Disarchivia",
"setBackground": "Imposta sfondo",
"share": "Condividi",
"newList": "Nuova lista"
},
"apiConfig": {
"url": "URL Vikunja",
"urlPlaceholder": "es. http://localhost:8080",
"change": "change",
"use": "Using Vikunja installation at {0}",
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
"success": "Using Vikunja installation at \"{domain}\".",
"urlRequired": "A url is required."
"change": "modifica",
"use": "Usa l'installazione di Vikunja a {0}",
"error": "Impossibile trovare o usare l'installazione di Vikunja su \"{domain}\". Prova per favore con un altro Url.",
"success": "Utilizzando l'installazione di Vikunja su \"{domain}\".",
"urlRequired": "L'URL è obbligatorio."
},
"loadingError": {
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
"tryAgain": "try again",
"contact": "contact us"
"failed": "Caricamento non riuscito, si prega di {0}. Se l'errore persiste, per favore {1}.",
"tryAgain": "riprova",
"contact": "Contattaci"
},
"notification": {
"title": "Notifications",
"none": "You don't have any notifications. Have a nice day!",
"explainer": "Notifications will appear here when actions on namespaces, lists or tasks you subscribed to happen."
"title": "Notifiche",
"none": "Nessuna notifica. Buona giornata!",
"explainer": "Le notifiche appariranno qui quando le azioni su Namespace, liste o attività a cui hai sottoscritto la sottoscrizione avvengono."
},
"quickActions": {
"commands": "Commands",
"placeholder": "Type a command or search…",
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
"tasks": "Tasks",
"commands": "Comandi",
"placeholder": "Digita un comando o cerca…",
"hint": "Puoi usare {list} per limitare la ricerca a una lista. Unisci {list} o {label} (etichette) alla ricerca per trovare un'attività con quelle etichette o in quella lista. Usa {assignee} per cercare solo i gruppi.",
"tasks": "Attivitá",
"lists": "Liste",
"teams": "Teams",
"newList": "Enter the title of the new list…",
"newTask": "Enter the title of the new task…",
"newNamespace": "Enter the title of the new namespace…",
"newTeam": "Enter the name of the new team…",
"createTask": "Create a task in the current list ({title})",
"createList": "Create a list in the current namespace ({title})",
"teams": "Gruppi",
"newList": "Inserisci il titolo della nuova lista…",
"newTask": "Inserisci il titolo della nuova attività…",
"newNamespace": "Inserisci il titolo del nuovo namespace…",
"newTeam": "Inserisci il nome del nuovo gruppo…",
"createTask": "Crea un'attività nella lista attuale ({title})",
"createList": "Crea una lista nel namespace attuale ({title})",
"cmds": {
"newTask": "New task",
"newList": "New list",
"newNamespace": "New namespace",
"newTeam": "New team"
"newTask": "Nuova attività",
"newList": "Nuova lista",
"newNamespace": "Nuovo Namespace",
"newTeam": "Nuovo gruppo"
}
},
"date": {
"locale": "en",
"locale": "it",
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
"error": {
"error": "Errore",
"success": "Success",
"success": "Fatto",
"0001": "Non ti è permesso farlo.",
"1001": "A user with this username already exists.",
"1001": "Esiste già un utente con questo nome utente.",
"1002": "Un utente con questo indirizzo e-mail esiste già.",
"1004": "No username and password specified.",
"1004": "Nessun nome utente e password specificati.",
"1005": "L'utente non esiste.",
"1006": "Impossibile ottenere l'id utente.",
"1008": "No password reset token provided.",
"1009": "Invalid password reset token.",
"1008": "Nessun codice di reimpostazione password fornito.",
"1009": "Codice di reimpostazione password non valido.",
"1010": "Token di conferma dell'e-mail non valido.",
"1011": "Wrong username or password.",
"1011": "Nome utente o password errati.",
"1012": "Indirizzo e-mail dell'utente non confermato.",
"1013": "La nuova password è vuota.",
"1014": "La vecchia password è vuota.",
"1015": "Autenticazione TOTP già abilitata per questo utente.",
"1016": "Autenticazione TOTP non abilitata per questo utente.",
"1017": "Codice TOTP non valido.",
"1018": "The user avatar type setting is invalid.",
"1018": "L'impostazione del tipo di avatar utente non è valida.",
"2001": "L'ID non può essere vuoto o 0.",
"2002": "Alcuni dati della richiesta non erano validi.",
"3001": "La lista non esiste.",
"3004": "You need to have read permissions on that list to perform that action.",
"3004": "Devi avere i permessi di lettura su quella lista per eseguire quell'azione.",
"3005": "Il titolo della lista non può essere vuoto.",
"3006": "The list share does not exist.",
"3006": "La condivisione della lista non esiste.",
"3007": "Esiste già una lista con questo identificatore.",
"3008": "The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list.",
"4001": "The list task text cannot be empty.",
"4002": "The list task does not exist.",
"3008": "La lista è archiviata e può quindi essere consultata solo in sola lettura. Questo vale anche per tutte le attività associate a questa lista.",
"4001": "Il testo delle attività della lista non può essere vuoto.",
"4002": "Lista di attività non esistente.",
"4003": "Tutte le attività di modifica in blocco devono appartenere alla stessa lista.",
"4004": "Hai bisogno di almeno un'attività quando si modificano in blocco le attività.",
"4005": "Non hai il permesso di vedere l'attività.",
"4006": "You can't set a parent task as the task itself.",
"4007": "You can't create a task relation with an invalid kind of relation.",
"4008": "You can't create a task relation which already exists.",
"4009": "The task relation does not exist.",
"4010": "Cannot relate a task with itself.",
"4011": "The task attachment does not exist.",
"4012": "The task attachment is too large.",
"4013": "The task sort param is invalid.",
"4014": "The task sort order is invalid.",
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.",
"4019": "Invalid task filter value.",
"5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.",
"5006": "The namespace name cannot be empty.",
"5009": "You need to have namespace read access to perform that action.",
"5010": "This team does not have access to that namespace.",
"5011": "This user has already access to that namespace.",
"5012": "The namespace is archived and can therefore only be accessed read only.",
"6001": "The team name cannot be empty.",
"6002": "The team does not exist.",
"6004": "The team already has access to that namespace or list.",
"6005": "The user is already a member of that team.",
"6006": "Cannot delete the last team member.",
"6007": "The team does not have access to the list to perform that action.",
"7002": "The user already has access to that list.",
"4006": "Non è possibile impostare un'attività principale come l'attività stessa.",
"4007": "Non è possibile creare una relazione di attività con un tipo di relazione non valido.",
"4008": "Non è possibile creare una relazione di attività già esistente.",
"4009": "La relazione di attività non esiste.",
"4010": "Non è possibile relazionare un'attività con se stessa.",
"4011": "L'allegato dell'attività non esiste.",
"4012": "L'allegato dell'attività è troppo grande.",
"4013": "Il parametro di ordinamento dei task non è valido.",
"4014": "L' ordinamento dei task non è valido.",
"4015": "Il commento all'attività non esiste.",
"4016": "Campo attività non valido.",
"4017": "Comparatore di filtri attività non valido.",
"4018": "Concatenatore filtro attività non valido.",
"4019": "Filtro attività non valido.",
"5001": "Il namespace non esiste.",
"5003": "Non hai accesso a questo namespace.",
"5006": "Il nome del namespace non può essere vuoto.",
"5009": "Devi avere accesso in lettura al namespace per effettuare questa operazione.",
"5010": "Il tuo gruppo non ha accesso a questo namespace.",
"5011": "Questo utente ha già accesso a quel namespace.",
"5012": "Il namespace è archiviato e può quindi essere accessibile solo in sola lettura.",
"6001": "Il nome del gruppo non può essere vuoto.",
"6002": "Gruppo non esistente.",
"6004": "Il team ha già accesso a questo namespace o lista.",
"6005": "L'utente è già membro di quel gruppo.",
"6006": "Non è possibile eliminare l'ultimo membro del gruppo.",
"6007": "Il gruppo non ha accesso alla lista per eseguire quell'azione.",
"7002": "L'utente ha già accesso a quella lista.",
"7003": "Non hai accesso a quella lista.",
"8001": "Questa etichetta esiste già in quell'attività.",
"8002": "L'etichetta non esiste.",
"8003": "Non hai accesso a questa etichetta.",
"9001": "The right is invalid.",
"10001": "The bucket does not exist.",
"10002": "The bucket does not belong to that list.",
"10003": "You cannot remove the last bucket on a list.",
"10004": "You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold.",
"10005": "There can be only one done bucket per list.",
"11001": "The saved filter does not exist.",
"11002": "Saved filters are not available for link shares.",
"12001": "The subscription entity type is invalid.",
"12002": "You are already subscribed to the entity itself or a parent entity.",
"13001": "This link share requires a password for authentication, but none was provided.",
"13002": "The provided link share password was invalid."
"9001": "Permesso non valido.",
"10001": "Colonna non esistente.",
"10002": "La colonna non appartiene a quella lista.",
"10003": "Non puoi rimuovere l'ultima colonna di una lista.",
"10004": "Non puoi aggiungere l'attività a questa colonna perché ha già superato il limite di attività che può contenere.",
"10005": "Ci può essere solo una colonna completati per lista.",
"11001": "Filtro salvato non esistente.",
"11002": "I filtri salvati non sono disponibili per i link di condivisione.",
"12001": "Il tipo di entità sottoscritto non è valido.",
"12002": "Sei già iscritto all'entità stessa o a un'entità principale.",
"13001": "Questa condivisione di link richiede una password per l'autenticazione, ma non è stato inserita.",
"13002": "La password inserita per il link di condivisione è valida."
},
"about": {
"title": "About",
"frontendVersion": "Frontend Version: {version}",
"apiVersion": "API Version: {version}"
"title": "Informazioni",
"frontendVersion": "Versione Frontend: {version}",
"apiVersion": "Versione API: {version}"
}
}

View File

@ -545,7 +545,7 @@
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"done": "Done!",
"done": "Mark task done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Updated {0}",
@ -781,7 +781,7 @@
"then": "then",
"task": {
"title": "Task Page",
"done": "Mark a task as done",
"done": "Done",
"assign": "Assign to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
@ -899,7 +899,7 @@
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.",
"4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.",
"5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.",

940
src/i18n/lang/pl-PL.json Normal file
View File

@ -0,0 +1,940 @@
{
"home": {
"welcomeNight": "Good Night {username}",
"welcomeMorning": "Good Morning {username}",
"welcomeDay": "Hi {username}",
"welcomeEvening": "Good Evening {username}",
"lastViewed": "Last viewed",
"list": {
"newText": "You can create a new list for your new tasks:",
"new": "New list",
"importText": "Or import your lists and tasks from other services into Vikunja:",
"import": "Import your data into Vikunja"
}
},
"404": {
"title": "Not found",
"text": "The page you requested does not exist."
},
"ready": {
"loading": "Vikunja is loading…",
"errorOccured": "An error occured:",
"checkApiUrl": "Please check if the api url is correct.",
"noApiUrlConfigured": "No API url was configured. Please set one below:"
},
"offline": {
"title": "You are offline.",
"text": "Please check your network connection and try again."
},
"user": {
"auth": {
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "Email address",
"emailPlaceholder": "e.g. frederic{'@'}vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"forgotPassword": "Forgot your password?",
"resetPassword": "Reset your password",
"resetPasswordAction": "Send me a password reset link",
"resetPasswordSuccess": "Check your inbox! You should have an e-mail with instructions on how to reset your password.",
"passwordsDontMatch": "Passwords don't match",
"confirmEmailSuccess": "You successfully confirmed your email! You can log in now.",
"totpTitle": "Two Factor Authentication Code",
"totpPlaceholder": "e.g. 123456",
"login": "Login",
"register": "Register",
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"logout": "Logout"
},
"settings": {
"title": "Settings",
"newPasswordTitle": "Update Your Password",
"newPassword": "New Password",
"newPasswordConfirm": "New Password Confirmation",
"currentPassword": "Current Password",
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Update Your E-Mail Address",
"updateEmailNew": "New Email Address",
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
"general": {
"title": "General Settings",
"name": "Name",
"newName": "The new Name",
"savedSuccess": "The settings were successfully updated.",
"emailReminders": "Send me reminders for tasks via Email",
"overdueReminders": "Send me reminders for overdue undone tasks via email each morning",
"discoverableByName": "Let other users find me when they search for my name",
"discoverableByEmail": "Let other users find me when they search for my full email",
"playSoundWhenDone": "Play a sound when marking tasks as done",
"weekStart": "Week starts on",
"weekStartSunday": "Sunday",
"weekStartMonday": "Monday",
"language": "Language",
"defaultList": "Default List"
},
"totp": {
"title": "Two Factor Authentication",
"enroll": "Enroll",
"finishSetupPart1": "To finish your setup, use this secret in your totp app (Google Authenticator or similar):",
"finishSetupPart2": "After that, enter a code from your app below.",
"scanQR": "Alternatively you can scan this QR code:",
"passcode": "Passcode",
"passcodePlaceholder": "A code generated by your totp application",
"setupSuccess": "You've sucessfully set up two factor authentication!",
"enterPassword": "Please Enter Your Password",
"disable": "Disable two factor authentication",
"confirmSuccess": "You've successfully confirmed your totp setup and can use it from now on!",
"disableSuccess": "Two factor authentication was sucessfully disabled."
},
"caldav": {
"title": "Caldav",
"howTo": "You can connect Vikunja to caldav clients to view and manage all tasks from different clients. Enter this url into your client:",
"more": "More information about caldav in Vikunja"
},
"avatar": {
"title": "Avatar",
"initials": "Initials",
"gravatar": "Gravatar",
"marble": "Marble",
"upload": "Upload",
"uploadAvatar": "Upload Avatar",
"statusUpdateSuccess": "Avatar status was updated successfully!",
"setSuccess": "The avatar has been set successfully!"
},
"quickAddMagic": {
"title": "Quick Add Magic Mode",
"disabled": "Disabled",
"todoist": "Todoist",
"vikunja": "Vikunja"
},
"appearance": {
"title": "Color Scheme",
"setSuccess": "Saved change of color scheme to {colorScheme}",
"colorScheme": {
"light": "Light",
"system": "System",
"dark": "Dark"
}
}
},
"deletion": {
"title": "Delete your Vikunja Account",
"text1": "The deletion of your account is permanent and cannot be undone. We will delete all your namespaces, lists, tasks and everything associated with it.",
"text2": "To proceed, please enter your password. You will receive an email with further instructions.",
"confirm": "Delete my account",
"requestSuccess": "The request was successful. You'll receive an email with further instructions.",
"passwordRequired": "Please enter your password.",
"confirmSuccess": "You've successfully confirmed the deletion of your account. We will delete your account in three days.",
"scheduled": "We will delete your Vikunja account at {date} ({dateSince}).",
"scheduledCancel": "To cancel the deletion of your account, click here.",
"scheduledCancelText": "To cancel the deletion of your account, please enter your password below:",
"scheduledCancelConfirm": "Cancel the deletion of my account",
"scheduledCancelSuccess": "We will not delete your account."
},
"export": {
"title": "Export your Vikunja data",
"description": "You can request a copy of all your Vikunja data. This include Namespaces, Lists, Tasks and everything associated to them. You can import this data in any Vikunja instance through the migration function.",
"descriptionPasswordRequired": "Please enter your password to proceed:",
"request": "Request a copy of my Vikunja Data",
"success": "You've successfully requested your Vikunja Data! We will send you an email once it's ready to download.",
"downloadTitle": "Download your exported Vikunja data"
}
},
"list": {
"archived": "This list is archived. It is not possible to create new or edit tasks for it.",
"title": "List Title",
"color": "Color",
"lists": "Lists",
"search": "Type to search for a list…",
"searchSelect": "Click or press enter to select this list",
"shared": "Shared Lists",
"create": {
"header": "New list",
"titlePlaceholder": "The list's title goes here…",
"addTitleRequired": "Please specify a title.",
"createdSuccess": "The list was successfully created.",
"addListRequired": "Please specify a list or set a default list in the settings."
},
"archive": {
"title": "Archive \"{list}\"",
"archive": "Archive this list",
"unarchive": "Un-Archive this list",
"unarchiveText": "You will be able to create new tasks or edit it.",
"archiveText": "You won't be able to edit this list or create new tasks until you un-archive it.",
"success": "The list was successfully archived."
},
"background": {
"title": "Set list background",
"remove": "Remove Background",
"upload": "Choose a background from your pc",
"searchPlaceholder": "Search for a background…",
"poweredByUnsplash": "Powered by Unsplash",
"loadMore": "Load more photos",
"success": "The background has been set successfully!",
"removeSuccess": "The background has been removed successfully!"
},
"delete": {
"title": "Delete \"{list}\"",
"header": "Delete this list",
"text1": "Are you sure you want to delete this list and all of its contents?",
"text2": "This includes all tasks and CANNOT BE UNDONE!",
"success": "The list was successfully deleted."
},
"duplicate": {
"title": "Duplicate this list",
"label": "Duplicate",
"text": "Select a namespace which should hold the duplicated list:",
"success": "The list was successfully duplicated."
},
"edit": {
"header": "Edit This List",
"title": "Edit \"{list}\"",
"titlePlaceholder": "The list title goes here…",
"identifierTooltip": "The list identifier can be used to uniquely identify a task across lists. You can set it to empty to disable it.",
"identifier": "List Identifier",
"identifierPlaceholder": "The list identifier goes here…",
"description": "Description",
"descriptionPlaceholder": "The lists description goes here…",
"color": "Color",
"success": "The list was successfully updated."
},
"share": {
"header": "Share this list",
"title": "Share \"{list}\"",
"share": "Share",
"links": {
"title": "Share Links",
"what": "What is a share link?",
"explanation": "Share Links allow you to easily share a list with other users who don't have an account on Vikunja.",
"create": "Create a new link share",
"name": "Name (optional)",
"namePlaceholder": "e.g. Lorem Ipsum",
"nameExplanation": "All actions done by this link share will show up with the name.",
"password": "Password (optional)",
"passwordExplanation": "When authenticating, the user will be required to enter this password.",
"noName": "No name set",
"remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this list with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted"
},
"userTeam": {
"typeUser": "user | users",
"typeTeam": "team | teams",
"shared": "Shared with these {type}",
"you": "You",
"notShared": "Not shared with any {type} yet.",
"removeHeader": "Remove a {type} from the {sharable}",
"removeText": "Are you sure you want to remove this {sharable} from the {type}? This cannot be undone!",
"removeSuccess": "The {sharable} was successfully removed from the {type}.",
"addedSuccess": "The {type} was successfully added.",
"updatedSuccess": "The {type} was successfully added."
},
"right": {
"title": "Right",
"read": "Read only",
"readWrite": "Read & write",
"admin": "Admin"
},
"attributes": {
"link": "Link",
"name": "Name",
"sharedBy": "Shared by",
"right": "Right",
"delete": "Delete"
}
},
"list": {
"title": "List",
"add": "Add",
"addPlaceholder": "Add a new task…",
"empty": "This list is currently empty.",
"newTaskCta": "Create a new task.",
"editTask": "Edit Task"
},
"gantt": {
"title": "Gantt",
"showTasksWithoutDates": "Show tasks which don't have dates set",
"size": "Size",
"default": "Default",
"month": "Month",
"day": "Day",
"from": "From",
"to": "To",
"noDates": "This task has no dates set."
},
"table": {
"title": "Table",
"columns": "Columns"
},
"kanban": {
"title": "Kanban",
"limit": "Limit: {limit}",
"noLimit": "Not Set",
"doneBucket": "Done bucket",
"doneBucketHint": "All tasks moved into this bucket will automatically marked as done.",
"doneBucketHintExtended": "All tasks moved into the done bucket will be marked as done automatically. All tasks marked as done from elsewhere will be moved as well.",
"doneBucketSavedSuccess": "The done bucket has been saved successfully.",
"deleteLast": "You cannot remove the last bucket.",
"addTaskPlaceholder": "Enter the new task title…",
"addTask": "Add a task",
"addAnotherTask": "Add another task",
"addBucket": "Create a new bucket",
"addBucketPlaceholder": "Enter the new bucket title…",
"deleteHeaderBucket": "Delete the bucket",
"deleteBucketText1": "Are you sure you want to delete this bucket?",
"deleteBucketText2": "This will not delete any tasks but move them into the default bucket.",
"deleteBucketSuccess": "The bucket has been deleted successfully.",
"bucketTitleSavedSuccess": "The bucket title has been saved successfully.",
"bucketLimitSavedSuccess": "The bucket limit been saved successfully.",
"collapse": "Collapse this bucket"
},
"pseudo": {
"favorites": {
"title": "Favorites"
}
}
},
"namespace": {
"title": "Namespaces & Lists",
"namespace": "Namespace",
"showArchived": "Show Archived",
"noneAvailable": "You don't have any namespaces right now.",
"unarchive": "Un-Archive",
"archived": "Archived",
"noLists": "This namespace does not contain any lists.",
"createList": "Create a new list in this namespace.",
"namespaces": "Namespaces",
"search": "Type to search for a namespace…",
"create": {
"title": "New namespace",
"titleRequired": "Please specify a title.",
"explanation": "A namespace is a collection of lists you can share and use to organize your lists with. In fact, every list belongs to a namepace.",
"tooltip": "What's a namespace?",
"success": "The namespace was successfully created."
},
"archive": {
"titleArchive": "Archive \"{namespace}\"",
"titleUnarchive": "Un-Archive \"{namespace}\"",
"archiveText": "You won't be able to edit this namespace or create new lists until you un-archive it. This will also archive all lists in this namespace.",
"unarchiveText": "You will be able to create new lists or edit it.",
"success": "The namespace was successfully archived.",
"description": "If a namespace is archived, you cannot create new lists or edit it."
},
"delete": {
"title": "Delete \"{namespace}\"",
"text1": "Are you sure you want to delete this namespace and all of its contents?",
"text2": "This includes all lists and tasks and CANNOT BE UNDONE!",
"success": "The namespace was successfully deleted."
},
"edit": {
"title": "Edit \"{namespace}\"",
"success": "The namespace was successfully updated."
},
"share": {
"title": "Share \"{namespace}\""
},
"attributes": {
"title": "Namespace Title",
"titlePlaceholder": "The namespace title goes here…",
"description": "Description",
"descriptionPlaceholder": "The namespaces description goes here…",
"color": "Color",
"archived": "Is Archived",
"isArchived": "This namespace is archived"
},
"pseudo": {
"sharedLists": {
"title": "Shared Lists"
},
"favorites": {
"title": "Favorites"
},
"savedFilters": {
"title": "Filters"
}
}
},
"filters": {
"title": "Filters",
"clear": "Clear Filters",
"attributes": {
"title": "Title",
"titlePlaceholder": "The saved filter title goes here…",
"description": "Description",
"descriptionPlaceholder": "The description goes here…",
"includeNulls": "Include Tasks which don't have a value set",
"requireAll": "Require all filters to be true for a task to show up",
"showDoneTasks": "Show Done Tasks",
"sortAlphabetically": "Sort Alphabetically",
"enablePriority": "Enable Filter By Priority",
"enablePercentDone": "Enable Filter By Percent Done",
"dueDateRange": "Due Date Range",
"startDateRange": "Start Date Range",
"endDateRange": "End Date Range",
"reminderRange": "Reminder Date Range"
},
"create": {
"title": "New Saved Filter",
"description": "A saved filter is a virtual list which is computed from a set of filters each time it is accessed. Once created, it will appear in a special namespace.",
"action": "Create new saved filter"
},
"delete": {
"header": "Delete this saved filter",
"text": "Are you sure you want to delete this saved filter?",
"success": "The filter was deleted successfully."
},
"edit": {
"title": "Edit This Saved Filter",
"success": "The filter was saved successfully."
}
},
"migrate": {
"title": "Migrate from other services to Vikunja",
"titleService": "Import your data from {name} into Vikunja",
"import": "Import your data into Vikunja",
"description": "Click on the logo of one of the third-party services below to get started.",
"descriptionDo": "Vikunja will import all lists, tasks, notes, reminders and files you have access to.",
"authorize": "To authorize Vikunja to access your {name} Account, click the button below.",
"getStarted": "Get Started",
"inProgress": "Importing in progress…",
"alreadyMigrated1": "It looks like you've already imported your stuff from {name} at {date}.",
"alreadyMigrated2": "Importing again is possible, but might create duplicates. Are you sure?",
"confirm": "I am sure, please start migrating now!",
"importUpload": "To import data from {name} into Vikunja, click the button below to select a file.",
"upload": "Upload file"
},
"label": {
"title": "Labels",
"manage": "Manage labels",
"description": "Click on a label to edit it. You can edit all labels you created, you can use all labels which are associated with a task to whose list you have access.",
"newCTA": "You currently do not have any labels.",
"search": "Type to search for a label…",
"create": {
"header": "New label",
"title": "Create a new label",
"titleRequired": "Please specify a title.",
"success": "The label was successfully created."
},
"edit": {
"header": "Edit Label",
"forbidden": "You are not allowed to edit this label because you dont own it.",
"success": "The label was successfully updated."
},
"deleteSuccess": "The label was successfully deleted.",
"attributes": {
"title": "Title",
"titlePlaceholder": "The label title goes here…",
"description": "Description",
"descriptionPlaceholder": "Label description",
"color": "Color"
}
},
"sharing": {
"authenticating": "Authenticating…",
"passwordRequired": "This shared list requires a password. Please enter it below:",
"error": "An error occured.",
"invalidPassword": "The password is invalid."
},
"navigation": {
"overview": "Overview",
"upcoming": "Upcoming",
"settings": "Settings",
"imprint": "Imprint",
"privacy": "Privacy Policy"
},
"misc": {
"loading": "Loading…",
"save": "Save",
"delete": "Delete",
"confirm": "Confirm",
"cancel": "Cancel",
"refresh": "Refresh",
"disable": "Disable",
"copy": "Copy to clipboard",
"search": "Search",
"searchPlaceholder": "Type to search…",
"previous": "Previous",
"next": "Next",
"poweredBy": "Powered by Vikunja",
"info": "Info",
"create": "Create",
"doit": "Do it!",
"saving": "Saving…",
"saved": "Saved!",
"default": "Default",
"close": "Close",
"download": "Download",
"showMenu": "Show the menu",
"hideMenu": "Hide the menu",
"forExample": "For example:",
"welcomeBack": "Welcome Back!"
},
"input": {
"resetColor": "Reset Color",
"datepicker": {
"today": "Today",
"tomorrow": "Tomorrow",
"nextMonday": "Next Monday",
"thisWeekend": "This Weekend",
"laterThisWeek": "Later This Week",
"nextWeek": "Next Week",
"chooseDate": "Choose a date"
},
"editor": {
"edit": "Edit",
"done": "Done",
"heading1": "Heading 1",
"heading2": "Heading 2",
"heading3": "Heading 3",
"headingSmaller": "Heading Smaller",
"headingBigger": "Heading Bigger",
"bold": "Bold",
"italic": "Italic",
"strikethrough": "Strikethrough",
"code": "Code",
"quote": "Quote",
"unorderedList": "Unordered List",
"orderedList": "Ordered List",
"cleanBlock": "Clean Block",
"link": "Link",
"image": "Image",
"table": "Table",
"horizontalRule": "Horizontal Rule",
"sideBySide": "Side By Side",
"guide": "Guide"
},
"multiselect": {
"createPlaceholder": "Create new",
"selectPlaceholder": "Click or press enter to select"
}
},
"task": {
"task": "Task",
"new": "Create a new task",
"delete": "Delete this task",
"createSuccess": "The task was successfully created.",
"addReminder": "Add a new reminder…",
"doneSuccess": "The task was successfully marked as done.",
"undoneSuccess": "The task was successfully un-marked as done.",
"openDetail": "Open task detail view",
"checklistTotal": "{checked} of {total} tasks",
"checklistAllDone": "{total} tasks",
"show": {
"titleCurrent": "Current Tasks",
"titleDates": "Tasks from {from} until {to}",
"noDates": "Show tasks without dates",
"current": "Current tasks",
"from": "Tasks from",
"until": "until",
"today": "Today",
"nextWeek": "Next Week",
"nextMonth": "Next Month",
"noTasks": "Nothing to do — Have a nice day!"
},
"detail": {
"chooseDueDate": "Click here to set a due date",
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"done": "Mark task done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Updated {0}",
"doneAt": "Done {0}",
"updateSuccess": "The task was saved successfully.",
"deleteSuccess": "The task has been deleted successfully.",
"belongsToList": "This task belongs to list '{list}'",
"due": "Due {at}",
"closePopup": "Close popup",
"delete": {
"header": "Delete this task",
"text1": "Are you sure you want to remove this task?",
"text2": "This will also remove all attachments, reminders and relations associated with this task and cannot be undone!"
},
"actions": {
"assign": "Assign to a user",
"label": "Add labels",
"priority": "Set Priority",
"dueDate": "Set Due Date",
"startDate": "Set a Start Date",
"endDate": "Set an End Date",
"reminders": "Set Reminders",
"repeatAfter": "Set a repeating interval",
"percentDone": "Set Percent Done",
"attachments": "Add attachments",
"relatedTasks": "Add task relations",
"moveList": "Move task",
"color": "Set task color",
"delete": "Delete task",
"favorite": "Save as favorite",
"unfavorite": "Remove from favorites"
}
},
"attributes": {
"assignees": "Assignees",
"color": "Color",
"created": "Created",
"createdBy": "Created By",
"description": "Description",
"done": "Done",
"dueDate": "Due Date",
"endDate": "End Date",
"labels": "Labels",
"percentDone": "% Done",
"priority": "Priority",
"relatedTasks": "Related Tasks",
"reminders": "Reminders",
"repeat": "Repeat",
"startDate": "Start Date",
"title": "Title",
"updated": "Updated"
},
"subscription": {
"subscribedThroughParent": "You can't unsubscribe here because you are subscribed to this {entity} through its {parent}.",
"subscribed": "You are currently subscribed to this {entity} and will receive notifications for changes.",
"notSubscribed": "You are not subscribed to this {entity} and won't receive notifications for changes.",
"subscribe": "Subscribe",
"unsubscribe": "Unsubscribe",
"subscribeSuccess": "You are now subscribed to this {entity}",
"unsubscribeSuccess": "You are now unsubscribed to this {entity}"
},
"attachment": {
"title": "Attachments",
"createdBy": "created {0} by {1}",
"downloadTooltip": "Download this attachment",
"upload": "Upload attachment",
"drop": "Drop files here to upload",
"delete": "Delete attachment",
"deleteTooltip": "Delete this attachment",
"deleteText1": "Are you sure you want to delete the attachment {filename}?",
"deleteText2": "This cannot be undone!",
"copyUrl": "Copy URL",
"copyUrlTooltip": "Copy the url of this attachment for usage in text"
},
"comment": {
"title": "Comments",
"loading": "Loading comments…",
"edited": "edited {date}",
"creating": "Creating comment…",
"placeholder": "Add your comment…",
"comment": "Comment",
"delete": "Delete this comment",
"deleteText1": "Are you sure you want to delete this comment?",
"deleteText2": "This cannot be undone!",
"addedSuccess": "The comment was added successfully."
},
"deferDueDate": {
"title": "Defer due date",
"1day": "1 day",
"3days": "3 days",
"1week": "1 week"
},
"description": {
"placeholder": "Click here to enter a description…",
"empty": "No description available yet."
},
"assignee": {
"placeholder": "Type to assign a user…",
"selectPlaceholder": "Assign this user",
"assignSuccess": "The user has been assigned successfully.",
"unassignSuccess": "The user has been unassigned successfully."
},
"label": {
"placeholder": "Type to add a new label…",
"createPlaceholder": "Add this as new label",
"addSuccess": "The label has been added successfully.",
"createSuccess": "The label has been created successfully.",
"removeSuccess": "The label has been removed successfully.",
"addCreateSuccess": "The label has been created and added successfully."
},
"priority": {
"unset": "Unset",
"low": "Low",
"medium": "Medium",
"high": "high",
"urgent": "Urgent",
"doNow": "DO NOW"
},
"relation": {
"add": "Add a New Task Relation",
"new": "New Task Relation",
"searchPlaceholder": "Type search for a new task to add as related…",
"createPlaceholder": "Add this as new related task",
"differentList": "This task belongs to a different list.",
"differentNamespace": "This task belongs to a different namespace.",
"noneYet": "No task relations yet.",
"delete": "Delete Task Relation",
"deleteText1": "Are you sure you want to delete this task relation?",
"deleteText2": "This cannot be undone!",
"select": "Select a relation kind",
"kinds": {
"subtask": "Subtask | Subtasks",
"parenttask": "Parent Task | Parent Tasks",
"related": "Related Task | Related Tasks",
"duplicateof": "Duplicate Of | Duplicates Of",
"duplicates": "Duplicates | Duplicates",
"blocking": "Blocking | Blocking",
"blocked": "Blocked By | Blocked By",
"precedes": "Precedes | Precedes",
"follows": "Follows | Follows",
"copiedfrom": "Copied From | Copied From",
"copiedto": "Copied To | Copied To"
}
},
"repeat": {
"everyDay": "Every Day",
"everyWeek": "Every Week",
"everyMonth": "Every Month",
"mode": "Repeat mode",
"monthly": "Monthly",
"fromCurrentDate": "From Current Date",
"each": "Each",
"specifyAmount": "Specify an amount…",
"hours": "Hours",
"days": "Days",
"weeks": "Weeks",
"months": "Months",
"years": "Years"
},
"quickAddMagic": {
"hint": "You can use Quick Add Magic",
"what": "What?",
"title": "Quick Add Magic",
"intro": "When creating a task, you can use special keywords to directly add attributes to the newly created task. This allows to add commonly used attributes to tasks much faster.",
"multiple": "You can use this multiple times.",
"label1": "To add a label, simply prefix the name of the label with {prefix}.",
"label2": "Vikunja will first check if the label already exist and create it if not.",
"label3": "To use spaces, simply add a \" around the label name.",
"label4": "For example: {prefix}\"Label with spaces\".",
"priority1": "To set a task's priority, add a number 1-5, prefixed with a {prefix}.",
"priority2": "The higher the number, the higher the priority.",
"assignees": "To directly assign the task to a user, add their username prefixed with {prefix} to the task.",
"list1": "To set a list for the task to appear in, enter its name prefixed with {prefix}.",
"list2": "This will return an error if the list does not exist.",
"dateAndTime": "Date and time",
"date": "Any date will be used as the due date of the new task. You can use dates in any of these formats:",
"dateWeekday": "any weekday, will use the next date with that date",
"dateCurrentYear": "will use the current year",
"dateNth": "will use the {day}th of the current month",
"dateTime": "Combine any of the date formats with \"{time}\" (or {timePM}) to set a time.",
"repeats": "Repeating tasks",
"repeatsDescription": "To set a task as repeating in an interval, simply add '{suffix}' to the task text. The amount needs to be a number and can be omitted to use just the type (see examples)."
}
},
"team": {
"title": "Teams",
"noTeams": "You are currently not part of any teams.",
"create": {
"title": "Create a new team",
"success": "The team was successfully created."
},
"edit": {
"title": "Edit Team \"{team}\"",
"members": "Team Members",
"search": "Type to search a user…",
"addUser": "Add to team",
"makeMember": "Make Member",
"makeAdmin": "Make Admin",
"success": "The team was successfully updated.",
"userAddedSuccess": "The team member was successfully added.",
"madeMember": "The team member was successfully made member.",
"madeAdmin": "The team member was successfully made admin.",
"delete": {
"header": "Delete the team",
"text1": "Are you sure you want to delete this team and all of its members?",
"text2": "All team members will lose access to lists and namespaces shared with this team. This CANNOT BE UNDONE!",
"success": "The team was successfully deleted."
},
"deleteUser": {
"header": "Remove a user from the team",
"text1": "Are you sure you want to remove this user from the team?",
"text2": "They will lose access to all lists and namespaces this team has access to. This CANNOT BE UNDONE!",
"success": "The user was successfully deleted from the team."
}
},
"attributes": {
"name": "Team Name",
"namePlaceholder": "The team's name goes here…",
"nameRequired": "Please specify a name.",
"description": "Description",
"descriptionPlaceholder": "The teams description goes here…",
"admin": "Admin",
"member": "Member"
}
},
"keyboardShortcuts": {
"title": "Keyboard Shortcuts",
"general": "General",
"allPages": "These shortcuts work on all pages.",
"currentPageOnly": "These shortcuts work only on the current page.",
"toggleMenu": "Toggle The Menu",
"quickSearch": "Open the search/quick action bar",
"then": "then",
"task": {
"title": "Task Page",
"done": "Done",
"assign": "Assign to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
"attachment": "Add an attachment to this task",
"related": "Modify related tasks of this task"
},
"list": {
"title": "List Views",
"switchToListView": "Switch to list view",
"switchToGanttView": "Switch to gantt view",
"switchToKanbanView": "Switch to kanban view",
"switchToTableView": "Switch to table view"
}
},
"update": {
"available": "There is an update for Vikunja available!",
"do": "Update Now"
},
"menu": {
"edit": "Edit",
"archive": "Archive",
"duplicate": "Duplicate",
"delete": "Delete",
"unarchive": "Un-Archive",
"setBackground": "Set background",
"share": "Share",
"newList": "New list"
},
"apiConfig": {
"url": "Vikunja URL",
"urlPlaceholder": "eg. https://localhost:3456",
"change": "change",
"use": "Using Vikunja installation at {0}",
"error": "Could not find or use Vikunja installation at \"{domain}\". Please try a different url.",
"success": "Using Vikunja installation at \"{domain}\".",
"urlRequired": "A url is required."
},
"loadingError": {
"failed": "Loading failed, please {0}. If the error persists, please {1}.",
"tryAgain": "try again",
"contact": "contact us"
},
"notification": {
"title": "Notifications",
"none": "You don't have any notifications. Have a nice day!",
"explainer": "Notifications will appear here when actions on namespaces, lists or tasks you subscribed to happen."
},
"quickActions": {
"commands": "Commands",
"placeholder": "Type a command or search…",
"hint": "You can use {list} to limit the search to a list. Combine {list} or {label} (labels) with a search query to search for a task with these labels or on that list. Use {assignee} to only search for teams.",
"tasks": "Tasks",
"lists": "Lists",
"teams": "Teams",
"newList": "Enter the title of the new list…",
"newTask": "Enter the title of the new task…",
"newNamespace": "Enter the title of the new namespace…",
"newTeam": "Enter the name of the new team…",
"createTask": "Create a task in the current list ({title})",
"createList": "Create a list in the current namespace ({title})",
"cmds": {
"newTask": "New task",
"newList": "New list",
"newNamespace": "New namespace",
"newTeam": "New team"
}
},
"date": {
"locale": "en",
"altFormatLong": "j M Y H:i",
"altFormatShort": "j M Y"
},
"error": {
"error": "Error",
"success": "Success",
"0001": "You're not allowed to do that.",
"1001": "A user with this username already exists.",
"1002": "A user with this email address already exists.",
"1004": "No username and password specified.",
"1005": "The user does not exist.",
"1006": "Could not get the user id.",
"1008": "No password reset token provided.",
"1009": "Invalid password reset token.",
"1010": "Invalid email confirm token.",
"1011": "Wrong username or password.",
"1012": "Email address of the user not confirmed.",
"1013": "New password is empty.",
"1014": "Old password is empty.",
"1015": "Totp is already enabled for this user.",
"1016": "Totp is not enabled for this user.",
"1017": "The totp passcode is invalid.",
"1018": "The user avatar type setting is invalid.",
"2001": "ID cannot be empty or 0.",
"2002": "Some of the request data was invalid.",
"3001": "The list does not exist.",
"3004": "You need to have read permissions on that list to perform that action.",
"3005": "The list title cannot be empty.",
"3006": "The list share does not exist.",
"3007": "A list with this identifier already exists.",
"3008": "The list is archived and can therefore only be accessed read only. This is also true for all tasks associated with this list.",
"4001": "The list task text cannot be empty.",
"4002": "The list task does not exist.",
"4003": "All bulk editing tasks must belong to the same list.",
"4004": "Need at least one task when bulk editing tasks.",
"4005": "You do not have the right to see the task.",
"4006": "You can't set a parent task as the task itself.",
"4007": "You can't create a task relation with an invalid kind of relation.",
"4008": "You can't create a task relation which already exists.",
"4009": "The task relation does not exist.",
"4010": "Cannot relate a task with itself.",
"4011": "The task attachment does not exist.",
"4012": "The task attachment is too large.",
"4013": "The task sort param is invalid.",
"4014": "The task sort order is invalid.",
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.",
"5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.",
"5006": "The namespace name cannot be empty.",
"5009": "You need to have namespace read access to perform that action.",
"5010": "This team does not have access to that namespace.",
"5011": "This user has already access to that namespace.",
"5012": "The namespace is archived and can therefore only be accessed read only.",
"6001": "The team name cannot be empty.",
"6002": "The team does not exist.",
"6004": "The team already has access to that namespace or list.",
"6005": "The user is already a member of that team.",
"6006": "Cannot delete the last team member.",
"6007": "The team does not have access to the list to perform that action.",
"7002": "The user already has access to that list.",
"7003": "You do not have access to that list.",
"8001": "This label already exists on that task.",
"8002": "The label does not exist.",
"8003": "You do not have access to this label.",
"9001": "The right is invalid.",
"10001": "The bucket does not exist.",
"10002": "The bucket does not belong to that list.",
"10003": "You cannot remove the last bucket on a list.",
"10004": "You cannot add the task to this bucket as it already exceeded the limit of tasks it can hold.",
"10005": "There can be only one done bucket per list.",
"11001": "The saved filter does not exist.",
"11002": "Saved filters are not available for link shares.",
"12001": "The subscription entity type is invalid.",
"12002": "You are already subscribed to the entity itself or a parent entity.",
"13001": "This link share requires a password for authentication, but none was provided.",
"13002": "The provided link share password was invalid."
},
"about": {
"title": "About",
"frontendVersion": "Frontend Version: {version}",
"apiVersion": "API Version: {version}"
}
}

View File

@ -31,10 +31,9 @@
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "E-mail address",
"email": "Email address",
"emailPlaceholder": "e.g. frederic{'@'}vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"forgotPassword": "Forgot your password?",
"resetPassword": "Reset your password",
@ -45,12 +44,19 @@
"totpTitle": "Two Factor Authentication Code",
"totpPlaceholder": "e.g. 123456",
"login": "Login",
"register": "Register",
"createAccount": "Create account",
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"logout": "Logout"
"logout": "Logout",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
"passwordRequired": "Please provide a password.",
"showPassword": "Show the password",
"hidePassword": "Hide the password",
"noAccountYet": "Don't have an account yet?",
"alreadyHaveAnAccount": "Already have an account?"
},
"settings": {
"title": "Settings",
@ -61,7 +67,7 @@
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Update Your E-Mail Address",
"updateEmailTitle": "Update Your Email Address",
"updateEmailNew": "New Email Address",
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
"general": {
@ -545,7 +551,7 @@
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"done": "Done!",
"done": "Mark task done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Updated {0}",
@ -781,7 +787,7 @@
"then": "then",
"task": {
"title": "Task Page",
"done": "Mark a task as done",
"done": "Done",
"assign": "Assign to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
@ -899,7 +905,7 @@
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.",
"4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.",
"5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.",

View File

@ -31,10 +31,9 @@
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "E-mail address",
"email": "Email address",
"emailPlaceholder": "e.g. frederic{'@'}vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"forgotPassword": "Forgot your password?",
"resetPassword": "Reset your password",
@ -45,12 +44,19 @@
"totpTitle": "Two Factor Authentication Code",
"totpPlaceholder": "e.g. 123456",
"login": "Login",
"register": "Register",
"createAccount": "Create account",
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"logout": "Logout"
"logout": "Logout",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
"passwordRequired": "Please provide a password.",
"showPassword": "Show the password",
"hidePassword": "Hide the password",
"noAccountYet": "Don't have an account yet?",
"alreadyHaveAnAccount": "Already have an account?"
},
"settings": {
"title": "Settings",
@ -61,7 +67,7 @@
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Update Your E-Mail Address",
"updateEmailTitle": "Update Your Email Address",
"updateEmailNew": "New Email Address",
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
"general": {
@ -545,7 +551,7 @@
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"done": "Done!",
"done": "Mark task done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Updated {0}",
@ -781,7 +787,7 @@
"then": "then",
"task": {
"title": "Task Page",
"done": "Mark a task as done",
"done": "Done",
"assign": "Assign to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
@ -899,7 +905,7 @@
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.",
"4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.",
"5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.",

View File

@ -31,10 +31,9 @@
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "E-mail address",
"email": "Email address",
"emailPlaceholder": "e.g. frederic{'@'}vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"forgotPassword": "Forgot your password?",
"resetPassword": "Reset your password",
@ -45,12 +44,19 @@
"totpTitle": "Two Factor Authentication Code",
"totpPlaceholder": "e.g. 123456",
"login": "Login",
"register": "Register",
"createAccount": "Create account",
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"logout": "Logout"
"logout": "Logout",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
"passwordRequired": "Please provide a password.",
"showPassword": "Show the password",
"hidePassword": "Hide the password",
"noAccountYet": "Don't have an account yet?",
"alreadyHaveAnAccount": "Already have an account?"
},
"settings": {
"title": "Settings",
@ -61,7 +67,7 @@
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Update Your E-Mail Address",
"updateEmailTitle": "Update Your Email Address",
"updateEmailNew": "New Email Address",
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
"general": {
@ -545,7 +551,7 @@
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"done": "Done!",
"done": "Mark task done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Updated {0}",
@ -781,7 +787,7 @@
"then": "then",
"task": {
"title": "Task Page",
"done": "Mark a task as done",
"done": "Done",
"assign": "Assign to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
@ -899,7 +905,7 @@
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.",
"4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.",
"5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.",

View File

@ -31,10 +31,9 @@
"username": "Имя пользователя",
"usernameEmail": "Имя пользователя или Email",
"usernamePlaceholder": "напр. frederick",
"email": "E-mail адрес",
"email": "Email address",
"emailPlaceholder": "напр. frederic{'@'}vikunja.io",
"password": "Пароль",
"passwordRepeat": "Пароль ещё раз",
"passwordPlaceholder": "напр. •••••••••••",
"forgotPassword": "Forgot your password?",
"resetPassword": "Сбросить пароль",
@ -45,12 +44,19 @@
"totpTitle": "Код двухфакторной аутентификации",
"totpPlaceholder": "напр. 123456",
"login": "Войти",
"register": "Зарегистрироваться",
"createAccount": "Create account",
"loginWith": "Войти через {provider}",
"authenticating": "Аутентификация…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"logout": "Выйти"
"logout": "Выйти",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
"passwordRequired": "Please provide a password.",
"showPassword": "Show the password",
"hidePassword": "Hide the password",
"noAccountYet": "Don't have an account yet?",
"alreadyHaveAnAccount": "Already have an account?"
},
"settings": {
"title": "Настройки",
@ -61,7 +67,7 @@
"currentPasswordPlaceholder": "Твой текущий пароль",
"passwordsDontMatch": "Новые пароли не совпадают.",
"passwordUpdateSuccess": "Пароль изменён.",
"updateEmailTitle": "Изменить E-mail",
"updateEmailTitle": "Update Your Email Address",
"updateEmailNew": "Новый Email адрес",
"updateEmailSuccess": "E-mail успешно изменён. Для подтверждения нажми на ссылку в письме, которое мы тебе отправили.",
"general": {
@ -545,7 +551,7 @@
"chooseStartDate": "Нажми для выбора даты начала",
"chooseEndDate": "Нажми для выбора даты завершения",
"move": "Переместить задачу в другой список",
"done": "Завершено!",
"done": "Mark task done!",
"undone": "Не завершено",
"created": "Создана {0} пользователем {1}",
"updated": "Обновлено {0}",
@ -781,7 +787,7 @@
"then": "then",
"task": {
"title": "Страница задачи",
"done": "Пометить задачу завершённой",
"done": "Done",
"assign": "Assign to a user",
"labels": "Добавить метки этой задаче",
"dueDate": "Изменить срок этой задачи",
@ -899,7 +905,7 @@
"4015": "Комментарий не существует.",
"4016": "Неверное поле задачи.",
"4017": "Неверный сравнитель фильтров задач.",
"4018": "Неверный соединитель фильтров задач.",
"4018": "Invalid task filter concatenator.",
"4019": "Неверное значение фильтра задач.",
"5001": "Пространство имён не существует.",
"5003": "Нет доступа к указанному пространству имён.",

View File

@ -31,10 +31,9 @@
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "E-mail address",
"email": "Email address",
"emailPlaceholder": "e.g. frederic{'@'}vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"forgotPassword": "Forgot your password?",
"resetPassword": "Reset your password",
@ -45,12 +44,19 @@
"totpTitle": "Two Factor Authentication Code",
"totpPlaceholder": "e.g. 123456",
"login": "Login",
"register": "Register",
"createAccount": "Create account",
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"logout": "Logout"
"logout": "Logout",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
"passwordRequired": "Please provide a password.",
"showPassword": "Show the password",
"hidePassword": "Hide the password",
"noAccountYet": "Don't have an account yet?",
"alreadyHaveAnAccount": "Already have an account?"
},
"settings": {
"title": "Settings",
@ -61,7 +67,7 @@
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Update Your E-Mail Address",
"updateEmailTitle": "Update Your Email Address",
"updateEmailNew": "New Email Address",
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
"general": {
@ -545,7 +551,7 @@
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"done": "Done!",
"done": "Mark task done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Updated {0}",
@ -781,7 +787,7 @@
"then": "then",
"task": {
"title": "Task Page",
"done": "Mark a task as done",
"done": "Done",
"assign": "Assign to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
@ -899,7 +905,7 @@
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.",
"4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.",
"5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.",

View File

@ -31,10 +31,9 @@
"username": "Username",
"usernameEmail": "Username Or Email Address",
"usernamePlaceholder": "e.g. frederick",
"email": "E-mail address",
"email": "Email address",
"emailPlaceholder": "e.g. frederic{'@'}vikunja.io",
"password": "Password",
"passwordRepeat": "Retype your password",
"passwordPlaceholder": "e.g. •••••••••••",
"forgotPassword": "Forgot your password?",
"resetPassword": "Reset your password",
@ -45,12 +44,19 @@
"totpTitle": "Two Factor Authentication Code",
"totpPlaceholder": "e.g. 123456",
"login": "Login",
"register": "Register",
"createAccount": "Create account",
"loginWith": "Log in with {provider}",
"authenticating": "Authenticating…",
"openIdStateError": "State does not match, refusing to continue!",
"openIdGeneralError": "An error occured while authenticating against the third party.",
"logout": "Logout"
"logout": "Logout",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
"passwordRequired": "Please provide a password.",
"showPassword": "Show the password",
"hidePassword": "Hide the password",
"noAccountYet": "Don't have an account yet?",
"alreadyHaveAnAccount": "Already have an account?"
},
"settings": {
"title": "Settings",
@ -61,7 +67,7 @@
"currentPasswordPlaceholder": "Your current password",
"passwordsDontMatch": "The new password and its confirmation don't match.",
"passwordUpdateSuccess": "The password was successfully updated.",
"updateEmailTitle": "Update Your E-Mail Address",
"updateEmailTitle": "Update Your Email Address",
"updateEmailNew": "New Email Address",
"updateEmailSuccess": "Your email address was successfully updated. We've sent you a link to confirm it.",
"general": {
@ -545,7 +551,7 @@
"chooseStartDate": "Click here to set a start date",
"chooseEndDate": "Click here to set an end date",
"move": "Move task to a different list",
"done": "Done!",
"done": "Mark task done!",
"undone": "Mark as undone",
"created": "Created {0} by {1}",
"updated": "Updated {0}",
@ -781,7 +787,7 @@
"then": "then",
"task": {
"title": "Task Page",
"done": "Mark a task as done",
"done": "Done",
"assign": "Assign to a user",
"labels": "Add labels to this task",
"dueDate": "Change the due date of this task",
@ -899,7 +905,7 @@
"4015": "The task comment does not exist.",
"4016": "Invalid task field.",
"4017": "Invalid task filter comparator.",
"4018": "Invalid task filter concatinator.",
"4018": "Invalid task filter concatenator.",
"4019": "Invalid task filter value.",
"5001": "The namespace does not exist.",
"5003": "You do not have access to the specified namespace.",

View File

@ -31,10 +31,9 @@
"username": "Tên người dùng",
"usernameEmail": "Tên người dùng hoặc Email",
"usernamePlaceholder": "ví dụ: frederick",
"email": "Địa chỉ Email",
"email": "Email address",
"emailPlaceholder": "ví dụ: frederic{'@'}vikunja.io",
"password": "Mật khẩu",
"passwordRepeat": "Nhập lại mật khẩu",
"passwordPlaceholder": "ví dụ: •••••••••••",
"forgotPassword": "Bạn quên mật khẩu?",
"resetPassword": "Reset mật khẩu của bạn",
@ -45,12 +44,19 @@
"totpTitle": "Mã xác thực hai lớp",
"totpPlaceholder": "ví dụ: 123456",
"login": "Đăng nhập",
"register": "Đăng ký",
"createAccount": "Create account",
"loginWith": "Đăng nhập với {provider}",
"authenticating": "Đang xác thực…",
"openIdStateError": "Trạng thái không khớp, từ chối tiếp tục!",
"openIdGeneralError": "Đã xảy ra lỗi khi xác thực với bên thứ ba.",
"logout": "Đăng xuất"
"logout": "Đăng xuất",
"emailInvalid": "Please enter a valid email address.",
"usernameRequired": "Please provide a username.",
"passwordRequired": "Please provide a password.",
"showPassword": "Show the password",
"hidePassword": "Hide the password",
"noAccountYet": "Don't have an account yet?",
"alreadyHaveAnAccount": "Already have an account?"
},
"settings": {
"title": "Cài đặt",
@ -61,7 +67,7 @@
"currentPasswordPlaceholder": "Nhập mật khẩu hiện tại của bạn",
"passwordsDontMatch": "Mật khẩu mới và xác nhận của nó không khớp.",
"passwordUpdateSuccess": "Mật khẩu đã được cập nhật thành công.",
"updateEmailTitle": "Cập nhật địa chỉ e-mail của bạn",
"updateEmailTitle": "Update Your Email Address",
"updateEmailNew": "Địa chỉ email mới",
"updateEmailSuccess": "Địa chỉ email của bạn đã được cập nhật thành công. Chúng tôi đã gửi cho bạn một liên kết để xác nhận nó.",
"general": {
@ -545,7 +551,7 @@
"chooseStartDate": "Bấm vào đây để đặt ngày bắt đầu",
"chooseEndDate": "Bấm vào đây để đặt ngày kết thúc",
"move": "Di chuyển công việc sang một danh sách khác",
"done": "Xong!",
"done": "Mark task done!",
"undone": "Đánh dấu Chưa xong",
"created": "Đã tạo được {0} bởi {1}",
"updated": "Đã cập nhật {0}",
@ -781,7 +787,7 @@
"then": "sau đó",
"task": {
"title": "Trang công việc",
"done": "Đánh dấu hoàn thành",
"done": "Done",
"assign": "Chỉ định một người",
"labels": "Thêm nhãn cho công việc này",
"dueDate": "Thay đổi ngày hết hạn của công việc này",
@ -899,7 +905,7 @@
"4015": "Bình luận không tồn tại.",
"4016": "Trường công việc không hợp lệ.",
"4017": "Bộ so sánh bộ lọc công việc không hợp lệ.",
"4018": "Bộ lọc kết hợp không hợp lệ.",
"4018": "Invalid task filter concatenator.",
"4019": "Giá trị bộ lọc công việc không hợp lệ.",
"5001": "Góc làm việc không có nữa.",
"5003": "Bạn chưa được phép bước vào vào góc làm việc được chỉ định.",

View File

@ -16,6 +16,8 @@ import {
faCocktail,
faCoffee,
faCog,
faEye,
faEyeSlash,
faEllipsisH,
faEllipsisV,
faExclamation,
@ -87,6 +89,8 @@ library.add(faCocktail)
library.add(faCoffee)
library.add(faCog)
library.add(faComments)
library.add(faEye)
library.add(faEyeSlash)
library.add(faEllipsisH)
library.add(faEllipsisV)
library.add(faExclamation)

View File

@ -18,7 +18,7 @@ declare global {
}
}
import {formatDate, formatDateShort, formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
import {formatDate, formatDateShort, formatDateLong, formatDateSince, formatISO} from '@/helpers/time/formatDate'
// @ts-ignore
import {VERSION} from './version.json'
@ -52,6 +52,7 @@ app.use(Notifications)
// directives
import focus from '@/directives/focus'
// @ts-ignore The export does exist, ts just doesn't find it.
import { VTooltip } from 'v-tooltip'
import 'v-tooltip/dist/v-tooltip.css'
import shortcut from '@/directives/shortcut'
@ -84,6 +85,7 @@ app.mixin({
format: formatDate,
formatDate: formatDateLong,
formatDateShort: formatDateShort,
formatISO,
getNamespaceTitle,
getListTitle,
setTitle,

View File

@ -10,9 +10,6 @@ import {parseDateOrNull} from '@/helpers/parseDateOrNull'
const SUPPORTS_TRIGGERED_NOTIFICATION = 'Notification' in window && 'showTrigger' in Notification.prototype
export default class TaskModel extends AbstractModel {
defaultColor = '198CFF'
constructor(data) {
super(data)

View File

@ -11,6 +11,7 @@ export default class UserSettingsModel extends AbstractModel {
overdueTasksRemindersEnabled: true,
defaultListId: undefined,
weekStart: 0,
timezone: '',
}
}
}

View File

@ -1,4 +1,4 @@
import {describe, it, expect} from 'vitest'
import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest'
import {parseTaskText} from './parseTaskText'
import {getDateFromText, getDateFromTextIn} from '../helpers/time/parseDate'
@ -6,6 +6,14 @@ import {calculateDayInterval} from '../helpers/time/calculateDayInterval'
import priorities from '../models/constants/priorities.json'
describe('Parse Task Text', () => {
beforeEach(() => {
vi.useFakeTimers()
})
afterEach(() => {
vi.useRealTimers()
})
it('should return text with no intents as is', () => {
expect(parseTaskText('Lorem Ipsum').text).toBe('Lorem Ipsum')
})
@ -33,6 +41,13 @@ describe('Parse Task Text', () => {
expect(result.assignees[0]).toBe('user')
})
it('should ignore email addresses', () => {
const text = 'Lorem Ipsum email@example.com'
const result = parseTaskText(text)
expect(result.text).toBe(text)
})
describe('Date Parsing', () => {
it('should not return any date if none was provided', () => {
const result = parseTaskText('Lorem Ipsum')
@ -204,17 +219,36 @@ describe('Parse Task Text', () => {
expect(`${result.date.getHours()}:${result.date.getMinutes()}`).toBe('14:0')
})
it('should recognize dates of the month in the past but next month', () => {
const date = new Date()
date.setDate(date.getDate() - 1)
const result = parseTaskText(`Lorem Ipsum ${date.getDate()}nd`)
const time = new Date(2022, 0, 15)
vi.setSystemTime(time)
const result = parseTaskText(`Lorem Ipsum ${time.getDate() - 1}th`)
expect(result.text).toBe('Lorem Ipsum')
expect(result.date.getDate()).toBe(date.getDate())
expect(result.date.getDate()).toBe(time.getDate() - 1)
expect(result.date.getMonth()).toBe(time.getMonth() + 1)
})
it('should recognize dates of the month in the past but next month when february is the next month', () => {
const jan = new Date(2022, 0, 30)
vi.setSystemTime(jan)
const nextMonthWithDate = result.date.getDate() === 31
? (date.getMonth() + 2) % 12
: (date.getMonth() + 1) % 12
expect(result.date.getMonth()).toBe(nextMonthWithDate)
const result = parseTaskText(`Lorem Ipsum ${jan.getDate() - 1}th`)
const expectedDate = new Date(2022, 2, jan.getDate() - 1)
expect(result.text).toBe('Lorem Ipsum')
expect(result.date.getDate()).toBe(expectedDate.getDate())
expect(result.date.getMonth()).toBe(expectedDate.getMonth())
})
it('should recognize dates of the month in the past but next month when the next month has less days than this one', () => {
const mar = new Date(2022, 2, 32)
vi.setSystemTime(mar)
const result = parseTaskText(`Lorem Ipsum 31st`)
const expectedDate = new Date(2022, 4, 31)
expect(result.text).toBe('Lorem Ipsum')
expect(result.date.getDate()).toBe(expectedDate.getDate())
expect(result.date.getMonth()).toBe(expectedDate.getMonth())
})
it('should recognize dates of the month in the future', () => {
const nextDay = new Date(+new Date() + 60 * 60 * 24 * 1000)
@ -235,6 +269,12 @@ describe('Parse Task Text', () => {
expect(result.text).toBe('Lorem Ipsum github')
expect(result.date).toBeNull()
})
it('should not recognize date number with no spacing around them', () => {
const result = parseTaskText('Lorem Ispum v1.1.1')
expect(result.text).toBe('Lorem Ispum v1.1.1')
expect(result.date).toBeNull()
})
describe('Parse weekdays', () => {

View File

@ -117,23 +117,30 @@ export const parseTaskText = (text: string, prefixesMode: PrefixMode = PrefixMod
const getItemsFromPrefix = (text: string, prefix: string): string[] => {
const items: string[] = []
const itemParts = text.split(prefix)
const itemParts = text.split(' ' + prefix)
if (text.startsWith(prefix)) {
const firstItem = text.split(prefix)[1]
itemParts.unshift(firstItem)
}
itemParts.forEach((p, index) => {
// First part contains the rest
if (index < 1) {
return
}
let labelText
p = p.replace(prefix, '')
let itemText
if (p.charAt(0) === '\'') {
labelText = p.split('\'')[1]
itemText = p.split('\'')[1]
} else if (p.charAt(0) === '"') {
labelText = p.split('"')[1]
itemText = p.split('"')[1]
} else {
// Only until the next space
labelText = p.split(' ')[0]
itemText = p.split(' ')[0]
}
items.push(labelText)
items.push(itemText)
})
return Array.from(new Set(items))

View File

@ -2,6 +2,8 @@ import { createRouter, createWebHistory, RouteLocation } from 'vue-router'
import {saveLastVisited} from '@/helpers/saveLastVisited'
import {store} from '@/store'
import {saveListView, getListView} from '@/helpers/saveListView'
import HomeComponent from '../views/Home.vue'
import NotFoundComponent from '../views/404.vue'
import About from '../views/About.vue'
@ -13,9 +15,8 @@ import DataExportDownload from '../views/user/DataExportDownload.vue'
// Tasks
import ShowTasksInRangeComponent from '../views/tasks/ShowTasksInRange.vue'
import LinkShareAuthComponent from '../views/sharing/LinkSharingAuth.vue'
import TaskDetailViewModal from '../views/tasks/TaskDetailViewModal.vue'
import TaskDetailView from '../views/tasks/TaskDetailView.vue'
import ListNamespaces from '../views/namespaces/ListNamespaces.vue'
import TaskDetailView from '../views/tasks/TaskDetailView.vue'
// Team Handling
import ListTeamsComponent from '../views/teams/ListTeams.vue'
// Label Handling
@ -25,11 +26,11 @@ import NewLabelComponent from '../views/labels/NewLabel.vue'
import MigrationComponent from '../views/migrator/Migrate.vue'
import MigrateServiceComponent from '../views/migrator/MigrateService.vue'
// List Views
import ShowListComponent from '../views/list/ShowList.vue'
import Kanban from '../views/list/views/Kanban.vue'
import List from '../views/list/views/List.vue'
import Gantt from '../views/list/views/Gantt.vue'
import Table from '../views/list/views/Table.vue'
import ListList from '../views/list/ListList.vue'
import ListGantt from '../views/list/ListGantt.vue'
import ListTable from '../views/list/ListTable.vue'
import ListKanban from '../views/list/ListKanban.vue'
// List Settings
import ListSettingEdit from '../views/list/settings/edit.vue'
import ListSettingBackground from '../views/list/settings/background.vue'
@ -80,7 +81,7 @@ const router = createRouter({
// Scroll to anchor should still work
if (to.hash) {
return {el: document.getElementById(to.hash.slice(1))}
return {el: to.hash}
}
// Otherwise just scroll to the top
@ -132,7 +133,7 @@ const router = createRouter({
name: 'user.register',
component: RegisterComponent,
meta: {
title: 'user.auth.register',
title: 'user.auth.createAccount',
},
},
{
@ -201,320 +202,170 @@ const router = createRouter({
{
path: '/namespaces/new',
name: 'namespace.create',
components: {
popup: NewNamespaceComponent,
},
},
{
path: '/namespaces/:id/list',
name: 'list.create',
components: {
popup: NewListComponent,
component: NewNamespaceComponent,
meta: {
showAsModal: true,
},
},
{
path: '/namespaces/:id/settings/edit',
name: 'namespace.settings.edit',
components: {
popup: NamespaceSettingEdit,
component: NamespaceSettingEdit,
meta: {
showAsModal: true,
},
},
{
path: '/namespaces/:id/settings/share',
path: '/namespaces/:namespaceId/settings/share',
name: 'namespace.settings.share',
components: {
popup: NamespaceSettingShare,
component: NamespaceSettingShare,
meta: {
showAsModal: true,
},
},
{
path: '/namespaces/:id/settings/archive',
name: 'namespace.settings.archive',
components: {
popup: NamespaceSettingArchive,
component: NamespaceSettingArchive,
meta: {
showAsModal: true,
},
},
{
path: '/namespaces/:id/settings/delete',
name: 'namespace.settings.delete',
components: {
popup: NamespaceSettingDelete,
component: NamespaceSettingDelete,
meta: {
showAsModal: true,
},
},
{
path: '/tasks/:id',
name: 'task.detail',
component: TaskDetailView,
props: route => ({ taskId: parseInt(route.params.id as string) }),
},
{
path: '/tasks/by/upcoming',
name: 'tasks.range',
component: ShowTasksInRangeComponent,
},
{
path: '/lists/new/:namespaceId/',
name: 'list.create',
component: NewListComponent,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/edit',
name: 'list.settings.edit',
components: {
popup: ListSettingEdit,
component: ListSettingEdit,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/background',
name: 'list.settings.background',
components: {
popup: ListSettingBackground,
component: ListSettingBackground,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/duplicate',
name: 'list.settings.duplicate',
components: {
popup: ListSettingDuplicate,
component: ListSettingDuplicate,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/share',
name: 'list.settings.share',
components: {
popup: ListSettingShare,
component: ListSettingShare,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/delete',
name: 'list.settings.delete',
components: {
popup: ListSettingDelete,
component: ListSettingDelete,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/archive',
name: 'list.settings.archive',
components: {
popup: ListSettingArchive,
component: ListSettingArchive,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/edit',
name: 'filter.settings.edit',
components: {
popup: FilterEdit,
component: FilterEdit,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId/settings/delete',
name: 'filter.settings.delete',
components: {
popup: FilterDelete,
component: FilterDelete,
meta: {
showAsModal: true,
},
},
{
path: '/lists/:listId',
name: 'list.index',
component: ShowListComponent,
children: [
{
path: '/lists/:listId/list',
name: 'list.list',
component: List,
children: [
{
path: '/tasks/:id',
name: 'task.list.detail',
component: TaskDetailViewModal,
},
{
path: '/lists/:listId/settings/edit',
name: 'list.list.settings.edit',
component: ListSettingEdit,
},
{
path: '/lists/:listId/settings/background',
name: 'list.list.settings.background',
component: ListSettingBackground,
},
{
path: '/lists/:listId/settings/duplicate',
name: 'list.list.settings.duplicate',
component: ListSettingDuplicate,
},
{
path: '/lists/:listId/settings/share',
name: 'list.list.settings.share',
component: ListSettingShare,
},
{
path: '/lists/:listId/settings/delete',
name: 'list.list.settings.delete',
component: ListSettingDelete,
},
{
path: '/lists/:listId/settings/archive',
name: 'list.list.settings.archive',
component: ListSettingArchive,
},
{
path: '/lists/:listId/settings/edit',
name: 'filter.list.settings.edit',
component: FilterEdit,
},
{
path: '/lists/:listId/settings/delete',
name: 'filter.list.settings.delete',
component: FilterDelete,
},
],
},
{
path: '/lists/:listId/gantt',
name: 'list.gantt',
component: Gantt,
children: [
{
path: '/tasks/:id',
name: 'task.gantt.detail',
component: TaskDetailViewModal,
},
{
path: '/lists/:listId/settings/edit',
name: 'list.gantt.settings.edit',
component: ListSettingEdit,
},
{
path: '/lists/:listId/settings/background',
name: 'list.gantt.settings.background',
component: ListSettingBackground,
},
{
path: '/lists/:listId/settings/duplicate',
name: 'list.gantt.settings.duplicate',
component: ListSettingDuplicate,
},
{
path: '/lists/:listId/settings/share',
name: 'list.gantt.settings.share',
component: ListSettingShare,
},
{
path: '/lists/:listId/settings/delete',
name: 'list.gantt.settings.delete',
component: ListSettingDelete,
},
{
path: '/lists/:listId/settings/archive',
name: 'list.gantt.settings.archive',
component: ListSettingArchive,
},
{
path: '/lists/:listId/settings/edit',
name: 'filter.gantt.settings.edit',
component: FilterEdit,
},
{
path: '/lists/:listId/settings/delete',
name: 'filter.gantt.settings.delete',
component: FilterDelete,
},
],
},
{
path: '/lists/:listId/table',
name: 'list.table',
component: Table,
children: [
{
path: '/lists/:listId/settings/edit',
name: 'list.table.settings.edit',
component: ListSettingEdit,
},
{
path: '/lists/:listId/settings/background',
name: 'list.table.settings.background',
component: ListSettingBackground,
},
{
path: '/lists/:listId/settings/duplicate',
name: 'list.table.settings.duplicate',
component: ListSettingDuplicate,
},
{
path: '/lists/:listId/settings/share',
name: 'list.table.settings.share',
component: ListSettingShare,
},
{
path: '/lists/:listId/settings/delete',
name: 'list.table.settings.delete',
component: ListSettingDelete,
},
{
path: '/lists/:listId/settings/archive',
name: 'list.table.settings.archive',
component: ListSettingArchive,
},
{
path: '/lists/:listId/settings/edit',
name: 'filter.table.settings.edit',
component: FilterEdit,
},
{
path: '/lists/:listId/settings/delete',
name: 'filter.table.settings.delete',
component: FilterDelete,
},
],
},
{
path: '/lists/:listId/kanban',
name: 'list.kanban',
component: Kanban,
children: [
{
path: '/tasks/:id',
name: 'task.kanban.detail',
component: TaskDetailViewModal,
},
{
path: '/lists/:listId/settings/edit',
name: 'list.kanban.settings.edit',
component: ListSettingEdit,
},
{
path: '/lists/:listId/settings/background',
name: 'list.kanban.settings.background',
component: ListSettingBackground,
},
{
path: '/lists/:listId/settings/duplicate',
name: 'list.kanban.settings.duplicate',
component: ListSettingDuplicate,
},
{
path: '/lists/:listId/settings/share',
name: 'list.kanban.settings.share',
component: ListSettingShare,
},
{
path: '/lists/:listId/settings/delete',
name: 'list.kanban.settings.delete',
component: ListSettingDelete,
},
{
path: '/lists/:listId/settings/archive',
name: 'list.kanban.settings.archive',
component: ListSettingArchive,
},
{
path: '/lists/:listId/settings/edit',
name: 'filter.kanban.settings.edit',
component: FilterEdit,
},
{
path: '/lists/:listId/settings/delete',
name: 'filter.kanban.settings.delete',
component: FilterDelete,
},
],
},
],
redirect(to) {
// Redirect the user to list view by default
const savedListView = getListView(to.params.listId)
console.debug('Replaced list view with', savedListView)
return {
name: router.hasRoute(savedListView)
? savedListView
: 'list.list',
params: {listId: to.params.listId},
}
},
},
{
path: '/lists/:listId/list',
name: 'list.list',
component: ListList,
beforeEnter: (to) => saveListView(to.params.listId, to.name),
props: route => ({ listId: parseInt(route.params.listId as string) }),
},
{
path: '/lists/:listId/gantt',
name: 'list.gantt',
component: ListGantt,
beforeEnter: (to) => saveListView(to.params.listId, to.name),
props: route => ({ listId: parseInt(route.params.listId as string) }),
},
{
path: '/lists/:listId/table',
name: 'list.table',
component: ListTable,
beforeEnter: (to) => saveListView(to.params.listId, to.name),
props: route => ({ listId: parseInt(route.params.listId as string) }),
},
{
path: '/lists/:listId/kanban',
name: 'list.kanban',
component: ListKanban,
beforeEnter: (to) => saveListView(to.params.listId, to.name),
props: route => ({ listId: parseInt(route.params.listId as string) }),
},
{
path: '/teams',
@ -524,8 +375,9 @@ const router = createRouter({
{
path: '/teams/new',
name: 'teams.create',
components: {
popup: NewTeamComponent,
component: NewTeamComponent,
meta: {
showAsModal: true,
},
},
{
@ -541,8 +393,9 @@ const router = createRouter({
{
path: '/labels/new',
name: 'labels.create',
components: {
popup: NewLabelComponent,
component: NewLabelComponent,
meta: {
showAsModal: true,
},
},
{
@ -558,8 +411,9 @@ const router = createRouter({
{
path: '/filters/new',
name: 'filters.create',
components: {
popup: FilterNew,
component: FilterNew,
meta: {
showAsModal: true,
},
},
{
@ -575,11 +429,7 @@ const router = createRouter({
],
})
router.beforeEach((to) => {
return checkAuth(to)
})
function checkAuth(route: RouteLocation) {
export function getAuthForRoute(route: RouteLocation) {
const authUser = store.getters['auth/authUser']
const authLinkShare = store.getters['auth/authLinkShare']

View File

@ -18,6 +18,8 @@ import lists from './modules/lists'
import attachments from './modules/attachments'
import labels from './modules/labels'
import ListModel from '@/models/list'
import ListService from '../services/list'
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
@ -37,13 +39,15 @@ export const store = createStore({
loading: false,
loadingModule: null,
// This is used to highlight the current list in menu for all list related views
currentList: {id: 0},
currentList: new ListModel({
id: 0,
isArchived: false,
}),
background: '',
hasTasks: false,
menuActive: true,
keyboardShortcutsActive: false,
quickActionsActive: false,
vikunjaReady: false,
},
mutations: {
[LOADING](state, loading) {
@ -79,9 +83,6 @@ export const store = createStore({
[BACKGROUND](state, background) {
state.background = background
},
vikunjaReady(state, ready) {
state.vikunjaReady = ready
},
},
actions: {
async [CURRENT_LIST]({state, commit}, currentList) {
@ -136,10 +137,9 @@ export const store = createStore({
commit(CURRENT_LIST, currentList)
},
async loadApp({commit, dispatch}) {
async loadApp({dispatch}) {
await checkAndSetApiUrl(window.API_URL)
await dispatch('auth/checkAuth')
commit('vikunjaReady', true)
},
},
})

View File

@ -1,12 +1,13 @@
import {HTTPFactory} from '@/http-common'
import {getCurrentLanguage, saveLanguage} from '@/i18n'
import {HTTPFactory, AuthenticatedHTTPFactory} from '@/http-common'
import {i18n, getCurrentLanguage, saveLanguage} from '@/i18n'
import {objectToSnakeCase} from '@/helpers/case'
import {LOADING} from '../mutation-types'
import UserModel from '@/models/user'
import UserSettingsService from '@/services/userSettings'
import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
import {setLoading} from '@/store/helper'
import {i18n} from '@/i18n'
import {success} from '@/message'
import {redirectToProvider} from '@/helpers/redirectToProvider'
const AUTH_TYPES = {
'UNKNOWN': 0,
@ -90,17 +91,8 @@ export default {
// Delete an eventually preexisting old token
removeToken()
const data = {
username: credentials.username,
password: credentials.password,
}
if (credentials.totpPasscode) {
data.totp_passcode = credentials.totpPasscode
}
try {
const response = await HTTP.post('login', data)
const response = await HTTP.post('login', objectToSnakeCase(credentials))
// Save the token to local storage for later use
saveToken(response.data.token, true)
@ -201,7 +193,19 @@ export default {
ctx.commit('authenticated', authenticated)
if (!authenticated) {
ctx.commit('info', null)
ctx.dispatch('config/redirectToProviderIfNothingElseIsEnabled', null, {root: true})
ctx.dispatch('redirectToProviderIfNothingElseIsEnabled')
}
},
redirectToProviderIfNothingElseIsEnabled({rootState}) {
const {auth} = rootState.config
if (
auth.local.enabled === false &&
auth.openidConnect.enabled &&
auth.openidConnect.providers?.length === 1 &&
window.location.pathname.startsWith('/login') // Kinda hacky, but prevents an endless loop.
) {
redirectToProvider(auth.openidConnect.providers[0], auth.openidConnect.redirectUrl)
}
},
@ -211,13 +215,9 @@ export default {
return
}
const HTTP = HTTPFactory()
const HTTP = AuthenticatedHTTPFactory(jwt)
try {
const response = await HTTP.get('user', {
headers: {
Authorization: `Bearer ${jwt}`,
},
})
const response = await HTTP.get('user')
const info = new UserModel(response.data)
info.type = state.info.type
info.email = state.info.email
@ -226,7 +226,7 @@ export default {
commit('info', info)
commit('lastUserRefresh')
if (typeof info.settings.language !== 'undefined') {
if (typeof info.settings.language === 'undefined' || info.settings.language === '') {
// save current language
await dispatch('saveUserSettings', {
settings: {

View File

@ -1,7 +1,6 @@
import {CONFIG} from '../mutation-types'
import {HTTPFactory} from '@/http-common'
import {objectToCamelCase} from '@/helpers/case'
import {redirectToProvider} from '../../helpers/redirectToProvider'
import {parseURL} from 'ufo'
export default {
@ -75,16 +74,5 @@ export default {
ctx.commit(CONFIG, info)
return info
},
redirectToProviderIfNothingElseIsEnabled(ctx) {
if (ctx.state.auth.local.enabled === false &&
ctx.state.auth.openidConnect.enabled &&
ctx.state.auth.openidConnect.providers &&
ctx.state.auth.openidConnect.providers.length === 1 &&
window.location.pathname.startsWith('/login') // Kinda hacky, but prevents an endless loop.
) {
redirectToProvider(ctx.state.auth.openidConnect.providers[0])
}
},
},
}

View File

@ -23,8 +23,6 @@ export default {
return
}
// FIXME: direct manipulation of the prop
// might not be a problem since this is happening in the mutation
if (!namespace.lists || namespace.lists.length === 0) {
namespace.lists = state.namespaces[namespaceIndex].lists
}
@ -136,8 +134,8 @@ export default {
},
loadNamespacesIfFavoritesDontExist(ctx) {
// The first namespace should be the one holding all favorites
if (ctx.state.namespaces[0].id !== -2) {
// The first or second namespace should be the one holding all favorites
if (ctx.state.namespaces[0].id !== -2 && ctx.state.namespaces[1]?.id !== -2) {
return ctx.dispatch('loadNamespaces')
}
},

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