Compare commits

...

677 Commits

Author SHA1 Message Date
a94198f2eb Fixed accidental change - restoring original 2022-01-08 15:52:33 -05:00
a6e7e6efa6 Changed button test to look for "Mark task done!" - to match button change 2022-01-08 15:49:50 -05:00
6f5f7b190a Changed "Done!" to "Mark task done" on green button 2022-01-08 15:48:47 -05:00
0473c385d6
fix: editor cursor color 2022-01-08 17:34:13 +01:00
68a76faacc
fix: don't reset active fields when saving
Resolves #590
2022-01-08 15:13:49 +01:00
4579dd3ce7
fix: button size on task detail view 2022-01-08 15:11:55 +01:00
f4fee26fe4 chore(deps): update dependency sass to v1.47.0 (#1333)
Reviewed-on: vikunja/frontend#1333
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-08 11:03:51 +00:00
00398085fd chore(deps): update dependency caniuse-lite to v1.0.30001298 (#1334)
Reviewed-on: vikunja/frontend#1334
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-08 10:54:58 +00:00
13c8e6dbcd fix(deps): update dependency v-tooltip to v4.0.0-beta.13 (#1332)
Reviewed-on: vikunja/frontend#1332
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-07 16:21:41 +00:00
1b5f8a069b chore(deps): update dependency netlify-cli to v8.6.15 (#1331)
Reviewed-on: vikunja/frontend#1331
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-07 16:21:27 +00:00
21fec9461d chore(deps): update dependency vitest to v0.0.139 (#1330)
Reviewed-on: vikunja/frontend#1330
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-07 16:21:11 +00:00
df3af739f8 chore(deps): update dependency autoprefixer to v10.4.2 (#1329)
Reviewed-on: vikunja/frontend#1329
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-07 16:20:45 +00:00
b08d34bc96 fix(deps): update dependency v-tooltip to v4.0.0-beta.11 (#1326)
Reviewed-on: vikunja/frontend#1326
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-07 08:26:54 +00:00
be03efd015 chore(deps): update dependency caniuse-lite to v1.0.30001297 (#1327)
Reviewed-on: vikunja/frontend#1327
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-07 08:26:35 +00:00
c353fd151d chore(deps): update dependency vitest to v0.0.136 (#1325)
Reviewed-on: vikunja/frontend#1325
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-06 21:23:31 +00:00
c32e9badf0 chore(deps): update dependency netlify-cli to v8.6.12 (#1322)
Reviewed-on: vikunja/frontend#1322
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-06 19:39:31 +00:00
8f64ab5dce chore(deps): update dependency vitest to v0.0.135 (#1323)
Reviewed-on: vikunja/frontend#1323
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-06 19:39:11 +00:00
63ca8ffc7c fix(deps): update dependency v-tooltip to v4.0.0-beta.10 (#1324)
Reviewed-on: vikunja/frontend#1324
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-06 19:38:46 +00:00
fe9ddf33ca fix(deps): update dependency marked to v4.0.9 (#1321)
Reviewed-on: vikunja/frontend#1321
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-06 16:26:02 +00:00
74777d6bed chore(deps): update dependency netlify-cli to v8.6.9 (#1320)
Reviewed-on: vikunja/frontend#1320
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-06 16:25:44 +00:00
76d1c56fab chore(deps): update dependency netlify-cli to v8.6.8 (#1318)
Reviewed-on: vikunja/frontend#1318
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-06 15:23:22 +00:00
f75e9135c2 fix(deps): update dependency highlight.js to v11.4.0 (#1319)
Reviewed-on: vikunja/frontend#1319
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-06 15:23:08 +00:00
480f0f8da9 fix(deps): update dependency v-tooltip to v4.0.0-beta.8 (#1317)
Reviewed-on: vikunja/frontend#1317
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-06 14:05:02 +00:00
ac832186d6 chore(deps): update dependency netlify-cli to v8.6.6 (#1316)
Reviewed-on: vikunja/frontend#1316
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-06 11:38:23 +00:00
738e1e8370 chore(deps): update dependency sass to v1.46.0 (#1315)
Reviewed-on: vikunja/frontend#1315
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-06 11:10:06 +00:00
Dominik Pschenitschni
9b85817ddb feat: run vue-tsc in ci (#1295)
We had this in for a while already, but never cared for the result.
Might make sense to integrate in the pipeline.

To make things easy from the start we could add [`failure: ignore`](https://docs.drone.io/pipeline/macstadium/syntax/steps/#failure) to the step

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1295
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-06 10:37:51 +00:00
Dominik Pschenitschni
49a6569db0 fix: remove obsolet code (#1312)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1312
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-06 10:34:14 +00:00
e762f7f073 chore(deps): update dependency vitest to v0.0.134 (#1314)
Reviewed-on: vikunja/frontend#1314
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-06 10:33:07 +00:00
e5d2b23cb3 chore(deps): update dependency netlify-cli to v8.6.5 2022-01-05 21:05:56 +00:00
6eddf23c0d fix(deps): update dependency vue-advanced-cropper to v2.7.1 2022-01-05 17:03:11 +00:00
70934c6a0b fix(deps): pin dependency @types/is-touch-device to 1.0.0 (#1308)
Reviewed-on: vikunja/frontend#1308
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-05 16:45:48 +00:00
49955eb03a
fix: remove some of the typescript warnings 2022-01-05 16:17:14 +01:00
2b302974cc chore(deps): update dependency vitest to v0.0.133 2022-01-05 14:52:22 +00:00
Dominik Pschenitschni
64d632b0a5 [skip ci] Updated translations via Crowdin 2022-01-05 14:36:24 +00:00
Dominik Pschenitschni
e28f0f5be4 chore(addTask) improve order (#1297)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1297
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-05 13:14:50 +00:00
c618b7e0b6 fix: namespace new buttons on mobile (#1262)
Before:

![image](/attachments/7626e28d-8a13-4f92-b162-697676f765c7)

After:

![image](/attachments/5331af47-4887-4ba5-98d7-ee70311e20d7)

Co-authored-by: kolaente <k@knt.li>
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1262
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2022-01-05 12:46:33 +00:00
380af7fbf2 fix(deps): update dependency @vueuse/router to v7.5.3 (#1303)
Reviewed-on: vikunja/frontend#1303
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-01-05 11:44:12 +00:00
9d3ef30be6 fix(deps): update dependency @vueuse/core to v7.5.2 2022-01-05 10:03:09 +00:00
fc00169863 chore(deps): update dependency vitest to v0.0.132 2022-01-05 09:21:26 +00:00
b652225a12 fix(deps): update dependency @vueuse/router to v7.5.2 2022-01-05 08:02:58 +00:00
29d8422e94
fix(ready): remove class form fragment 2022-01-04 21:15:02 +01:00
Dominik Pschenitschni
cdbd1c2ac4 feat: create BaseButton component (#1123)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1123
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-04 18:58:06 +00:00
cb37fd773d feat: convert to composable useDateTimeSalutation 2022-01-04 18:44:07 +00:00
d2577f1df6 feat: use useNow to provide auto updates 2022-01-04 18:44:07 +00:00
27534a98e9 feat: return full translation key 2022-01-04 18:44:07 +00:00
de77393905 feat: move the calculation of the current salutation to a different function 2022-01-04 18:44:07 +00:00
dd450263fb chore(deps): pin dependency happy-dom to 2.25.1 2022-01-04 18:03:16 +00:00
d8106dcb73 chore(deps): update dependency vitest to v0.0.131 2022-01-04 14:03:26 +00:00
8114012997
feat: replace jest with vitest 2022-01-04 14:16:47 +01:00
bc4ea82639 chore(deps): update dependency netlify-cli to v8.6.4 2022-01-04 13:03:16 +00:00
cd97cfe612 chore(deps): update dependency netlify-cli to v8.6.3 2022-01-04 12:03:19 +00:00
e6136fdee4 chore(deps): update dependency caniuse-lite to v1.0.30001296 2022-01-04 11:48:22 +00:00
2c395c720a chore(deps): update dependency vue-tsc to v0.30.2 2022-01-04 11:48:01 +00:00
8b639fd4af chore(deps): update dependency rollup to v2.63.0 2022-01-04 08:03:11 +00:00
f7bd5f13ac fix(deps): update dependency v-tooltip to v4.0.0-beta.6 2022-01-03 20:03:15 +00:00
0ae774b95c chore(deps): update typescript-eslint monorepo to v5.9.0 2022-01-03 19:04:33 +00:00
be899c3eb0 chore(deps): update dependency eslint to v8.6.0 2022-01-03 15:40:28 +00:00
dc02827a33 chore(deps): update dependency slugify to v1.6.5 2022-01-03 15:39:59 +00:00
951e511bf9
chore(deps): update dependency postcss-preset-env to v7.2.0 2022-01-03 16:21:43 +01:00
5f1d936ca4 fix(deps): update dependency v-tooltip to v4.0.0-beta.5 2022-01-03 15:15:05 +00:00
c3845e5690 chore(deps): update dependency netlify-cli to v8.6.1 2022-01-03 14:03:10 +00:00
f4db2df37b chore(deps): update dependency caniuse-lite to v1.0.30001295 2022-01-02 00:02:54 +00:00
5668fc7a2c chore(deps): update dependency esbuild to v0.14.10 2021-12-31 17:02:57 +00:00
12a0099fbf chore(deps): update dependency sass to v1.45.2 (#1271)
Reviewed-on: vikunja/frontend#1271
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-31 15:31:03 +00:00
7f4027cbe8 fix(deps): update dependency @vueuse/core to v7.5.1 (#1272)
Reviewed-on: vikunja/frontend#1272
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-31 15:30:46 +00:00
b57aa33cae fix(deps): update dependency @vueuse/router to v7.5.1 (#1273)
Reviewed-on: vikunja/frontend#1273
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-31 13:06:00 +00:00
b80de79594 fix(deps): update dependency @vueuse/router to v7.4.3 2021-12-31 00:02:51 +00:00
ead3e45a59 chore(deps): update dependency @types/jest to v27.4.0 2021-12-30 21:02:53 +00:00
4a7d2d8414 feat: save and restore the user language on the server (#1181)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1181
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-12-30 20:20:45 +00:00
0befa58908 fix: blockquote styling in dark mode 2021-12-30 15:59:59 +00:00
8ae84eaf42 fix: padding and centering of the kanban limit and dropdown 2021-12-30 15:59:59 +00:00
cd10bc9d7a fix(gantt): use function to create default date 2021-12-30 13:05:29 +00:00
f4545fbe2f fix(deps): update dependency @vueuse/core to v7.4.3 (#1266)
Reviewed-on: vikunja/frontend#1266
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-30 12:04:16 +00:00
c6ffe8acab chore(deps): update dependency vite to v2.7.10 (#1265)
Reviewed-on: vikunja/frontend#1265
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-30 08:52:41 +00:00
2fb16f9a77 fix(deps): update dependency vue-i18n to v9.2.0-beta.26 (#1263)
Reviewed-on: vikunja/frontend#1263
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-29 23:13:35 +00:00
be427449e4 chore(deps): update dependency netlify-cli to v8.6.0 (#1259)
Reviewed-on: vikunja/frontend#1259
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-29 20:56:08 +00:00
5e889ebe36 chore(deps): update dependency autoprefixer to v10.4.1 (#1260)
Reviewed-on: vikunja/frontend#1260
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-29 20:06:07 +00:00
8c62c96109 chore(deps): update dependency esbuild to v0.14.9 (#1258)
Reviewed-on: vikunja/frontend#1258
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-29 13:34:16 +00:00
7908a1e657 chore(deps): update dependency caniuse-lite to v1.0.30001294 (#1257)
Reviewed-on: vikunja/frontend#1257
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-28 22:03:48 +00:00
09b62da2ae fix(deps): update dependency date-fns to v2.28.0 (#1256)
Reviewed-on: vikunja/frontend#1256
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-28 17:30:07 +00:00
076494afa5 chore(deps): update dependency netlify-cli to v8.5.0 (#1255)
Reviewed-on: vikunja/frontend#1255
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-28 17:12:35 +00:00
b604d5da75 chore(deps): update dependency vite to v2.7.9 (#1254)
Reviewed-on: vikunja/frontend#1254
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-28 17:11:48 +00:00
59889cccf3 chore(deps): update typescript-eslint monorepo to v5.8.1 (#1253)
Reviewed-on: vikunja/frontend#1253
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-27 19:19:38 +00:00
f8ffb428d3 chore(deps): update dependency rollup to v2.62.0 (#1246)
Reviewed-on: vikunja/frontend#1246
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-27 11:00:46 +00:00
c2ea932c09 [skip ci] Updated translations via Crowdin 2021-12-26 19:09:18 +00:00
033d97e919 chore(deps): update dependency @vue/eslint-config-typescript to v10 (#1243)
Reviewed-on: vikunja/frontend#1243
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-26 17:45:55 +00:00
4694c14760 chore(deps): update dependency vue-tsc to v0.30.1 (#1248)
Reviewed-on: vikunja/frontend#1248
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-26 17:45:33 +00:00
7e8e26679c chore(deps): update dependency vite to v2.7.7 (#1247)
Reviewed-on: vikunja/frontend#1247
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-26 09:33:35 +00:00
838db379ff fix(deps): update dependency @vueuse/core to v7.4.1 (#1244)
Reviewed-on: vikunja/frontend#1244
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-26 09:33:12 +00:00
77da84d21a fix(deps): update dependency @vueuse/router to v7.4.1 (#1245)
Reviewed-on: vikunja/frontend#1245
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-25 20:23:47 +00:00
91b80cba67 chore(deps): update dependency esbuild to v0.14.8 (#1242)
Reviewed-on: vikunja/frontend#1242
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-23 08:50:34 +00:00
ac57c8572a fix(deps): update dependency vue-i18n to v9.2.0-beta.25 (#1240)
Reviewed-on: vikunja/frontend#1240
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-23 08:49:52 +00:00
858448b877 chore(deps): update dependency esbuild to v0.14.7 2021-12-22 11:03:15 +00:00
8409db98f6 fix(deps): update dependency vue-i18n to v9.2.0-beta.24 (#1238)
Reviewed-on: vikunja/frontend#1238
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-22 10:12:07 +00:00
04cc01daaf chore(deps): update dependency postcss-preset-env to v7.1.0 (#1237)
Reviewed-on: vikunja/frontend#1237
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-22 10:11:51 +00:00
70f8d46b6c chore(deps): update dependency cypress to v9.2.0 (#1232)
Reviewed-on: vikunja/frontend#1232
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-22 10:11:15 +00:00
15114e487b chore(deps): update dependency caniuse-lite to v1.0.30001292 (#1234)
Reviewed-on: vikunja/frontend#1234
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-22 08:25:19 +00:00
7f79f2a7b3 chore(deps): update dependency netlify-cli to v8.4.2 (#1235)
Reviewed-on: vikunja/frontend#1235
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-22 08:24:56 +00:00
7387112172 chore(deps): update dependency vite to v2.7.6 (#1236)
Reviewed-on: vikunja/frontend#1236
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-22 08:24:08 +00:00
Dominik Pschenitschni
3743cdc058 [skip ci] Updated translations via Crowdin 2021-12-22 02:09:16 +00:00
Dominik Pschenitschni
ce02462cfe [skip ci] Updated translations via Crowdin 2021-12-21 22:09:27 +00:00
Dominik Pschenitschni
7af21c48d5 [skip ci] Updated translations via Crowdin 2021-12-21 18:07:40 +00:00
Dominik Pschenitschni
943e554a58 feat: improve playPop helper (#1229)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1229
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-12-21 17:56:48 +00:00
e885d8ae70 chore(deps): update dependency vue-tsc to v0.30.0 2021-12-21 17:04:57 +00:00
2cef6cb343 chore(deps): update dependency netlify-cli to v8.4.1 2021-12-21 16:33:57 +00:00
7ebca9afc5 feat/alphabetical-sort (#1162)
Alphabetical sorting.
Disables re-ordering when applied.
Does not work with the search button as expected, but neither do the filters tbh... Works fine with the search in the filters menu.

I know we talked about having a dropdown but since this is pretty much finished I thought I'd submit a PR. I am a bit short on time these days but may submit a new PR to add the dropdown ( should be simple enough )

Co-authored-by: Stefan Genov <stefantigro@gmail.com>
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1162
Reviewed-by: konrad <k@knt.li>
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: Michaelpalacce <stefantigro@gmail.com>
Co-committed-by: Michaelpalacce <stefantigro@gmail.com>
2021-12-21 16:29:49 +00:00
d86eb9ea0b chore(deps): update dependency netlify-cli to v8.3.0 (#1228)
Reviewed-on: vikunja/frontend#1228
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-21 14:29:51 +00:00
Dominik Pschenitschni
063592ca3d fix: auth and move logic to router (#1201)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1201
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-12-21 08:02:03 +00:00
d9fa0dd2bc chore(deps): update dependency sass to v1.45.1 (#1227)
Reviewed-on: vikunja/frontend#1227
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-21 07:36:28 +00:00
4cbacafbbb chore(deps): update dependency netlify-cli to v8.2.4 (#1226)
Reviewed-on: vikunja/frontend#1226
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-20 22:23:49 +00:00
2ce260bcf1 chore(deps): update typescript-eslint monorepo to v5.8.0 (#1225)
Reviewed-on: vikunja/frontend#1225
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-20 20:30:51 +00:00
d606be3459 chore(deps): update dependency netlify-cli to v8.2.3 (#1224)
Reviewed-on: vikunja/frontend#1224
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-20 19:11:12 +00:00
d79b14221d chore(deps): update dependency netlify-cli to v8.2.1 (#1223)
Reviewed-on: vikunja/frontend#1223
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-20 18:38:14 +00:00
fdbacc2084 chore(deps): update dependency netlify-cli to v8.2.0 (#1222)
This PR contains the following updates:

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

---

### Release Notes

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

### [`v8.2.0`](https://github.com/netlify/cli/blob/master/CHANGELOG.md#&#8203;820-httpswwwgithubcomnetlifyclicomparev819v820-2021-12-20)

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

##### Features

-   remove unused `--name` flag ([#&#8203;3766](https://www.github.com/netlify/cli/issues/3766)) ([07c4bcc](07c4bcc950))

##### [8.1.9](https://www.github.com/netlify/cli/compare/v8.1.8...v8.1.9) (2021-12-20)

##### Bug Fixes

-   **deps:** update dependency [@&#8203;types/node](https://github.com/types/node) to v14.18.1 ([#&#8203;3855](https://www.github.com/netlify/cli/issues/3855)) ([9145946](9145946ff1))
-   **deps:** update rust crate simple_logger to 1.16.0 ([#&#8203;3857](https://www.github.com/netlify/cli/issues/3857)) ([2c12cb4](2c12cb4e20))
-   **deps:** update rust crate tokio to 1.15.0 ([#&#8203;3859](https://www.github.com/netlify/cli/issues/3859)) ([a48643f](a48643fc00))
-   **switch:** fix switch command error when no user logged in ([#&#8203;3851](https://www.github.com/netlify/cli/issues/3851)) ([74cc879](74cc879291))

##### [8.1.8](https://www.github.com/netlify/cli/compare/v8.1.7...v8.1.8) (2021-12-17)

##### Bug Fixes

-   fixes an issue with yarn pnp on installing ([#&#8203;3841](https://www.github.com/netlify/cli/issues/3841)) ([aa14231](aa14231094))

##### [8.1.7](https://www.github.com/netlify/cli/compare/v8.1.6...v8.1.7) (2021-12-16)

##### Bug Fixes

-   **deps:** update dependency [@&#8203;netlify/plugin-edge-handlers](https://github.com/netlify/plugin-edge-handlers) to v3 ([#&#8203;3754](https://www.github.com/netlify/cli/issues/3754)) ([475c9e6](475c9e6606))

##### [8.1.6](https://www.github.com/netlify/cli/compare/v8.1.5...v8.1.6) (2021-12-16)

##### Bug Fixes

-   **deps:** update dependency [@&#8203;netlify/config](https://github.com/netlify/config) to v17 ([#&#8203;3838](https://www.github.com/netlify/cli/issues/3838)) ([ede3514](ede351485b))

##### [8.1.5](https://www.github.com/netlify/cli/compare/v8.1.4...v8.1.5) (2021-12-16)

##### Bug Fixes

-   **deps:** update dependency stripe to v8.194.0 ([#&#8203;3806](https://www.github.com/netlify/cli/issues/3806)) ([ed1ae44](ed1ae448e6))

##### [8.1.4](https://www.github.com/netlify/cli/compare/v8.1.3...v8.1.4) (2021-12-15)

##### Bug Fixes

-   remove oclif framework and fixes an issue with sites:create ([#&#8203;3717](https://www.github.com/netlify/cli/issues/3717)) ([a920292](a920292165))

##### [8.1.3](https://www.github.com/netlify/cli/compare/v8.1.2...v8.1.3) (2021-12-14)

##### Bug Fixes

-   **deps:** update dependency [@&#8203;netlify/build](https://github.com/netlify/build) to ^20.3.1 ([#&#8203;3823](https://www.github.com/netlify/cli/issues/3823)) ([79ca026](79ca026c8d))

##### [8.1.2](https://www.github.com/netlify/cli/compare/v8.1.1...v8.1.2) (2021-12-14)

##### Bug Fixes

-   **deps:** update dependency [@&#8203;netlify/framework-info](https://github.com/netlify/framework-info) to v7 ([#&#8203;3821](https://www.github.com/netlify/cli/issues/3821)) ([7866fa9](7866fa9c7e))
-   **deps:** update dependency [@&#8203;netlify/plugins-list](https://github.com/netlify/plugins-list) to ^6.2.0 ([#&#8203;3819](https://www.github.com/netlify/cli/issues/3819)) ([0ce075f](0ce075f0f2))

##### [8.1.1](https://www.github.com/netlify/cli/compare/v8.1.0...v8.1.1) (2021-12-13)

##### Bug Fixes

-   **deps:** update dependency [@&#8203;netlify/plugins-list](https://github.com/netlify/plugins-list) to ^6.1.0 ([#&#8203;3813](https://www.github.com/netlify/cli/issues/3813)) ([74c2020](74c2020e1a))
-   **deps:** update dependency typescript to v4.5.3 ([#&#8203;3802](https://www.github.com/netlify/cli/issues/3802)) ([b018b3f](b018b3f006))
-   **deps:** update rust crate simple_logger to 1.15.1 ([#&#8203;3804](https://www.github.com/netlify/cli/issues/3804)) ([0481ab6](0481ab610b))

</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: vikunja/frontend#1222
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-20 15:43:24 +00:00
dd123dadd2 chore(deps): update dependency netlify-cli to v8.1.9 (#1221)
This PR contains the following updates:

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

---

### Release Notes

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

### [`v8.1.9`](https://github.com/netlify/cli/blob/master/CHANGELOG.md#&#8203;819-httpswwwgithubcomnetlifyclicomparev818v819-2021-12-20)

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

</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: vikunja/frontend#1221
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-20 14:46:13 +00:00
b088a5e864 chore(deps): update dependency vite to v2.7.4 2021-12-20 12:49:37 +00:00
63008e59a3 fix(deps): update dependency codemirror to v5.65.0 2021-12-20 11:02:53 +00:00
3a1da44c94 chore(deps): update dependency eslint to v8.5.0 (#1213)
Reviewed-on: vikunja/frontend#1213
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-20 08:37:38 +00:00
fccb0dcc61 chore(deps): update dependency esbuild to v0.14.6 (#1218)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [esbuild](https://github.com/evanw/esbuild) | devDependencies | patch | [`0.14.5` -> `0.14.6`](https://renovatebot.com/diffs/npm/esbuild/0.14.5/0.14.6) |

---

### Release Notes

<details>
<summary>evanw/esbuild</summary>

### [`v0.14.6`](https://github.com/evanw/esbuild/blob/master/CHANGELOG.md#&#8203;0146)

[Compare Source](https://github.com/evanw/esbuild/compare/v0.14.5...v0.14.6)

-   Fix a minifier bug with BigInt literals

    Previously expression simplification optimizations in the minifier incorrectly assumed that numeric operators always return numbers. This used to be true but has no longer been true since the introduction of BigInt literals in ES2020. Now numeric operators can return either a number or a BigInt depending on the arguments. This oversight could potentially have resulted in behavior changes. For example, this code printed `false` before being minified and `true` after being minified because esbuild shortened `===` to `==` under the false assumption that both operands were numbers:

    ```js
    var x = 0;
    console.log((x ? 2 : -1n) === -1);
    ```

    The type checking logic has been rewritten to take into account BigInt literals in this release, so this incorrect simplification is no longer applied.

-   Enable removal of certain unused template literals ([#&#8203;1853](https://github.com/evanw/esbuild/issues/1853))

    This release contains improvements to the minification of unused template literals containing primitive values:

    ```js
    // Original code
    `${1}${2}${3}`;
    `${x ? 1 : 2}${y}`;

    // Old output (with --minify)
    ""+1+2+3,""+(x?1:2)+y;

    // New output (with --minify)
    x,`${y}`;
    ```

    This can arise when the template literals are nested inside of another function call that was determined to be unnecessary such as an unused call to a function marked with the `/* @&#8203;__PURE__ */` pragma.

    This release also fixes a bug with this transformation where minifying the unused expression `` `foo ${bar}` `` into `"" + bar` changed the meaning of the expression. Template string interpolation always calls `toString` while string addition may call `valueOf` instead. This unused expression is now minified to `` `${bar}` ``, which is slightly longer but which avoids the behavior change.

-   Allow `keyof`/`readonly`/`infer` in TypeScript index signatures ([#&#8203;1859](https://github.com/evanw/esbuild/pull/1859))

    This release fixes a bug that prevented these keywords from being used as names in index signatures. The following TypeScript code was previously rejected, but is now accepted:

    ```ts
    interface Foo {
      [keyof: string]: number
    }
    ```

    This fix was contributed by [@&#8203;magic-akari](https://github.com/magic-akari).

-   Avoid warning about `import.meta` if it's replaced ([#&#8203;1868](https://github.com/evanw/esbuild/issues/1868))

    It's possible to replace the `import.meta` expression using the `--define:` feature. Previously doing that still warned that the `import.meta` syntax was not supported when targeting ES5. With this release, there will no longer be a warning in this case.

</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: vikunja/frontend#1218
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-20 07:14:24 +00:00
Dominik Pschenitschni
b3b7669983 fix: llama color (#1212)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1212
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-12-19 20:08:59 +00:00
d114c673d8 fix(deps): update dependency @vueuse/core to v7.4.0 2021-12-19 18:02:38 +00:00
a6771b8d37 chore(deps): update dependency slugify to v1.6.4 (#1209)
Reviewed-on: vikunja/frontend#1209
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-19 17:29:14 +00:00
91b06a9af6 chore(deps): update dependency caniuse-lite to v1.0.30001291 (#1214)
Reviewed-on: vikunja/frontend#1214
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-19 17:28:22 +00:00
08502619c4 fix(deps): update dependency @vueuse/router to v7.4.0 (#1216)
Reviewed-on: vikunja/frontend#1216
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-19 17:27:47 +00:00
26de8e66fa fix(deps): update dependency marked to v4.0.8 (#1217)
Reviewed-on: vikunja/frontend#1217
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-19 17:27:31 +00:00
f944c35e99 chore: simplify focus directive 2021-12-17 16:34:27 +00:00
36fb250d1f chore: directly use redirectToProvider function 2021-12-17 15:41:11 +00:00
b7aa7891e9 feat: redirect to calculated url everywhere 2021-12-17 15:41:11 +00:00
ccaed029f2 feat: build openid redirect url dynamically 2021-12-17 15:41:11 +00:00
770578240a chore(deps): update dependency ts-jest to v27.1.2 2021-12-17 14:27:34 +00:00
791678720b chore(deps): update dependency netlify-cli to v8.1.8 2021-12-17 11:02:46 +00:00
21e44e15bd [skip ci] Updated translations via Crowdin 2021-12-17 07:11:12 +00:00
9a612849a3 chore(deps): update dependency express to v4.17.2 (#1211)
Reviewed-on: vikunja/frontend#1211
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-17 06:49:18 +00:00
c4173c3c35 chore(deps): update dependency vite to v2.7.3 (#1207)
Reviewed-on: vikunja/frontend#1207
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-16 15:01:35 +00:00
46afeb159a chore(deps): update dependency caniuse-lite to v1.0.30001287 (#1205)
Reviewed-on: vikunja/frontend#1205
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-16 12:29:54 +00:00
940494a02d chore(deps): update dependency postcss-preset-env to v7.0.2 (#1206)
Reviewed-on: vikunja/frontend#1206
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-16 12:28:24 +00:00
1a55d04c97 chore(deps): update dependency vite-plugin-pwa to v0.11.12 (#1204)
Reviewed-on: vikunja/frontend#1204
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-15 22:30:04 +00:00
8ccfe716f6 chore(deps): update dependency netlify-cli to v8.1.4 (#1199)
Reviewed-on: vikunja/frontend#1199
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-15 12:13:05 +00:00
e3243f012d fix(deps): update dependency @github/hotkey to v1.6.1 (#1197)
Reviewed-on: vikunja/frontend#1197
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-14 19:31:49 +00:00
eef37c4f70 chore(deps): update dependency @vitejs/plugin-vue to v2.0.1 (#1196)
Reviewed-on: vikunja/frontend#1196
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-14 19:31:30 +00:00
e7e1cc0e55 chore(deps): update dependency browserslist to v4.19.1 (#1198)
Reviewed-on: vikunja/frontend#1198
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-14 19:31:17 +00:00
f8031b5aef chore(deps): update dependency esbuild to v0.14.5 (#1200)
Reviewed-on: vikunja/frontend#1200
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-14 19:30:52 +00:00
ac61e8d449 chore(deps): update typescript-eslint monorepo to v5.7.0 (#1192)
Reviewed-on: vikunja/frontend#1192
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-14 08:31:19 +00:00
2c671c6a13 chore(deps): update dependency jest to v27.4.5 (#1193)
Reviewed-on: vikunja/frontend#1193
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-14 08:31:04 +00:00
d01c3eabb9 chore(deps): update dependency browserslist to v4.19.0 (#1195)
Reviewed-on: vikunja/frontend#1195
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-14 07:42:55 +00:00
d85d86eaa7 chore(deps): update dependency typescript to v4.5.4 (#1194)
Reviewed-on: vikunja/frontend#1194
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-14 07:42:41 +00:00
e1b04eed72 chore(deps): update dependency netlify-cli to v8.1.1 (#1190)
Reviewed-on: vikunja/frontend#1190
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-14 07:42:15 +00:00
543dae2f30 fix: saving default list (#1143)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1143
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-12-13 22:20:45 +00:00
40479d0c6e chore(deps): update dependency vite to v2.7.2 (#1191)
Reviewed-on: vikunja/frontend#1191
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-13 22:19:24 +00:00
f5fba7880f chore(deps): update dependency postcss to v8.4.5 (#1189)
Reviewed-on: vikunja/frontend#1189
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-13 07:34:48 +00:00
ed332f5dd3 chore(deps): update dependency esbuild to v0.14.3 (#1187)
Reviewed-on: vikunja/frontend#1187
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-12 18:47:58 +00:00
9ecd18a5ee [skip ci] Updated translations via Crowdin 2021-12-12 18:34:43 +00:00
63070e63bd chore(deps): pin dependency caniuse-lite to 1.0.30001286 (#1185)
Reviewed-on: vikunja/frontend#1185
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-12 17:40:28 +00:00
32353e3b76 feat: restyle unauthenticated screens (#1103)
I wanted to give the no-auth screens a new look. Here's what I ended up with:

![image](/attachments/d272f36b-03c1-40ca-91f6-30f34e03e5fd)

The image is something we could change with every release.

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1103
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-12-12 16:40:13 +00:00
14f1ee1885 fix(deps): pin dependency @vueuse/router to 7.3.0 (#1182)
Reviewed-on: vikunja/frontend#1182
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-12 15:38:17 +00:00
8440869bcd
chore: explicitly add caniuse-lite to dependencies 2021-12-12 16:29:24 +01:00
14397ffb31
revert: "feat: always use latest browserlist (#1021)"
This reverts commit ed6dc94873.
2021-12-12 16:26:43 +01:00
c3c4d2a0a5 feat: use script setup and ts in app auth components 2021-12-12 14:37:20 +00:00
b03d5d80cd chore(deps): update dependency cypress to v9 2021-12-12 13:28:29 +00:00
2bf9be3676 fix(deps): update dependency @vueuse/core to v7.3.0 (#1178)
Reviewed-on: vikunja/frontend#1178
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-12 11:29:30 +00:00
807fb6a9fe
fix: use watcher to check for user query tokens 2021-12-12 12:20:33 +01:00
a106511646
fix: spacing for deletion message 2021-12-12 12:00:24 +01:00
7ed71f66ea chore(deps): update dependency sass to v1.45.0 (#1177)
Reviewed-on: vikunja/frontend#1177
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-12 10:02:56 +00:00
4463b83b78 chore(deps): update dependency @vitejs/plugin-vue to v2 (#1180)
Reviewed-on: vikunja/frontend#1180
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-12 10:02:28 +00:00
14f14f6d3e fix(deps): update vue monorepo to v3.2.26 (#1179)
Reviewed-on: vikunja/frontend#1179
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-12 10:02:11 +00:00
e1449642bd fix(deps): update sentry-javascript monorepo to v6.16.1 (#1175)
Reviewed-on: vikunja/frontend#1175
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-11 20:21:01 +00:00
5dfaeb39ca chore(deps): update dependency rollup to v2.61.1 (#1174)
Reviewed-on: vikunja/frontend#1174
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-11 20:20:52 +00:00
9584ef127a chore(deps): update dependency netlify-cli to v8.0.20 (#1168)
Reviewed-on: vikunja/frontend#1168
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-11 16:34:18 +00:00
Dominik Pschenitschni
cb9e1e891d fix: unindent styles in pagination (#1172)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1172
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-12-11 13:00:35 +00:00
41c0594bd2 fix(deps): update dependency marked to v4.0.7 (#1170)
Reviewed-on: vikunja/frontend#1170
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-10 06:43:05 +00:00
9acc9039a6 chore(deps): update dependency typescript to v4.5.3 (#1169)
Reviewed-on: vikunja/frontend#1169
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-10 06:42:40 +00:00
f530d4763e chore(deps): update dependency jest to v27.4.4 (#1171)
Reviewed-on: vikunja/frontend#1171
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-10 06:42:03 +00:00
3f4bcbcecd chore(deps): update dependency rollup to v2.61.0 2021-12-09 18:03:53 +00:00
89d8c9d639 chore(deps): update dependency vite-plugin-pwa to v0.11.11 2021-12-09 17:13:48 +00:00
2de523f2e0 chore(deps): update dependency netlify-cli to v8.0.18 2021-12-09 16:03:06 +00:00
22e62a2cea chore(deps): update dependency vite-svg-loader to v3.1.1 2021-12-09 15:34:24 +00:00
92c89d145c chore(deps): update dependency netlify-cli to v8.0.17 2021-12-09 15:03:05 +00:00
306d562f65 [skip ci] Updated translations via Crowdin 2021-12-08 16:05:40 +00:00
4b8a7e1556 fix(deps): update dependency @vueuse/core to v7.2.2 2021-12-08 12:03:02 +00:00
d0c6576efa fix(deps): update dependency @vueuse/core to v7.2.1 (#1158)
Reviewed-on: vikunja/frontend#1158
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-08 11:38:43 +00:00
e684e9a90b chore(deps): update dependency ts-jest to v27.1.1 2021-12-08 09:02:52 +00:00
805e1bc554 [skip ci] Updated translations via Crowdin 2021-12-07 23:05:23 +00:00
8ee793c054 [skip ci] Updated translations via Crowdin 2021-12-07 22:10:24 +00:00
1a119f97c5 feat: add support to set the marble avatar in user settings (#1156)
Frontend for vikunja/api#1060

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1156
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-12-07 21:27:04 +00:00
10fe38cef6
fix: default sentry dsn in docker 2021-12-07 22:05:19 +01:00
b4cbe1e1fd fix(deps): update sentry-javascript monorepo to v6.16.0 (#1155)
Reviewed-on: vikunja/frontend#1155
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-07 21:03:27 +00:00
8b8e413af0 feat: recurring for quick add magic (#1105)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1105
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-12-07 20:08:39 +00:00
c8029ec3c4 chore(deps): update dependency vite to v2.7.1 (#1154)
Reviewed-on: vikunja/frontend#1154
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-07 19:36:57 +00:00
6225c54447 fix(deps): update dependency dompurify to v2.3.4 2021-12-07 15:03:02 +00:00
809e876091 chore(deps): update dependency @vitejs/plugin-legacy to v1.6.4 (#1152)
Reviewed-on: vikunja/frontend#1152
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-07 12:06:02 +00:00
470022899f chore(deps): update dependency netlify-cli to v8.0.16 (#1147)
Reviewed-on: vikunja/frontend#1147
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-07 09:29:06 +00:00
8d1d60ba80 chore(deps): update dependency @vitejs/plugin-vue to v1.10.2 (#1150)
Reviewed-on: vikunja/frontend#1150
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-07 09:28:38 +00:00
028ad3dc14 chore(deps): update dependency vite to v2.7.0 (#1151)
Reviewed-on: vikunja/frontend#1151
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-07 09:28:13 +00:00
f4df628e47 chore(deps): update typescript-eslint monorepo to v5.6.0 (#1148)
Reviewed-on: vikunja/frontend#1148
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-07 06:14:44 +00:00
150b847638 chore(deps): update dependency eslint to v8.4.1 (#1149)
Reviewed-on: vikunja/frontend#1149
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-07 06:13:38 +00:00
684acc01bd fix(deps): update vue monorepo to v3.2.24 2021-12-06 10:02:46 +00:00
3218cf60f0 chore(deps): update dependency eslint-plugin-vue to v8.2.0 (#1145)
Reviewed-on: vikunja/frontend#1145
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-06 06:35:53 +00:00
bba9a8e008
fix: checklist update not working
Resolves #1114
2021-12-05 14:18:59 +01:00
852d71e8b7 chore(deps): update dependency ts-jest to v27.1.0 (#1141)
Reviewed-on: vikunja/frontend#1141
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-05 10:21:04 +00:00
c65bb4e93b chore(deps): update dependency vite-plugin-pwa to v0.11.10 (#1140)
Reviewed-on: vikunja/frontend#1140
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-04 17:13:33 +00:00
1c3f655323 chore(deps): update dependency esbuild to v0.14.2 (#1139)
Reviewed-on: vikunja/frontend#1139
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-04 14:55:14 +00:00
bd19234041 chore(deps): update workbox monorepo to v6.4.2 (#1133)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1133
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-04 14:47:52 +00:00
Dominik Pschenitschni
ac630ac775 feat: convert simple components to script setup and use typescript (#1120)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1120
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-12-04 14:47:32 +00:00
f758eefa88 fix(deps): update dependency vue-i18n to v9.2.0-beta.23 (#1138)
Reviewed-on: vikunja/frontend#1138
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-04 14:22:11 +00:00
Dominik Pschenitschni
4137bab7fc fix: Home view (#1129)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1129
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-12-04 13:56:25 +00:00
d253d2e743 chore(deps): update dependency eslint to v8.4.0 (#1136)
Reviewed-on: vikunja/frontend#1136
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-04 13:53:31 +00:00
fe5770082a chore(deps): update dependency netlify-cli to v8.0.15 (#1135)
This PR contains the following updates:

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

---

### Release Notes

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

### [`v8.0.15`](https://github.com/netlify/cli/blob/master/CHANGELOG.md#&#8203;8015-httpswwwgithubcomnetlifyclicomparev8014v8015-2021-12-03)

[Compare Source](https://github.com/netlify/cli/compare/v8.0.14...v8.0.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: vikunja/frontend#1135
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-03 16:35:45 +00:00
2041722b8a chore(deps): update dependency jest to v27.4.3 2021-12-02 20:03:56 +00:00
648b947a05 chore(deps): update dependency netlify-cli to v8.0.14 (#1132)
Reviewed-on: vikunja/frontend#1132
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-12-02 19:50:07 +00:00
f58e114947 chore(deps): update dependency netlify-cli to v8.0.13 2021-12-02 17:23:47 +00:00
144e7bd10c fix(deps): update dependency marked to v4.0.6 2021-12-02 17:23:19 +00:00
b96e89ca8c fix: remove unused variable 2021-12-02 17:05:28 +00:00
Dominik Pschenitschni
20f0496fa5 fix: unit test for "should recognize dates of the month in the past but next month" (#1131)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1131
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-12-02 15:39:46 +00:00
e535584412 chore(deps): update dependency vite-plugin-pwa to v0.11.9 (#1124)
Reviewed-on: vikunja/frontend#1124
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-30 22:43:23 +00:00
c5b9e2a1ff chore(deps): update dependency netlify-cli to v8.0.6 (#1125)
Reviewed-on: vikunja/frontend#1125
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-30 22:43:03 +00:00
Dominik Pschenitschni
e45bc83132 fix: duplicate filter in gantt-component (#1121)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1121
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-11-30 21:55:24 +00:00
Dominik Pschenitschni
bc8b04fc7a fix: add import url suffix for vite svg loader (#1122)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1122
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-11-30 21:03:26 +00:00
Dominik Pschenitschni
84284a6211 feat: harden textarea auto height algorithm (#985)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#985
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-11-30 20:20:40 +00:00
Dominik Pschenitschni
716de2c99c feat: convert home view to script setup and ts (#1119)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1119
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-11-30 20:06:15 +00:00
Dominik Pschenitschni
769d94e879 fix:cleanup some scss vars (#1118)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1118
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-11-30 20:04:15 +00:00
Dominik Pschenitschni
baa86530c8 fix: useColorScheme (#1117)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1117
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2021-11-30 20:03:27 +00:00
7613afbf27 fix(deps): update dependency date-fns to v2.27.0 2021-11-30 13:08:36 +00:00
aeb886e4c5 chore(deps): update typescript-eslint monorepo to v5.5.0 2021-11-30 12:52:53 +00:00
306bd1c179 chore(deps): update dependency esbuild to v0.14.1 2021-11-30 12:52:30 +00:00
f3cf79fa65 chore(deps): update dependency rollup to v2.60.2 (#1112)
Reviewed-on: vikunja/frontend#1112
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-30 12:34:27 +00:00
8c945b049a chore(deps): update dependency jest to v27.4.2 (#1115)
Reviewed-on: vikunja/frontend#1115
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-30 12:33:59 +00:00
f69111c105 chore(deps): update dependency vue-tsc to v0.29.8 (#1111)
Reviewed-on: vikunja/frontend#1111
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-30 06:45:57 +00:00
03afbfc6c8 chore(deps): update dependency sass to v1.44.0 (#1110)
Reviewed-on: vikunja/frontend#1110
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-30 06:45:01 +00:00
c07288dd8b chore(deps): update dependency jest to v27.4.0 (#1107)
Reviewed-on: vikunja/frontend#1107
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-29 20:50:12 +00:00
03f3c52548 chore(deps): update dependency netlify-cli to v8.0.5 (#1108)
Reviewed-on: vikunja/frontend#1108
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-29 20:00:12 +00:00
384037c694 chore(deps): update dependency vue-tsc to v0.29.7 (#1106)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [vue-tsc](https://github.com/johnsoncodehk/volar) | devDependencies | patch | [`0.29.6` -> `0.29.7`](https://renovatebot.com/diffs/npm/vue-tsc/0.29.6/0.29.7) |

---

### Release Notes

<details>
<summary>johnsoncodehk/volar</summary>

### [`v0.29.7`](https://github.com/johnsoncodehk/volar/blob/master/CHANGELOG.md#&#8203;0297)

[Compare Source](https://github.com/johnsoncodehk/volar/compare/v0.29.6...v0.29.7)

-   feat: support html, css custom data ([#&#8203;707](https://github.com/johnsoncodehk/volar/issues/707))
-   feat: support extends tsconfig `vueCompilerOptions` ([#&#8203;731](https://github.com/johnsoncodehk/volar/issues/731))
-   fix: cannot config project reference by directory path ([#&#8203;712](https://github.com/johnsoncodehk/volar/issues/712))
-   fix: pug attrs type-check borken by nested tags ([#&#8203;721](https://github.com/johnsoncodehk/volar/issues/721))
-   fix: import path rename result incorrect ([#&#8203;723](https://github.com/johnsoncodehk/volar/issues/723))
-   fix: `editor.codeActionsOnSave: ["source.organizeImports"]` not working ([#&#8203;726](https://github.com/johnsoncodehk/volar/issues/726))
-   fix: goto definition not working with some component import statement ([#&#8203;728](https://github.com/johnsoncodehk/volar/issues/728))
-   fix: don't show volar commands in non-vue document ([#&#8203;733](https://github.com/johnsoncodehk/volar/issues/733))
-   fix: vue-tsc not working with symlink ([#&#8203;738](https://github.com/johnsoncodehk/volar/issues/738))

</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: vikunja/frontend#1106
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-28 21:40:03 +00:00
734db0795c
fix: switching from a list with a background to settings would not remove the background 2021-11-28 15:56:08 +01:00
8f6c0f3738 chore(deps): update dependency vite-plugin-pwa to v0.11.8 (#1102)
Reviewed-on: vikunja/frontend#1102
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-28 14:50:57 +00:00
f3324c6aaf [skip ci] Updated translations via Crowdin 2021-11-28 14:27:28 +00:00
f8d009a6aa feat: add message component (#1082)
This PR adds a simple message component that replaces bulma's default message.

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1082
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-28 14:18:27 +00:00
dpschen
59e915cc10 feat: add packageManager field to package.json (#1099)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1099
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-27 19:36:57 +00:00
dpschen
0c9dad9891 fix: remove obsolete code (#1097)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1097
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-27 19:33:37 +00:00
dpschen
b7ad29f056 fix: upgrade cypress image (#1096)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1096
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-27 19:32:48 +00:00
a7434f24df chore(deps): update dependency postcss to v8.4.4 (#1100)
Reviewed-on: vikunja/frontend#1100
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-27 19:32:29 +00:00
f61d5bac46 [skip ci] Updated translations via Crowdin 2021-11-26 22:26:39 +00:00
2911dee3cc chore(deps): update dependency esbuild to v0.14.0 (#1095)
Reviewed-on: vikunja/frontend#1095
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-26 21:29:31 +00:00
58c361ee29
fix(translations): typo 2021-11-26 21:40:20 +01:00
d3fc1439b5 chore(deps): update dependency postcss to v8.4.3 (#1094)
Reviewed-on: vikunja/frontend#1094
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-26 20:33:33 +00:00
bb544c353c chore(deps): update dependency postcss to v8.4.2 (#1093)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [postcss](https://postcss.org/) ([source](https://github.com/postcss/postcss)) | devDependencies | patch | [`8.4.1` -> `8.4.2`](https://renovatebot.com/diffs/npm/postcss/8.4.1/8.4.2) |

---

### Release Notes

<details>
<summary>postcss/postcss</summary>

### [`v8.4.2`](https://github.com/postcss/postcss/blob/master/CHANGELOG.md#&#8203;842)

[Compare Source](https://github.com/postcss/postcss/compare/8.4.1...8.4.2)

-   Fixed previous source map support in zero plugins mode.

</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: vikunja/frontend#1093
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-26 19:59:47 +00:00
cc90a1cea5 fix(deps): update dependency @vueuse/core to v7.1.2 (#1092)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@vueuse/core](https://github.com/vueuse/vueuse) | dependencies | patch | [`7.1.1` -> `7.1.2`](https://renovatebot.com/diffs/npm/@vueuse%2fcore/7.1.1/7.1.2) |

---

### Release Notes

<details>
<summary>vueuse/vueuse</summary>

### [`v7.1.2`](https://github.com/vueuse/vueuse/releases/v7.1.2)

[Compare Source](https://github.com/vueuse/vueuse/compare/v7.1.1...v7.1.2)

##### Bug Fixes

-   **useAsyncState:** optional argument of `execute` ([#&#8203;972](https://github.com/vueuse/vueuse/issues/972)) ([6661a09](https://github.com/vueuse/vueuse/commit/6661a09))
-   **UseVirtualList:** fix list not being reactive in component ([#&#8203;968](https://github.com/vueuse/vueuse/issues/968)) ([fb7cd78](https://github.com/vueuse/vueuse/commit/fb7cd78))

##### Features

-   **useEyeDropper:** add initialValue option, improve types ([#&#8203;950](https://github.com/vueuse/vueuse/issues/950)) ([cf8ced3](https://github.com/vueuse/vueuse/commit/cf8ced3))

</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: vikunja/frontend#1092
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-26 19:59:31 +00:00
cffba33748 chore(deps): update dependency @vitejs/plugin-vue to v1.10.1 (#1091)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@vitejs/plugin-vue](https://github.com/vitejs/vite) | devDependencies | patch | [`1.10.0` -> `1.10.1`](https://renovatebot.com/diffs/npm/@vitejs%2fplugin-vue/1.10.0/1.10.1) |

---

### Release Notes

<details>
<summary>vitejs/vite</summary>

### [`v1.10.1`](https://github.com/vitejs/vite/blob/master/packages/plugin-vue/CHANGELOG.md#&#8203;1101-httpsgithubcomvitejsvitecompareplugin-vue1100plugin-vue1101-2021-11-26)

##### Bug Fixes

-   **plugin-vue:** fix hmr issue in vuejs/vue-next[#&#8203;4358](https://github.com/vitejs/vite/issues/4358) ([709e4b0](709e4b0428))

</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: vikunja/frontend#1091
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-26 08:34:32 +00:00
055e0a2901 fix(deps): update vue monorepo to v3.2.23 (#1090)
Reviewed-on: vikunja/frontend#1090
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-26 07:12:49 +00:00
c8d1921bcd chore(deps): update dependency netlify-cli to v8.0.3 (#1089)
This PR contains the following updates:

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

---

### Release Notes

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

### [`v8.0.3`](https://github.com/netlify/cli/blob/master/CHANGELOG.md#&#8203;803-httpswwwgithubcomnetlifyclicomparev802v803-2021-11-25)

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

</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: vikunja/frontend#1089
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-25 21:44:19 +00:00
9e09314f75 chore(deps): update dependency netlify-cli to v8.0.2 (#1088)
This PR contains the following updates:

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

---

### Release Notes

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

### [`v8.0.2`](https://github.com/netlify/cli/blob/master/CHANGELOG.md#&#8203;802-httpswwwgithubcomnetlifyclicomparev801v802-2021-11-25)

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

</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: vikunja/frontend#1088
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-25 19:56:17 +00:00
c3833d90d8 chore(deps): update dependency sass to v1.43.5 2021-11-25 13:16:09 +00:00
e24cb55e1b fix(deps): update dependency vue-i18n to v9.2.0-beta.22 2021-11-25 11:02:55 +00:00
f897611ad1 chore(deps): update dependency postcss to v8.4.1 (#1083)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [postcss](https://postcss.org/) ([source](https://github.com/postcss/postcss)) | devDependencies | patch | [`8.4.0` -> `8.4.1`](https://renovatebot.com/diffs/npm/postcss/8.4.0/8.4.1) |

---

### Release Notes

<details>
<summary>postcss/postcss</summary>

### [`v8.4.1`](https://github.com/postcss/postcss/blob/master/CHANGELOG.md#&#8203;841)

[Compare Source](https://github.com/postcss/postcss/compare/8.4.0...8.4.1)

-   Fixed `Stringifier` types (by James Garbutt).

</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: vikunja/frontend#1083
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-25 06:10:45 +00:00
ea8fe297d9 fix(deps): update dependency marked to v4.0.5 (#1085)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [marked](https://marked.js.org) ([source](https://github.com/markedjs/marked)) | dependencies | patch | [`4.0.4` -> `4.0.5`](https://renovatebot.com/diffs/npm/marked/4.0.4/4.0.5) |

---

### Release Notes

<details>
<summary>markedjs/marked</summary>

### [`v4.0.5`](https://github.com/markedjs/marked/releases/v4.0.5)

[Compare Source](https://github.com/markedjs/marked/compare/v4.0.4...v4.0.5)

##### Bug Fixes

-   table after paragraph without blank line ([#&#8203;2298](https://github.com/markedjs/marked/issues/2298)) ([5714212](5714212afd))

</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: vikunja/frontend#1085
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-25 06:10:06 +00:00
c6b604f1fa fix(deps): update dependency @vueuse/core to v7.1.1 (#1086)
This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| [@vueuse/core](https://github.com/vueuse/vueuse) | dependencies | patch | [`7.1.0` -> `7.1.1`](https://renovatebot.com/diffs/npm/@vueuse%2fcore/7.1.0/7.1.1) |

---

### Release Notes

<details>
<summary>vueuse/vueuse</summary>

### [`v7.1.1`](https://github.com/vueuse/vueuse/releases/v7.1.1)

[Compare Source](https://github.com/vueuse/vueuse/compare/v7.1.0...v7.1.1)

##### Bug Fixes

-   improve package.json ([0c4e770](https://github.com/vueuse/vueuse/commit/0c4e770))
-   **useMemory:** import path ([bd2346b](https://github.com/vueuse/vueuse/commit/bd2346b))

</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: vikunja/frontend#1086
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-25 06:09:55 +00:00
4792adfbd1 chore(deps): update dependency netlify-cli to v8.0.1 (#1081)
Reviewed-on: vikunja/frontend#1081
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-24 21:32:12 +00:00
dpschen
04c94418d7
fix: logo overflow on login (#1050)
Resolves #1046

Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1050
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
(cherry picked from commit 44f8e3ea9b)
2021-11-24 22:30:47 +01:00
12bec04c42 chore(deps): pin dependency autoprefixer to 10.4.0 (#1080)
Reviewed-on: vikunja/frontend#1080
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-24 20:13:02 +00:00
e8eb94d71b chore(deps): update dependency postcss to v8.4.0 (#1075)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1075
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-24 19:50:33 +00:00
5137f9f6cb fix(deps): update dependency @vueuse/core to v7.1.0 (#1078)
Reviewed-on: vikunja/frontend#1078
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-24 19:31:43 +00:00
a2f65d86c2 chore(deps): update dependency netlify-cli to v8 (#1077)
Reviewed-on: vikunja/frontend#1077
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-24 16:33:51 +00:00
08dcc897cc chore(deps): update dependency @4tw/cypress-drag-drop to v2.1.0 (#1076)
Reviewed-on: vikunja/frontend#1076
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-24 16:33:11 +00:00
c975fb0fee fix(deps): update dependency vue-i18n to v9.2.0-beta.21 (#1072)
Reviewed-on: vikunja/frontend#1072
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-24 13:30:24 +00:00
b73cf344b6 [skip ci] Updated translations via Crowdin 2021-11-24 09:25:56 +00:00
d4b45dc255 fix(dark mode): flatpickr colors 2021-11-23 22:58:25 +00:00
e2beaba3b9 fix(dark mode): disabled input colors 2021-11-23 22:58:25 +00:00
dd9be97793 fix(deps): update dependency @vueuse/core to v7.0.3 (#1071)
Reviewed-on: vikunja/frontend#1071
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-23 22:31:56 +00:00
6cde8e2640 fix(dark mode): dark mode adjustments (#1069)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1069
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-23 21:20:50 +00:00
5c6fcffd75 chore(deps): update dependency vite-plugin-pwa to v0.11.7 2021-11-23 20:02:55 +00:00
9b243873c5
chore: 0.18.2 release preparations
(cherry picked from commit 771eef9e54)
2021-11-23 20:22:07 +01:00
dc347ed8ba
fix: edit task comment
Resolves #1055
2021-11-23 19:21:45 +01:00
d0d1086dac chore(deps): update dependency netlify-cli to v7.1.0 (#1067)
Reviewed-on: vikunja/frontend#1067
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-23 18:12:36 +00:00
fb91b71395 fix(deps): pin dependency bulma-css-variables to 0.9.33 (#1065)
Reviewed-on: vikunja/frontend#1065
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-23 09:20:40 +00:00
dpschen
b688f35446 feat: reduce import size by only importing used modules (#1023)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1023
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-23 07:09:17 +00:00
dpschen
91580f97a1 feat: use popper.js v2 vue3 version of v-tooltip (#1038)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1038
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-23 07:08:21 +00:00
d95fc32d67 fix(deps): update dependency @vueuse/core to v7 (#1066)
Reviewed-on: vikunja/frontend#1066
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-23 07:07:44 +00:00
drone
6b5ac20ef8 [skip ci] Updated translations via Crowdin 2021-11-23 02:27:22 +00:00
drone
66f0df5037 [skip ci] Updated translations via Crowdin 2021-11-23 01:27:19 +00:00
dpschen
981babd691 fix: remove mentioning of context (#1017)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1017
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-22 21:36:17 +00:00
46fa43d67f Migrate to bulma-css-variables and introduce dark mode (#954)
Co-authored-by: Adrian Simmons <adrian@perlucida.co.uk>
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#954
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Reviewed-by: konrad <k@knt.li>
Co-authored-by: adrinux <adrian@perlucida.co.uk>
Co-committed-by: adrinux <adrian@perlucida.co.uk>
2021-11-22 21:12:54 +00:00
4ef54f1bc2
fix: deleting a namespace
Deleting a namespace would remove the item from the store and then try to remove it from the search index. That failed because the item was already removed from the store and thus undefined when it was passed to the store.
2021-11-22 21:49:19 +01:00
dpschen
b029889f27 fix: check for notification api (#1043)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1043
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-22 19:03:27 +00:00
dpschen
44f8e3ea9b fix #1046 logo overflow on login (#1050)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1050
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-22 19:01:26 +00:00
dpschen
ae36c041a7 feat: improve namespace explanation (#1040)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1040
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-22 18:41:00 +00:00
8a722f294c chore(deps): update dependency @vitejs/plugin-legacy to v1.6.3 2021-11-22 17:58:01 +00:00
5674acbee6 chore(deps): update dependency netlify-cli to v7.0.4 2021-11-22 17:57:31 +00:00
bf9371c60a chore(deps): update dependency slugify to v1.6.3 2021-11-22 15:02:59 +00:00
181930f537 chore(deps): update dependency esbuild to v0.13.15 2021-11-22 12:00:50 +00:00
cee22a1942 chore(deps): update dependency rollup to v2.60.1 2021-11-22 12:00:23 +00:00
673458b41d chore(deps): update dependency @vitejs/plugin-vue to v1.10.0 2021-11-22 11:02:45 +00:00
b56e99bfdf chore(deps): update dependency vue-tsc to v0.29.6 2021-11-21 12:26:11 +00:00
a5b5d99129 fix(deps): update dependency codemirror to v5.64.0 2021-11-21 12:25:48 +00:00
e1b9a9921c chore(deps): update dependency eslint to v8.3.0 2021-11-21 07:02:48 +00:00
709ebdf567 chore(deps): update dependency netlify-cli to v7.0.2 2021-11-19 18:02:47 +00:00
d41ee3dc8c chore(deps): update dependency netlify-cli to v7.0.1 2021-11-19 15:32:41 +00:00
e342f6e3ed fix(deps): update dependency marked to v4.0.4 2021-11-19 15:03:03 +00:00
8b2450d6f9 chore(deps): update dependency postcss-preset-env to v7.0.1 2021-11-19 12:02:37 +00:00
943eab5e7e fix(deps): update dependency date-fns to v2.26.0 2021-11-19 08:02:43 +00:00
d55328e03b chore(deps): update dependency vite-plugin-pwa to v0.11.6 (#1031)
Reviewed-on: vikunja/frontend#1031
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-18 21:54:59 +00:00
30aa1cd1cf chore(deps): update dependency @types/jest to v27.0.3 (#1030)
Reviewed-on: vikunja/frontend#1030
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-18 21:54:33 +00:00
01f3196938 chore(deps): update dependency netlify-cli to v7 (#1029)
Reviewed-on: vikunja/frontend#1029
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-18 20:49:36 +00:00
ced8e0fd3c chore(deps): update dependency netlify-cli to v6.15.0 (#1028)
Reviewed-on: vikunja/frontend#1028
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-18 16:24:38 +00:00
dpschen
b838e7494d fix attribute coercion for contenteditable (#1025)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1025
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-18 13:00:54 +00:00
745b4b56ec chore(deps): update dependency eslint-plugin-vue to v8.1.1 (#1026)
Reviewed-on: vikunja/frontend#1026
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-18 13:00:13 +00:00
c5b539912d fix(deps): update dependency vue-i18n to v9.2.0-beta.20 2021-11-18 09:02:52 +00:00
dpschen
ed6dc94873 feat: always use latest browserlist (#1021)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1021
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-17 19:15:57 +00:00
233b9693eb chore(deps): update dependency typescript to v4.5.2 (#1024)
Reviewed-on: vikunja/frontend#1024
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-17 19:15:01 +00:00
dpschen
2656c74f37 feat: add postcss-preset-env (#1022)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1022
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-17 17:58:46 +00:00
28b571588e fix(deps): update dependency vue-i18n to v9.2.0-beta.19 2021-11-17 14:10:37 +00:00
ae5d3ecac5 fix: kanban card spacing (#1005)
Co-authored-by: kolaente <k@knt.li>
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1005
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-16 21:44:07 +00:00
26213d5e8c
fix: api not found by default 2021-11-16 22:34:08 +01:00
ae0ecb9f23 fix(deps): update sentry-javascript monorepo to v6.15.0 (#1015)
Reviewed-on: vikunja/frontend#1015
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-16 20:52:33 +00:00
75af78eecc chore(deps): update workbox monorepo to v6.4.1 (#1012)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1012
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-16 20:52:20 +00:00
cf202738be fix(deps): update dependency @vue/compat to v3.2.22 (#1016)
Reviewed-on: vikunja/frontend#1016
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-16 20:02:43 +00:00
dpschen
ed78a83ed9 fix: comment alignment (#1008)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#1008
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-16 20:02:11 +00:00
d0635ae4a1 chore(deps): update dependency esbuild to v0.13.14 (#1014)
Reviewed-on: vikunja/frontend#1014
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-16 19:52:04 +00:00
552751b346 chore(deps): update dependency @vue/eslint-config-typescript to v9.1.0 (#1018)
Reviewed-on: vikunja/frontend#1018
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-16 19:51:49 +00:00
e83cf50e51 chore(deps): update typescript-eslint monorepo to v5.4.0 (#1011)
Reviewed-on: vikunja/frontend#1011
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-16 07:15:50 +00:00
810635f5a5 chore(deps): update dependency browserslist to v4.18.1 (#1010)
Reviewed-on: vikunja/frontend#1010
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-16 07:15:15 +00:00
213cbfb440 chore(deps): update dependency netlify-cli to v6.14.25 (#1009)
Reviewed-on: vikunja/frontend#1009
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-16 07:14:37 +00:00
11e5ff42a6 chore(deps): update dependency vue-tsc to v0.29.5 (#1007)
Reviewed-on: vikunja/frontend#1007
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-15 14:50:28 +00:00
bb64452382 chore: use a class to set the logo size (#1004)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1004
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-15 14:26:46 +00:00
e31d388ec1 fix(deps): update dependency vue to v3.2.22 (#1006)
Reviewed-on: vikunja/frontend#1006
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-15 06:46:34 +00:00
a4dd8ec0d1 [skip ci] Updated translations via Crowdin 2021-11-15 04:24:26 +00:00
7b4b97b0d3 chore(deps): pin dependencies (#1003)
Reviewed-on: vikunja/frontend#1003
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-14 21:45:03 +00:00
665cc84174 feat: directly open general settings when opening user settings and none selected (#1001)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1001
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-14 21:12:18 +00:00
6c6ccc647e fix: currentPage of pagination component is undefined (#1002)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#1002
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-14 21:06:25 +00:00
0684806db0
fix: vikunja logo size when migrating 2021-11-14 22:03:25 +01:00
dpschen
d0d4096f8b feature/use-setup-api-for-user-and-about-pages (#929)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#929
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-14 20:57:36 +00:00
507a73e74c feat: use flexsearch for all local searches (#997)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#997
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-14 20:49:52 +00:00
1fa164453c
fix: logo on ready screen 2021-11-14 21:16:33 +01:00
fcadbc352b feat: re-style the keyboard shortcuts menu (#996)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#996
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-14 16:35:41 +00:00
7824ba089a chore(deps): update dependency vite-plugin-pwa to v0.11.5 (#1000)
Reviewed-on: vikunja/frontend#1000
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-14 16:35:18 +00:00
31f344503c fix: show current host if configured api url is /api/v1 instead of "" (#994)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#994
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-14 16:16:05 +00:00
dpschen
e63fd587c8 feat: use script setup for filter views (#951)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#951
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-14 15:56:52 +00:00
c1c6f21ad2
chore(ci): make sure you cannot tamper the deploy script in a PR 2021-11-14 16:50:07 +01:00
060057e268 fix(deps): update dependency vue-advanced-cropper to v2.7.0 (#999)
Reviewed-on: vikunja/frontend#999
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-14 13:28:09 +00:00
4e02f77382 chore(deps): update dependency browserslist to v4.18.0 (#998)
Reviewed-on: vikunja/frontend#998
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-14 08:35:57 +00:00
780ac4eb74 [skip ci] Updated translations via Crowdin 2021-11-14 08:24:35 +00:00
d46374839e [skip ci] Updated translations via Crowdin 2021-11-13 22:24:24 +00:00
ce45034776 [skip ci] Updated translations via Crowdin 2021-11-13 21:24:35 +00:00
7fada671fc fix(deps): pin dependency @github/hotkey to 1.6.0 (#995)
Reviewed-on: vikunja/frontend#995
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-13 21:22:42 +00:00
feea191ecf feat: add v-shortcut directive for keyboard shortcuts (#942)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#942
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-13 20:28:29 +00:00
db605e0d21 feat: show namespace of related tasks if they are different than the current one (#923)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#923
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-13 20:27:23 +00:00
0fe433891a feat: search in quick actions (#943)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#943
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-13 20:26:03 +00:00
0a2d5ef820 feat: defer everything until the api config is loaded (#926)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#926
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-13 19:49:02 +00:00
31f0c384ac feat: add button to clear active filters (#924)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#924
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-13 19:48:06 +00:00
73651ef964
feat: remove ssl generation from docker image 2021-11-13 17:28:56 +01:00
03eee061ff fix: preview deploy for PRs (#990)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#990
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-13 15:36:53 +00:00
9a499f68e8 [skip ci] Updated translations via Crowdin 2021-11-13 15:24:30 +00:00
b5927be136 chore(deps): pin dependency vite-svg-loader to 3.1.0 (#989)
Reviewed-on: vikunja/frontend#989
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-13 15:13:09 +00:00
be78fc177d
fix: don't try to deploy a review env when not a pr 2021-11-13 15:43:34 +01:00
dpschen
30cc89fe25 feat: add vite-svg-loader and add Logo component (#971)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#971
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-13 14:16:14 +00:00
dpschen
8e6e52bf02 feat: wrap edit-task with card (#948)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#948
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-13 14:13:56 +00:00
dpschen
20e059c921 fix ShowList margin in Home (#987)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#987
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-13 13:32:35 +00:00
36f1e846bb chore(deps): update dependency netlify-cli to v6.14.23 (#986)
Reviewed-on: vikunja/frontend#986
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-13 13:30:32 +00:00
1e50aeebd8 fix(deps): update dependency marked to v4.0.3 (#988)
Reviewed-on: vikunja/frontend#988
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-13 13:29:39 +00:00
05e624ce39 chore(deps): update dependency rollup to v2.60.0 (#983)
Reviewed-on: vikunja/frontend#983
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-12 13:29:43 +00:00
4e6b9f4cbd chore(deps): update dependency vue-tsc to v0.29.4 (#981)
Reviewed-on: vikunja/frontend#981
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-12 06:58:13 +00:00
17bc35864b fix(deps): update sentry-javascript monorepo to v6.14.3 (#982)
Reviewed-on: vikunja/frontend#982
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-12 06:57:39 +00:00
52c115cea8 chore(deps): update dependency netlify-cli to v6.14.21 (#980)
Reviewed-on: vikunja/frontend#980
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-12 06:55:35 +00:00
df1a7dd19e fix(deps): update sentry-javascript monorepo to v6.14.2 (#979)
Reviewed-on: vikunja/frontend#979
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-11 17:05:00 +00:00
34994e919b fix(deps): update dependency marked to v4.0.1 (#977)
Reviewed-on: vikunja/frontend#977
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-11 07:09:27 +00:00
d6669fa8e2 [skip ci] Updated translations via Crowdin 2021-11-10 23:24:25 +00:00
6d86d4ce59 chore(deps): update dependency cypress to v9 (#975)
Reviewed-on: vikunja/frontend#975
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-10 22:27:02 +00:00
15da2657d3 chore(deps): pin dependencies (#974)
Reviewed-on: vikunja/frontend#974
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-10 22:25:45 +00:00
1c23a8c570 [skip ci] Updated translations via Crowdin 2021-11-10 22:24:23 +00:00
054f804427
fix: typo in quick actions translation 2021-11-10 22:38:51 +01:00
052cd36085 feat: properly return 404 when the file does not exist (#966)
Co-authored-by: Nick Douma <n.douma@nekoconeko.nl>
Reviewed-on: vikunja/frontend#966
Reviewed-by: konrad <k@knt.li>
Co-authored-by: LordGaav <n.douma+kolaente@nekoconeko.nl>
Co-committed-by: LordGaav <n.douma+kolaente@nekoconeko.nl>
2021-11-10 21:38:07 +00:00
e49fd16a3a feat: add preview deploys with netlify (#972)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#972
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-10 21:25:50 +00:00
c92b59db1d [skip ci] Updated translations via Crowdin 2021-11-10 19:24:24 +00:00
1cef4f6e0b feat: add .editorconfig for scss and css files (#970)
Co-authored-by: Adrian Simmons <adrian@perlucida.co.uk>
Reviewed-on: vikunja/frontend#970
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: adrinux <adrian@perlucida.co.uk>
Co-committed-by: adrinux <adrian@perlucida.co.uk>
2021-11-10 17:15:03 +00:00
46e3a9516a fix(deps): update dependency vue-i18n to v9.2.0-beta.18 (#969)
Reviewed-on: vikunja/frontend#969
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-10 12:43:57 +00:00
8674c5da47 chore(deps): update dependency vue-tsc to v0.29.3 (#968)
Reviewed-on: vikunja/frontend#968
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-10 12:43:31 +00:00
f299241a6c chore(deps): update dependency esbuild to v0.13.13 (#964)
Reviewed-on: vikunja/frontend#964
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-09 22:50:54 +00:00
038fb55801 chore(deps): update dependency vite to v2.6.14 (#967)
Reviewed-on: vikunja/frontend#967
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-09 22:25:12 +00:00
dpschen
8f43619f73 feat: #947 remove reset color button if no color set (#957)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#957
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-09 19:09:52 +00:00
9b7882de7a feat: allow selecting multiple labels at once (#945)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#945
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-09 19:07:06 +00:00
70a50ca1c2 chore(deps): update typescript-eslint monorepo to v5.3.1 (#962)
Reviewed-on: vikunja/frontend#962
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-09 07:16:11 +00:00
99d38e1f8e chore(deps): update dependency vue-tsc to v0.29.2 (#963)
Reviewed-on: vikunja/frontend#963
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-09 07:15:08 +00:00
00be1d4095 chore(deps): update dependency vue-tsc to v0.29.0 (#960)
Reviewed-on: vikunja/frontend#960
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-07 16:40:32 +00:00
04a971e767 chore(deps): update dependency eslint to v8.2.0 (#959)
Reviewed-on: vikunja/frontend#959
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-06 00:38:09 +00:00
917006b69f fix(deps): update sentry-javascript monorepo to v6.14.1 (#958)
Reviewed-on: vikunja/frontend#958
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-05 16:32:20 +00:00
a87d5836c1 chore(deps): pin dependency vue-tsc to 0.28.10 (#955)
Reviewed-on: vikunja/frontend#955
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-04 22:32:43 +00:00
dpschen
e23f3c2570 feat: add vue-tsc (#949)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#949
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-04 21:28:10 +00:00
dpschen
b7207c6eaf chore: remove some unused notification styles (#953)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#953
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-04 16:32:00 +00:00
58986c4a7a fix: adding or creating a label with quick add magic (#944)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#944
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-04 16:30:30 +00:00
7e82aa83e6 fix: label spacing (#946)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#946
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-04 16:28:20 +00:00
b6c4bb1801 [skip ci] Updated translations via Crowdin 2021-11-04 07:24:24 +00:00
a47d106926
chore: remove weblate ping script 2021-11-03 18:12:40 +01:00
3be15b0a5f chore(deps): update dependency eslint-plugin-vue to v8 (#913)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#913
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-03 17:11:31 +00:00
6e44f9eaba chore(deps): update dependency @vue/eslint-config-typescript to v9.0.1 (#941)
Reviewed-on: vikunja/frontend#941
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-03 16:12:35 +00:00
525ff7903c fix(deps): update sentry-javascript monorepo to v6.14.0 (#940)
Reviewed-on: vikunja/frontend#940
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-03 12:58:17 +00:00
6e043e3b9e
fix: lint 2021-11-02 19:57:00 +01:00
b34213c301 chore: remove setting loading state in register component (#939)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#939
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-02 18:53:45 +00:00
e831c3eb65
fix: reloading in error component 2021-11-02 19:29:35 +01:00
dpschen
d3c303ba2a fix: navigation show and hide animation (#927)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#927
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-02 18:20:14 +00:00
dpschen
108e7af578 feat: use script setup for ShowTasksinRange.vue (#931)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#931
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-02 18:18:54 +00:00
dpschen
d1ff800b41 chore: don't spread arguments (#933)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#933
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-02 18:17:25 +00:00
dpschen
ee430b8687 fix(style) restrict new task input size (#938)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#938
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-11-02 18:11:24 +00:00
4e6f961a24 fix(deps): update dependency vue-i18n to v9.2.0-beta.17 (#937)
Reviewed-on: vikunja/frontend#937
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-02 18:07:32 +00:00
f72f0424cc chore(deps): update dependency browserslist to v4.17.6 (#936)
Reviewed-on: vikunja/frontend#936
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-02 18:07:16 +00:00
061e2b42c9 fix(deps): update dependency marked to v4 (#935)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#935
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-02 17:39:56 +00:00
b2aa2df59d fix(deps): update vue monorepo to v3.2.21 (#934)
Reviewed-on: vikunja/frontend#934
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-02 12:52:39 +00:00
ead145b9ce chore(deps): update typescript-eslint monorepo to v5.3.0 (#932)
Reviewed-on: vikunja/frontend#932
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-01 18:16:01 +00:00
d8d4803e2d feat: show indicator on a repeating task (#925)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#925
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-11-01 16:06:03 +00:00
b06b419817 chore(deps): update dependency rollup to v2.59.0 (#928)
Reviewed-on: vikunja/frontend#928
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-11-01 08:43:27 +00:00
14472a45ed
feat(ci): update translations only on cron schedule 2021-10-31 15:10:51 +01:00
dpschen
28a448a1aa fix: loadTeam in EditTeam (#922)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#922
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-10-31 13:39:37 +00:00
ecb5be4b17 feat: disable password settings for users authenticated with third party auth (#921)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#921
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-10-31 13:38:13 +00:00
1873c74776 feat: add releases to sentry (#919)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#919
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-10-31 13:37:57 +00:00
7aede352f1 fix: loading tasks with infinite scroll in kanban buckets (#920)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#920
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-10-31 13:37:18 +00:00
5b406b0172 feat: allow openid users to export their data without a password (#918)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#918
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-10-31 13:16:28 +00:00
a515b0c3a4
fix: don't try to check undefined relations 2021-10-31 13:15:43 +01:00
85e85aa2bb
fix: don't crash when an error does not contain a request 2021-10-31 13:10:29 +01:00
54c5cabf48
fix: showing deletion scheduled at for non-scheduled users 2021-10-31 13:01:36 +01:00
8389587a60
fix: check if notifications are available at all before checking if triggered are available
Should fix sentry issue FRONTEND-OSS-J
2021-10-31 12:56:32 +01:00
8bbdbe62a4 chore(deps): update dependency esbuild to v0.13.12 (#917)
Reviewed-on: vikunja/frontend#917
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-31 09:15:56 +00:00
1dda3240e7 [skip ci] Updated translations via Crowdin 2021-10-30 09:03:55 +00:00
2c47102c56 chore(deps): update dependency esbuild to v0.13.11 (#916)
Reviewed-on: vikunja/frontend#916
Reviewed-by: konrad <k@knt.li>
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-30 08:58:58 +00:00
6d61a4b24d fix(deps): update dependency vue-i18n to v9.2.0-beta.16 (#915)
Reviewed-on: vikunja/frontend#915
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-29 17:33:39 +00:00
65ea02d861 chore(deps): update dependency @vue/eslint-config-typescript to v9 (#914)
Reviewed-on: vikunja/frontend#914
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-29 09:58:51 +00:00
e9d58d12f4 chore(deps): update dependency autoprefixer to v10.4.0 (#911)
Reviewed-on: vikunja/frontend#911
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-28 11:57:45 +00:00
8bbbfde5aa [skip ci] Updated translations via Crowdin 2021-10-28 07:06:54 +00:00
9b84d57f0e chore(deps): update dependency esbuild to v0.13.10 (#910)
Reviewed-on: vikunja/frontend#910
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-28 07:02:22 +00:00
ab1f5047a1
feat: add czech language 2021-10-27 18:27:47 +02:00
3c11660cf7 chore(deps): update dependency vite to v2.6.13 (#909)
Reviewed-on: vikunja/frontend#909
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-27 12:15:33 +00:00
776bd2e86f chore(deps): update dependency @vitejs/plugin-vue to v1.9.4 (#908)
Reviewed-on: vikunja/frontend#908
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-27 06:10:55 +00:00
226319a6f0 [skip ci] Updated translations via Crowdin 2021-10-27 05:54:19 +00:00
7298ecb7b7 chore(deps): update dependency sass to v1.43.4 (#907)
Reviewed-on: vikunja/frontend#907
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-27 05:44:33 +00:00
5040a76781 feat: move user settings to multiple components (#889)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#889
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-10-26 20:58:02 +00:00
e1a7fb4999 fix: migration icons are not resolved properly (#864)
Co-authored-by: kolaente <k@knt.li>
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#864
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-10-26 20:21:19 +00:00
6394485524
fix: create multiple tasks at once with multiline input now correctly uses the titles per line 2021-10-26 21:29:58 +02:00
1e4dd415cf fix(deps): pin dependencies (#905)
Reviewed-on: vikunja/frontend#905
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-26 19:15:10 +00:00
1774fdc604 feat: add sentry (#879)
Co-authored-by: kolaente <k@knt.li>
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#879
Reviewed-by: dpschen <dpschen@noreply.kolaente.de>
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-10-26 18:53:17 +00:00
dpschen
782abbb82a [skip ci] Updated translations via Crowdin 2021-10-26 18:30:19 +00:00
dpschen
3bd9b02768 feat: improve icons (#903)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#903
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-10-26 18:20:09 +00:00
a0903dd7be chore(deps): update dependency vite to v2.6.12 (#904)
Reviewed-on: vikunja/frontend#904
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-26 17:56:16 +00:00
dpschen
2f2e639a6e [skip ci] Updated translations via Crowdin 2021-10-26 16:59:43 +00:00
dpschen
77fc5c0c6f fix: use correct dash for english translation (#902)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#902
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-10-26 16:48:47 +00:00
dpschen
d4fe3781f7 fix: logout error (#901)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#901
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-10-26 16:48:04 +00:00
27c8bb3d19 [skip ci] Updated translations via Crowdin 2021-10-26 06:07:20 +00:00
20d57c2446 chore(deps): update dependency cypress to v8.7.0 (#900)
Reviewed-on: vikunja/frontend#900
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-26 06:01:26 +00:00
48224e28b8
feat: add vietnamese and italian languages 2021-10-25 21:15:54 +02:00
54148683f8
chore(deps): update typescript-eslint monorepo to v5.2.0 (#898)
Reviewed-on: vikunja/frontend#898
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 21:02:21 +02:00
fd3f058d22
chore(deps): update dependency axios to v0.24.0 (#897)
Reviewed-on: vikunja/frontend#897
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 21:02:21 +02:00
e9ad3c2126
[skip ci] Updated translations via Crowdin 2021-10-25 21:02:21 +02:00
fac41b63b9
chore(deps): update dependency rollup to v2.58.3 (#895)
Reviewed-on: vikunja/frontend#895
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 21:02:21 +02:00
283b7c7082
chore(deps): update dependency vite to v2.6.11 (#896)
Reviewed-on: vikunja/frontend#896
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 21:02:21 +02:00
5058046018
fix(deps): update dependency vue-i18n to v9.2.0-beta.15 (#894)
Reviewed-on: vikunja/frontend#894
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 21:02:21 +02:00
8e7e86808e
fix(deps): update dependency marked to v3.0.8 (#893)
Reviewed-on: vikunja/frontend#893
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 21:02:21 +02:00
dd26d0de0f
chore(deps): update dependency esbuild to v0.13.9 (#892)
Reviewed-on: vikunja/frontend#892
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 21:02:21 +02:00
8d90d61fd2
chore(deps): update dependency browserslist to v4.17.5 (#891)
Reviewed-on: vikunja/frontend#891
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 16:40:48 +02:00
e0645d4525
[skip ci] Updated translations via Crowdin 2021-10-25 16:40:48 +02:00
3293e9892c
chore(deps): update dependency eslint to v8.1.0 (#890)
Reviewed-on: vikunja/frontend#890
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 16:40:47 +02:00
8a6774f8e3
[skip ci] Updated translations via Crowdin 2021-10-25 16:40:47 +02:00
2004478c88
revert: chore(deps): update node.js to v17 (#883)
This reverts commit 149defdd because node 17 has issues when building
docker images for arm.
2021-10-25 16:40:47 +02:00
4d48090411
revert: fix: update node in .nvmrc aswell (#886)
This reverts commit 0fdfccce
2021-10-25 16:40:47 +02:00
02b30832b3
chore(deps): update dependency sass to v1.43.3 (#888)
Reviewed-on: vikunja/frontend#888
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-25 16:40:46 +02:00
877b243c69
feat: add close task popup link (#880)
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#880
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-10-25 16:40:46 +02:00
c6b24dd8f1
feat: add Done component 2021-10-25 16:39:35 +02:00
89cd8eafc4
fix: use $shadow variable directly
By using the $shadow variable directly that defines the $card-shadow variable in bulma we don't need to import card.scss
2021-10-25 16:39:35 +02:00
9e6afdb752
fix kanban height calculation with hack 2021-10-25 16:39:34 +02:00
2645edc9e0
fix: label spacing 2021-10-25 16:39:34 +02:00
87d2b4fed3
fix: use :deep() selector instead of ::v-deep 2021-10-25 16:39:34 +02:00
f496c9d678
fix: user dropdown-trigger background 2021-10-25 16:39:34 +02:00
19a4b17004
feat: move some form.scss styles to button.vue 2021-10-25 16:39:34 +02:00
4f8cce0f45
feat: add FIXME comments 2021-10-25 16:39:34 +02:00
32a0106819
chore: small CSS format changes 2021-10-25 16:39:34 +02:00
7ca355db66
feat: merge update-notification.scss styles with the update.vue component 2021-10-25 16:39:34 +02:00
6ba974f9fa
fix: move .progress styles together as close as possible 2021-10-25 16:39:33 +02:00
986130a0ac
feat: merge offline.scss with component 2021-10-25 16:39:33 +02:00
7824ddc13f
feat: divide most navigation.scss styles into components
- navigation.vue and topNavigation.vue
- contentAuth.vue
2021-10-25 16:39:33 +02:00
57d5afa530
feat: moved some link-share.scss styles to the contentLinkShare and linkSharing components 2021-10-25 16:39:33 +02:00
3e456782df
feat: divide most content.scss styles into components
- contentAuth
- contentNoAuth.vue
- Login.vue
- button.vue
- comments.vue
2021-10-25 16:39:33 +02:00
2aecf3245b
feat: moved some background.scss styles to the contentLinkShare.vue component 2021-10-25 16:39:33 +02:00
4d2c27e74e
feat: merge teams.scss with component styles 2021-10-25 16:39:33 +02:00
4a6126287a
chore: remove unneeded styles from tasks.scss 2021-10-25 16:39:33 +02:00
14dd49e4b0
feat: divide most tasks.scss styles into components
- ShowTasks.vue
- List.vue
- defer-task.vue
- edit-task.vue
- Kanban.vue, relatedTasks.vue and singleTaskInView.vue
2021-10-25 16:39:33 +02:00
e0fd5f8fe0
feat: merge taskRelations.scss with component styles 2021-10-25 16:39:32 +02:00
c9e23cdd29
feat: moved most task.scss styles to the TaskDetailView.vue component 2021-10-25 16:39:32 +02:00
f7c7ea50eb
feat: merge table-view.scss with component styles 2021-10-25 16:39:32 +02:00
55bed2e5e8
feat: merge switch-view.scss with component styles 2021-10-25 16:39:32 +02:00
370186726a
feat: merge reminders.scss with component styles 2021-10-25 16:39:32 +02:00
0aff057f7b
feat: merge quick-actions.scss with component styles 2021-10-25 16:39:32 +02:00
a5a716e09b
feat: merge notifications.vue with component styles 2021-10-25 16:39:32 +02:00
0f7caafd54
feat: moved most of namespaces.scss styles to the ListNamespaces.vue component 2021-10-25 16:39:32 +02:00
0eb87663e0
feat: merge migrator.scss with component styles 2021-10-25 16:39:31 +02:00
87f7a515a6
feat: divide most list.scss styles into components
- list-card.vue and the Home.vue
- listSearch.vue
- topNavigation.vue
- EditTeam.vue
- List.vue
- ShowList.vue
2021-10-25 16:39:31 +02:00
4d15f7ae98
feat: merge list-backgrounds.scss with component 2021-10-25 16:39:31 +02:00
4223d23ce5
feat: merge legal.scss with component 2021-10-25 16:39:31 +02:00
f74cf516d2
feat: merge keyboard-shortcuts.scss styles with component 2021-10-25 16:39:31 +02:00
9ca8857d89
feat: merge kanban.scss styles with component
.ghost-task-drop class was removed because it was used nowhere.
2021-10-25 16:39:31 +02:00
ba1942e757
feat: merge gantt.scss with component styles 2021-10-25 16:39:31 +02:00
3cb68c945f
feat: merge datepicker.scss styles with component 2021-10-25 16:39:31 +02:00
46ebd45a74
feat: merge comments.scss styles with component 2021-10-25 16:39:30 +02:00
be35c73f6e
feat: merge color-picker.scss with component styles 2021-10-25 16:39:30 +02:00
08f84bf7e3
feat: merge attachment styles with component
also add bounce animation that is just used there
2021-10-25 16:39:30 +02:00
265081417d
feat: merge api-config.scss styles with component 2021-10-25 16:39:30 +02:00
6195637001
feat: move scrollbar styles and add variables locally 2021-10-25 16:39:30 +02:00
b304712b1e
feat: merge multiselect.scss with component styles 2021-10-25 16:39:30 +02:00
b9eba00603
feat: merge fancycheckbox.scss with component styles 2021-10-25 16:39:30 +02:00
a33758e37e
feat: moved some card.scss styles to the card.vue component 2021-10-25 16:39:30 +02:00
1f5283d548
feat: rework style imports 2021-10-25 16:39:29 +02:00
a0ca6bb8fb
chore: remove obsolete _all.scss variables 2021-10-25 16:39:29 +02:00
dpschen
e0fe8dc673 [skip ci] Updated translations via Crowdin 2021-10-21 17:30:48 +00:00
dpschen
0fdfcccee9 fix: update node in .nvmrc aswell (#886)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#886
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-10-21 17:12:03 +00:00
de33dc6c89 fix(deps): update dependency vue-i18n to v9.2.0-beta.14 (#885)
Reviewed-on: vikunja/frontend#885
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-21 17:11:14 +00:00
bbb3c81f68 chore(deps): update dependency postcss to v8.3.11 (#887)
Reviewed-on: vikunja/frontend#887
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-21 17:10:49 +00:00
149defddda chore(deps): update node.js to v17 (#883)
Reviewed-on: vikunja/frontend#883
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-20 19:23:27 +00:00
cb95a569d3 chore(deps): update dependency postcss to v8.3.10 (#882)
Reviewed-on: vikunja/frontend#882
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-20 18:56:53 +00:00
3918da3bc7 chore(deps): update dependency eslint-plugin-vue to v7.20.0 (#881)
Reviewed-on: vikunja/frontend#881
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-20 07:17:43 +00:00
a6bb2daf81 chore(deps): update dependency @fortawesome/vue-fontawesome to v3.0.0-5 (#875)
Reviewed-on: vikunja/frontend#875
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-19 16:31:20 +00:00
6f11102153 fix(deps): update dependency vue-i18n to v9.2.0-beta.13 (#871)
Reviewed-on: vikunja/frontend#871
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-19 15:55:22 +00:00
53ff2e0209 chore(deps): update dependency vite to v2.6.10 (#876)
Reviewed-on: vikunja/frontend#876
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-19 15:54:48 +00:00
59add72cd6 chore(deps): update typescript-eslint monorepo to v5.1.0 (#877)
Reviewed-on: vikunja/frontend#877
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-19 15:54:35 +00:00
0c72d836fe chore(deps): update dependency jest to v27.3.1 (#878)
Reviewed-on: vikunja/frontend#878
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-19 15:53:03 +00:00
b5b5facde6 chore(deps): update dependency vite to v2.6.9 (#873)
Reviewed-on: vikunja/frontend#873
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-18 09:48:03 +00:00
5bcd51cc5f chore(deps): pin dependencies (#870)
Reviewed-on: vikunja/frontend#870
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-18 06:12:47 +00:00
cb00c869e1 chore(deps): update dependency jest to v27.3.0 (#866)
Reviewed-on: vikunja/frontend#866
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-18 05:51:52 +00:00
cb81497e8d fix(deps): update dependency highlight.js to v11.3.1 (#869)
Reviewed-on: vikunja/frontend#869
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-18 05:51:25 +00:00
b24145c47c fix(deps): update dependency vuedraggable to v4.1.0 (#872)
Reviewed-on: vikunja/frontend#872
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-18 05:50:58 +00:00
dpschen
1ce5a21e66 [skip ci] Updated translations via Crowdin 2021-10-17 21:38:12 +00:00
dpschen
a70b92253a feat: throw errors (#867)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#867
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-10-17 21:32:21 +00:00
bc7b577748 [skip ci] Updated translations via Crowdin 2021-10-17 21:26:23 +00:00
1a784d0709
feat(ci): remove vue3 branch trigger 2021-10-17 23:20:06 +02:00
dpschen
ee2c56156b Merge pull request 'vue3' (#815) from vue3 into main
Reviewed-on: vikunja/frontend#815
2021-10-17 21:17:04 +00:00
6d472bf5ca
fix: task attachment upload 2021-10-17 22:29:05 +02:00
c7b4c25caa
fix: remove nonexisting prop 2021-10-17 22:27:18 +02:00
852b864ee6
chore: TRANSITION_GROUP_ROOT silence transition-group warning 2021-10-17 22:17:49 +02:00
f795d2d0f3
fix: ATTR_ENUMERATED_COERCION errors with contenteditable 2021-10-17 22:08:58 +02:00
3ba9cd2d99
fix: ATTR_ENUMERATED_COERCION errors with editor and contenteditable 2021-10-17 22:05:23 +02:00
c1a981c60b
chore: remove unused method 2021-10-17 21:59:13 +02:00
dpschen
0a1d0084e2 feat: keep errorMessage local (#865)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Co-authored-by: kolaente <k@knt.li>
Co-authored-by: konrad <k@knt.li>
Reviewed-on: vikunja/frontend#865
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-10-17 19:38:30 +00:00
1fc857d9a2
chore: re-add vue3 branch 2021-10-17 20:50:22 +02:00
571b019c00
fix: ATTR_ENUMERATED_COERCION in a few places 2021-10-17 18:52:05 +02:00
1864359751
fix: lint 2021-10-17 18:10:48 +02:00
4c24118b48
fix: vuex store mutation violation when saving user settings 2021-10-17 17:39:18 +02:00
dpschen
dd0e04b106 feature/vue3-make-workbox-debug-configurable (#862)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#862
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-10-17 15:33:56 +00:00
49cd66581d
Merge branch 'main' into vue3
# Conflicts:
#	package.json
#	yarn.lock
2021-10-17 17:32:50 +02:00
eb7b1bf432
chore: remove vue3 from the drone branch trigger 2021-10-17 17:32:00 +02:00
f4372ecd05
fix: adding a list to favorites 2021-10-17 17:31:22 +02:00
3f61c6b21a
fix: use correct prop for CreateEdit 2021-10-17 17:30:00 +02:00
36d5262f1d
fix: ATTR_ENUMERATED_COERCION spellcheck on kanban board 2021-10-17 17:21:33 +02:00
dpschen
4e893a3196 Merge pull request 'feature/vue3-async-await' (#832) from dpschen/frontend:feature/vue3-async-await into vue3
Reviewed-on: vikunja/frontend#832
2021-10-17 15:18:09 +00:00
1d46b85170
fix: loading labels after login 2021-10-17 17:06:38 +02:00
ae971b23bc
fix: sort order by dueDate, then by id 2021-10-17 16:30:34 +02:00
2de94bc902
fix: lint 2021-10-17 16:26:17 +02:00
9fc158831b
Merge branch 'vue3' into feature/vue3-async-await 2021-10-17 16:22:43 +02:00
9d48700cd9
fix: vuex store mutation error when moving a task with attributes on kanban 2021-10-17 16:21:55 +02:00
a3657abaf3 fix(deps): update dependency highlight.js to v11.3.0 (#863)
Reviewed-on: vikunja/frontend#863
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-17 14:11:49 +00:00
2ac3d29c13
Merge branch 'vue3' into feature/vue3-async-await
# Conflicts:
#	src/i18n/index.js
#	src/store/modules/labels.js
#	src/store/modules/tasks.js
#	src/views/list/views/Kanban.vue
#	src/views/tasks/ShowTasks.vue
#	src/views/tasks/TaskDetailView.vue
2021-10-17 16:06:58 +02:00
0cc7166767
fix: don't search for first letter images 2021-10-17 15:19:21 +02:00
1c8e26bdc6
fix: set the current list when opening a task 2021-10-17 15:16:26 +02:00
cc32ca244c
feat: don't update the bucket after updating task position 2021-10-17 14:52:48 +02:00
c1078255fc
fix: use lodash.debounce for searching unsplash background 2021-10-17 14:47:30 +02:00
c329c37c7b
chore: cleanup 2021-10-17 14:01:48 +02:00
2fc96cb6a7
fix: change the ui locale 2021-10-17 14:01:15 +02:00
74d785d606
fix: "TypeError: i18n.setLocaleMessage is not a function" when changing languages 2021-10-17 13:55:25 +02:00
56365591cf
fix: await getAuthUrl 2021-10-17 13:48:53 +02:00
f884020c55
fix: creating a new task while specifying the list in quick add magic 2021-10-17 13:46:56 +02:00
1d2abf56f9
Merge branch 'main' into vue3
# Conflicts:
#	src/components/tasks/mixins/createTask.js
2021-10-17 13:37:20 +02:00
e52c139c9f
fix: task edit pane spacing 2021-10-17 13:36:21 +02:00
529b3d2890
fix: label search in tasks not working 2021-10-17 13:20:51 +02:00
7e29dde717
fix: new tasks were always created in the default list 2021-10-17 12:59:32 +02:00
a85e27b497 chore(deps): update dependency esbuild to v0.13.8 (#861)
Reviewed-on: vikunja/frontend#861
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-17 10:18:27 +00:00
5766ae48d7
fix: kanban card elements spacing 2021-10-16 23:25:02 +02:00
df32893ce6
fix: always sort tasks the same order in chrome and firefox
Discussion at https://community.vikunja.io/t/custom-sorting-tasks-on-overview/392
2021-10-16 21:37:21 +02:00
a60ad77bdc
fix: use async action to change current list 2021-10-16 20:44:39 +02:00
7f5f44d7f0
fix: call loadList just once 2021-10-16 18:59:54 +02:00
54d456e886
fix: await namespace creation 2021-10-16 18:59:54 +02:00
1d43d1bd65
chore: simplify MENU_ACTIVE mutation 2021-10-16 18:59:54 +02:00
12a3c238b8
feat: use computed for namespace title 2021-10-16 18:59:54 +02:00
a6db1e7391
fix: remove disabled prop for editor 2021-10-16 18:59:54 +02:00
bb94c1ba3a
feat: use async / await where it makes sense 2021-10-16 18:59:53 +02:00
a776e1d2f3
chore: don't resolve when returning from promise & improve list store module 2021-10-16 18:59:53 +02:00
3b940cb56c
feat: don't rethrow same error and handle errors globally 2021-10-16 18:59:53 +02:00
6f51921588
chore: upgrade vue3 packages 2021-10-16 18:58:48 +02:00
b79d238bdc
Merge branch 'main' into vue3
# Conflicts:
#	package.json
#	src/components/tasks/mixins/createTask.js
#	yarn.lock
2021-10-16 18:17:14 +02:00
f05e81190f fix: setting background to state mutation violation (#858)
State mutations must be synchronous. Using a promise.then handler to set the background is a violation of that.

Co-authored-by: kolaente <k@knt.li>
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#858
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-10-16 15:51:27 +00:00
373a766f5c fix: pagination in vue 3 (#859)
This fixes using Vikunja with pagination.

Co-authored-by: kolaente <k@knt.li>
Reviewed-on: vikunja/frontend#859
Co-authored-by: konrad <k@knt.li>
Co-committed-by: konrad <k@knt.li>
2021-10-16 15:39:50 +00:00
515300d43a chore(deps): update dependency ts-jest to v27.0.7 (#857)
Reviewed-on: vikunja/frontend#857
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 12:40:22 +00:00
80163ee992
fix: use componentData prop in draggable to set class 2021-10-16 13:54:24 +02:00
9c730d3381
fix: remove wrong active prop 2021-10-16 13:54:01 +02:00
f6d46ce394 chore(deps): update dependency axios to v0.23.0 (#848)
Reviewed-on: vikunja/frontend#848
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 10:38:57 +00:00
526d2c0cfc fix(deps): update dependency marked to v3.0.7 (#846)
Reviewed-on: vikunja/frontend#846
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 10:38:22 +00:00
3ee8231b9e chore(deps): update dependency jest to v27.2.5 (#842)
Reviewed-on: vikunja/frontend#842
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 10:38:12 +00:00
fc320d0067 chore(deps): update dependency esbuild to v0.13.7 (#841)
Reviewed-on: vikunja/frontend#841
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 10:38:02 +00:00
b988e3c0ec chore(deps): update dependency typescript to v4.4.4 (#844)
Reviewed-on: vikunja/frontend#844
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 10:33:44 +00:00
IDDQD69
26568fe5c6
fix: add null check for parsedTask listId (#31)
Co-authored-by: AJ Nieminen <ajnieminen@kapsi.fi>
2021-10-16 12:09:10 +02:00
accd3ef392 chore(deps): update dependency browserslist to v4.17.4 (#840)
Reviewed-on: vikunja/frontend#840
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 09:48:35 +00:00
f36e1d78b2 chore(deps): update dependency vite to v2.6.7 (#845)
Reviewed-on: vikunja/frontend#845
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 09:48:10 +00:00
a105883ef0 chore(deps): update dependency @vue/eslint-config-typescript to v8 (#854)
Reviewed-on: vikunja/frontend#854
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 09:47:37 +00:00
3271144406 chore(deps): update dependency eslint to v8 (#855)
Reviewed-on: vikunja/frontend#855
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 09:47:13 +00:00
1965535605 chore(deps): update dependency ts-jest to v27.0.6 (#843)
Reviewed-on: vikunja/frontend#843
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 08:21:36 +00:00
b8a2160a93 chore(deps): update dependency autoprefixer to v10.3.7 (#839)
Reviewed-on: vikunja/frontend#839
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 08:21:04 +00:00
2b12b0fafd fix(deps): update dependency vue-i18n to v8.26.5 (#847)
Reviewed-on: vikunja/frontend#847
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 07:30:33 +00:00
ccce0c6ac4 chore(deps): update dependency cypress to v8.6.0 (#849)
Reviewed-on: vikunja/frontend#849
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 07:29:42 +00:00
f101cf4f82 chore(deps): update dependency sass to v1.43.2 (#850)
Reviewed-on: vikunja/frontend#850
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 07:29:10 +00:00
d072a7d277 chore(deps): update dependency vite-plugin-vue2 to v1.9.0 (#851)
Reviewed-on: vikunja/frontend#851
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 07:28:41 +00:00
217eb9102e fix(deps): update dependency date-fns to v2.25.0 (#853)
Reviewed-on: vikunja/frontend#853
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 07:27:52 +00:00
e6f140912e chore(deps): update typescript-eslint monorepo to v5 (major) (#856)
Reviewed-on: vikunja/frontend#856
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-16 07:26:51 +00:00
ee3de6730c chore(deps): update dependency @vitejs/plugin-legacy to v1.6.2 (#838)
Reviewed-on: vikunja/frontend#838
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-15 21:50:08 +00:00
51e55732ff chore(deps): update dependency @fortawesome/vue-fontawesome to v2.0.5 (#837)
Reviewed-on: vikunja/frontend#837
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-15 21:49:52 +00:00
81a1aa202f [skip ci] Updated translations via Crowdin 2021-10-15 21:12:55 +00:00
10c262fe96 chore(deps): pin dependencies (#834)
Reviewed-on: vikunja/frontend#834
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-15 20:42:04 +00:00
ba4f825bc6 chore(deps): update dependency @fortawesome/vue-fontawesome to v2.0.4 (#835)
Reviewed-on: vikunja/frontend#835
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2021-10-15 20:41:24 +00:00
8e1ab8e09b
Merge branch 'main' into vue3 2021-10-15 20:43:11 +02:00
fa54e1f1de
feat: review changes 2021-10-15 19:57:14 +02:00
2bf83a672c [skip ci] Updated translations via Crowdin 2021-10-13 20:06:52 +00:00
435535f8cd
fix: wrong word in en base text string 2021-10-13 21:59:53 +02:00
9a2f95ecc6
feat: redirect the user to the last page they were on before logging in after login 2021-10-13 21:53:39 +02:00
97dd55d946
feat: show up to 4 recent lists on the overview page 2021-10-13 21:20:46 +02:00
4fef047d74
fix: user dropdown padding on mobile 2021-10-13 21:14:34 +02:00
3f96ce6d60
fix: task input height after removing a line now works correctly 2021-10-13 21:08:29 +02:00
c30c2e00cb
fix: task input height on devices with smaller font size 2021-10-13 20:37:03 +02:00
b5b56a6e4a
fix: switch view height on devices with smaller font size 2021-10-13 20:16:45 +02:00
69821fb663
fix: editing a label works now 2021-10-13 20:12:37 +02:00
50fa592aad
fix: wrong async order 2021-10-11 21:40:36 +02:00
a3a3ef850c
chore: remove console.log 2021-10-11 21:25:54 +02:00
dpschen
e6a935f49d fix: disable service workers in cypress (#830)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#830
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-10-11 18:39:27 +00:00
cc8b03778c
fix: use correct method for fakers uuid 2021-10-11 19:18:06 +02:00
18c31482df
fix: remove side effect from computed
was firing a commit
2021-10-11 19:17:15 +02:00
427f18d59e
fix: watch deep in listSearch 2021-10-11 19:15:34 +02:00
d1b61a1489
feat: some vue3 package improvements
- add codemirror as direct dependency
- remove @vue/runtime-dom (was needed for volar with vue2)
- remove setting for using volar with vue2
2021-10-11 18:49:44 +02:00
5cfb99bfc2
Merge branch 'main' into feature/vue3-implementation-improvements
# Conflicts:
#	package.json
#	vite.config.js
#	yarn.lock
2021-10-11 18:40:06 +02:00
18d7ca0820
feat: also use createRandomID helper in editor 2021-10-11 18:28:41 +02:00
fd77aaa123
fix: add timeout to wait for move to finish 2021-10-11 18:08:33 +02:00
e0456cdfa1
fix: remove broken getTaskIndices helper 2021-10-11 18:08:33 +02:00
0b68a473ef
fix: use correct listId to load next tasks 2021-10-11 18:08:33 +02:00
7c3ece5816
fix: addTasksToBucket mutation 2021-10-11 18:08:33 +02:00
d7ed5b8f11
fix: use correct listId when deleting bucket 2021-10-11 18:08:32 +02:00
dfe401a9dc
fix: watch deep for other arrays
see: https://v3.vuejs.org/guide/migration/watch.html
2021-10-11 18:08:32 +02:00
0bf68effb8
fix: watch deep for multiselect modelValue changes 2021-10-11 18:08:32 +02:00
9b2e9fc17f
fix: getTaskById function 2021-10-11 18:08:32 +02:00
35c861b711
feat: create randomId helper and use everywhere
Co-authored-by: konrad <konrad@kola-entertainments.de>
2021-10-11 18:08:32 +02:00
3750b0f78b
fix: mutation errors by make a copy of the store settings 2021-10-11 18:08:32 +02:00
eec02a55a4
fix: wait with redirect until route name is available 2021-10-11 18:08:32 +02:00
60ef07da0f
fix: give the dom some time to update for some tests to pass 2021-10-11 18:08:31 +02:00
e064c3bf96
fix: access namespace only if loaded 2021-10-11 18:08:31 +02:00
1964c1352c
chore: make functions of linkSharing less dependent on component state 2021-10-11 18:08:31 +02:00
6fee114610
chore: remove unneeded var 2021-10-11 18:08:31 +02:00
4ae18ec162
fix: kanban drag task test 2021-10-11 18:08:30 +02:00
d66ad12f5c
feat: improve kanban implementation 2021-10-11 18:08:30 +02:00
43b22360a5
chore: add vue3 branch as drone branch trigger 2021-10-11 17:53:14 +02:00
17d791027c
feat: add legacy build 2021-10-07 20:48:16 +02:00
dpschen
3c5c3cad10 chore: remove unneeded babel packages and add peerDependencies (#828)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#828
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-10-07 18:14:00 +00:00
dpschen
36d4599276 fix: unassign user success messgage (#831)
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: vikunja/frontend#831
Reviewed-by: konrad <k@knt.li>
Co-authored-by: dpschen <dpschen@noreply.kolaente.de>
Co-committed-by: dpschen <dpschen@noreply.kolaente.de>
2021-10-07 18:09:54 +00:00
59401bc1da
fix: vue3 types 2021-10-07 12:55:59 +02:00
3a7a4bdc42
Merge branch 'main' into vue3
# Conflicts:
#	src/components/input/editor.vue
#	src/components/list/partials/filters.vue
#	src/components/tasks/partials/editAssignees.vue
#	src/helpers/find.ts
#	src/helpers/time/formatDate.js
#	src/main.ts
#	src/store/modules/attachments.js
#	src/store/modules/kanban.js
#	src/views/list/views/List.vue
#	yarn.lock
2021-10-07 12:20:52 +02:00
66648be6d5
chrome: some dropdown.vue improvements 2021-10-03 13:25:09 +02:00
117980a8fc
chore: some editor improvements 2021-10-03 13:25:09 +02:00
34d0f28678
Merge branch 'main' into fix/vue3
# Conflicts:
#	src/views/list/settings/edit.vue
2021-10-03 13:24:17 +02:00
0cbffad49d
fix: dropdown routes 2021-10-02 17:40:49 +02:00
7bb1b1b769
feat: fix some Kanban errors with vue3 2021-10-01 21:26:47 +02:00
6b358107b6
feat: watch taskId instead of whole route 2021-10-01 20:48:04 +02:00
15640e98ec
feat: simplify filter-popup 2021-10-01 20:14:21 +02:00
8d88b3792d
feat: always use index for buckets 2021-10-01 20:06:06 +02:00
672d63fbed
feat: remove createTask mixin 2021-10-01 20:06:06 +02:00
aeabc42844
fix newList.vue 2021-10-01 20:06:05 +02:00
8fc01f774a
fix: emailPlaceholder translation 2021-10-01 20:06:05 +02:00
a745966984
feat: forked vue-easymde 2021-10-01 20:06:05 +02:00
e779681905
feat: upgrade to packages for vue 3
- vue3-notification
- vue-advanced-cropper 2
- vuedraggable 4
- vue-shortkey -> moved in repo
2021-10-01 18:49:54 +02:00
7c3c2945f8
feat: use vue-i18n 9 for vue3 2021-10-01 18:49:54 +02:00
3d6aca3510
feat: use vuex 4 for vue3 2021-10-01 18:47:41 +02:00
72518212da
feat: use vue-router 4 for vue3 2021-10-01 18:47:41 +02:00
b31da0cefe
feat: use vue-flatpickr-component for vue3 2021-10-01 18:47:11 +02:00
b75c79fd5e
feat: use vue-fontawesome for vue3 2021-10-01 18:46:40 +02:00
421ff9a188
feat: use new async component definition
- fix see: https://v3.vuejs.org/guide/migration/async-components.html
- put async editor in separate component
2021-10-01 18:46:40 +02:00
51a740f53c
fix: use vue3 v-model bindings
see: https://v3.vuejs.org/guide/migration/v-model.html
2021-10-01 18:45:42 +02:00
2ef2bb7700
fix: transition class names
see: https://v3.vuejs.org/guide/migration/transition.html
2021-10-01 18:45:42 +02:00
ecc3d3cf3f
fix: life cycle hook naming 2021-10-01 18:45:42 +02:00
db49b9b532
fix: directly set arrays, objects and delete directly
Not needed since vue3 uses proxies
2021-10-01 18:45:42 +02:00
2b20f328cb
fix: compiler warnings
see:
- https://v3.vuejs.org/guide/migration/key-attribute.html
- https://v3.vuejs.org/guide/migration/v-bind.html
- https://v3.vuejs.org/guide/migration/v-on-native-modifier-removed.html
2021-10-01 18:45:42 +02:00
3c89147ee2
fix: new directive syntax
see: https://v3.vuejs.org/guide/migration/custom-directives.html
2021-10-01 18:45:41 +02:00
78a5096e0d
feat: add vue3 in compat mode
See: https://v3.vuejs.org/guide/migration/migration-build.html#installation
2021-10-01 18:45:41 +02:00
4ee7a8bac6
feat: native color picker instead of verte
Prepare for vue 3
2021-10-01 18:44:14 +02:00
0c678b6e44
fix: use dynamic imports instead of old async components for router views
prepare for vue 3, see: https://next.router.vuejs.org/guide/advanced/lazy-loading.html
2021-10-01 18:44:14 +02:00
321 changed files with 25761 additions and 14071 deletions

View File

@ -1,3 +1,4 @@
---
kind: pipeline
name: build
@ -79,25 +80,13 @@ steps:
depends_on:
- dependencies
# Building in dev mode to avoid the service worker for testing
- name: build-dev
image: node:16
pull: true
environment:
YARN_CACHE_FOLDER: .cache/yarn/
CYPRESS_CACHE_FOLDER: .cache/cypress/
commands:
- yarn build:dev
depends_on:
- dependencies
- name: build-prod
image: node:16
pull: true
environment:
YARN_CACHE_FOLDER: .cache/yarn/
commands:
- yarn build --dest dist-prod
- yarn build
depends_on:
- dependencies
@ -109,8 +98,17 @@ steps:
depends_on:
- dependencies
- name: typecheck
failure: ignore
image: node:16
pull: true
commands:
- yarn typecheck
depends_on:
- dependencies
- name: test-frontend
image: cypress/browsers:node14.17.0-chrome91-ff89
image: cypress/browsers:node16.5.0-chrome94-ff93
pull: true
environment:
CYPRESS_API_URL: http://api:3456/api/v1
@ -119,12 +117,12 @@ steps:
CYPRESS_CACHE_FOLDER: .cache/cypress/
CYPRESS_DEFAULT_COMMAND_TIMEOUT: 60000
commands:
- sed -i 's/localhost/api/g' dist-dev/index.html
- yarn serve:dist-dev & npx wait-on http://localhost:5000
- sed -i 's/localhost/api/g' dist/index.html
- yarn serve:dist & npx wait-on http://localhost:5000
- yarn test:frontend --browser chrome
depends_on:
- dependencies
- build-dev
- build-prod
- name: upload-test-results
image: plugins/s3
@ -148,6 +146,26 @@ steps:
- failure
- success
- name: deploy-preview
image: node:16
pull: true
environment:
NETLIFY_AUTH_TOKEN:
from_secret: netlify_auth_token
NETLIFY_SITE_ID:
from_secret: netlify_site_id
GITEA_TOKEN:
from_secret: gitea_token
commands:
- shasum -a 384 -c ./scripts/deploy-preview-netlify.js.sha384
- node ./scripts/deploy-preview-netlify.js
depends_on:
- build-prod
when:
event:
include:
- pull_request
---
kind: pipeline
name: release-latest
@ -340,6 +358,9 @@ trigger:
ref:
- refs/heads/main
- "refs/tags/**"
event:
exclude:
- cron
steps:
- name: docker-unstable
@ -437,6 +458,9 @@ trigger:
ref:
- refs/heads/main
- "refs/tags/**"
event:
exclude:
- cron
steps:
- name: docker-unstable
@ -483,6 +507,9 @@ trigger:
ref:
- refs/heads/main
- "refs/tags/**"
event:
exclude:
- cron
depends_on:
- docker-amd64-release
@ -544,6 +571,9 @@ trigger:
ref:
- refs/heads/main
- "refs/tags/**"
event:
exclude:
- cron
depends_on:
- build
@ -580,7 +610,9 @@ trigger:
branch:
- main
event:
- push
- cron
cron:
- update_translations
steps:
- name: download
@ -631,3 +663,8 @@ steps:
environment:
CROWDIN_KEY:
from_secret: crowdin_key
---
kind: signature
hmac: 188ee90100c5fc5922a445e531e7a47453121edddb2a64a182eb23ed2bf602de
...

View File

@ -21,5 +21,9 @@ indent_size = 2
indent_style = space
indent_size = 2
[*.{scss,css}]
indent_style = space
indent_size = 2
[.nvmrc]
insert_final_newline = false

3
.gitignore vendored
View File

@ -26,3 +26,6 @@ stats.html
# Test files
cypress/screenshots
cypress/videos
# Local Netlify folder
.netlify

View File

@ -9,6 +9,13 @@ All releases can be found on https://code.vikunja.io/frontend/releases.
The releases aim at the api versions which is why there are missing versions.
## [0.18.2] - 2021-11-23
### Fixed
* fix(docker): properly replace api url
* fix: edit saved filter title
## [0.18.1] - 2021-09-08
### Added

View File

@ -24,12 +24,6 @@ RUN \
# Stage 2: copy
FROM nginx
RUN apt-get update && apt-get install -y apt-utils openssl && \
mkdir -p /etc/nginx/ssl && \
openssl genrsa -out /etc/nginx/ssl/dummy.key 2048 && \
openssl req -new -key /etc/nginx/ssl/dummy.key -out /etc/nginx/ssl/dummy.csr -subj "/C=DE/L=Berlin/O=Vikunja/CN=Vikunja Snakeoil" && \
openssl x509 -req -days 3650 -in /etc/nginx/ssl/dummy.csr -signkey /etc/nginx/ssl/dummy.key -out /etc/nginx/ssl/dummy.crt
COPY nginx.conf /etc/nginx/nginx.conf
COPY run.sh /run.sh

View File

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

View File

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

View File

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

View File

@ -31,15 +31,14 @@ describe('Lists', () => {
cy.url()
.should('contain', '/namespaces/1/list')
cy.get('.card-header-title')
.contains('Create a new list')
.contains('New list')
cy.get('input.input')
.type('New List')
cy.get('.button')
.contains('Create')
.click()
cy.wait(1000) // Waiting until the request to create the new list is done
cy.get('.global-notification')
cy.get('.global-notification', { timeout: 1000 }) // Waiting until the request to create the new list is done
.should('contain', 'Success')
cy.url()
.should('contain', '/lists/')
@ -102,7 +101,7 @@ describe('Lists', () => {
.click()
cy.url()
.should('contain', '/settings/delete')
cy.get('.modal-mask .modal-container .modal-content .actions a.button')
cy.get('[data-cy="modalPrimary"]')
.contains('Do it')
.click()
@ -220,10 +219,10 @@ describe('Lists', () => {
cy.get('.table-view .filter-container .items .button')
.contains('Columns')
.click()
cy.get('.table-view .filter-container .card .card-content .fancycheckbox .check')
cy.get('.table-view .filter-container .card.columns-filter .card-content .fancycheckbox .check')
.contains('Priority')
.click()
cy.get('.table-view .filter-container .card .card-content .fancycheckbox .check')
cy.get('.table-view .filter-container .card.columns-filter .card-content .fancycheckbox .check')
.contains('Done')
.click()
@ -393,7 +392,7 @@ describe('Lists', () => {
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field input.input')
.first()
.type(3)
cy.get('.kanban .bucket .bucket-header .dropdown.options .dropdown-menu .dropdown-item .field a.button.is-primary')
cy.get('[data-cy="setBucketLimit"]')
.first()
.click()
@ -446,7 +445,7 @@ describe('Lists', () => {
cy.get('.kanban .bucket .tasks .task')
.contains(tasks[0].title)
.first()
.drag('.kanban .bucket:nth-child(2) .tasks .dropper div')
.drag('.kanban .bucket:nth-child(2) .tasks .dropper')
cy.get('.kanban .bucket:nth-child(2) .tasks')
.should('contain', tasks[0].title)
@ -501,7 +500,7 @@ describe('Lists', () => {
.first()
.click()
cy.get('.global-notification')
cy.get('.global-notification', { timeout: 1000 })
.should('contain', 'Success')
cy.go('back')
cy.get('.kanban .bucket')

View File

@ -15,7 +15,7 @@ describe('Namepaces', () => {
it('Should be all there', () => {
cy.visit('/namespaces')
cy.get('.namespace h1 span')
cy.get('[data-cy="namespace-title"]')
.should('contain', namespaces[0].title)
})
@ -23,14 +23,14 @@ describe('Namepaces', () => {
const newNamespaceTitle = 'New Namespace'
cy.visit('/namespaces')
cy.get('a.button')
.contains('Create a new namespace')
cy.get('[data-cy="new-namespace"]')
.should('contain', 'New namespace')
.click()
cy.url()
.should('contain', '/namespaces/new')
cy.get('.card-header-title')
.should('contain', 'Create a new namespace')
.should('contain', 'New namespace')
cy.get('input.input')
.type(newNamespaceTitle)
cy.get('.button')
@ -67,12 +67,12 @@ describe('Namepaces', () => {
.contains('Save')
.click()
cy.get('.global-notification')
cy.get('.global-notification', { timeout: 1000 })
.should('contain', 'Success')
cy.get('.namespace-container .menu.namespaces-lists')
.should('contain', newNamespaceName)
.should('not.contain', newNamespaces[0].title)
cy.get('.content.namespaces-list')
cy.get('[data-cy="namespaces-list"]')
.should('contain', newNamespaceName)
.should('not.contain', newNamespaces[0].title)
})
@ -89,7 +89,7 @@ describe('Namepaces', () => {
.click()
cy.url()
.should('contain', '/settings/delete')
cy.get('.modal-mask .modal-container .modal-content .actions a.button')
cy.get('[data-cy="modalPrimary"]')
.contains('Do it')
.click()
@ -116,30 +116,30 @@ describe('Namepaces', () => {
// Initial
cy.visit('/namespaces')
cy.get('.namespaces-list .namespace')
cy.get('.namespace')
.should('not.contain', 'Archived')
// Show archived
cy.get('.namespaces-list .fancycheckbox.show-archived-check label.check span')
cy.get('[data-cy="show-archived-check"] label.check span')
.should('be.visible')
.click()
cy.get('.namespaces-list .fancycheckbox.show-archived-check input')
cy.get('[data-cy="show-archived-check"] input')
.should('be.checked')
cy.get('.namespaces-list .namespace')
cy.get('.namespace')
.should('contain', 'Archived')
// Don't show archived
cy.get('.namespaces-list .fancycheckbox.show-archived-check label.check span')
cy.get('[data-cy="show-archived-check"] label.check span')
.should('be.visible')
.click()
cy.get('.namespaces-list .fancycheckbox.show-archived-check input')
cy.get('[data-cy="show-archived-check"] input')
.should('not.be.checked')
// Second time visiting after unchecking
cy.visit('/namespaces')
cy.get('.namespaces-list .fancycheckbox.show-archived-check input')
cy.get('[data-cy="show-archived-check"] input')
.should('not.be.checked')
cy.get('.namespaces-list .namespace')
cy.get('.namespace')
.should('not.contain', 'Archived')
})
})

View File

@ -1,35 +0,0 @@
import '../../support/authenticateUser'
const setHours = hours => {
const date = new Date()
date.setHours(hours)
cy.clock(+date)
}
describe('Home Page', () => {
it('shows the right salutation in the night', () => {
setHours(4)
cy.visit('/')
cy.get('h2').should('contain', 'Good Night')
})
it('shows the right salutation in the morning', () => {
setHours(8)
cy.visit('/')
cy.get('h2').should('contain', 'Good Morning')
})
it('shows the right salutation in the day', () => {
setHours(13)
cy.visit('/')
cy.get('h2').should('contain', 'Hi')
})
it('shows the right salutation in the night', () => {
setHours(20)
cy.visit('/')
cy.get('h2').should('contain', 'Good Evening')
})
it('shows the right salutation in the night again', () => {
setHours(23)
cy.visit('/')
cy.get('h2').should('contain', 'Good Night')
})
})

View File

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

View File

@ -128,7 +128,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')
@ -168,7 +168,7 @@ describe('Task', () => {
.click()
cy.get('.task-view .details.content.description .editor .vue-easymde .EasyMDEContainer .CodeMirror-scroll')
.type('{selectall}New Description')
cy.get('.task-view .details.content.description .editor a')
cy.get('[data-cy="saveEditor"]')
.contains('Save')
.click()
@ -263,8 +263,7 @@ describe('Task', () => {
cy.visit(`/tasks/${tasks[0].id}`)
cy.get('.task-view .action-buttons .button')
.contains('Assign this task to a user')
cy.get('[data-cy="taskDetail.assign"]')
.click()
cy.get('.task-view .column.assignees .multiselect input')
.type(users[1].username)
@ -405,7 +404,7 @@ describe('Task', () => {
cy.get('.datepicker .datepicker-popup a')
.contains('Tomorrow')
.click()
cy.get('.datepicker .datepicker-popup a.button')
cy.get('[data-cy="closeDatepicker"]')
.contains('Confirm')
.click()

View File

@ -8,7 +8,7 @@ const testAndAssertFailed = fixture => {
cy.wait(5000) // It can take waaaayy too long to log the user in
cy.url().should('include', '/')
cy.get('div.notification.is-danger').contains('Wrong username or password.')
cy.get('div.message.danger').contains('Wrong username or password.')
}
context('Login', () => {

View File

@ -24,15 +24,15 @@ context('Registration', () => {
cy.visit('/register')
cy.get('#username').type(fixture.username)
cy.get('#email').type(fixture.email)
cy.get('#password1').type(fixture.password)
cy.get('#password2').type(fixture.password)
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
cy.get('h2').should('contain', `Hi ${fixture.username}!`)
})
it('Should fail', () => {
it.only('Should fail', () => {
const fixture = {
username: 'test',
password: '123456',
@ -42,9 +42,9 @@ context('Registration', () => {
cy.visit('/register')
cy.get('#username').type(fixture.username)
cy.get('#email').type(fixture.email)
cy.get('#password1').type(fixture.password)
cy.get('#password2').type(fixture.password)
cy.get('#password').type(fixture.password)
cy.get('#passwordValidation').type(fixture.password)
cy.get('#register-submit').click()
cy.get('div.notification.is-danger').contains('A user with this username already exists.')
cy.get('div.message.danger').contains('A user with this username already exists.')
})
})

View File

@ -8,17 +8,17 @@ describe('User Settings', () => {
})
it('Changes the user avatar', () => {
cy.visit('/user/settings')
cy.visit('/user/settings/avatar')
cy.get('input[name=avatarProvider][value=upload]')
.click()
cy.get('input[type=file]')
cy.get('input[type=file]', { timeout: 1000 })
.attachFile('image.jpg')
cy.get('.vue-handler-wrapper.vue-handler-wrapper--south .vue-simple-handler.vue-simple-handler--south')
.trigger('mousedown', {which: 1})
.trigger('mousemove', {clientY: 100})
.trigger('mouseup')
cy.get('a.button.is-primary')
cy.get('[data-cy="uploadAvatar"]')
.contains('Upload Avatar')
.click()
@ -28,11 +28,12 @@ describe('User Settings', () => {
})
it('Updates the name', () => {
cy.visit('/user/settings')
cy.visit('/user/settings/general')
cy.get('input#newName')
cy.get('.general-settings .control input.input')
.first()
.type('Lorem Ipsum')
cy.get('.card.general-settings .button.is-primary')
cy.get('[data-cy="saveGeneralSettings"]')
.contains('Save')
.click()

View File

@ -2,3 +2,10 @@
import './commands'
import 'cypress-file-upload'
import '@4tw/cypress-drag-drop'
// see https://github.com/cypress-io/cypress/issues/702#issuecomment-587127275
Cypress.on('window:before:load', (win) => {
// disable service workers
// @ts-ignore
delete win.navigator.__proto__.ServiceWorker
})

View File

@ -30,7 +30,10 @@
// It has to be the full url, including the last /api/v1 part and port.
// You can change this if your api is not reachable on the same port as the frontend.
window.API_URL = 'http://localhost:3456/api/v1'
//
// Enable error tracking with sentry. If this is set to true, will send anonymized data to
// our sentry instance to notify us of potential problems.
window.SENTRY_ENABLED = false
window.SENTRY_DSN = 'https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480'
</script>
</body>
</html>

15
netlify.toml Normal file
View File

@ -0,0 +1,15 @@
[build]
command = "yarn build"
publish = "dist"
[[redirects]]
from = "/*"
to = "/index.html"
status = 200
[[headers]]
for = "/*"
[headers.values]
X-Frame-Options = "DENY"
X-XSS-Protection = "1; mode=block"
X-Robots-Tag = "noindex"

View File

@ -60,19 +60,20 @@ http {
server {
listen 80;
listen 81 default_server http2 proxy_protocol; ## Needed when behind HAProxy with SSL termination + HTTP/2 support
listen 443 default_server ssl http2;
server_name _;
expires $expires;
ssl_certificate /etc/nginx/ssl/dummy.crt;
ssl_certificate_key /etc/nginx/ssl/dummy.key;
location ~* .(txt|webmanifest|css|js|mjs|map|svg|jpg|jpeg|png|ico|ttf|woff|woff2|wav)$ {
root /usr/share/nginx/html;
try_files $uri $uri/ =404;
}
location / {
root /usr/share/nginx/html;
try_files $uri $uri/ /;
index index.html index.htm;
try_files $uri $uri/ /index.html;
index index.html;
}
error_page 500 502 503 504 /50x.html;

View File

@ -49,7 +49,7 @@
inkscape:label="ink_ext_XXXXXX 1"
style="display:inline"
transform="translate(-92.67749,-674.48297)"><circle
style="fill:#5974d9;fill-opacity:1;stroke:none;stroke-width:2.88757133;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
style="fill:#196aff;fill-opacity:1;stroke:none;stroke-width:2.88757133;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;paint-order:normal"
id="path920"
cx="242.67749"
cy="828.77881"

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 47 KiB

View File

@ -7,84 +7,103 @@
"serve:dist-dev": "node scripts/serve-dist.js",
"serve:dist": "vite preview",
"build": "vite build && workbox copyLibraries dist/",
"build:modern-only": "BUILD_MODERN_ONLY=true vite build && workbox copyLibraries dist/",
"build:dev": "vite build -m development --outDir dist-dev/",
"typecheck": "vue-tsc --noEmit",
"lint": "eslint --ignore-pattern '*.test.*' ./src --ext .vue,.js,.ts",
"cypress:open": "cypress open",
"test:unit": "jest",
"test:frontend": "cypress run"
"test:unit": "vitest run",
"test:frontend": "cypress run",
"browserslist:update": "npx browserslist@latest --update-db"
},
"dependencies": {
"browserslist": "4.17.1",
"bulma": "0.9.3",
"@github/hotkey": "1.6.1",
"@kyvg/vue3-notification": "2.3.4",
"@sentry/tracing": "6.16.1",
"@sentry/vue": "6.16.1",
"@types/is-touch-device": "1.0.0",
"@vue/compat": "3.2.26",
"@vueuse/core": "7.5.2",
"@vueuse/router": "7.5.3",
"bulma-css-variables": "0.9.33",
"camel-case": "4.1.2",
"codemirror": "5.65.0",
"copy-to-clipboard": "3.3.1",
"date-fns": "2.24.0",
"dompurify": "2.3.3",
"highlight.js": "11.2.0",
"date-fns": "2.28.0",
"dompurify": "2.3.4",
"easymde": "2.15.0",
"flatpickr": "4.6.9",
"flexsearch": "0.7.21",
"highlight.js": "11.4.0",
"is-touch-device": "1.0.1",
"marked": "3.0.4",
"lodash.clonedeep": "4.5.0",
"lodash.debounce": "4.0.8",
"marked": "4.0.9",
"register-service-worker": "1.7.2",
"snake-case": "3.0.4",
"ufo": "0.7.9",
"verte": "0.0.12",
"vue": "2.6.14",
"vue-advanced-cropper": "1.8.2",
"vue-drag-resize": "1.5.4",
"vue-easymde": "1.4.0",
"vue-i18n": "8.26.3",
"vue-shortkey": "3.1.7",
"vuedraggable": "2.24.3",
"vuex": "3.6.2",
"workbox-precaching": "6.3.0"
"v-tooltip": "4.0.0-beta.13",
"vue": "3.2.26",
"vue-advanced-cropper": "2.7.1",
"vue-drag-resize": "2.0.3",
"vue-flatpickr-component": "9.0.5",
"vue-i18n": "9.2.0-beta.26",
"vue-router": "4.0.12",
"vuedraggable": "4.1.0",
"vuex": "4.0.2",
"workbox-precaching": "6.4.2"
},
"devDependencies": {
"@4tw/cypress-drag-drop": "2.0.0",
"@4tw/cypress-drag-drop": "2.1.0",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-regular-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/vue-fontawesome": "2.0.2",
"@types/jest": "27.0.2",
"@typescript-eslint/eslint-plugin": "4.32.0",
"@typescript-eslint/parser": "4.32.0",
"@vue/babel-preset-app": "4.5.13",
"@vue/eslint-config-typescript": "7.0.0",
"@vue/runtime-dom": "latest",
"autoprefixer": "10.3.6",
"axios": "0.21.4",
"babel-eslint": "10.1.0",
"cypress": "8.5.0",
"@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",
"@vitejs/plugin-legacy": "1.6.4",
"@vitejs/plugin-vue": "2.0.1",
"@vue/eslint-config-typescript": "10.0.0",
"autoprefixer": "10.4.2",
"axios": "0.24.0",
"browserslist": "4.19.1",
"caniuse-lite": "1.0.30001298",
"cypress": "9.2.0",
"cypress-file-upload": "5.0.8",
"esbuild": "0.13.3",
"eslint": "7.32.0",
"eslint-plugin-vue": "7.18.0",
"express": "4.17.1",
"esbuild": "0.14.10",
"eslint": "8.6.0",
"eslint-plugin-vue": "8.2.0",
"express": "4.17.2",
"faker": "5.5.3",
"jest": "27.2.4",
"rollup-plugin-terser": "7.0.2",
"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.42.1",
"ts-jest": "27.0.5",
"typescript": "4.4.3",
"vite": "2.6.1",
"vite-plugin-pwa": "0.11.2",
"vite-plugin-vue2": "1.8.2",
"vue-flatpickr-component": "8.1.7",
"vue-notification": "1.3.20",
"vue-router": "3.5.2",
"vue-template-compiler": "2.6.14",
"sass": "1.47.0",
"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",
"wait-on": "6.0.0",
"workbox-cli": "6.3.0"
"workbox-cli": "6.4.2"
},
"eslintConfig": {
"root": true,
"env": {
"browser": true,
"es2021": true,
"node": true
"node": true,
"vue/setup-compiler-macros": true
},
"extends": [
"eslint:recommended",
"plugin:vue/essential",
"plugin:vue/vue3-essential",
"@vue/typescript"
],
"rules": {
@ -103,7 +122,9 @@
"semi": [
"error",
"never"
]
],
"vue/script-setup-uses-vars": "error",
"vue/multi-word-component-names": 0
},
"parser": "vue-eslint-parser",
"parserOptions": {
@ -113,37 +134,16 @@
"ignorePatterns": [
"*.test.*",
"cypress/*"
]
],
"globals": {
"defineProps": "readonly"
}
},
"postcss": {
"plugins": {
"autoprefixer": {}
}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie > 0",
"not dead",
"Firefox ESR"
],
"jest": {
"testPathIgnorePatterns": [
"cypress"
],
"testEnvironment": "jsdom",
"preset": "ts-jest",
"roots": [
"<rootDir>/src"
],
"transform": {
"^.+\\.(js|tsx?)$": "ts-jest"
},
"moduleFileExtensions": [
"ts",
"js",
"json"
]
},
"license": "AGPL-3.0-or-later"
"license": "AGPL-3.0-or-later",
"packageManager": "yarn@1.22.17"
}

View File

@ -1,8 +0,0 @@
#!/bin/sh
set -e
# Shell script because yaml doesn't understand the header is a string literal and not a yaml symbol
curl -d operation=pull -H "Authorization: Token $WEBLATE_TOKEN" https://hosted.weblate.org/api/projects/vikunja/repository/
curl -d operation=push -H "Authorization: Token $WEBLATE_TOKEN" https://hosted.weblate.org/api/projects/vikunja/repository/

5
run.sh
View File

@ -3,7 +3,8 @@
# This shell script sets the api url based on an environment variable and starts nginx in foreground.
VIKUNJA_API_URL="${VIKUNJA_API_URL:-"/api/v1"}"
VIKUNJA_SENTRY_ENABLED="${VIKUNJA_SENTRY_ENABLED:-"false"}"
VIKUNJA_SENTRY_DSN="${VIKUNJA_SENTRY_DSN:-"https://85694a2d757547cbbc90cd4b55c5a18d@o1047380.ingest.sentry.io/6024480"}"
VIKUNJA_HTTP_PORT="${VIKUNJA_HTTP_PORT:-80}"
VIKUNJA_HTTPS_PORT="${VIKUNJA_HTTPS_PORT:-443}"
@ -14,6 +15,8 @@ VIKUNJA_API_URL=$(echo $VIKUNJA_API_URL |sed 's/\//\\\//g')
sed -i "s/http\:\/\/localhost\:3456//g" /usr/share/nginx/html/index.html # replacing in two steps to make sure api urls from releases are properly replaced as well
sed -i "s/'\/api\/v1/'$VIKUNJA_API_URL/g" /usr/share/nginx/html/index.html
sed -i "s/\.SENTRY_ENABLED = false/\.SENTRY_ENABLED = $VIKUNJA_SENTRY_ENABLED/g" /usr/share/nginx/html/index.html
sed -i "s/\.SENTRY_DSN = '.*'/\.SENTRY_DSN = '$VIKUNJA_SENTRY_DSN'/g" /usr/share/nginx/html/index.html
sed -i "s/listen 80/listen $VIKUNJA_HTTP_PORT/g" /etc/nginx/nginx.conf
sed -i "s/listen 443/listen $VIKUNJA_HTTPS_PORT/g" /etc/nginx/nginx.conf

View File

@ -0,0 +1,66 @@
const slugify = require('slugify')
const {exec} = require('child_process')
const axios = require('axios')
const BOT_USER_ID = 513
const giteaToken = process.env.GITEA_TOKEN
const siteId = process.env.NETLIFY_SITE_ID
const branchSlug = slugify(process.env.DRONE_SOURCE_BRANCH)
const prNumber = process.env.DRONE_PULL_REQUEST
const prIssueCommentsUrl = `https://kolaente.dev/api/v1/repos/vikunja/frontend/issues/${prNumber}/comments`
const alias = `${prNumber}-${branchSlug}`
const fullPreviewUrl = `https://${alias}--vikunja-frontend-preview.netlify.app`
const promiseExec = cmd => {
return new Promise((resolve, reject) => {
exec(cmd, (error, stdout, stderr) => {
if (error) {
reject(error)
return
}
resolve(stdout)
})
})
}
(async function () {
let stdout = await promiseExec(`./node_modules/.bin/netlify link --id ${siteId}`)
console.log(stdout)
stdout = await promiseExec(`./node_modules/.bin/netlify deploy --alias ${alias}`)
console.log(stdout)
const {data} = await axios.get(prIssueCommentsUrl)
const hasComment = data.some(c => c.user.id === BOT_USER_ID)
if (hasComment) {
console.log(`PR #${prNumber} already has a comment with a link, not sending another comment.`)
return
}
await axios.post(prIssueCommentsUrl, {
body: `
Hi ${process.env.DRONE_COMMIT_AUTHOR}!
Thank you for creating a PR!
I've deployed the changes of this PR on a preview environment under this URL: ${fullPreviewUrl}
You can use this url to view the changes live and test them out.
You will need to manually connect this to an api running somehwere. The easiest to use is https://try.vikunja.io/.
Have a nice day!
> Beep boop, I'm a bot.
`,
}, {
headers: {
'Content-Type': 'application/json',
'accept': 'application/json',
'Authorization': `token ${giteaToken}`,
},
})
console.log(`Preview comment sent successfully to PR #${prNumber}!`)
})()

View File

@ -0,0 +1 @@
55ce0faaa2c1919341617ccfaeccbb6029ac12107964ff488985cff13dd952f1a991df3ab0d4b0705deb761e508e6434 ./scripts/deploy-preview-netlify.js

View File

@ -1,116 +1,94 @@
<template>
<div :class="{'is-touch': isTouch}">
<div :class="{'is-hidden': !online}">
<!-- This is a workaround to get the sw to "see" the to-be-cached version of the offline background image -->
<div class="offline" style="height: 0;width: 0;"></div>
<top-navigation v-if="authUser"/>
<content-auth v-if="authUser"/>
<content-link-share v-else-if="authLinkShare"/>
<content-no-auth v-else/>
<notification/>
</div>
<div class="app offline" v-if="!online">
<div class="offline-message">
<h1>You are offline.</h1>
<p>Please check your network connection and try again.</p>
</div>
</div>
<ready>
<template v-if="authUser">
<top-navigation/>
<content-auth/>
</template>
<content-link-share v-else-if="authLinkShare"/>
<no-auth-wrapper v-else>
<router-view/>
</no-auth-wrapper>
<Notification/>
<transition name="fade">
<keyboard-shortcuts v-if="keyboardShortcutsActive"/>
</transition>
</div>
</ready>
</template>
<script>
import {mapState, mapGetters} from 'vuex'
<script lang="ts" setup>
import {computed, watch, Ref} from 'vue'
import {useRouter} from 'vue-router'
import {useRouteQuery} from '@vueuse/router'
import {useStore} from 'vuex'
import {useI18n} from 'vue-i18n'
import isTouchDevice from 'is-touch-device'
import {success} from '@/message'
import Notification from './components/misc/notification'
import {KEYBOARD_SHORTCUTS_ACTIVE, ONLINE} from './store/mutation-types'
import KeyboardShortcuts from './components/misc/keyboard-shortcuts'
import TopNavigation from './components/home/topNavigation'
import ContentAuth from './components/home/contentAuth'
import ContentLinkShare from './components/home/contentLinkShare'
import ContentNoAuth from './components/home/contentNoAuth'
import {setLanguage} from './i18n/setup'
import Notification from '@/components/misc/notification.vue'
import KeyboardShortcuts from './components/misc/keyboard-shortcuts/index.vue'
import TopNavigation from './components/home/topNavigation.vue'
import ContentAuth from './components/home/contentAuth.vue'
import ContentLinkShare from './components/home/contentLinkShare.vue'
import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
import Ready from '@/components/misc/ready.vue'
import {setLanguage} from './i18n'
import AccountDeleteService from '@/services/accountDelete'
export default {
name: 'app',
components: {
ContentNoAuth,
ContentLinkShare,
ContentAuth,
TopNavigation,
KeyboardShortcuts,
Notification,
},
beforeMount() {
this.setupOnlineStatus()
this.setupPasswortResetRedirect()
this.setupEmailVerificationRedirect()
this.setupAccountDeletionVerification()
},
beforeCreate() {
this.$store.dispatch('config/update')
.then(() => {
this.$store.dispatch('auth/checkAuth')
})
this.$store.dispatch('auth/checkAuth')
import {useColorScheme} from '@/composables/useColorScheme'
import {useBodyClass} from '@/composables/useBodyClass'
setLanguage()
},
created() {
// Make sure to always load the home route when running with electron
if (this.$route.fullPath.endsWith('frontend/index.html')) {
this.$router.push({name: 'home'})
}
},
computed: {
isTouch() {
return isTouchDevice()
},
...mapState({
online: ONLINE,
keyboardShortcutsActive: KEYBOARD_SHORTCUTS_ACTIVE,
}),
...mapGetters('auth', [
'authUser',
'authLinkShare',
]),
},
methods: {
setupOnlineStatus() {
this.$store.commit(ONLINE, navigator.onLine)
window.addEventListener('online', () => this.$store.commit(ONLINE, navigator.onLine))
window.addEventListener('offline', () => this.$store.commit(ONLINE, navigator.onLine))
},
setupPasswortResetRedirect() {
if (typeof this.$route.query.userPasswordReset !== 'undefined') {
localStorage.removeItem('passwordResetToken') // Delete an eventually preexisting old token
localStorage.setItem('passwordResetToken', this.$route.query.userPasswordReset)
this.$router.push({name: 'user.password-reset.reset'})
}
},
setupEmailVerificationRedirect() {
if (typeof this.$route.query.userEmailConfirm !== 'undefined') {
localStorage.removeItem('emailConfirmToken') // Delete an eventually preexisting old token
localStorage.setItem('emailConfirmToken', this.$route.query.userEmailConfirm)
this.$router.push({name: 'user.login'})
}
},
setupAccountDeletionVerification() {
if (typeof this.$route.query.accountDeletionConfirm !== 'undefined') {
const accountDeletionService = new AccountDeleteService()
accountDeletionService.confirm(this.$route.query.accountDeletionConfirm)
.then(() => {
this.$message.success({message: this.$t('user.deletion.confirmSuccess')})
this.$store.dispatch('auth/refreshUserInfo')
})
.catch(e => this.$message.error(e))
}
},
},
}
const store = useStore()
const router = useRouter()
useBodyClass('is-touch', isTouchDevice)
const keyboardShortcutsActive = computed(() => store.state.keyboardShortcutsActive)
const authUser = computed(() => store.getters['auth/authUser'])
const authLinkShare = computed(() => store.getters['auth/authLinkShare'])
const {t} = useI18n()
// setup account deletion verification
const accountDeletionConfirm = useRouteQuery('accountDeletionConfirm') as Ref<null | string>
watch(accountDeletionConfirm, async (accountDeletionConfirm) => {
if (accountDeletionConfirm === null) {
return
}
const accountDeletionService = new AccountDeleteService()
await accountDeletionService.confirm(accountDeletionConfirm)
success({message: t('user.deletion.confirmSuccess')})
store.dispatch('auth/refreshUserInfo')
}, { immediate: true })
// setup passwort reset redirect
const userPasswordReset = useRouteQuery('userPasswordReset') as Ref<null | string>
watch(userPasswordReset, (userPasswordReset) => {
if (userPasswordReset === null) {
return
}
localStorage.setItem('passwordResetToken', userPasswordReset)
router.push({name: 'user.password-reset.reset'})
}, { immediate: true })
// setup email verification redirect
const userEmailConfirm = useRouteQuery('userEmailConfirm') as Ref<null | string>
watch(userEmailConfirm, (userEmailConfirm) => {
if (userEmailConfirm === null) {
return
}
localStorage.setItem('emailConfirmToken', userEmailConfirm)
router.push({name: 'user.login'})
}, { immediate: true })
setLanguage()
useColorScheme()
</script>
<style lang="scss">
@import '@/styles/global.scss';
</style>

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.3 KiB

After

Width:  |  Height:  |  Size: 8.3 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.5 KiB

After

Width:  |  Height:  |  Size: 6.6 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,4 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" width="256" height="256">
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" viewBox="0 0 256 256" width="256" height="256">
<path d="M2268.2 2512.3a953.7 953.7 0 0 1-50 57c-180.5 189.5-426.2 294-691.6 294A953.7 953.7 0 0 1 847.8 2582a952.7 952.7 0 0 1-281.2-678.8 953.8 953.8 0 0 1 281.2-678.9 953.7 953.7 0 0 1 678.8-281.1 953.7 953.7 0 0 1 678.8 281.1 953.7 953.7 0 0 1 281.2 678.9c0 219.2-78.9 437.2-218.4 609" style="fill:#196aff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
<path d="M1823.7 1650.9c35.7 104.2 94.7 136.1 102 297 2.6 56.5-14.7 236-14.7 236s28 72-25.8 152.3c-83.5 124.3-255.4 132.8-345.7 132.8-90.3 0-260.2-8.5-343.7-132.8C1142 2256 1170 2184 1170 2184s-9.5-92.4-16.7-173.8c-1.7-19.1.1-94.7 2.4-113a453 453 0 0 1 25.8-96.2c14.4-39.6 36.8-79.9 54-120.5 51.8-122.8 8.4-274.9 11.1-407.3 2.2-94-20-189.3-28.7-281.2a960.4 960.4 0 0 1 308.7-50.6 958.6 958.6 0 0 1 344.9 63.6c-20.4 115-44.1 224.2-47.8 265.9-10.6 125.9-41.3 259.4 0 380" style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36655635" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
<path d="M1162.9 2383.9c1.1-18.8 3-38 8.3-56.2 1.6-5.7 4-19.7 11.4-21.8 9-2.6 25.9 8.3 32.3 13 12.3 9 23.9 18.5 36.2 27.6 8 6 16.5 10.5 24.3 16.5 8.4 6.6 14.7 14.5 21.7 22.2 8.4 9.4 14.8 19 21.3 29.5 5.1 8.2 37.1 13.5 42.2 21 5.6 8.3 1 18.6 1 28.7 0 74.2 4.4 147.6 6.1 220.3 1.8 50 21.4 109.2-53.4 85.8-160.3-50-158.5-271.3-151.4-386.6M1869.1 2279.7c-1.6 1.8-4.2 3.2-6.3 4.8a208 208 0 0 0-25.1 21.5c-9.4 9.6-19.2 19-28.2 28.9-7.9 8.7-17.3 16.6-25 25.6-5.1 6-10 12.3-14.6 18.5-2.3 3.2-3.5 7-5.3 10.4-2.7 5-40 10.1-36.2 15 6.3 8.3 20.3 15.4 23.7 25 17.2 48.6 24.8 244.5 26.8 294.5 5.4 127.8 117.6-6.3 137.2-57.7 57-149.7 23.2-258.8-46.3-386.6" style="fill:#fff;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>

Before

Width:  |  Height:  |  Size: 5.4 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 519 KiB

View File

@ -0,0 +1,118 @@
<template>
<component
:is="componentNodeName"
class="base-button"
:class="{ 'base-button--type-button': isButton }"
v-bind="elementBindings"
:disabled="disabled || undefined"
>
<slot />
</component>
</template>
<script lang="ts">
// see https://v3.vuejs.org/api/sfc-script-setup.html#usage-alongside-normal-script
export default {
inheritAttrs: false,
}
</script>
<script lang="ts" setup>
// this component removes styling differences between links / vue-router links and button elements
// by doing so we make it easy abstract the functionality from style and enable easier and semantic
// correct button and link usage. Also see: https://css-tricks.com/a-complete-guide-to-links-and-buttons/#accessibility-considerations
// the component tries to heuristically determine what it should be checking the props (see the
// componentNodeName and elementBindings ref for this).
// NOTE: Do NOT use buttons with @click to push routes. => Use router-links instead!
import { ref, watchEffect, computed, useAttrs, PropType } from 'vue'
const BASE_BUTTON_TYPES_MAP = Object.freeze({
button: 'button',
submit: 'submit',
})
type BaseButtonTypes = keyof typeof BASE_BUTTON_TYPES_MAP
const props = defineProps({
type: {
type: String as PropType<BaseButtonTypes>,
default: 'button',
},
disabled: {
type: Boolean,
default: false,
},
})
const componentNodeName = ref<Node['nodeName']>('button')
interface ElementBindings {
type?: string;
rel?: string,
}
const elementBindings = ref({})
const attrs = useAttrs()
watchEffect(() => {
// by default this component is a button element with the attribute of the type "button" (default prop value)
let nodeName = 'button'
let bindings: ElementBindings = {type: props.type}
// if we find a "to" prop we set it as router-link
if ('to' in attrs) {
nodeName = 'router-link'
bindings = {}
}
// if there is a href we assume the user wants an external link via a link element
// we also set the attribute rel to "noopener" but make it possible to overwrite this by the user.
if ('href' in attrs) {
nodeName = 'a'
bindings = {rel: 'noopener'}
}
componentNodeName.value = nodeName
elementBindings.value = {
...bindings,
...attrs,
}
})
const isButton = computed(() => componentNodeName.value === 'button')
</script>
<style lang="scss">
// NOTE: we do not use scoped styles to reduce specifity and make it easy to overwrite
// We reset the default styles of a button element to enable easier styling
:where(.base-button--type-button) {
border: 0;
margin: 0;
padding: 0;
text-decoration: none;
background-color: transparent;
text-align: center;
appearance: none;
}
:where(.base-button) {
cursor: pointer;
display: block;
color: inherit;
font: inherit;
user-select: none;
pointer-events: auto; // disable possible resets
&:focus {
outline: transparent;
}
&[disabled] {
cursor: default;
}
}
</style>

View File

@ -0,0 +1,17 @@
<script setup lang="ts">
import { computed } from 'vue'
import LogoFull from '@/assets/logo-full.svg?component'
import LogoFullPride from '@/assets/logo-full-pride.svg?component'
const Logo = computed(() => new Date().getMonth() === 5 ? LogoFullPride : LogoFull)
</script>
<template>
<Logo alt="Vikunja" class="logo" />
</template>
<style lang="scss" scoped>
.logo {
color: var(--logo-text-color);
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<button
type="button"
@click="$store.commit('toggleMenu')"
class="menu-show-button"
@shortkey="() => $store.commit('toggleMenu')"
v-shortcut="'Control+e'"
:title="$t('keyboardShortcuts.toggleMenu')"
:aria-label="menuActive ? $t('misc.hideMenu') : $t('misc.showMenu')"
/>
</template>
<script setup>
import {computed} from 'vue'
import {store} from '@/store'
const menuActive = computed(() => store.menuActive)
</script>
<style lang="scss" scoped>
$lineWidth: 2rem;
$size: $lineWidth + 1rem;
.menu-show-button {
// FIXME: create general button component
appearance: none;
background-color: transparent;
border: 0;
min-height: $size;
width: $size;
position: relative;
$transformX: translateX(-50%);
&::before,
&::after {
content: '';
display: block;
position: absolute;
height: 3px;
width: $lineWidth;
left: 50%;
transform: $transformX;
background-color: var(--grey-400);
border-radius: 2px;
transition: all $transition;
}
&::before {
top: 50%;
transform: $transformX translateY(-0.4rem)
}
&::after {
bottom: 50%;
transform: $transformX translateY(0.4rem)
}
&:hover,
&:focus {
&::before,
&::after {
background-color: var(--grey-600);
}
&::before {
transform: $transformX translateY(-0.5rem);
}
&::after {
transform: $transformX translateY(0.5rem)
}
}
}
</style>

View File

@ -0,0 +1,20 @@
<template>
<a class="menu-bottom-link" :href="poweredByUrl" target="_blank" rel="noreferrer noopener nofollow">
{{ $t('misc.poweredBy') }}
</a>
</template>
<script setup lang="ts">
import {POWERED_BY as poweredByUrl} from '@/urls'
</script>
<style lang="scss">
.menu-bottom-link {
color: var(--grey-300);
text-align: center;
display: block;
padding-top: 1rem;
padding-bottom: 1rem;
font-size: .8rem;
}
</style>

View File

@ -1,7 +1,7 @@
<template>
<div>
<a @click="$store.commit('menuActive', false)" class="menu-hide-button" v-if="menuActive">
<icon icon="times"></icon>
<icon icon="times" />
</a>
<div
:class="{'has-background': background}"
@ -22,15 +22,16 @@
<router-view/>
<transition name="modal">
<router-view name="popup"/>
</transition>
<router-view name="popup" v-slot="{ Component }">
<transition name="modal">
<component :is="Component" />
</transition>
</router-view>
<a
class="keyboard-shortcuts-button"
@click="showKeyboardShortcuts()"
@shortkey="showKeyboardShortcuts()"
v-shortkey="['?']"
v-shortcut="'?'"
>
<icon icon="keyboard"/>
</a>
@ -39,97 +40,187 @@
</div>
</template>
<script>
import {mapState} from 'vuex'
<script lang="ts" setup>
import {watch, computed} from 'vue'
import {useStore} from 'vuex'
import {useRoute, useRouter} from 'vue-router'
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'
export default {
name: 'contentAuth',
components: {QuickActions, Navigation},
watch: {
'$route': {
handler: 'doStuffAfterRoute',
deep: true,
},
},
created() {
this.renewTokenOnFocus()
this.loadLabels()
},
computed: mapState({
background: 'background',
menuActive: MENU_ACTIVE,
userInfo: state => state.auth.info,
authenticated: state => state.auth.authenticated,
}),
methods: {
doStuffAfterRoute() {
// this.setTitle('') // Reset the title if the page component does not set one itself
this.hideMenuOnMobile()
this.resetCurrentList()
},
resetCurrentList() {
// Reset the current list highlight in menu if the current list is not list related.
if (
this.$route.name === 'home' ||
this.$route.name === 'namespace.edit' ||
this.$route.name === 'teams.index' ||
this.$route.name === 'teams.edit' ||
this.$route.name === 'tasks.range' ||
this.$route.name === 'labels.index' ||
this.$route.name === 'migrate.start' ||
this.$route.name === 'migrate.wunderlist' ||
this.$route.name === 'user.settings' ||
this.$route.name === 'namespaces.index'
) {
this.$store.commit(CURRENT_LIST, null)
}
},
renewTokenOnFocus() {
// Try renewing the token every time vikunja is loaded initially
// (When opening the browser the focus event is not fired)
this.$store.dispatch('auth/renewToken')
const store = useStore()
// Check if the token is still valid if the window gets focus again to maybe renew it
window.addEventListener('focus', () => {
const background = computed(() => store.state.background)
const menuActive = computed(() => store.state.menuActive)
if (!this.authenticated) {
return
}
const expiresIn = (this.userInfo !== null ? this.userInfo.exp : 0) - +new Date() / 1000
// If the token expiry is negative, it is already expired and we have no choice but to redirect
// the user to the login page
if (expiresIn < 0) {
this.$store.dispatch('auth/checkAuth')
this.$router.push({name: 'user.login'})
return
}
// Check if the token is valid for less than 60 hours and renew if thats the case
if (expiresIn < 60 * 3600) {
this.$store.dispatch('auth/renewToken')
console.debug('renewed token')
}
})
},
hideMenuOnMobile() {
if (window.innerWidth < 769) {
this.$store.commit(MENU_ACTIVE, false)
}
},
showKeyboardShortcuts() {
this.$store.commit(KEYBOARD_SHORTCUTS_ACTIVE, true)
},
loadLabels() {
this.$store.dispatch('labels/loadAllLabels')
.catch(e => {
this.$message.error(e)
})
},
},
function showKeyboardShortcuts() {
store.commit(KEYBOARD_SHORTCUTS_ACTIVE, true)
}
const route = useRoute()
// hide menu on mobile
watch(() => route.fullPath, () => window.innerWidth < 769 && store.commit(MENU_ACTIVE, false))
// FIXME: this is really error prone
// Reset the current list highlight in menu if the current route is not list related.
watch(() => route.name as string, (routeName) => {
if (
routeName &&
(
[
'home',
'namespace.edit',
'teams.index',
'teams.edit',
'tasks.range',
'labels.index',
'migrate.start',
'migrate.wunderlist',
'namespaces.index',
].includes(routeName) ||
routeName.startsWith('user.settings')
)
) {
store.dispatch(CURRENT_LIST, null)
}
})
// TODO: Reset the title if the page component does not set one itself
function useRenewTokenOnFocus() {
const router = useRouter()
const userInfo = computed(() => store.state.auth.info)
const authenticated = computed(() => store.state.auth.authenticated)
// Try renewing the token every time vikunja is loaded initially
// (When opening the browser the focus event is not fired)
store.dispatch('auth/renewToken')
// Check if the token is still valid if the window gets focus again to maybe renew it
useEventListener('focus', () => {
if (!authenticated.value) {
return
}
const expiresIn = (userInfo.value !== null ? userInfo.value.exp : 0) - +new Date() / 1000
// If the token expiry is negative, it is already expired and we have no choice but to redirect
// the user to the login page
if (expiresIn < 0) {
store.dispatch('auth/checkAuth')
router.push({name: 'user.login'})
return
}
// Check if the token is valid for less than 60 hours and renew if thats the case
if (expiresIn < 60 * 3600) {
store.dispatch('auth/renewToken')
console.debug('renewed token')
}
})
}
useRenewTokenOnFocus()
store.dispatch('labels/loadAllLabels')
</script>
<style lang="scss" scoped>
.menu-hide-button {
position: fixed;
top: 0.5rem;
right: 0.5rem;
z-index: 31;
width: 3rem;
height: 3rem;
display: flex;
justify-content: center;
align-items: center;
font-size: 2rem;
color: var(--grey-400);
line-height: 1;
transition: all $transition;
@media screen and (min-width: $tablet) {
display: none;
}
&:hover,
&:focus {
height: 1rem;
color: var(--grey-600);
}
}
.app-container {
min-height: calc(100vh - 65px);
@media screen and (max-width: $tablet) {
padding-top: $navbar-height;
}
.app-content {
padding: $navbar-height + 1.5rem 1.5rem 1rem 1.5rem;
z-index: 2;
@media screen and (max-width: $tablet) {
margin-left: 0;
padding-top: 1.5rem;
min-height: calc(100vh - 4rem);
}
&.is-menu-enabled {
margin-left: $navbar-width;
@media screen and (max-width: $tablet) {
min-width: 100%;
margin-left: 0;
}
}
&.task\.detail {
padding-left: 0;
padding-right: 0;
}
.card {
background: var(--white);
}
}
}
.mobile-overlay {
display: none;
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
background: rgba(250, 250, 250, 0.8);
z-index: 5;
opacity: 0;
transition: all $transition;
@media screen and (max-width: $tablet) {
display: block;
opacity: 1;
}
}
.keyboard-shortcuts-button {
position: fixed;
bottom: calc(1rem - 4px);
right: 1rem;
z-index: 4500; // The modal has a z-index of 4000
color: var(--grey-500);
transition: color $transition;
@media screen and (max-width: $tablet) {
display: none;
}
}
</style>

View File

@ -1,12 +1,12 @@
<template>
<div
:class="[background ? 'has-background' : '', $route.name+'-view']"
:class="[background ? 'has-background' : '', $route.name as string +'-view']"
:style="{'background-image': `url(${background})`}"
class="link-share-container"
>
<div class="container has-text-centered link-share-view">
<div class="column is-10 is-offset-1">
<img alt="Vikunja" class="logo" :src="logoUrl" />
<Logo class="logo" />
<h1
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
class="title">
@ -14,37 +14,47 @@
</h1>
<div class="box has-text-left view">
<router-view/>
<a class="menu-bottom-link" href="https://vikunja.io" target="_blank" rel="noreferrer noopener nofollow">
{{ $t('misc.poweredBy') }}
</a>
<PoweredByLink />
</div>
</div>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex'
import {CURRENT_LIST} from '@/store/mutation-types'
<script lang="ts" setup>
import {computed} from 'vue'
import {useStore} from 'vuex'
import logoUrl from '@/assets/logo-full.svg'
import Logo from '@/components/home/Logo.vue'
import PoweredByLink from './PoweredByLink.vue'
export default {
name: 'contentLinkShare',
data() {
return {
logoUrl,
}
},
computed: mapState({
currentList: CURRENT_LIST,
background: 'background',
}),
methods: {
logout() {
this.$store.dispatch('auth/logout')
this.$router.push({name: 'user.login'})
},
},
}
const store = useStore()
const currentList = computed(() => store.state.currentList)
const background = computed(() => store.state.background)
</script>
<style lang="scss" scoped>
.link-share-container.has-background .view {
background-color: transparent;
border: none;
}
.logo {
max-width: 300px;
width: 90%;
margin: 2rem 0 1.5rem;
}
.column {
max-width: 100%;
}
.title {
text-shadow: 0 0 1rem var(--white);
}
// FIXME: this should be defined somewhere deep
.link-share-view .card {
background-color: var(--white);
}
</style>

View File

@ -1,54 +0,0 @@
<template>
<div class="no-auth-wrapper">
<div class="noauth-container">
<img alt="Vikunja" :src="logoUrl" width="400" height="117" />
<div class="message is-info" v-if="motd !== ''">
<div class="message-header">
<p>{{ $t('misc.info') }}</p>
</div>
<div class="message-body">
{{ motd }}
</div>
</div>
<router-view/>
</div>
</div>
</template>
<script>
import {mapState} from 'vuex'
import logoUrl from '@/assets/logo-full.svg'
export default {
name: 'contentNoAuth',
data() {
return {
logoUrl,
}
},
created() {
this.redirectToHome()
},
computed: mapState({
motd: state => state.config.motd,
}),
methods: {
redirectToHome() {
// Check if the user is already logged in and redirect them to the home page if not
if (
this.$route.name !== 'user.login' &&
this.$route.name !== 'user.password-reset.request' &&
this.$route.name !== 'user.password-reset.reset' &&
this.$route.name !== 'user.register' &&
this.$route.name !== 'link-share.auth' &&
this.$route.name !== 'openid.auth' &&
localStorage.getItem('passwordResetToken') === null &&
localStorage.getItem('emailConfirmToken') === null
) {
this.$router.push({name: 'user.login'})
}
},
},
}
</script>

View File

@ -2,7 +2,7 @@
<div :class="{'is-active': menuActive}" class="namespace-container">
<div class="menu top-menu">
<router-link :to="{name: 'home'}" class="logo">
<img alt="Vikunja" :src="logoUrl" width="164" height="48"/>
<Logo width="164" height="48" />
</router-link>
<ul class="menu-list">
<li>
@ -48,20 +48,20 @@
</ul>
</div>
<aside class="menu namespaces-lists loader-container" :class="{'is-loading': loading}">
<template v-for="(n, nk) in namespaces">
<div :key="n.id" class="namespace-title" :class="{'has-menu': n.id > 0}">
<aside 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="getNamespaceTitle(n) + ' (' + n.lists.filter(l => !l.isArchived).length + ')'">
v-tooltip="namespaceTitles[nk]">
<span class="name">
<span
:style="{ backgroundColor: n.hexColor }"
class="color-bubble"
v-if="n.hexColor !== ''">
</span>
{{ getNamespaceTitle(n) }} ({{ n.lists.filter(l => !l.isArchived).length }})
{{ namespaceTitles[nk] }}
</span>
</span>
<a
@ -74,9 +74,9 @@
<namespace-settings-dropdown :namespace="n" v-if="n.id > 0"/>
</div>
<div
v-if="listsVisible[n.id] ?? true"
:key="n.id + 'child'"
class="more-container"
v-if="typeof listsVisible[n.id] !== 'undefined' ? listsVisible[n.id] : true"
>
<!--
NOTE: a v-model / computed setter is not possible, since the updateActiveLists function
@ -84,78 +84,96 @@
-->
<draggable
v-bind="dragOptions"
:value="activeLists[nk]"
@input="(lists) => updateActiveLists(n, lists)"
:modelValue="activeLists[nk]"
@update:modelValue="(lists) => updateActiveLists(n, lists)"
:group="`namespace-${n.id}-lists`"
@start="() => drag = true"
@end="e => saveListPosition(e, nk)"
handle=".handle"
:disabled="n.id < 0"
:class="{'dragging-disabled': n.id < 0}"
:disabled="n.id < 0 || null"
tag="transition-group"
item-key="id"
:component-data="{
type: 'transition',
tag: 'ul',
name: !drag ? 'flip-list' : null,
class: [
'menu-list can-be-hidden',
{ 'dragging-disabled': n.id < 0 }
]
}"
>
<transition-group
type="transition"
:name="!drag ? 'flip-list' : null"
tag="ul"
class="menu-list can-be-hidden"
>
<template #item="{element: l}">
<li
v-for="l in activeLists[nk]"
:key="l.id"
class="loader-container"
class="loader-container is-loading-small"
:class="{'is-loading': listUpdating[l.id]}"
>
<router-link
class="list-menu-link"
:class="{'router-link-exact-active': currentList.id === l.id}"
:to="{ name: 'list.index', params: { listId: l.id} }"
tag="span"
v-slot="{ href, navigate, isActive }"
custom
>
<span class="icon handle">
<icon icon="grip-lines"/>
</span>
<span
:style="{ backgroundColor: l.hexColor }"
class="color-bubble"
v-if="l.hexColor !== ''">
</span>
<span class="list-menu-title">
{{ getListTitle(l) }}
</span>
<span
:class="{'is-favorite': l.isFavorite}"
@click.stop="toggleFavoriteList(l)"
class="favorite">
<icon icon="star" v-if="l.isFavorite"/>
<icon :icon="['far', 'star']" v-else/>
</span>
<a
@click="navigate"
:href="href"
class="list-menu-link"
:class="{'router-link-exact-active': isActive || currentList?.id === l.id}"
>
<span class="icon handle">
<icon icon="grip-lines"/>
</span>
<span
:style="{ backgroundColor: l.hexColor }"
class="color-bubble"
v-if="l.hexColor !== ''">
</span>
<span class="list-menu-title">
{{ getListTitle(l) }}
</span>
<span
:class="{'is-favorite': l.isFavorite}"
@click.prevent.stop="toggleFavoriteList(l)"
class="favorite">
<icon :icon="l.isFavorite ? 'star' : ['far', 'star']" />
</span>
</a>
</router-link>
<list-settings-dropdown :list="l" v-if="l.id > 0"/>
<span class="list-setting-spacer" v-else></span>
</li>
</transition-group>
</template>
</draggable>
</div>
</template>
</aside>
<a class="menu-bottom-link" href="https://vikunja.io" target="_blank" rel="noreferrer noopener nofollow">
{{ $t('misc.poweredBy') }}
</a>
<PoweredByLink />
</div>
</template>
<script>
import {mapState} from 'vuex'
import {CURRENT_LIST, MENU_ACTIVE, LOADING, LOADING_MODULE} from '@/store/mutation-types'
import draggable from 'vuedraggable'
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
import NamespaceSettingsDropdown from '@/components/namespace/namespace-settings-dropdown.vue'
import draggable from 'vuedraggable'
import PoweredByLink from '@/components/home/PoweredByLink.vue'
import Logo from '@/components/home/Logo.vue'
import {CURRENT_LIST, MENU_ACTIVE, LOADING, LOADING_MODULE} from '@/store/mutation-types'
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
import logoUrl from '@/assets/logo-full.svg'
export default {
name: 'navigation',
components: {
ListSettingsDropdown,
NamespaceSettingsDropdown,
draggable,
Logo,
PoweredByLink,
},
data() {
return {
listsVisible: {},
@ -165,14 +183,8 @@ export default {
ghostClass: 'ghost',
},
listUpdating: {},
logoUrl,
}
},
components: {
ListSettingsDropdown,
NamespaceSettingsDropdown,
draggable,
},
computed: {
...mapState({
namespaces: state => state.namespaces.namespaces.filter(n => !n.isArchived),
@ -182,15 +194,22 @@ 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 => !item.isArchived))
},
namespaceTitles() {
return this.namespaces.map((namespace, index) => {
const title = this.getNamespaceTitle(namespace)
return `${title} (${this.activeLists[index]?.length ?? 0})`
})
},
},
beforeCreate() {
// FIXME: async action in beforeCreate, might be unfinished when component mounts
this.$store.dispatch('namespaces/loadNamespaces')
.then(namespaces => {
namespaces.forEach(n => {
if (typeof this.listsVisible[n.id] === 'undefined') {
this.$set(this.listsVisible, n.id, true)
this.listsVisible[n.id] = true
}
})
})
@ -209,18 +228,13 @@ export default {
return
}
this.$store.dispatch('lists/toggleListFavorite', list)
.catch(e => this.$message.error(e))
},
resize() {
// Hide the menu by default on mobile
if (window.innerWidth < 770) {
this.$store.commit(MENU_ACTIVE, false)
} else {
this.$store.commit(MENU_ACTIVE, true)
}
this.$store.commit(MENU_ACTIVE, window.innerWidth >= 770)
},
toggleLists(namespaceId) {
this.$set(this.listsVisible, namespaceId, !this.listsVisible[namespaceId] ?? false)
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
@ -240,34 +254,309 @@ export default {
this.$store.commit('namespaces/setNamespaceById', newNamespace)
},
saveListPosition(e, namespaceIndex) {
async saveListPosition(e, namespaceIndex) {
const listsActive = this.activeLists[namespaceIndex]
const list = listsActive[e.newIndex]
const listBefore = listsActive[e.newIndex - 1] ?? null
const listAfter = listsActive[e.newIndex + 1] ?? null
this.$set(this.listUpdating, list.id, true)
this.listUpdating[list.id] = true
const position = calculateItemPosition(listBefore !== null ? listBefore.position : null, listAfter !== null ? listAfter.position : null)
// create a copy of the list in order to not violate vuex mutations
this.$store.dispatch('lists/updateList', {
...list,
position,
})
.catch(e => {
this.$message.error(e)
})
.finally(() => {
this.$set(this.listUpdating, list.id, false)
try {
// create a copy of the list in order to not violate vuex mutations
await this.$store.dispatch('lists/updateList', {
...list,
position,
})
} finally {
this.listUpdating[list.id] = false
}
},
},
}
</script>
<style scoped>
<style lang="scss" scoped>
$navbar-padding: 2rem;
$vikunja-nav-background: var(--site-background);
$vikunja-nav-color: var(--grey-700);
$vikunja-nav-selected-width: 0.4rem;
.namespace-container {
z-index: 6;
background: $vikunja-nav-background;
color: $vikunja-nav-color;
padding: 0 0 1rem;
transition: transform $transition-duration ease-in;
position: fixed;
top: $navbar-height;
bottom: 0;
left: 0;
transform: translateX(-100%);
overflow-x: auto;
width: $navbar-width;
@media screen and (max-width: $tablet) {
top: 0;
width: 70vw;
}
&.is-active {
transform: translateX(0);
transition: transform $transition-duration ease-out;
}
.menu {
.menu-label {
font-size: 1rem;
font-weight: 700;
font-weight: bold;
font-family: $vikunja-font;
color: $vikunja-nav-color;
font-weight: 500;
min-height: 2.5rem;
padding-top: 0;
padding-left: $navbar-padding;
overflow: hidden;
}
.menu-label,
.menu-list span.list-menu-link,
.menu-list a {
display: flex;
align-items: center;
justify-content: space-between;
cursor: pointer;
.list-menu-title {
overflow: hidden;
text-overflow: ellipsis;
width: 100%;
}
.color-bubble {
height: 12px;
flex: 0 0 12px;
}
.favorite {
margin-left: .25rem;
transition: opacity $transition, color $transition;
opacity: 0;
&:hover {
color: var(--warning);
}
&.is-favorite {
opacity: 1;
color: var(--warning);
}
}
&:hover .favorite {
opacity: 1;
}
}
.menu-label {
.color-bubble {
width: 14px !important;
height: 14px !important;
}
.is-archived {
min-width: 85px;
}
}
.namespace-title {
display: flex;
align-items: center;
justify-content: space-between;
.menu-label {
margin-bottom: 0;
flex: 1 1 auto;
.name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
a:not(.dropdown-item) {
color: $vikunja-nav-color;
padding: 0 .25rem;
}
:deep(.dropdown-trigger) {
padding: .5rem;
cursor: pointer;
}
.toggle-lists-icon {
svg {
transition: all $transition;
transform: rotate(90deg);
opacity: 1;
}
&.active svg {
transform: rotate(0deg);
opacity: 0;
}
}
&:hover .toggle-lists-icon svg {
opacity: 1;
}
&:not(.has-menu) .toggle-lists-icon {
padding-right: 1rem;
}
}
.menu-label,
.nsettings,
.menu-list span.list-menu-link,
.menu-list a {
color: $vikunja-nav-color;
}
.menu-list {
li {
height: 44px;
display: flex;
align-items: center;
&:hover {
background: var(--white);
}
:deep(.dropdown-trigger) {
opacity: 0;
padding: .5rem;
cursor: pointer;
transition: $transition;
}
&:hover :deep(.dropdown-trigger) {
opacity: 1;
}
}
.flip-list-move {
transition: transform $transition-duration;
}
.ghost {
background: var(--grey-200);
* {
opacity: 0;
}
}
a:hover {
background: transparent;
}
span.list-menu-link, li > a {
padding: 0.75rem .5rem 0.75rem ($navbar-padding * 1.5 - 1.75rem);
transition: all 0.2s ease;
border-radius: 0;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
width: 100%;
border-left: $vikunja-nav-selected-width solid transparent;
.icon {
height: 1rem;
vertical-align: middle;
padding-right: 0.5rem;
&.handle {
opacity: 0;
transition: opacity $transition;
margin-right: .25rem;
cursor: grab;
}
}
&:hover .icon.handle {
opacity: 1;
}
&.router-link-exact-active {
color: var(--primary);
border-left: $vikunja-nav-selected-width solid var(--primary);
.icon {
color: var(--primary);
}
}
&:hover {
border-left: $vikunja-nav-selected-width solid var(--primary);
}
}
}
.logo {
display: block;
padding-left: 2rem;
margin-right: 1rem;
@media screen and (min-width: $tablet) {
display: none;
}
}
&.namespaces-lists {
padding-top: math.div($navbar-padding, 2);
}
.icon {
color: var(--grey-400) !important;
}
}
.top-menu {
margin-top: math.div($navbar-padding, 2);
.menu-list {
li {
font-weight: 500;
font-family: $vikunja-font;
}
span.list-menu-link, li > a {
padding-left: 2rem;
display: inline-block;
.icon {
padding-bottom: .25rem;
}
}
}
}
}
.list-setting-spacer {
width: 32px;
flex-shrink: 0;
}
.namespaces-list.loader-container.is-loading {
min-height: calc(100vh - #{$navbar-height + 1.5rem + 1rem + 1.5rem});
}
</style>

View File

@ -5,24 +5,11 @@
class="navbar main-theme is-fixed-top"
role="navigation"
>
<div class="navbar-brand">
<router-link :to="{name: 'home'}" class="navbar-item logo">
<img width="164" height="48" alt="Vikunja" :src="logoUrl" />
</router-link>
<a
@click="$store.commit('toggleMenu')"
class="menu-show-button"
@shortkey="() => $store.commit('toggleMenu')"
v-shortkey="['ctrl', 'e']"
>
</a>
</div>
<a
@click="$store.commit('toggleMenu')"
class="menu-show-button"
>
</a>
<div class="list-title" ref="listTitle" :style="{'display': currentList.id ? '': 'none'}">
<router-link :to="{name: 'home'}" class="logo-link">
<Logo width="164" height="48"/>
</router-link>
<MenuButton class="menu-button"/>
<div class="list-title" ref="listTitle" v-show="currentList.id">
<template v-if="currentList.id">
<h1
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
@ -39,8 +26,8 @@
<a
@click="openQuickActions"
class="trigger-button pr-0"
@shortkey="openQuickActions"
v-shortkey="['ctrl', 'k']"
v-shortcut="'Control+k'"
:title="$t('keyboardShortcuts.quickSearch')"
>
<icon icon="search"/>
</a>
@ -50,7 +37,7 @@
<dropdown class="is-right" ref="usernameDropdown">
<template #trigger>
<x-button
type="secondary"
variant="secondary"
:shadow="false">
<span class="username">{{ userInfo.name !== '' ? userInfo.name : userInfo.username }}</span>
<span class="icon is-small">
@ -101,9 +88,8 @@ import Update from '@/components/home/update.vue'
import ListSettingsDropdown from '@/components/list/list-settings-dropdown.vue'
import Dropdown from '@/components/misc/dropdown.vue'
import Notifications from '@/components/notifications/notifications.vue'
import logoUrl from '@/assets/logo-full.svg'
import logoFullPrideUrl from '@/assets/logo-full-pride.svg'
import Logo from '@/components/home/Logo.vue'
import MenuButton from '@/components/home/MenuButton.vue'
export default {
name: 'topNavigation',
@ -112,11 +98,10 @@ export default {
Dropdown,
ListSettingsDropdown,
Update,
Logo,
MenuButton,
},
computed: {
logoUrl() {
return (new Date()).getMonth() === 5 ? logoFullPrideUrl : logoUrl
},
...mapState({
userInfo: state => state.auth.info,
userAvatar: state => state.auth.avatarUrl,
@ -149,3 +134,160 @@ export default {
},
}
</script>
<style lang="scss" scoped>
$vikunja-nav-logo-full-width: 164px;
$user-dropdown-width-mobile: 5rem;
$hamburger-menu-icon-spacing: 1rem;
$hamburger-menu-icon-width: 28px;
.navbar {
z-index: 4 !important;
}
.logo-link {
display: none;
padding: 0.5rem 0.75rem;
@media screen and (min-width: $tablet) {
align-self: stretch;
display: flex;
align-items: center;
padding-left: 2rem;
margin-right: 1.5rem;
}
}
.menu-button {
align-self: stretch;
margin-right: auto;
@media screen and (max-width: $tablet) {
margin-left: $hamburger-menu-icon-spacing;
}
}
.navbar.main-theme {
background: var(--site-background);
z-index: 5 !important;
justify-content: space-between;
align-items: center;
@media screen and (max-width: $desktop) {
display: flex;
justify-content: space-between;
}
.title {
margin: 0;
font-size: 1.75rem;
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}
.navbar-end {
margin-left: 0;
align-items: center;
display: flex;
}
@media screen and (max-width: $tablet) {
.user {
width: $user-dropdown-width-mobile;
display: flex;
align-items: center;
:deep(.dropdown-trigger) {
line-height: 1;
.button {
padding: 0 0.25rem;
height: 1rem;
.icon {
width: .5rem;
}
}
}
.username {
display: none;
}
}
}
}
.navbar {
// FIXME: notifications should provide a slot for the icon instead, so that we can style it as we want
:deep() {
.trigger-button {
cursor: pointer;
color: var(--grey-400);
padding: .5rem;
font-size: 1.25rem;
position: relative;
}
> * > .trigger-button {
width: $navbar-icon-width;
}
}
.user {
display: flex;
align-items: center;
span {
font-family: $vikunja-font;
}
.avatar {
border-radius: 100%;
vertical-align: middle;
height: 40px;
}
:deep(.dropdown-trigger .button) {
background: none;
&:focus:not(:active), &:active {
outline: none !important;
box-shadow: none !important;
}
}
}
}
.list-title {
display: flex;
align-items: center;
justify-content: center;
$edit-icon-width: 1rem;
@media screen and (min-width: $tablet) {
// We need a fixed width for overflowing ellipsis to work
--nav-username-width: 0;
width: calc(100vw - #{$user-dropdown-width-mobile} - #{2 * $hamburger-menu-icon-spacing} - #{$hamburger-menu-icon-width} - #{$edit-icon-width} - #{2 * $navbar-icon-width} - #{$vikunja-nav-logo-full-width} - var(--nav-username-width));
}
@media screen and (max-width: $tablet) {
// We need a fixed width for overflowing ellipsis to work
width: calc(100vw - #{$user-dropdown-width-mobile} - #{2 * $hamburger-menu-icon-spacing} - #{$hamburger-menu-icon-width} - #{$edit-icon-width} - #{2 * $navbar-icon-width});
}
h1 {
margin: 0;
}
:deep(.dropdown-trigger) {
color: var(--grey-400);
margin-left: 1rem;
height: 1rem;
width: 1rem;
cursor: pointer;
}
}
</style>

View File

@ -47,3 +47,42 @@ export default {
},
}
</script>
<style lang="scss" scoped>
.update-notification {
margin: 1rem;
display: flex;
align-items: center;
background: $warning;
padding: 0 0 0 .5rem;
border-radius: $radius;
font-size: .9rem;
color: var(--grey-900);
justify-content: space-between;
@media screen and (max-width: $desktop) {
position: fixed;
bottom: 1rem;
margin: 0;
width: 450px;
left: calc(50vw - 225px);
}
@media screen and (max-width: $tablet) {
position: fixed;
left: 1rem;
right: 1rem;
bottom: 1rem;
width: auto;
}
p {
text-align: center;
width: 100%;
}
> * + * {
margin-left: .5rem;
}
}
</style>

View File

@ -0,0 +1,12 @@
import { defineAsyncComponent } from 'vue'
import ErrorComponent from '@/components/misc/error.vue'
import LoadingComponent from '@/components/misc/loading.vue'
const Editor = () => import('@/components/input/editor.vue')
export default defineAsyncComponent({
loader: Editor,
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
timeout: 60000,
})

View File

@ -1,74 +1,103 @@
<template>
<a
<BaseButton
class="button"
:class="{
'is-loading': loading,
'has-no-shadow': !shadow,
'is-primary': type === 'primary',
'is-outlined': type === 'secondary',
'is-text is-inverted has-no-shadow underline-none':
type === 'tertary',
}"
:disabled="disabled || null"
@click="click"
:href="href !== '' ? href : null"
:class="[
variantClass,
{
'is-loading': loading,
'has-no-shadow': !shadow || variant === 'tertiary',
}
]"
>
<icon :icon="icon" v-if="showIconOnly"/>
<span class="icon is-small" v-else-if="icon !== ''">
<icon :icon="icon"/>
</span>
<slot></slot>
</a>
<slot />
</BaseButton>
</template>
<script>
<script lang="ts">
export default {
name: 'x-button',
props: {
type: {
type: String,
default: 'primary',
},
href: {
type: String,
default: '',
},
to: {
default: false,
},
icon: {
default: '',
},
loading: {
type: Boolean,
default: false,
},
shadow: {
type: Boolean,
default: true,
},
disabled: {
type: Boolean,
default: false,
},
},
computed: {
showIconOnly() {
return this.icon !== '' && typeof this.$slots.default === 'undefined'
},
},
methods: {
click(e) {
if (this.disabled) {
return
}
if (this.to !== false) {
this.$router.push(this.to)
}
this.$emit('click', e)
},
},
}
</script>
</script>
<script setup lang="ts">
import {computed, useSlots, PropType} from 'vue'
import BaseButton from '@/components/base/BaseButton.vue'
const BUTTON_TYPES_MAP = Object.freeze({
primary: 'is-primary',
secondary: 'is-outlined',
tertiary: 'is-text is-inverted underline-none',
})
type ButtonTypes = keyof typeof BUTTON_TYPES_MAP
const props = defineProps({
variant: {
type: String as PropType<ButtonTypes>,
default: 'primary',
},
icon: {
default: '',
},
loading: {
type: Boolean,
default: false,
},
shadow: {
type: Boolean,
default: true,
},
})
const variantClass = computed(() => BUTTON_TYPES_MAP[props.variant])
const slots = useSlots()
const showIconOnly = computed(() => props.icon !== '' && typeof slots.default === 'undefined')
</script>
<style lang="scss" scoped>
.button {
transition: all $transition;
border: 0;
text-transform: uppercase;
font-size: 0.85rem;
font-weight: bold;
height: $button-height;
box-shadow: var(--shadow-sm);
display: inline-flex;
&:hover {
box-shadow: var(--shadow-md);
}
&.fullheight {
padding-right: 7px;
height: 100%;
}
&.is-active,
&.is-focused,
&:active,
&:focus,
&:focus:not(:active) {
box-shadow: var(--shadow-xs) !important;
}
&.is-primary.is-outlined:hover {
color: var(--white);
}
}
.is-small {
border-radius: $radius;
}
.underline-none {
text-decoration: none !important;
}
</style>

View File

@ -1,31 +1,50 @@
<template>
<div class="color-picker-container">
<verte
:showHistory="true"
:colorHistory="[
'#1973ff',
'#7F23FF',
'#ff4136',
'#ff851b',
'#ffeb10',
'#00db60',
]"
:enableAlpha="false"
:menuPosition="menuPosition"
:rgbSliders="true"
model="hex"
picker="square"
v-model="color"
:class="{'is-empty': empty}"
/>
<x-button @click="reset" class="is-small ml-2" :shadow="false" type="secondary">
<datalist :id="colorListID">
<option v-for="color in defaultColors" :key="color" :value="color" />
</datalist>
<div class="picker">
<input
class="picker__input"
type="color"
v-model="color"
:list="colorListID"
:class="{'is-empty': isEmpty}"
/>
<svg class="picker__pattern" v-show="isEmpty" viewBox="0 0 22 22" fill="fff">
<pattern id="checker" width="11" height="11" patternUnits="userSpaceOnUse" fill="FFF">
<rect fill="#cccccc" x="0" width="5.5" height="5.5" y="0"></rect>
<rect fill="#cccccc" x="5.5" width="5.5" height="5.5" y="5.5"></rect>
</pattern>
<rect width="22" height="22" fill="url(#checker)"></rect>
</svg>
</div>
<x-button
v-if="!isEmpty"
:disabled="isEmpty"
@click="reset"
class="is-small ml-2"
:shadow="false"
variant="secondary"
>
{{ $t('input.resetColor') }}
</x-button>
</div>
</template>
<script>
import verte from 'verte'
import {createRandomID} from '@/helpers/randomId'
const DEFAULT_COLORS = [
'#1973ff',
'#7F23FF',
'#ff4136',
'#ff851b',
'#ffeb10',
'#00db60',
]
export default {
name: 'colorPicker',
@ -33,13 +52,12 @@ export default {
return {
color: '',
lastChangeTimeout: null,
defaultColors: DEFAULT_COLORS,
colorListID: createRandomID(),
}
},
components: {
verte,
},
props: {
value: {
modelValue: {
required: true,
},
menuPosition: {
@ -47,10 +65,11 @@ export default {
default: 'top',
},
},
emits: ['update:modelValue', 'change'],
watch: {
value: {
handler(value) {
this.color = value
modelValue: {
handler(modelValue) {
this.color = modelValue
},
immediate: true,
},
@ -59,14 +78,14 @@ export default {
},
},
computed: {
empty() {
isEmpty() {
return this.color === '#000000' || this.color === ''
},
},
methods: {
update(force = false) {
if(this.empty && !force) {
if(this.isEmpty && !force) {
return
}
@ -75,7 +94,7 @@ export default {
}
this.lastChangeTimeout = setTimeout(() => {
this.$emit('input', this.color)
this.$emit('update:modelValue', this.color)
this.$emit('change')
}, 500)
},
@ -89,16 +108,53 @@ export default {
}
</script>
<style lang="scss">
@import 'verte/dist/verte.css';
<style lang="scss" scoped>
.color-picker-container {
display: flex;
justify-content: center;
align-items: center;
.verte.is-empty {
.verte__icon {
opacity: 0;
// reset / see https://stackoverflow.com/a/11471224/15522256
input[type="color"] {
-webkit-appearance: none;
border: none;
}
input[type="color"]::-webkit-color-swatch-wrapper {
padding: 0;
}
input[type="color"]::-webkit-color-swatch {
border: none;
}
.verte__guide {
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAYAAACNMs+9AAAAGklEQVQYlWM4c+bMf3TMgA0MBYWDzDkUKQQAlHCpV9ycHeMAAAAASUVORK5CYII=);
$PICKER_SIZE: 24px;
$BORDER_WIDTH: 1px;
.picker {
display: grid;
width: $PICKER_SIZE;
height: $PICKER_SIZE;
overflow: hidden;
border-radius: 100%;
border: $BORDER_WIDTH solid var(--grey-300);
box-shadow: $shadow;
& > * {
grid-row: 1;
grid-column: 1;
}
}
input.picker__input {
padding: 0;
width: $PICKER_SIZE - 2 * $BORDER_WIDTH;
height: $PICKER_SIZE - 2 * $BORDER_WIDTH;
}
.picker__input.is-empty {
opacity: 0;
}
.picker__pattern {
pointer-events: none;
}
}
</style>

View File

@ -101,6 +101,7 @@
class="is-fullwidth"
:shadow="false"
@click="close"
v-cy="'closeDatepicker'"
>
{{ $t('misc.confirm') }}
</x-button>
@ -112,6 +113,7 @@
<script>
import flatPickr from 'vue-flatpickr-component'
import 'flatpickr/dist/flatpickr.css'
import { i18n } from '@/i18n'
import {format} from 'date-fns'
import {calculateDayInterval} from '@/helpers/time/calculateDayInterval'
@ -136,13 +138,13 @@ export default {
flatPickr,
},
props: {
value: {
modelValue: {
validator: prop => prop instanceof Date || prop === null || typeof prop === 'string',
},
chooseDateLabel: {
type: String,
default() {
return this.$t('input.datepicker.chooseDate')
return i18n.global.t('input.datepicker.chooseDate')
},
},
disabled: {
@ -150,14 +152,15 @@ export default {
default: false,
},
},
emits: ['update:modelValue', 'change', 'close', 'close-on-change'],
mounted() {
document.addEventListener('click', this.hideDatePopup)
},
beforeDestroy() {
beforeUnmount() {
document.removeEventListener('click', this.hideDatePopup)
},
watch: {
value: {
modelValue: {
handler: 'setDateValue',
immediate: true,
},
@ -191,7 +194,7 @@ export default {
},
updateData() {
this.changed = true
this.$emit('input', this.date)
this.$emit('update:modelValue', this.date)
this.$emit('change', this.date)
},
toggleDatePopup() {
@ -241,3 +244,74 @@ export default {
},
}
</script>
<style lang="scss" scoped>
.datepicker {
input.input {
display: none;
}
&.disabled a {
cursor: default;
}
.datepicker-popup {
position: absolute;
z-index: 99;
width: 320px;
background: var(--white);
border-radius: $radius;
box-shadow: $shadow;
@media screen and (max-width: ($tablet)) {
width: calc(100vw - 5rem);
}
a:not(.button) {
display: flex;
align-items: center;
padding: 0 .5rem;
width: 100%;
height: 2.25rem;
color: var(--text);
transition: all $transition;
&:first-child {
border-radius: $radius $radius 0 0;
}
&:hover {
background: var(--light);
}
.text {
width: 100%;
font-size: .85rem;
display: flex;
justify-content: space-between;
padding-right: .25rem;
.weekday {
color: var(--text-light);
text-transform: capitalize;
}
}
.icon {
width: 2rem;
text-align: center;
}
}
a.button {
margin: 1rem;
width: calc(100% - 2rem);
}
:deep(.flatpickr-calendar) {
margin: 0 auto 8px;
box-shadow: none;
}
}
}
</style>

View File

@ -5,7 +5,7 @@
<vue-easymde
:configs="config"
@change="bubble"
@input="handleInput"
@update:modelValue="handleInput"
class="content"
v-if="isEditActive"
v-model="text"/>
@ -21,12 +21,10 @@
</p>
<ul class="actions" v-if="bottomActions.length > 0">
<template v-if="isEditEnabled && !showPreviewText && showSave">
<li>
<a v-if="showEditButton" @click="toggleEdit">{{ $t('input.editor.edit') }}</a>
<a v-else-if="isEditActive" @click="toggleEdit" class="done-edit">{{ $t('misc.save') }}</a>
</li>
</template>
<li v-if="isEditEnabled && !showPreviewText && showSave">
<a v-if="showEditButton" @click="toggleEdit">{{ $t('input.editor.edit') }}</a>
<a v-else-if="isEditActive" @click="toggleEdit" class="done-edit">{{ $t('misc.save') }}</a>
</li>
<li v-for="(action, k) in bottomActions" :key="k">
<a @click="action.action">{{ action.title }}</a>
</li>
@ -37,7 +35,7 @@
<a @click="toggleEdit">{{ $t('input.editor.edit') }}</a>
</li>
</ul>
<x-button v-else-if="isEditActive" @click="toggleEdit" type="secondary" :shadow="false">
<x-button v-else-if="isEditActive" @click="toggleEdit" variant="secondary" :shadow="false" v-cy="'saveEditor'">
{{ $t('misc.save') }}
</x-button>
</template>
@ -45,15 +43,17 @@
</template>
<script>
import VueEasymde from 'vue-easymde'
import EasyMDE from 'easymde'
import marked from 'marked'
import VueEasymde from './vue-easymde/vue-easymde.vue'
import {marked} from 'marked'
import DOMPurify from 'dompurify'
import hljs from 'highlight.js/lib/common'
import {createEasyMDEConfig} from './editorConfig'
import AttachmentModel from '../../models/attachment'
import AttachmentService from '../../services/attachment'
import {findCheckboxesInText} from '../../helpers/checklistFromText'
import {createRandomID} from '@/helpers/randomId'
export default {
name: 'editor',
@ -61,7 +61,7 @@ export default {
VueEasymde,
},
props: {
value: {
modelValue: {
type: String,
default: '',
},
@ -71,9 +71,7 @@ export default {
},
uploadEnabled: {
type: Boolean,
default() {
return false
},
default: false,
},
uploadCallback: {
type: Function,
@ -94,13 +92,14 @@ export default {
},
emptyText: {
type: String,
default: () => '',
default: '',
},
showSave: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue', 'change'],
computed: {
showPreviewText() {
return this.isPreviewActive && this.text === '' && this.emptyText !== ''
@ -119,154 +118,30 @@ export default {
preview: '',
attachmentService: null,
loadedAttachments: {},
config: {
autoDownloadFontAwesome: false,
spellChecker: false,
config: createEasyMDEConfig({
placeholder: this.placeholder,
uploadImage: this.uploadEnabled,
imageUploadFunction: this.uploadCallback,
minHeight: '150px',
toolbar: [
{
name: 'heading-1',
action: EasyMDE.toggleHeading1,
title: this.$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>',
},
{
name: 'heading-2',
action: EasyMDE.toggleHeading2,
title: this.$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>',
},
{
name: 'heading-3',
action: EasyMDE.toggleHeading3,
title: this.$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>',
},
{
name: 'heading-smaller',
action: EasyMDE.toggleHeadingSmaller,
title: this.$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>',
},
{
name: 'heading-bigger',
action: EasyMDE.toggleHeadingBigger,
title: this.$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>',
},
'|',
{
name: 'bold',
action: EasyMDE.toggleBold,
title: this.$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>',
},
{
name: 'italic',
action: EasyMDE.toggleItalic,
title: this.$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>',
},
{
name: 'strikethrough',
action: EasyMDE.toggleStrikethrough,
title: this.$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>',
},
{
name: 'code',
action: EasyMDE.toggleCodeBlock,
title: this.$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>',
},
{
name: 'quote',
action: EasyMDE.toggleBlockquote,
title: this.$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>',
},
{
name: 'unordered-list',
action: EasyMDE.toggleUnorderedList,
title: this.$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>',
},
{
name: 'ordered-list',
action: EasyMDE.toggleOrderedList,
title: this.$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>',
},
'|',
{
name: 'clean-block',
action: EasyMDE.cleanBlock,
title: this.$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>',
},
{
name: 'link',
action: EasyMDE.drawLink,
title: this.$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>',
},
{
name: 'image',
action: EasyMDE.drawImage,
title: this.$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>',
},
{
name: 'table',
action: EasyMDE.drawTable,
title: this.$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>',
},
{
name: 'horizontal-rule',
action: EasyMDE.drawHorizontalRule,
title: this.$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>',
},
'|',
{
name: 'side-by-side',
action: EasyMDE.toggleSideBySide,
title: this.$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>',
},
{
name: 'guide',
action: () => {
window.open('https://www.markdownguide.org/basic-syntax/', '_blank')
},
title: this.$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>',
},
],
},
}),
checkboxId: createRandomID(),
}
},
watch: {
value(newVal) {
this.text = newVal
modelValue(modelValue) {
this.text = modelValue
this.$nextTick(this.renderPreview)
},
text(newVal, oldVal) {
// Only bubble the new value if it actually changed, but not if the component just got mounted and the text changed from the outside.
if (oldVal === '' && this.text === this.value) {
if (oldVal === '' && this.text === this.modelValue) {
return
}
this.bubble()
},
},
mounted() {
if (this.value !== '') {
this.text = this.value
if (this.modelValue !== '') {
this.text = this.modelValue
}
if (this.previewIsDefault && this.hasPreview) {
@ -299,7 +174,7 @@ export default {
}
this.changeTimeout = setTimeout(() => {
this.$emit('input', this.text)
this.$emit('update:modelValue',this.text)
this.$emit('change', this.text)
}, timeout)
},
@ -335,7 +210,7 @@ export default {
}
checkboxNum++
return `<input type="checkbox" data-checkbox-num="${checkboxNum}" ${checked} class="text-checkbox-${this._uid}"/>`
return `<input type="checkbox" data-checkbox-num="${checkboxNum}" ${checked} class="text-checkbox-${this.checkboxId}"/>`
},
link: (href, title, text) => {
const isLocal = href.startsWith(`${location.protocol}//${location.hostname}`)
@ -358,7 +233,7 @@ export default {
// dom tree. If we're calling this right after setting this.preview it could be the images were
// not already made available.
// Some docs at https://stackoverflow.com/q/62865160/10924593
this.$nextTick(() => {
this.$nextTick(async () => {
const attachmentImage = document.getElementsByClassName('attachment-image')
if (attachmentImage) {
for (const img of attachmentImage) {
@ -379,15 +254,13 @@ export default {
this.attachmentService = new AttachmentService()
}
this.attachmentService.getBlobUrl(attachment)
.then(url => {
img.src = url
this.loadedAttachments[cacheKey] = url
})
const url = await this.attachmentService.getBlobUrl(attachment)
img.src = url
this.loadedAttachments[cacheKey] = url
}
}
const textCheckbox = document.getElementsByClassName(`text-checkbox-${this._uid}`)
const textCheckbox = document.getElementsByClassName(`text-checkbox-${this.checkboxId}`)
if (textCheckbox) {
for (const check of textCheckbox) {
check.removeEventListener('change', this.handleCheckboxClick)
@ -435,8 +308,11 @@ export default {
</script>
<style lang="scss">
@import '../../../node_modules/highlight.js/scss/base16/equilibrium-gray-light';
@import '../../../node_modules/easymde/dist/easymde.min.css';
@import 'codemirror/lib/codemirror.css';
@import './vue-easymde/vue-easymde.css';
@import 'highlight.js/scss/base16/equilibrium-gray-light';
$editor-border-color: #ddd;
.editor {
.clear {
@ -462,15 +338,20 @@ export default {
.CodeMirror {
padding: .5rem;
border: 1px solid $editor-border-color;
background: var(--white);
&-lines pre {
margin: 0 !important;
}
&-placeholder {
color: $grey-400 !important;
color: var(--grey-400) !important;
font-style: italic;
}
&-cursor {
border-color: var(--grey-700);
}
}
.editor-preview {
@ -497,7 +378,7 @@ export default {
}
}
&:after {
&::after {
position: absolute;
top: 24px;
margin-left: -3px;
@ -507,7 +388,7 @@ export default {
pre.CodeMirror-line {
margin-bottom: 0 !important;
color: $grey-700 !important;
color: var(--grey-700) !important;
}
.cm-header {
@ -522,7 +403,7 @@ ul.actions {
li {
display: inline-block;
&:after {
&::after {
content: '·';
padding: 0 .25rem;
}
@ -533,10 +414,10 @@ ul.actions {
}
&, a {
color: $grey-500;
color: var(--grey-500);
&.done-edit {
color: $primary;
color: var(--primary);
}
}

View File

@ -0,0 +1,134 @@
import EasyMDE from 'easymde'
import {i18n} from '@/i18n'
export function createEasyMDEConfig({ placeholder, uploadImage, imageUploadFunction }) {
return {
autoDownloadFontAwesome: false,
spellChecker: false,
placeholder,
uploadImage,
imageUploadFunction,
minHeight: '150px',
toolbar: [
{
name: 'heading-1',
action: EasyMDE.toggleHeading1,
title: i18n.global.t('input.editor.heading1'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-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>',
},
{
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>',
},
{
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>',
},
{
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>',
},
'|',
{
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>',
},
{
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>',
},
{
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>',
},
{
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>',
},
{
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>',
},
{
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>',
},
{
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>',
},
'|',
{
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>',
},
{
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>',
},
{
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>',
},
{
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>',
},
{
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>',
},
'|',
{
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>',
},
{
name: 'guide',
action() {
window.open('https://www.markdownguide.org/basic-syntax/', '_blank')
},
title: i18n.global.t('input.editor.guide'),
icon: '<svg viewBox="0 0 24 24"><rect fill="none" rx="0" ry="0"/><path fill-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

@ -2,10 +2,9 @@
<div :class="{'is-disabled': disabled}" class="fancycheckbox">
<input
:checked="checked"
:disabled="disabled"
:disabled="disabled || null"
:id="checkBoxId"
@change="(event) => updateData(event.target.checked)"
style="display: none;"
type="checkbox"/>
<label :for="checkBoxId" class="check">
<svg height="18px" viewBox="0 0 18 18" width="18px">
@ -21,16 +20,18 @@
</template>
<script>
import {createRandomID} from '@/helpers/randomId'
export default {
name: 'fancycheckbox',
data() {
return {
checked: false,
checkBoxId: 'fancycheckbox' + Math.random(),
checkBoxId: `fancycheckbox_${createRandomID()}`,
}
},
props: {
value: {
modelValue: {
required: false,
},
disabled: {
@ -39,10 +40,11 @@ export default {
default: false,
},
},
emits: ['update:modelValue', 'change'],
watch: {
value: {
handler(value) {
this.checked = value
modelValue: {
handler(modelValue) {
this.checked = modelValue
},
immediate: true,
@ -51,9 +53,90 @@ export default {
methods: {
updateData(checked) {
this.checked = checked
this.$emit('input', checked)
this.$emit('update:modelValue', checked)
this.$emit('change', checked)
},
},
}
</script>
<style lang="scss" scoped>
.fancycheckbox {
display: inline-block;
padding-right: 5px;
padding-top: 3px;
// FIXME: should be a prop
&.is-block {
margin: .5rem .2rem;
}
}
input[type=checkbox] {
display: none;
}
.check {
cursor: pointer;
position: relative;
margin: auto;
width: 18px;
height: 18px;
-webkit-tap-highlight-color: transparent;
transform: translate3d(0, 0, 0);
}
span {
font-size: 0.8rem;
vertical-align: top;
padding-left: .5rem;
}
svg {
position: relative;
z-index: 1;
fill: none;
stroke-linecap: round;
stroke-linejoin: round;
stroke: #c8ccd4;
stroke-width: 1.5;
transform: translate3d(0, 0, 0);
transition: all 0.2s ease;
}
.check:hover svg {
stroke: var(--primary);
}
.is-disabled .check:hover svg {
stroke: #c8ccd4;
}
path {
stroke-dasharray: 60;
stroke-dashoffset: 0;
}
polyline {
stroke-dasharray: 22;
stroke-dashoffset: 66;
}
input[type=checkbox]:checked + .check {
svg {
stroke: var(--primary);
}
path {
stroke-dashoffset: 60;
transition: all 0.3s linear;
}
polyline {
stroke-dashoffset: 42;
transition: all 0.2s linear;
transition-delay: 0.15s;
}
}
</style>

View File

@ -7,7 +7,9 @@
@focus="focus"
>
<div class="control" :class="{'is-loading': loading || localLoading}">
<div class="input-wrapper input" :class="{'has-multiple': multiple && Array.isArray(internalValue) && internalValue.length > 0}">
<div
class="input-wrapper input"
:class="{'has-multiple': hasMultiple}">
<template v-if="Array.isArray(internalValue)">
<template v-for="(item, key) in internalValue">
<slot name="tag" :item="item">
@ -81,16 +83,9 @@
</template>
<script>
import {i18n} from '@/i18n'
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
/**
* Available events:
* @search: Triggered every time the search query input changes
* @select: Triggered every time an option from the search results is selected. Also triggers a change in v-model.
* @create: If nothing or no exact match was found and `creatable` is true, this event is triggered with the current value of the search query.
* @remove: If `multiple` is enabled, this will be fired every time an item is removed from the array of selected items.
*/
export default {
name: 'multiselect',
data() {
@ -133,7 +128,7 @@ export default {
},
},
// The object with the value, updated every time an entry is selected.
value: {
modelValue: {
default() {
return null
},
@ -141,44 +136,36 @@ export default {
// If true, will provide an "add this as a new value" entry which fires an @create event when clicking on it.
creatable: {
type: Boolean,
default() {
return false
},
default: false,
},
// The text shown next to the new value option.
createPlaceholder: {
type: String,
default() {
return this.$t('input.multiselect.createPlaceholder')
return i18n.global.t('input.multiselect.createPlaceholder')
},
},
// The text shown next to an option.
selectPlaceholder: {
type: String,
default() {
return this.$t('input.multiselect.selectPlaceholder')
return i18n.global.t('input.multiselect.selectPlaceholder')
},
},
// If true, allows for selecting multiple items. v-model will be an array with all selected values in that case.
multiple: {
type: Boolean,
default() {
return false
},
default: false,
},
// If true, displays the search results inline instead of using a dropdown.
inline: {
type: Boolean,
default() {
return false
},
default: false,
},
// If true, shows search results when no query is specified.
showEmpty: {
type: Boolean,
default() {
return true
},
default: true,
},
// The delay in ms after which the search event will be fired. Used to avoid hitting the network on every keystroke.
searchDelay: {
@ -187,19 +174,34 @@ export default {
return 200
},
},
closeAfterSelect: {
type: Boolean,
default: true,
},
},
/**
* Available events:
* @search: Triggered every time the search query input changes
* @select: Triggered every time an option from the search results is selected. Also triggers a change in v-model.
* @create: If nothing or no exact match was found and `creatable` is true, this event is triggered with the current value of the search query.
* @remove: If `multiple` is enabled, this will be fired every time an item is removed from the array of selected items.
*/
emits: ['update:modelValue', 'search', 'select', 'create', 'remove'],
mounted() {
document.addEventListener('click', this.hideSearchResultsHandler)
},
beforeDestroy() {
beforeUnmount() {
document.removeEventListener('click', this.hideSearchResultsHandler)
},
watch: {
value: {
modelValue: {
handler(value) {
this.setSelectedObject(value)
},
immediate: true,
deep: true,
},
},
computed: {
@ -230,6 +232,9 @@ export default {
return this.searchResults
},
hasMultiple() {
return this.multiple && Array.isArray(this.internalValue) && this.internalValue.length > 0
},
},
methods: {
// Searching will be triggered with a 200ms delay to avoid searching on every keyup event.
@ -278,13 +283,15 @@ export default {
this.internalValue = object
}
this.$emit('input', this.internalValue)
this.$emit('update:modelValue', this.internalValue)
this.$emit('select', object)
this.setSelectedObject(object)
this.closeSearchResults()
if (this.closeAfterSelect && this.filteredSearchResults.length > 0 && !this.creatableAvailable) {
this.closeSearchResults()
}
},
setSelectedObject(object, resetOnly = false) {
this.$set(this, 'internalValue', object)
this.internalValue = object
// We assume we're getting an array when multiple is enabled and can therefore leave the query
// value etc as it is
@ -352,7 +359,7 @@ export default {
}
}
this.$emit('input', this.internalValue)
this.$emit('update:modelValue', this.internalValue)
this.$emit('remove', item)
},
focus() {
@ -361,3 +368,131 @@ export default {
},
}
</script>
<style lang="scss" scoped>
.multiselect {
width: 100%;
position: relative;
.control.is-loading::after {
top: .75rem;
}
&.has-search-results .input-wrapper {
border-radius: $radius $radius 0 0;
border-color: var(--primary) !important;
background: var(--white) !important;
&, &:focus-within {
border-bottom-color: var(--grey-200) !important;
}
}
.input-wrapper {
padding: 0;
background: var(--white) !important;
border-color: var(--grey-200) !important;
flex-wrap: wrap;
height: auto;
&:hover {
border-color: var(--grey-300) !important;
}
.input {
display: flex;
max-width: 100%;
width: 100%;
align-items: center;
border: none !important;
background: transparent;
height: auto;
&::placeholder {
font-style: normal !important;
}
}
&.has-multiple .input {
max-width: 250px;
input {
padding-left: 0;
}
}
&:focus-within {
border-color: var(--primary) !important;
background: var(--white) !important;
}
.loader {
margin: 0 .5rem;
}
}
.search-results {
background: var(--white);
border-radius: 0 0 $radius $radius;
border: 1px solid var(--primary);
border-top: none;
max-height: 50vh;
overflow-x: auto;
position: absolute;
z-index: 100;
max-width: 100%;
min-width: 100%;
&-inline {
position: static;
}
button {
background: transparent;
display: block;
text-align: left;
box-shadow: none;
border-radius: 0;
text-transform: none;
font-family: $family-sans-serif;
font-weight: normal;
padding: .5rem;
border: none;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
overflow: hidden;
.search-result {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
padding: .5rem .75rem;
}
.hint-text {
font-size: .75rem;
color: transparent;
transition: color $transition;
padding-left: .5rem;
}
&:focus, &:hover {
background: var(--grey-100);
box-shadow: none !important;
.hint-text {
color: var(--text);
}
}
&:active {
background: var(--grey-200);
}
}
}
}
</style>

View File

@ -0,0 +1,374 @@
.EasyMDEContainer {
display: block;
}
.EasyMDEContainer.sided--no-fullscreen {
display: flex;
flex-direction: row;
flex-wrap: wrap;
}
.EasyMDEContainer .CodeMirror {
box-sizing: border-box;
height: auto;
border: 1px solid #ddd;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
padding: 10px;
font: inherit;
z-index: 0;
word-wrap: break-word;
}
.EasyMDEContainer .CodeMirror-scroll {
cursor: text;
}
.EasyMDEContainer .CodeMirror-fullscreen {
background: #fff;
position: fixed !important;
top: 50px;
left: 0;
right: 0;
bottom: 0;
height: auto;
z-index: 8;
border-right: none !important;
border-bottom-right-radius: 0 !important;
}
.EasyMDEContainer .CodeMirror-sided {
width: 50% !important;
}
.EasyMDEContainer.sided--no-fullscreen .CodeMirror-sided {
border-right: none!important;
border-bottom-right-radius: 0px;
position: relative;
flex: 1 1 auto;
}
.EasyMDEContainer .CodeMirror-placeholder {
opacity: .5;
}
.EasyMDEContainer .CodeMirror-focused .CodeMirror-selected {
background: #d9d9d9;
}
.editor-toolbar {
position: relative;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
padding: 9px 10px;
border-top: 1px solid #bbb;
border-left: 1px solid #bbb;
border-right: 1px solid #bbb;
border-top-left-radius: 4px;
border-top-right-radius: 4px;
}
.editor-toolbar.fullscreen {
width: 100%;
height: 50px;
padding-top: 10px;
padding-bottom: 10px;
box-sizing: border-box;
background: #fff;
border: 0;
position: fixed;
top: 0;
left: 0;
opacity: 1;
z-index: 9;
}
.editor-toolbar.fullscreen::before {
width: 20px;
height: 50px;
background: -moz-linear-gradient(left, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 1)), color-stop(100%, rgba(255, 255, 255, 0)));
background: -webkit-linear-gradient(left, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
background: -o-linear-gradient(left, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
background: -ms-linear-gradient(left, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
background: linear-gradient(to right, rgba(255, 255, 255, 1) 0%, rgba(255, 255, 255, 0) 100%);
position: fixed;
top: 0;
left: 0;
margin: 0;
padding: 0;
}
.editor-toolbar.fullscreen::after {
width: 20px;
height: 50px;
background: -moz-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
background: -webkit-gradient(linear, left top, right top, color-stop(0%, rgba(255, 255, 255, 0)), color-stop(100%, rgba(255, 255, 255, 1)));
background: -webkit-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
background: -o-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
background: -ms-linear-gradient(left, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
background: linear-gradient(to right, rgba(255, 255, 255, 0) 0%, rgba(255, 255, 255, 1) 100%);
position: fixed;
top: 0;
right: 0;
margin: 0;
padding: 0;
}
.EasyMDEContainer.sided--no-fullscreen .editor-toolbar {
width: 100%;
}
.editor-toolbar button, .editor-toolbar .easymde-dropdown {
background: transparent;
display: inline-block;
text-align: center;
text-decoration: none !important;
height: 30px;
margin: 0;
padding: 0;
border: 1px solid transparent;
border-radius: 3px;
cursor: pointer;
}
.editor-toolbar button {
width: 30px;
}
.editor-toolbar button.active,
.editor-toolbar button:hover {
background: #fcfcfc;
border-color: #95a5a6;
}
.editor-toolbar i.separator {
display: inline-block;
width: 0;
border-left: 1px solid #d9d9d9;
border-right: 1px solid #fff;
color: transparent;
text-indent: -10px;
margin: 0 6px;
}
.editor-toolbar button:after {
font-family: Arial, "Helvetica Neue", Helvetica, sans-serif;
font-size: 65%;
vertical-align: text-bottom;
position: relative;
top: 2px;
}
.editor-toolbar button.heading-1:after {
content: "1";
}
.editor-toolbar button.heading-2:after {
content: "2";
}
.editor-toolbar button.heading-3:after {
content: "3";
}
.editor-toolbar button.heading-bigger:after {
content: "▲";
}
.editor-toolbar button.heading-smaller:after {
content: "▼";
}
.editor-toolbar.disabled-for-preview button:not(.no-disable) {
opacity: .6;
pointer-events: none;
}
@media only screen and (max-width: 700px) {
.editor-toolbar i.no-mobile {
display: none;
}
}
.editor-statusbar {
padding: 8px 10px;
font-size: 12px;
color: #959694;
text-align: right;
}
.EasyMDEContainer.sided--no-fullscreen .editor-statusbar {
width: 100%;
}
.editor-statusbar span {
display: inline-block;
min-width: 4em;
margin-left: 1em;
}
.editor-statusbar .lines:before {
content: 'lines: '
}
.editor-statusbar .words:before {
content: 'words: '
}
.editor-statusbar .characters:before {
content: 'characters: '
}
.editor-preview-full {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 7;
overflow: auto;
display: none;
box-sizing: border-box;
}
.editor-preview-side {
position: fixed;
bottom: 0;
width: 50%;
top: 50px;
right: 0;
z-index: 9;
overflow: auto;
display: none;
box-sizing: border-box;
border: 1px solid #ddd;
word-wrap: break-word;
}
.editor-preview-active-side {
display: block
}
.EasyMDEContainer.sided--no-fullscreen .editor-preview-active-side {
flex: 1 1 auto;
height: auto;
position: static;
}
.editor-preview-active {
display: block
}
.editor-preview {
padding: 10px;
background: #fafafa;
}
.editor-preview > p {
margin-top: 0
}
.editor-preview pre {
background: #eee;
margin-bottom: 10px;
}
.editor-preview table td,
.editor-preview table th {
border: 1px solid #ddd;
padding: 5px;
}
.cm-s-easymde .cm-tag {
color: #63a35c;
}
.cm-s-easymde .cm-attribute {
color: #795da3;
}
.cm-s-easymde .cm-string {
color: #183691;
}
.cm-s-easymde .cm-header-1 {
font-size: 200%;
line-height: 200%;
}
.cm-s-easymde .cm-header-2 {
font-size: 160%;
line-height: 160%;
}
.cm-s-easymde .cm-header-3 {
font-size: 125%;
line-height: 125%;
}
.cm-s-easymde .cm-header-4 {
font-size: 110%;
line-height: 110%;
}
.cm-s-easymde .cm-comment {
background: rgba(0, 0, 0, .05);
border-radius: 2px;
}
.cm-s-easymde .cm-link {
color: #7f8c8d;
}
.cm-s-easymde .cm-url {
color: #aab2b3;
}
.cm-s-easymde .cm-quote {
color: #7f8c8d;
font-style: italic;
}
.editor-toolbar .easymde-dropdown {
position: relative;
background: linear-gradient(to bottom right, #fff 0%, #fff 84%, #333 50%, #333 100%);
border-radius: 0;
border: 1px solid #fff;
}
.editor-toolbar .easymde-dropdown:hover {
background: linear-gradient(to bottom right, #fff 0%, #fff 84%, #333 50%, #333 100%);
}
.easymde-dropdown-content {
display: block;
visibility: hidden;
position: absolute;
background-color: #f9f9f9;
box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2);
padding: 8px;
z-index: 2;
top: 30px;
}
.easymde-dropdown:active .easymde-dropdown-content,
.easymde-dropdown:focus .easymde-dropdown-content {
visibility: visible;
}
span[data-img-src]::after{
content: '';
background-image: var(--bg-image);
display: block;
max-height: 100%;
max-width: 100%;
background-size: contain;
height: 0;
padding-top: var(--height);
width: var(--width);
background-repeat: no-repeat;
}

View File

@ -0,0 +1,157 @@
<template>
<div class="vue-easymde">
<textarea
class="vue-simplemde-textarea"
:name="name"
:value="modelValue"
@input="handleInput($event.target.value)"
/>
</div>
</template>
<script>
import EasyMDE from 'easymde'
import {marked} from 'marked'
export default {
name: 'vue-easymde',
props: {
modelValue: String,
name: String,
previewClass: String,
autoinit: {
type: Boolean,
default: true,
},
highlight: {
type: Boolean,
default: false,
},
sanitize: {
type: Boolean,
default: false,
},
configs: {
type: Object,
default() {
return {}
},
},
previewRender: {
type: Function,
},
},
emits: ['update:modelValue', 'blur', 'initialized'],
data() {
return {
isValueUpdateFromInner: false,
easymde: null,
}
},
mounted() {
if (this.autoinit) this.initialize()
},
deactivated() {
const editor = this.easymde
if (!editor) return
const isFullScreen = editor.codemirror.getOption('fullScreen')
if (isFullScreen) editor.toggleFullScreen()
},
beforeUnmount() {
if (this.easymde) {
this.easymde.toTextArea()
this.easymde.cleanup()
this.easymde = null
}
},
methods: {
initialize() {
const configs = Object.assign({
element: this.$el.firstElementChild,
initialValue: this.modelValue,
previewRender: this.previewRender,
renderingConfig: {},
}, this.configs)
// Synchronize the values of value and initialValue
if (configs.initialValue) {
this.$emit('update:modelValue', configs.initialValue)
}
// Determine whether to enable code highlighting
if (this.highlight) {
configs.renderingConfig.codeSyntaxHighlighting = true
}
// Set whether to render the input html
marked.setOptions({ sanitize: this.sanitize })
// Instantiated editor
this.easymde = new EasyMDE(configs)
// Add a custom previewClass
const className = this.previewClass || ''
this.addPreviewClass(className)
// Binding event
this.bindingEvents()
this.$nextTick(() => {
this.$emit('initialized', this.easymde)
})
},
addPreviewClass(className) {
const wrapper = this.easymde.codemirror.getWrapperElement()
const preview = document.createElement('div')
wrapper.nextSibling.className += ` ${className}`
preview.className = `editor-preview ${className}`
wrapper.appendChild(preview)
},
bindingEvents() {
this.easymde.codemirror.on('change', this.handleCodemirrorInput)
this.easymde.codemirror.on('blur', this.handleCodemirrorBlur)
},
handleCodemirrorInput(instance, changeObj) {
if (changeObj.origin === 'setValue') {
return
}
const val = this.easymde.value()
this.handleInput(val)
},
handleCodemirrorBlur() {
const val = this.easymde.value()
this.isValueUpdateFromInner = true
this.$emit('blur', val)
},
handleInput(val) {
this.isValueUpdateFromInner = true
this.$emit('update:modelValue', val)
},
},
watch: {
modelValue(val) {
if (this.isValueUpdateFromInner) {
this.isValueUpdateFromInner = false
} else {
this.easymde.value(val)
}
},
},
}
</script>
<style lang="scss" scoped>
.vue-easymde .markdown-body {
padding: 0.5em
}
.vue-easymde .editor-preview-active, .vue-easymde .editor-preview-active-side {
display: block;
}
</style>

View File

@ -2,13 +2,13 @@
<dropdown>
<template v-if="isSavedFilter">
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.edit`, params: { listId: list.id } }"
:to="{ name: `${listRoutePrefix}.edit`, params: { listId: list.id } }"
icon="pen"
>
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.delete`, params: { listId: list.id } }"
:to="{ name: `${listRoutePrefix}.delete`, params: { listId: list.id } }"
icon="trash-alt"
>
{{ $t('misc.delete') }}
@ -16,7 +16,7 @@
</template>
<template v-else-if="list.isArchived">
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.archive`, params: { listId: list.id } }"
:to="{ name: `${listRoutePrefix}.archive`, params: { listId: list.id } }"
icon="archive"
>
{{ $t('menu.unarchive') }}
@ -24,32 +24,32 @@
</template>
<template v-else>
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.edit`, params: { listId: list.id } }"
:to="{ name: `${listRoutePrefix}.edit`, params: { listId: list.id } }"
icon="pen"
>
{{ $t('menu.edit') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.background`, params: { listId: list.id } }"
:to="{ name: `${listRoutePrefix}.background`, params: { listId: list.id } }"
v-if="backgroundsEnabled"
icon="image"
>
{{ $t('menu.setBackground') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.share`, params: { listId: list.id } }"
:to="{ name: `${listRoutePrefix}.share`, params: { listId: list.id } }"
icon="share-alt"
>
{{ $t('menu.share') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.duplicate`, params: { listId: list.id } }"
:to="{ name: `${listRoutePrefix}.duplicate`, params: { listId: list.id } }"
icon="paste"
>
{{ $t('menu.duplicate') }}
</dropdown-item>
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.archive`, params: { listId: list.id } }"
:to="{ name: `${listRoutePrefix}.archive`, params: { listId: list.id } }"
icon="archive"
>
{{ $t('menu.archive') }}
@ -63,7 +63,7 @@
@change="sub => subscription = sub"
/>
<dropdown-item
:to="{ name: `${listRoutePrefix}.settings.delete`, params: { listId: list.id } }"
:to="{ name: `${listRoutePrefix}.delete`, params: { listId: list.id } }"
icon="trash-alt"
class="has-text-danger"
>
@ -106,15 +106,19 @@ export default {
listRoutePrefix() {
let name = 'list'
if (this.$route.name !== null && this.$route.name.startsWith('list.')) {
name = this.$route.name
// 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
return `${name}.settings`
},
isSavedFilter() {
return getSavedFilterIdFromListId(this.list.id) > 0

View File

@ -1,67 +1,98 @@
<template>
<transition name="fade">
<filters
@change="change"
v-if="visibleInternal"
v-model="params"
ref="filters"
/>
</transition>
<x-button
v-if="hasFilters"
variant="secondary"
@click="clearFilters"
>
{{ $t('filters.clear') }}
</x-button>
<popup>
<template #trigger="{toggle}">
<x-button
@click.prevent.stop="toggle()"
variant="secondary"
icon="filter"
>
{{ $t('filters.title') }}
</x-button>
</template>
<template #content="{isOpen}">
<filters
v-model="value"
ref="filters"
class="filter-popup"
:class="{'is-open': isOpen}"
/>
</template>
</popup>
</template>
<script>
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import Filters from '../../../components/list/partials/filters'
import Filters from '@/components/list/partials/filters'
import {getDefaultParams} from '@/components/tasks/mixins/taskList'
import Popup from '@/components/misc/popup'
export default {
name: 'filter-popup',
data() {
return {
params: null,
visibleInternal: false,
}
},
components: {
Popup,
Filters,
},
mounted() {
document.addEventListener('click', this.hidePopup)
props: {
modelValue: {
required: true,
},
},
beforeDestroy() {
document.removeEventListener('click', this.hidePopup)
emits: ['update:modelValue'],
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
},
},
hasFilters() {
// this.value also contains the page parameter which we don't want to include in filters
// eslint-disable-next-line no-unused-vars
const {filter_by, filter_value, filter_comparator, filter_concat, s} = this.value
const def = {...getDefaultParams()}
const params = {filter_by, filter_value, filter_comparator, filter_concat, s}
const defaultParams = {
filter_by: def.filter_by,
filter_value: def.filter_value,
filter_comparator: def.filter_comparator,
filter_concat: def.filter_concat,
s: s ? def.s : undefined,
}
return JSON.stringify(params) !== JSON.stringify(defaultParams)
},
},
watch: {
value: {
modelValue: {
handler(value) {
this.params = value
this.value = value
},
immediate: true,
},
visible() {
this.visibleInternal = !this.visibleInternal
},
},
props: {
value: {
required: true,
},
visible: {
type: Boolean,
default: false,
},
},
methods: {
change() {
this.$emit('change', this.params)
this.$emit('input', this.params)
},
hidePopup(e) {
if (this.visibleInternal) {
closeWhenClickedOutside(e, this.$refs.filters.$el, () => {
this.visibleInternal = false
})
}
clearFilters() {
this.value = {...getDefaultParams()}
},
},
}
</script>
<style scoped lang="scss">
.filter-popup {
margin: 0;
&.is-open {
margin: 2rem 0 1rem;
}
}
</style>

View File

@ -1,23 +1,24 @@
<template>
<card class="filters has-overflow">
<fancycheckbox v-model="params.filter_include_nulls">
{{ $t('filters.attributes.includeNulls') }}
</fancycheckbox>
<fancycheckbox
v-model="filters.requireAllFilters"
@change="setFilterConcat()"
>
{{ $t('filters.attributes.requireAll') }}
</fancycheckbox>
<div class="field">
<label class="label">
<fancycheckbox v-model="params.filter_include_nulls">
{{ $t('filters.attributes.includeNulls') }}
</fancycheckbox>
<fancycheckbox
v-model="filters.requireAllFilters"
@change="setFilterConcat()"
>
{{ $t('filters.attributes.requireAll') }}
</fancycheckbox>
<fancycheckbox @change="setDoneFilter" v-model="filters.done">
{{ $t('filters.attributes.showDoneTasks') }}
</label>
<div class="control">
<fancycheckbox @change="setDoneFilter" v-model="filters.done">
{{ $t('filters.attributes.showDoneTasks') }}
</fancycheckbox>
</div>
</fancycheckbox>
<fancycheckbox
v-if="!$route.name.includes('list.kanban') || !$route.name.includes('list.table')"
v-model="sortAlphabetically"
>
{{ $t('filters.attributes.sortAlphabetically') }}
</fancycheckbox>
</div>
<div class="field">
<label class="label">{{ $t('misc.search') }}</label>
@ -35,7 +36,7 @@
<label class="label">{{ $t('task.attributes.priority') }}</label>
<div class="control single-value-control">
<priority-select
:disabled="!filters.usePriority"
:disabled="!filters.usePriority || null"
v-model.number="filters.priority"
@change="setPriority"
/>
@ -53,7 +54,7 @@
<percent-done-select
v-model.number="filters.percentDone"
@change="setPercentDoneFilter"
:disabled="!filters.usePercentDone"
:disabled="!filters.usePercentDone || null"
/>
<fancycheckbox
v-model="filters.usePercentDone"
@ -189,6 +190,9 @@ import ListService from '@/services/list'
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'
// FIXME: merge with DEFAULT_PARAMS in taskList.js
const DEFAULT_PARAMS = {
sort_by: [],
@ -218,6 +222,8 @@ const DEFAULT_FILTERS = {
namespace: '',
}
export const ALPHABETICAL_SORT = 'title'
export default {
name: 'filters',
components: {
@ -253,20 +259,35 @@ export default {
this.filters.requireAllFilters = this.params.filter_concat === 'and'
},
props: {
value: {
modelValue: {
required: true,
},
},
emits: ['update:modelValue', 'change'],
watch: {
value: {
modelValue: {
handler(value) {
this.params = value
// FIXME: filters should only be converted to snake case in
// the last moment
this.params = objectToSnakeCase(value)
this.prepareFilters()
},
immediate: true,
},
},
computed: {
sortAlphabetically: {
get() {
return this.params?.sort_by?.find(sortBy => sortBy === ALPHABETICAL_SORT) !== undefined
},
set(sortAlphabetically) {
this.params.sort_by = sortAlphabetically
? [ALPHABETICAL_SORT]
: getDefaultParams().sort_by
this.change()
},
},
foundLabels() {
return this.$store.getters['labels/filterLabelsByQuery'](this.labels, this.query)
},
@ -286,7 +307,7 @@ export default {
},
methods: {
change() {
this.$emit('input', this.params)
this.$emit('update:modelValue', this.params)
this.$emit('change', this.params)
},
prepareFilters() {
@ -342,11 +363,11 @@ export default {
this.params.filter_by.forEach((f, i) => {
if (f === filterName && this.params.filter_comparator[i] === 'greater_equals') {
foundStart = true
this.$set(this.params.filter_value, i, formatISO(new Date(parts[0])))
this.params.filter_value[i] = formatISO(new Date(parts[0]))
}
if (f === filterName && this.params.filter_comparator[i] === 'less_equals') {
foundEnd = true
this.$set(this.params.filter_value, i, formatISO(new Date(parts[1])))
this.params.filter_value[i] = formatISO(new Date(parts[1]))
}
})
@ -404,7 +425,7 @@ export default {
this.params.filter_by.forEach((f, i) => {
if (f === filterName) {
found = true
this.$set(this.params.filter_value, i, this.filters[variableName])
this.params.filter_value[i] = this.filters[variableName]
}
})
@ -457,17 +478,9 @@ export default {
return
}
let foundDone = false
this.params.filter_by.forEach((f, i) => {
if (f === 'done') {
foundDone = i
}
})
if (foundDone === false) {
this.$set(this.filters, 'done', true)
}
this.filters.done = this.params.filter_by.some((f) => f === 'done') === false
},
prepareRelatedObjectFilter(kind, filterName = null, servicePrefix = null) {
async prepareRelatedObjectFilter(kind, filterName = null, servicePrefix = null) {
if (filterName === null) {
filterName = kind
}
@ -477,13 +490,11 @@ export default {
}
this.prepareSingleValue(filterName)
if (typeof this.filters[filterName] !== 'undefined' && this.filters[filterName] !== '') {
this[`${servicePrefix}Service`].getAll({}, {s: this.filters[filterName]})
.then(r => {
this.$set(this, kind, r)
})
.catch(e => this.$message.error(e))
if (typeof this.filters[filterName] === 'undefined' || this.filters[filterName] === '') {
return
}
this[kind] = await this[`${servicePrefix}Service`].getAll({}, {s: this.filters[filterName]})
},
setDoneFilter() {
if (this.filters.done) {
@ -521,24 +532,18 @@ export default {
this.setDateFilter('reminders')
},
clear(kind) {
this.$set(this, `found${kind}`, [])
this[`found${kind}`] = []
},
find(kind, query) {
async find(kind, query) {
if (query === '') {
this.clear(kind)
}
this[`${kind}Service`].getAll({}, {s: query})
.then(response => {
// Filter users from the results who are already assigned
const unassignedUsers = response.filter(({id}) => !includesById(this[kind], id))
const response = await this[`${kind}Service`].getAll({}, {s: query})
this.$set(this, `found${kind}`, unassignedUsers)
})
.catch(e => {
this.$message.error(e)
})
// Filter users from the results who are already assigned
this[`found${kind}`] = response.filter(({id}) => !includesById(this[kind], id))
},
add(kind, filterName) {
this.$nextTick(() => {
@ -562,7 +567,7 @@ export default {
ids.push(u.id)
})
this.$set(this.filters, filterName, ids.join(','))
this.filters[filterName] = ids.join(',')
this.setSingleValueFilter(filterName, filterName, '', 'in')
},
findLabels(query) {
@ -597,7 +602,7 @@ export default {
labelIDs.push(u.id)
})
this.$set(this.filters, 'labels', labelIDs.join(','))
this.filters.labels = labelIDs.join(',')
this.setSingleValueFilter('labels', 'labels', '', 'in')
},
},

View File

@ -10,7 +10,6 @@
}"
:to="{ name: 'list.index', params: { listId: list.id} }"
class="list-card"
tag="span"
v-if="list !== null && (showArchived ? true : !list.isArchived)"
>
<div class="is-archived-container">
@ -21,67 +20,189 @@
:class="{'is-favorite': list.isFavorite, 'is-archived': list.isArchived}"
@click.stop="toggleFavoriteList(list)"
class="favorite">
<icon icon="star" v-if="list.isFavorite"/>
<icon :icon="['far', 'star']" v-else/>
<icon :icon="list.isFavorite ? 'star' : ['far', 'star']" />
</span>
</div>
<div class="title">{{ list.title }}</div>
</router-link>
</template>
<script>
<script lang="ts" setup>
import {ref, watch} from 'vue'
import {useStore} from 'vuex'
import ListService from '@/services/list'
export default {
name: 'list-card',
data() {
return {
background: null,
backgroundLoading: false,
}
},
props: {
list: {
required: true,
},
showArchived: {
default: false,
type: Boolean,
},
},
watch: {
list: {
handler: 'loadBackground',
immediate: true,
},
},
methods: {
loadBackground() {
if (this.list === null || !this.list.backgroundInformation || this.backgroundLoading) {
return
}
import {colorIsDark} from '@/helpers/color/colorIsDark'
this.backgroundLoading = true
const background = ref<string | null>(null)
const backgroundLoading = ref(false)
const listService = new ListService()
listService.background(this.list)
.then(b => {
this.$set(this, 'background', b)
})
.catch(e => {
this.$message.error(e)
})
.finally(() => this.backgroundLoading = false)
},
toggleFavoriteList(list) {
// The favorites pseudo list is always favorite
// Archived lists cannot be marked favorite
if (list.id === -1 || list.isArchived) {
return
}
this.$store.dispatch('lists/toggleListFavorite', list)
.catch(e => this.$message.error(e))
},
const props = defineProps({
list: {
type: Object,
required: true,
},
showArchived: {
default: false,
type: Boolean,
},
})
watch(props.list, loadBackground, { immediate: true })
async function loadBackground() {
if (props.list === null || !props.list.backgroundInformation || backgroundLoading.value) {
return
}
backgroundLoading.value = true
const listService = new ListService()
try {
background.value = await listService.background(props.list)
} finally {
backgroundLoading.value = false
}
}
const store = useStore()
function toggleFavoriteList(list) {
// The favorites pseudo list is always favorite
// Archived lists cannot be marked favorite
if (list.id === -1 || list.isArchived) {
return
}
store.dispatch('lists/toggleListFavorite', list)
}
</script>
<style lang="scss" scoped>
.list-card {
cursor: pointer;
width: calc((100% - #{($lists-per-row - 1) * 1rem}) / #{$lists-per-row});
height: $list-height;
background: var(--white);
margin: 0 $list-spacing $list-spacing 0;
padding: 1rem;
border-radius: $radius;
box-shadow: var(--shadow-sm);
transition: box-shadow $transition;
display: flex;
justify-content: space-between;
flex-wrap: wrap;
&:hover {
box-shadow: var(--shadow-md);
}
&:active,
&:focus,
&:focus:not(:active) {
box-shadow: var(--shadow-xs) !important;
}
@media screen and (min-width: $widescreen) {
&:nth-child(#{$lists-per-row}n) {
margin-right: 0;
}
}
@media screen and (max-width: $widescreen) and (min-width: $tablet) {
$lists-per-row: 3;
& {
width: calc((100% - #{($lists-per-row - 1) * 1rem}) / #{$lists-per-row});
}
&:nth-child(#{$lists-per-row}n) {
margin-right: 0;
}
}
@media screen and (max-width: $tablet) {
$lists-per-row: 2;
& {
width: calc((100% - #{($lists-per-row - 1) * 1rem}) / #{$lists-per-row});
}
&:nth-child(#{$lists-per-row}n) {
margin-right: 0;
}
}
@media screen and (max-width: $mobile) {
$lists-per-row: 1;
& {
width: 100%;
margin-right: 0;
}
}
.is-archived-container {
width: 100%;
text-align: right;
.is-archived {
font-size: .75rem;
float: left;
}
}
.title {
align-self: flex-end;
font-family: $vikunja-font;
font-weight: 400;
font-size: 1.5rem;
color: var(--text);
width: 100%;
margin-bottom: 0;
max-height: calc(100% - 2rem); // 1rem padding, 1rem height of the "is archived" badge
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
}
&.has-light-text .title {
color: var(--light);
}
&.has-background {
background-size: cover;
background-repeat: no-repeat;
background-position: center;
.title {
text-shadow: 0 0 10px var(--black), 1px 1px 5px var(--grey-700), -1px -1px 5px var(--grey-700);
color: var(--white);
}
}
.favorite {
transition: opacity $transition, color $transition;
opacity: 0;
&:hover {
color: var(--warning);
}
&.is-archived {
display: none;
}
&.is-favorite {
display: inline-block;
opacity: 1;
color: var(--warning);
}
}
&:hover .favorite {
opacity: 1;
}
}
</style>

View File

@ -1,196 +0,0 @@
<template>
<div class="content">
<h1>{{ $t('migrate.titleService', {name: name}) }}</h1>
<p>{{ $t('migrate.descriptionDo') }}</p>
<template v-if="isMigrating === false && message === '' && lastMigrationDate === null">
<template v-if="isFileMigrator">
<p>{{ $t('migrate.importUpload', {name: name}) }}</p>
<input
@change="migrate"
class="is-hidden"
ref="uploadInput"
type="file"
/>
<x-button
:loading="migrationService.loading"
:disabled="migrationService.loading"
@click="$refs.uploadInput.click()"
>
{{ $t('migrate.upload') }}
</x-button>
</template>
<template v-else>
<p>{{ $t('migrate.authorize', {name: name}) }}</p>
<x-button
:loading="migrationService.loading"
:disabled="migrationService.loading"
:href="authUrl"
>
{{ $t('migrate.getStarted') }}
</x-button>
</template>
</template>
<div
class="migration-in-progress-container"
v-else-if="isMigrating === true && message === '' && lastMigrationDate === null">
<div class="migration-in-progress">
<img :alt="name" :src="serviceIconSource"/>
<div class="progress-dots">
<span v-for="i in progressDotsCount" :key="i" />
</div>
<img alt="Vikunja" :src="logoUrl">
</div>
<p>{{ $t('migrate.inProgress') }}</p>
</div>
<div v-else-if="lastMigrationDate">
<p>
{{ $t('migrate.alreadyMigrated1', {name: name, date: formatDate(lastMigrationDate)}) }}<br/>
{{ $t('migrate.alreadyMigrated2') }}
</p>
<div class="buttons">
<x-button @click="migrate">{{ $t('migrate.confirm') }}</x-button>
<x-button :to="{name: 'home'}" type="tertary" class="has-text-danger">{{ $t('misc.cancel') }}</x-button>
</div>
</div>
<div v-else>
<div class="message is-primary">
<div class="message-body">
{{ message }}
</div>
</div>
<x-button :to="{name: 'home'}">{{ $t('misc.refresh') }}</x-button>
</div>
</div>
</template>
<script>
import AbstractMigrationService from '../../services/migrator/abstractMigration'
import AbstractMigrationFileService from '../../services/migrator/abstractMigrationFile'
import {SERVICE_ICONS} from '../../helpers/migrator'
import logoUrl from '@/assets/logo.svg'
const PROGRESS_DOTS_COUNT = 8
export default {
name: 'migration',
data() {
return {
progressDotsCount: PROGRESS_DOTS_COUNT,
authUrl: '',
isMigrating: false,
lastMigrationDate: null,
message: '',
migratorAuthCode: '',
migrationService: null,
logoUrl,
}
},
props: {
name: {
type: String,
required: true,
},
identifier: {
type: String,
required: true,
},
isFileMigrator: {
type: Boolean,
default: false,
},
},
computed: {
serviceIconSource() {
return SERVICE_ICONS[this.identifier]()
},
},
created() {
this.message = ''
if (this.isFileMigrator) {
this.migrationService = new AbstractMigrationFileService(this.identifier)
return
}
this.migrationService = new AbstractMigrationService(this.identifier)
this.getAuthUrl()
if (typeof this.$route.query.code !== 'undefined' || location.hash.startsWith('#token=')) {
if (location.hash.startsWith('#token=')) {
this.migratorAuthCode = location.hash.substring(7)
console.debug(location.hash.substring(7))
} else {
this.migratorAuthCode = this.$route.query.code
}
this.migrationService.getStatus()
.then(r => {
if (r.time) {
if (typeof r.time === 'string' && r.time.startsWith('0001-')) {
this.lastMigrationDate = null
} else {
this.lastMigrationDate = new Date(r.time)
}
if (this.lastMigrationDate) {
return
}
}
this.migrate()
})
.catch(e => {
this.$message.error(e)
})
}
},
methods: {
getAuthUrl() {
this.migrationService.getAuthUrl()
.then(r => {
this.authUrl = r.url
})
.catch(e => {
this.$message.error(e)
})
},
migrate() {
this.isMigrating = true
this.lastMigrationDate = null
this.message = ''
if (this.isFileMigrator) {
return this.migrateFile()
}
this.migrationService.migrate({code: this.migratorAuthCode})
.then(r => {
this.message = r.message
this.$store.dispatch('namespaces/loadNamespaces')
})
.catch(e => {
this.$message.error(e)
})
.finally(() => {
this.isMigrating = false
})
},
migrateFile() {
if (this.$refs.uploadInput.files.length === 0) {
return
}
this.migrationService.migrate(this.$refs.uploadInput.files[0])
.then(r => {
this.message = r.message
this.$store.dispatch('namespaces/loadNamespaces')
})
.catch(e => {
this.$message.error(e)
})
.finally(() => {
this.isMigrating = false
})
},
},
}
</script>

View File

@ -0,0 +1,42 @@
<template>
<div
v-if="isDone"
class="is-done"
:class="{ 'is-done--small': variant === 'small' }"
>
{{ $t('task.attributes.done') }}
</div>
</template>
<script lang="ts" setup>
import {PropType} from 'vue'
type Variants = 'default' | 'small'
defineProps({
isDone: {
type: Boolean,
default: false,
},
variant: {
type: String as PropType<Variants>,
default: 'default',
},
})
</script>
<style lang="scss" scoped>
.is-done {
background: var(--success);
color: var(--white);
padding: .5rem;
font-weight: bold;
line-height: 1;
border-radius: 4px;
text-align: center;
}
.is-done--small {
padding: .2rem .3rem;
}
</style>

View File

@ -16,42 +16,39 @@
/>
</div>
<div class="control">
<x-button @click="setApiUrl" :disabled="apiUrl === ''">
<x-button @click="setApiUrl" :disabled="apiUrl === '' || null">
{{ $t('apiConfig.change') }}
</x-button>
</div>
</div>
</div>
<div class="api-url-info" v-else>
<i18n path="apiConfig.signInOn">
<i18n-t keypath="apiConfig.use">
<span class="url" v-tooltip="apiUrl"> {{ apiDomain }} </span>
</i18n>
<br />
</i18n-t>
<br/>
<a @click="() => (configureApi = true)">{{ $t('apiConfig.change') }}</a>
</div>
<div
class="notification is-success mt-2"
v-if="successMsg !== '' && errorMsg === ''"
>
<message variant="success" v-if="successMsg !== '' && errorMsg === ''" class="mt-2">
{{ successMsg }}
</div>
<div
class="notification is-danger mt-2"
v-if="errorMsg !== '' && successMsg === ''"
>
</message>
<message variant="danger" v-if="errorMsg !== '' && successMsg === ''" class="mt-2">
{{ errorMsg }}
</div>
</message>
</div>
</template>
<script>
import { parseURL } from 'ufo'
const API_DEFAULT_PORT = 3456
import Message from '@/components/misc/message'
import {parseURL} from 'ufo'
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
export default {
name: 'apiConfig',
components: {
Message,
},
data() {
return {
configureApi: false,
@ -60,6 +57,7 @@ export default {
successMsg: '',
}
},
emits: ['foundApi'],
created() {
if (this.apiUrl === '') {
this.configureApi = true
@ -67,132 +65,67 @@ export default {
},
computed: {
apiDomain() {
return parseURL(this.apiUrl).host
return parseURL(this.apiUrl).host || parseURL(window.location.href).host
},
},
props: {
configureOpen: {
type: Boolean,
required: false,
default: false,
},
},
watch: {
configureOpen: {
handler(value) {
this.configureApi = value
},
immediate: true,
},
},
methods: {
setApiUrl() {
async setApiUrl() {
if (this.apiUrl === '') {
// Don't try to check and set an empty url
this.errorMsg = this.$t('apiConfig.urlRequired')
return
}
let urlToCheck = this.apiUrl
try {
const url = await checkAndSetApiUrl(this.apiUrl)
// Check if the url has an http prefix
if (
!urlToCheck.startsWith('http://') &&
!urlToCheck.startsWith('https://')
) {
urlToCheck = `http://${urlToCheck}`
if (url === '') {
// If the config setter function could not figure out a url
throw new Error('URL cannot be empty.')
}
// Set it + save it to local storage to save us the hoops
this.errorMsg = ''
this.$message.success({message: this.$t('apiConfig.success', {domain: this.apiDomain})})
this.configureApi = false
this.apiUrl = url
this.$emit('foundApi', this.apiUrl)
} catch (e) {
// Still not found, url is still invalid
this.successMsg = ''
this.errorMsg = this.$t('apiConfig.error', {domain: this.apiDomain})
}
urlToCheck = new URL(urlToCheck)
const origUrlToCheck = urlToCheck
const oldUrl = window.API_URL
window.API_URL = urlToCheck.toString()
// Check if the api is reachable at the provided url
this.$store
.dispatch('config/update')
.catch((e) => {
// Check if it is reachable at /api/v1 and http
if (
!urlToCheck.pathname.endsWith('/api/v1') &&
!urlToCheck.pathname.endsWith('/api/v1/')
) {
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch((e) => {
// Check if it has a port and if not check if it is reachable at https
if (urlToCheck.protocol === 'http:') {
urlToCheck.protocol = 'https:'
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at /api/v1 and https
urlToCheck.pathname = origUrlToCheck.pathname
if (
!urlToCheck.pathname.endsWith('/api/v1') &&
!urlToCheck.pathname.endsWith('/api/v1/')
) {
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at port API_DEFAULT_PORT and https
if (urlToCheck.port !== API_DEFAULT_PORT) {
urlToCheck.protocol = 'https:'
urlToCheck.port = API_DEFAULT_PORT
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and https
urlToCheck.pathname = origUrlToCheck.pathname
if (
!urlToCheck.pathname.endsWith('/api/v1') &&
!urlToCheck.pathname.endsWith('/api/v1/')
) {
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at port API_DEFAULT_PORT and http
if (urlToCheck.port !== API_DEFAULT_PORT) {
urlToCheck.protocol = 'http:'
urlToCheck.port = API_DEFAULT_PORT
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch((e) => {
// Check if it is reachable at :API_DEFAULT_PORT and /api/v1 and http
urlToCheck.pathname = origUrlToCheck.pathname
if (
!urlToCheck.pathname.endsWith('/api/v1') &&
!urlToCheck.pathname.endsWith('/api/v1/')
) {
urlToCheck.pathname = `${urlToCheck.pathname}api/v1`
window.API_URL = urlToCheck.toString()
return this.$store.dispatch('config/update')
}
return Promise.reject(e)
})
.catch(() => {
// Still not found, url is still invalid
this.successMsg = ''
this.errorMsg = this.$t('apiConfig.error', {domain: this.apiDomain})
window.API_URL = oldUrl
})
.then((r) => {
if (typeof r !== 'undefined') {
// Set it + save it to local storage to save us the hoops
this.errorMsg = ''
this.successMsg = this.$t('apiConfig.success', {domain: this.apiDomain})
localStorage.setItem('API_URL', window.API_URL)
this.configureApi = false
this.apiUrl = window.API_URL
this.$emit('foundApi', this.apiUrl)
}
})
},
},
}
</script>
<style lang="scss" scoped>
.api-config {
margin-bottom: .75rem;
}
.api-url-info {
font-size: .9rem;
text-align: right;
}
.url {
border-bottom: 1px dashed var(--primary);
}
</style>

View File

@ -4,7 +4,13 @@
<p class="card-header-title">
{{ title }}
</p>
<a @click="$emit('close')" class="card-header-icon" v-if="hasClose">
<a
v-if="hasClose"
class="card-header-icon"
:aria-label="$t('misc.close')"
@click="$emit('close')"
v-tooltip="$t('misc.close')"
>
<span class="icon">
<icon :icon="closeIcon"/>
</span>
@ -18,38 +24,59 @@
</div>
</template>
<script>
export default {
name: 'card',
props: {
title: {
type: String,
default: '',
},
padding: {
type: Boolean,
default: true,
},
hasClose: {
type: Boolean,
default: false,
},
closeIcon: {
type: String,
default: 'angle-right',
},
shadow: {
type: Boolean,
default: true,
},
hasContent: {
type: Boolean,
default: true,
},
loading: {
type: Boolean,
default: false,
},
<script setup lang="ts">
defineProps({
title: {
type: String,
default: '',
},
}
padding: {
type: Boolean,
default: true,
},
hasClose: {
type: Boolean,
default: false,
},
closeIcon: {
type: String,
default: 'times',
},
shadow: {
type: Boolean,
default: true,
},
hasContent: {
type: Boolean,
default: true,
},
loading: {
type: Boolean,
default: false,
},
})
defineEmits(['close'])
</script>
<style lang="scss" scoped>
.card {
background-color: var(--white);
border-radius: $radius;
margin-bottom: 1rem;
border: 1px solid var(--card-border-color);
box-shadow: var(--shadow-sm);
}
.card-header {
box-shadow: none;
border-bottom: 1px solid var(--card-border-color);
border-radius: $radius $radius 0 0;
}
// FIXME: should maybe be merged somehow with modal
:deep(.modal-card-foot) {
background-color: var(--grey-50);
border-top: 0;
}
</style>

View File

@ -6,7 +6,6 @@
:padding="false"
class="has-text-left has-overflow"
:has-close="true"
close-icon="times"
@close="$router.back()"
:loading="loading"
>
@ -15,25 +14,25 @@
</div>
<footer class="modal-card-foot is-flex is-justify-content-flex-end">
<x-button
v-if="tertiary !== ''"
:shadow="false"
type="tertary"
@click.prevent.stop="$emit('tertary')"
v-if="tertary !== ''"
variant="tertiary"
@click.prevent.stop="$emit('tertiary')"
>
{{ tertary }}
{{ tertiary }}
</x-button>
<x-button
type="secondary"
variant="secondary"
@click.prevent.stop="$router.back()"
>
{{ $t('misc.cancel') }}
</x-button>
<x-button
type="primary"
v-if="primaryLabel !== ''"
variant="primary"
@click.prevent.stop="primary"
:icon="primaryIcon"
:disabled="primaryDisabled"
v-if="primaryLabel !== ''"
>
{{ primaryLabel }}
</x-button>
@ -43,6 +42,8 @@
</template>
<script>
import { i18n } from '@/i18n'
export default {
name: 'create-edit',
props: {
@ -53,7 +54,7 @@ export default {
primaryLabel: {
type: String,
default() {
return this.$t('misc.create')
return i18n.global.t('misc.create')
},
},
primaryIcon: {
@ -64,7 +65,7 @@ export default {
type: Boolean,
default: false,
},
tertary: {
tertiary: {
type: String,
default: '',
},
@ -77,6 +78,7 @@ export default {
default: false,
},
},
emits: ['create', 'primary', 'tertiary'],
methods: {
primary() {
this.$emit('create')

View File

@ -11,18 +11,15 @@
</router-link>
</template>
<script>
export default {
name: 'dropdown-item',
props: {
to: {
required: true,
},
icon: {
type: String,
required: false,
default: '',
},
<script lang="ts" setup>
defineProps({
to: {
required: true,
},
}
icon: {
type: String,
required: false,
default: '',
},
})
</script>

View File

@ -1,14 +1,14 @@
<template>
<div class="dropdown is-right is-active" ref="dropdown">
<div class="dropdown-trigger" @click="open = !open">
<slot name="trigger">
<div class="dropdown-trigger is-flex" @click="open = !open">
<slot name="trigger" :close="close">
<icon :icon="triggerIcon" class="icon"/>
</slot>
</div>
<transition name="fade">
<div class="dropdown-menu" v-if="open">
<div class="dropdown-content">
<slot></slot>
<slot :close="close"></slot>
</div>
</div>
</transition>
@ -26,10 +26,10 @@ export default {
}
},
mounted() {
document.addEventListener('click', this.hide)
document.addEventListener('click', this.handleClickOutside)
},
beforeDestroy() {
document.removeEventListener('click', this.hide)
beforeUnmount() {
document.removeEventListener('click', this.handleClickOutside)
},
props: {
triggerIcon: {
@ -37,14 +37,22 @@ export default {
default: 'ellipsis-h',
},
},
emits: ['close'],
methods: {
hide(e) {
if (this.open) {
closeWhenClickedOutside(e, this.$refs.dropdown, () => {
this.open = false
this.$emit('close', e)
})
close() {
this.open = false
},
toggleOpen() {
this.open = !this.open
},
handleClickOutside(e) {
if (!this.open) {
return
}
closeWhenClickedOutside(e, this.$refs.dropdown, () => {
this.open = false
this.$emit('close', e)
})
},
},
}

View File

@ -1,14 +1,16 @@
<template>
<div class="notification is-danger">
<i18n path="loadingError.failed">
<a @click="() => location.reload()">{{ $t('loadingError.tryAgain') }}</a>
<message variant="danger">
<i18n-t keypath="loadingError.failed">
<a @click="reload">{{ $t('loadingError.tryAgain') }}</a>
<a href="https://vikunja.io/contact/" rel="noreferrer noopener nofollow" target="_blank">{{ $t('loadingError.contact') }}</a>
</i18n>
</div>
</i18n-t>
</message>
</template>
<script>
export default {
name: 'error',
<script lang="ts" setup>
import Message from '@/components/misc/message.vue'
function reload() {
window.location.reload()
}
</script>

View File

@ -1,72 +0,0 @@
<template>
<modal @close="close()">
<card class="has-background-white has-no-shadow" :title="$t('keyboardShortcuts.title')">
<div class="message is-primary">
<div class="message-body">
{{ $t('keyboardShortcuts.allPages') }}
</div>
</div>
<p>
<strong>{{ $t('keyboardShortcuts.toggleMenu') }}</strong>
<shortcut :keys="['ctrl', 'e']"/>
</p>
<p>
<strong>{{ $t('keyboardShortcuts.quickSearch') }}</strong>
<shortcut :keys="['ctrl', 'k']"/>
</p>
<h3>{{ $t('list.kanban.title') }}</h3>
<div class="message is-primary" v-if="$route.name === 'list.kanban'">
<div class="message-body">
{{ $t('keyboardShortcuts.currentPageOnly') }}
</div>
</div>
<p>
<strong>{{ $t('keyboardShortcuts.task.done') }}</strong>
<shortcut :keys="['ctrl', 'click']"/>
</p>
<h3>{{ $t('keyboardShortcuts.task.title') }}</h3>
<div
class="message is-primary"
v-if="$route.name === 'task.detail' || $route.name === 'task.list.detail' || $route.name === 'task.gantt.detail' || $route.name === 'task.kanban.detail' || $route.name === 'task.detail'">
<div class="message-body">
{{ $t('keyboardShortcuts.currentPageOnly') }}
</div>
</div>
<p>
<strong>{{ $t('keyboardShortcuts.task.assign') }}</strong>
<shortcut :keys="['a']"/>
</p>
<p>
<strong>{{ $t('keyboardShortcuts.task.labels') }}</strong>
<shortcut :keys="['l']"/>
</p>
<p>
<strong>{{ $t('keyboardShortcuts.task.dueDate') }}</strong>
<shortcut :keys="['d']"/>
</p>
<p>
<strong>{{ $t('keyboardShortcuts.task.attachment') }}</strong>
<shortcut :keys="['f']"/>
</p>
<p>
<strong>{{ $t('keyboardShortcuts.task.related') }}</strong>
<shortcut :keys="['r']"/>
</p>
</card>
</modal>
</template>
<script>
import {KEYBOARD_SHORTCUTS_ACTIVE} from '@/store/mutation-types'
import Shortcut from '@/components/misc/shortcut.vue'
export default {
name: 'keyboard-shortcuts',
components: {Shortcut},
methods: {
close() {
this.$store.commit(KEYBOARD_SHORTCUTS_ACTIVE, false)
},
},
}
</script>

View File

@ -0,0 +1,79 @@
<template>
<modal @close="close()">
<card class="has-background-white has-no-shadow keyboard-shortcuts" :title="$t('keyboardShortcuts.title')">
<template v-for="(s, i) in shortcuts" :key="i">
<h3>{{ $t(s.title) }}</h3>
<message>
{{
s.available($route) ? $t('keyboardShortcuts.currentPageOnly') : $t('keyboardShortcuts.allPages')
}}
</message>
<dl class="shortcut-list">
<template v-for="(sc, si) in s.shortcuts" :key="si">
<dt class="shortcut-title">{{ $t(sc.title) }}</dt>
<shortcut
class="shortcut-keys"
is="dd"
:keys="sc.keys"
:combination="typeof sc.combination !== 'undefined' ? $t(`keyboardShortcuts.${sc.combination}`) : null"/>
</template>
</dl>
</template>
</card>
</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'
export default {
name: 'keyboard-shortcuts',
components: {
Message,
Shortcut,
},
data() {
return {
shortcuts: KEYBOARD_SHORTCUTS,
}
},
methods: {
close() {
this.$store.commit(KEYBOARD_SHORTCUTS_ACTIVE, false)
},
},
}
</script>
<style scoped>
.keyboard-shortcuts {
text-align: left;
}
.message:not(:last-child) {
margin-bottom: 1rem;
}
.message-body {
padding: .75rem;
}
.shortcut-list {
display: grid;
grid-template-columns: 2fr 1fr;
}
.shortcut-title {
margin-bottom: .5rem;
}
.shortcut-keys {
justify-content: end;
margin-bottom: .5rem;
}
</style>

View File

@ -0,0 +1,88 @@
import {isAppleDevice} from '@/helpers/isAppleDevice'
const ctrl = isAppleDevice() ? '⌘' : 'ctrl'
export const KEYBOARD_SHORTCUTS = [
{
title: 'keyboardShortcuts.general',
available: () => null,
shortcuts: [
{
title: 'keyboardShortcuts.toggleMenu',
keys: [ctrl, 'e'],
},
{
title: 'keyboardShortcuts.quickSearch',
keys: [ctrl, 'k'],
},
],
},
{
title: 'list.kanban.title',
available: (route) => route.name === 'list.kanban',
shortcuts: [
{
title: 'keyboardShortcuts.task.done',
keys: [ctrl, 'click'],
},
],
},
{
title: 'keyboardShortcuts.list.title',
available: (route) => route.name.startsWith('list.'),
shortcuts: [
{
title: 'keyboardShortcuts.list.switchToListView',
keys: ['g', 'l'],
combination: 'then',
},
{
title: 'keyboardShortcuts.list.switchToGanttView',
keys: ['g', 'g'],
combination: 'then',
},
{
title: 'keyboardShortcuts.list.switchToTableView',
keys: ['g', 't'],
combination: 'then',
},
{
title: 'keyboardShortcuts.list.switchToKanbanView',
keys: ['g', 'k'],
combination: 'then',
},
],
},
{
title: 'keyboardShortcuts.task.title',
available: (route) => [
'task.detail',
'task.list.detail',
'task.gantt.detail',
'task.kanban.detail',
'task.detail',
].includes(route.name),
shortcuts: [
{
title: 'keyboardShortcuts.task.assign',
keys: ['a'],
},
{
title: 'keyboardShortcuts.task.labels',
keys: ['l'],
},
{
title: 'keyboardShortcuts.task.dueDate',
keys: ['d'],
},
{
title: 'keyboardShortcuts.task.attachment',
keys: ['f'],
},
{
title: 'keyboardShortcuts.task.related',
keys: ['r'],
},
],
},
]

View File

@ -6,14 +6,21 @@
</div>
</template>
<script>
import {mapState} from 'vuex'
<script lang="ts" setup>
import {computed} from 'vue'
import {useStore} from 'vuex'
export default {
name: 'legal',
computed: mapState({
imprintUrl: state => state.config.legal.imprintUrl,
privacyPolicyUrl: state => state.config.legal.privacyPolicyUrl,
}),
}
const store = useStore()
const imprintUrl = computed(() => store.state.config.legal.imprintUrl)
const privacyPolicyUrl = computed(() => store.state.config.legal.privacyPolicyUrl)
</script>
<style lang="scss" scoped>
.legal-links {
margin-top: 1rem;
text-align: right;
color: var(--grey-300);
font-size: 1rem;
}
</style>

View File

@ -2,12 +2,6 @@
<div class="loader-container is-loading"></div>
</template>
<script>
export default {
name: 'loading',
}
</script>
<style scoped>
.loader-container {
height: 100%;

View File

@ -0,0 +1,48 @@
<template>
<div class="message-wrapper">
<div class="message" :class="variant">
<slot/>
</div>
</div>
</template>
<script lang="ts" setup>
defineProps({
variant: {
type: String,
default: 'info',
},
})
</script>
<style lang="scss" scoped>
.message-wrapper {
border-radius: $radius;
background: var(--white);
}
.message {
padding: .75rem 1rem;
border-radius: $radius;
}
.info {
border: 1px solid var(--primary);
background: hsla(var(--primary-hsl), .05);
}
.danger {
border: 1px solid var(--danger);
background: hsla(var(--danger-h), var(--danger-s), var(--danger-l), .05);
}
.warning {
border: 1px solid var(--warning);
background: hsla(var(--warning-h), var(--warning-s), var(--warning-l), .05);
}
.success {
border: 1px solid var(--success);
background: hsla(var(--success-h), var(--success-s), var(--success-l), .05);
}
</style>

View File

@ -0,0 +1,134 @@
<template>
<div class="no-auth-wrapper">
<Logo class="logo" width="200" height="58"/>
<div class="noauth-container">
<section class="image" :class="{'has-message': motd !== ''}">
<Message v-if="motd !== ''">
{{ motd }}
</Message>
<h2 class="image-title">
{{ $t('misc.welcomeBack') }}
</h2>
</section>
<section class="content">
<div>
<h2 class="title" v-if="title">{{ title }}</h2>
<api-config/>
<slot/>
</div>
<legal/>
</section>
</div>
</div>
</template>
<script lang="ts" setup>
import Logo from '@/components/home/Logo.vue'
import Message from '@/components/misc/message.vue'
import Legal from '@/components/misc/legal.vue'
import ApiConfig from '@/components/misc/api-config.vue'
import {useStore} from 'vuex'
import {computed} from 'vue'
import {useRoute} from 'vue-router'
import {useI18n} from 'vue-i18n'
import {useTitle} from '@/composables/useTitle'
const route = useRoute()
const store = useStore()
const {t} = useI18n()
const motd = computed(() => store.state.config.motd)
// @ts-ignore
const title = computed(() => t(route.meta.title ?? ''))
useTitle(() => title.value)
</script>
<style lang="scss" scoped>
.no-auth-wrapper {
background: var(--site-background) url('@/assets/llama.svg?url') no-repeat fixed bottom left;
min-height: 100vh;
display: flex;
flex-direction: column;
place-items: center;
@media screen and (max-width: $fullhd) {
padding-bottom: 15rem;
}
}
.noauth-container {
max-width: $desktop;
width: 100%;
min-height: 60vh;
display: flex;
background-color: var(--white);
box-shadow: var(--shadow-md);
overflow: hidden;
@media screen and (min-width: $desktop) {
border-radius: $radius;
}
}
.image {
width: 50%;
padding: 1rem;
display: flex;
flex-direction: column;
justify-content: flex-end;
@media screen and (max-width: $tablet) {
display: none;
}
@media screen and (min-width: $tablet) {
background: url('@/assets/no-auth-image.jpg') no-repeat bottom/cover;
position: relative;
&.has-message {
justify-content: space-between;
}
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, .2);
}
> * {
position: relative;
}
}
}
.content {
display: flex;
justify-content: space-between;
flex-direction: column;
padding: 2rem 2rem 1.5rem;
@media screen and (max-width: $desktop) {
width: 100%;
max-width: 450px;
margin-inline: auto;
}
@media screen and (min-width: $desktop) {
width: 50%;
}
}
.logo {
max-width: 100%;
margin: 1rem 0;
}
.image-title {
color: var(--white);
font-size: 2.5rem;
}
</style>

View File

@ -11,7 +11,7 @@
>
<div v-if="item.title" class="notification-title">{{ item.title }}</div>
<div class="notification-content">
<template v-for="(t, k) in item.text">{{ t }}<br :key="k"/></template>
<template v-for="(t, k) in item.text" :key="k">{{ t }}<br /></template>
</div>
<div
class="buttons is-right"
@ -26,7 +26,7 @@
@click="action.callback"
:shadow="false"
class="is-small"
type="secondary"
variant="secondary"
v-for="(action, i) in item.data.actions"
>
{{ action.title }}

View File

@ -1,46 +1,43 @@
<template>
<nav
aria-label="pagination"
class="pagination is-centered p-4"
role="navigation"
v-if="totalPages > 1"
>
<router-link
:disabled="currentPage === 1"
:to="getRouteForPagination(currentPage - 1)"
class="pagination-previous"
tag="button">
{{ $t('misc.previous') }}
</router-link>
<router-link
:disabled="currentPage === totalPages"
:to="getRouteForPagination(currentPage + 1)"
class="pagination-next"
tag="button">
{{ $t('misc.next') }}
</router-link>
<ul class="pagination-list">
<template v-for="(p, i) in pages">
<li :key="'page' + i" v-if="p.isEllipsis">
<span class="pagination-ellipsis">&hellip;</span>
</li>
<li :key="'page' + i" v-else>
<router-link
:aria-label="'Goto page ' + p.number"
:class="{ 'is-current': p.number === currentPage }"
:to="getRouteForPagination(p.number)"
class="pagination-link"
>
{{ p.number }}
</router-link>
</li>
</template>
</ul>
</nav>
<nav
aria-label="pagination"
class="pagination is-centered p-4"
role="navigation"
v-if="totalPages > 1"
>
<router-link
:disabled="currentPage === 1 || null"
:to="getRouteForPagination(currentPage - 1)"
class="pagination-previous">
{{ $t('misc.previous') }}
</router-link>
<router-link
:disabled="currentPage === totalPages || null"
:to="getRouteForPagination(currentPage + 1)"
class="pagination-next">
{{ $t('misc.next') }}
</router-link>
<ul class="pagination-list">
<li :key="`page-${i}`" v-for="(p, i) in pages">
<span class="pagination-ellipsis" v-if="p.isEllipsis">&hellip;</span>
<router-link
v-else
class="pagination-link"
:aria-label="'Goto page ' + p.number"
:class="{ 'is-current': p.number === currentPage }"
:to="getRouteForPagination(p.number)"
>
{{ p.number }}
</router-link>
</li>
</ul>
</nav>
</template>
<script>
function createPagination(totalPages, currentPage) {
<script lang="ts" setup>
import {computed} from 'vue'
function createPagination(totalPages: number, currentPage: number) {
const pages = []
for (let i = 0; i < totalPages; i++) {
@ -84,42 +81,30 @@ function getRouteForPagination(page = 1, type = 'list') {
}
}
export default {
name: 'Pagination',
props: {
totalPages: {
type: Number,
required: true,
},
currentPage: {
type: Number,
required: true,
},
const props = defineProps({
totalPages: {
type: Number,
required: true,
},
computed: {
pages() {
return createPagination(this.totalPages, this.currentPage)
},
currentPage: {
type: Number,
default: 0,
},
})
methods: {
getRouteForPagination,
},
}
const pages = computed(() => createPagination(props.totalPages, props.currentPage))
</script>
<style lang="scss" scoped>
.pagination {
padding-bottom: 1rem;
padding-bottom: 1rem;
}
.pagination-previous,
.pagination-next {
&:not(:disabled):hover {
background: $scheme-main;
cursor: pointer;
}
}
.pagination-previous,
.pagination-next {
&:not(:disabled):hover {
background: $scheme-main;
cursor: pointer;
}
}
</style>

View File

@ -0,0 +1,54 @@
<template>
<slot name="trigger" :isOpen="open" :toggle="toggle"></slot>
<div class="popup" :class="{'is-open': open}" ref="popup">
<slot name="content" :isOpen="open"/>
</div>
</template>
<script setup>
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
import {onBeforeUnmount, onMounted, ref} from 'vue'
const open = ref(false)
const popup = ref(null)
const toggle = () => {
open.value = !open.value
}
function hidePopup(e) {
if (!open.value) {
return
}
// we actually want to use popup.$el, not its value.
// eslint-disable-next-line vue/no-ref-as-operand
closeWhenClickedOutside(e, popup.value, () => {
open.value = false
})
}
onMounted(() => {
document.addEventListener('click', hidePopup)
})
onBeforeUnmount(() => {
document.removeEventListener('click', hidePopup)
})
</script>
<style scoped lang="scss">
.popup {
transition: opacity $transition;
opacity: 0;
height: 0;
overflow: hidden;
position: absolute;
top: 1rem;
&.is-open {
opacity: 1;
height: auto;
}
}
</style>

View File

@ -0,0 +1,129 @@
<template>
<!-- This is a workaround to get the sw to "see" the to-be-cached version of the offline background image -->
<div class="offline" style="height: 0;width: 0;"></div>
<div class="app offline" v-if="!online">
<div class="offline-message">
<h1>{{ $t('offline.title') }}</h1>
<p>{{ $t('offline.text') }}</p>
</div>
</div>
<template v-else-if="ready">
<slot/>
</template>
<section v-else-if="error !== ''">
<no-auth-wrapper>
<card>
<p v-if="error === ERROR_NO_API_URL">
{{ $t('ready.noApiUrlConfigured') }}
</p>
<message variant="danger" v-else>
<p>
{{ $t('ready.errorOccured') }}<br/>
{{ error }}
</p>
<p>
{{ $t('ready.checkApiUrl') }}
</p>
</message>
<api-config :configure-open="true" @found-api="load"/>
</card>
</no-auth-wrapper>
</section>
<transition name="fade">
<section class="vikunja-loading" v-if="showLoading">
<Logo class="logo"/>
<p>
<span class="loader-container is-loading-small is-loading"></span>
{{ $t('ready.loading') }}
</p>
</section>
</transition>
</template>
<script lang="ts" setup>
import {ref, computed} from 'vue'
import {useStore} from 'vuex'
import Logo from '@/assets/logo.svg?component'
import ApiConfig from '@/components/misc/api-config.vue'
import Message from '@/components/misc/message.vue'
import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl'
import {useOnline} from '@/composables/useOnline'
const store = useStore()
const ready = computed(() => store.state.vikunjaReady)
const online = useOnline()
const error = ref('')
const showLoading = computed(() => !ready.value && error.value === '')
async function load() {
try {
await store.dispatch('loadApp')
} catch(e: any) {
error.value = e
}
}
load()
</script>
<style lang="scss" scoped>
.vikunja-loading {
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
width: 100vw;
flex-direction: column;
position: fixed;
top: 0;
left: 0;
bottom: 0;
right: 0;
background: var(--grey-100);
z-index: 99;
}
.logo {
margin-bottom: 1rem;
width: 100px;
height: 100px;
}
.loader-container {
margin-right: 1rem;
&.is-loading::after {
border-left-color: var(--grey-400);
border-bottom-color: var(--grey-400);
}
}
.offline {
background: url('@/assets/llama-nightscape.jpg') no-repeat center;
background-size: cover;
height: 100vh;
}
.offline-message {
text-align: center;
position: absolute;
width: 100vw;
bottom: 5vh;
color: $white;
padding: 0 1rem;
h1 {
font-weight: bold;
font-size: 1.5rem;
text-align: center;
color: $white;
font-weight: 700 !important;
font-size: 1.5rem;
}
}
</style>

View File

@ -1,22 +1,27 @@
<template>
<span class="shortcuts">
<template v-for="(k, i) in keys">
<kbd :key="i">{{ k }}</kbd>
<span v-if="i < keys.length - 1" :key="`plus${i}`">+</span>
<component :is="is" class="shortcuts">
<template v-for="(k, i) in keys" :key="i">
<kbd>{{ k }}</kbd>
<span v-if="i < keys.length - 1">{{ combination }}</span>
</template>
</span>
</component>
</template>
<script>
export default {
name: 'shortcut',
props: {
keys: {
type: Array,
required: true,
},
<script lang="ts" setup>
defineProps({
keys: {
type: Array,
required: true,
},
}
combination: {
type: String,
default: '+',
},
is: {
type: String,
default: 'div',
},
})
</script>
<style lang="scss" scoped>
@ -27,8 +32,8 @@ export default {
kbd {
padding: .1rem .35rem;
border: 1px solid $grey-300;
background: $grey-100;
border: 1px solid var(--grey-300);
background: var(--grey-100);
border-radius: 3px;
font-size: .75rem;
}

View File

@ -1,10 +1,10 @@
<template>
<x-button
type="secondary"
variant="secondary"
:icon="icon"
v-tooltip="tooltipText"
@click="changeSubscription"
:disabled="disabled"
:disabled="disabled || null"
v-if="isButton"
>
{{ buttonText }}
@ -22,100 +22,89 @@
</a>
</template>
<script>
<script lang="ts" setup>
import {computed, shallowRef} from 'vue'
import {useI18n} from 'vue-i18n'
import SubscriptionService from '@/services/subscription'
import SubscriptionModel from '@/models/subscription'
export default {
name: 'task-subscription',
data() {
return {
subscriptionService: new SubscriptionService(),
}
},
props: {
entity: {
required: true,
type: String,
},
subscription: {
required: true,
},
entityId: {
required: true,
},
isButton: {
type: Boolean,
default: true,
},
},
computed: {
tooltipText() {
if (this.disabled) {
return this.$t('task.subscription.subscribedThroughParent', {
entity: this.entity,
parent: this.subscription.entity,
})
}
import {success} from '@/message'
return this.subscription !== null ?
this.$t('task.subscription.subscribed', {entity: this.entity}) :
this.$t('task.subscription.notSubscribed', {entity: this.entity})
},
buttonText() {
return this.subscription !== null ? this.$t('task.subscription.unsubscribe') : this.$t('task.subscription.subscribe')
},
icon() {
return this.subscription !== null ? ['far', 'bell-slash'] : 'bell'
},
disabled() {
if (this.subscription === null) {
return false
}
return this.subscription.entity !== this.entity
},
const props = defineProps({
entity: {
required: true,
type: String,
},
methods: {
changeSubscription() {
if (this.disabled) {
return
}
if (this.subscription === null) {
this.subscribe()
} else {
this.unsubscribe()
}
},
subscribe() {
const subscription = new SubscriptionModel({
entity: this.entity,
entityId: this.entityId,
})
this.subscriptionService.create(subscription)
.then(() => {
this.$emit('change', subscription)
this.$message.success({message: this.$t('task.subscription.subscribeSuccess', {entity: this.entity})})
})
.catch(e => {
this.$message.error(e)
})
},
unsubscribe() {
const subscription = new SubscriptionModel({
entity: this.entity,
entityId: this.entityId,
})
this.subscriptionService.delete(subscription)
.then(() => {
this.$emit('change', null)
this.$message.success({message: this.$t('task.subscription.unsubscribeSuccess', {entity: this.entity})})
})
.catch(e => {
this.$message.error(e)
})
},
subscription: {
required: true,
},
entityId: {
required: true,
},
isButton: {
type: Boolean,
default: true,
},
})
const emit = defineEmits(['change'])
const subscriptionService = shallowRef(new SubscriptionService())
const {t} = useI18n()
const tooltipText = computed(() => {
if (disabled.value) {
return t('task.subscription.subscribedThroughParent', {
entity: props.entity,
parent: props.subscription.entity,
})
}
return props.subscription !== null ?
t('task.subscription.subscribed', {entity: props.entity}) :
t('task.subscription.notSubscribed', {entity: props.entity})
})
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 disabled = computed(() => {
if (props.subscription === null) {
return false
}
return props.subscription.entity !== props.entity
})
function changeSubscription() {
if (disabled.value) {
return
}
if (props.subscription === null) {
subscribe()
} else {
unsubscribe()
}
}
async function subscribe() {
const subscription = new SubscriptionModel({
entity: props.entity,
entityId: props.entityId,
})
await subscriptionService.value.create(subscription)
emit('change', subscription)
success({message: t('task.subscription.subscribeSuccess', {entity: props.entity})})
}
async function unsubscribe() {
const subscription = new SubscriptionModel({
entity: props.entity,
entityId: props.entityId,
})
await subscriptionService.value.delete(subscription)
emit('change', null)
success({message: t('task.subscription.unsubscribeSuccess', {entity: props.entity})})
}
</script>

View File

@ -11,31 +11,28 @@
</div>
</template>
<script>
export default {
name: 'user',
props: {
user: {
required: true,
type: Object,
},
showUsername: {
required: false,
type: Boolean,
default: true,
},
avatarSize: {
required: false,
type: Number,
default: 50,
},
isInline: {
required: false,
type: Boolean,
default: false,
},
<script lang="ts" setup>
defineProps({
user: {
required: true,
type: Object,
},
}
showUsername: {
required: false,
type: Boolean,
default: true,
},
avatarSize: {
required: false,
type: Number,
default: 50,
},
isInline: {
required: false,
type: Boolean,
default: false,
},
})
</script>
<style lang="scss" scoped>

View File

@ -12,8 +12,7 @@
class="modal-container"
:class="{'has-overflow': overflow}"
@click.self.prevent.stop="$emit('close')"
@shortkey="$emit('close')"
v-shortkey="['esc']"
v-shortcut="'Escape'"
>
<div
class="modal-content"
@ -32,14 +31,15 @@
<div class="actions">
<x-button
@click="$emit('close')"
type="tertary"
variant="tertiary"
class="has-text-danger"
>
{{ $t('misc.cancel') }}
</x-button>
<x-button
@click="$emit('submit')"
type="primary"
variant="primary"
v-cy="'modalPrimary'"
:shadow="false"
>
{{ $t('misc.doit') }}
@ -102,6 +102,7 @@ export default {
validator: validValue(VARIANTS),
},
},
emits: ['close', 'submit'],
}
</script>
@ -180,7 +181,7 @@ export default {
.hint-modal {
z-index: 4600;
::v-deep.card-content {
:deep(.card-content) {
text-align: left;
.info {
@ -193,10 +194,6 @@ export default {
align-items: center;
}
.message-body {
padding: .5rem .75rem;
}
}
}

View File

@ -9,39 +9,23 @@
/>
</template>
<script>
<script lang="ts" setup>
import {ref, computed} from 'vue'
import {useStore} from 'vuex'
import Multiselect from '@/components/input/multiselect.vue'
export default {
name: 'namespace-search',
data() {
return {
query: '',
}
},
components: {
Multiselect,
},
computed: {
namespaces() {
if (this.query === '') {
return []
}
return this.$store.state.namespaces.namespaces.filter(n => {
return !n.isArchived &&
n.id > 0 &&
n.title.toLowerCase().includes(this.query.toLowerCase())
})
},
},
methods: {
findNamespaces(query) {
this.query = query
},
select(namespace) {
this.$emit('selected', namespace)
},
},
const emit = defineEmits(['selected'])
const query = ref('')
const store = useStore()
const namespaces = computed(() => store.getters['namespaces/searchNamespace'](query.value))
function findNamespaces(newQuery: string) {
query.value = newQuery
}
function select(namespace) {
emit('selected', namespace)
}
</script>

View File

@ -52,30 +52,22 @@
</dropdown>
</template>
<script>
<script setup lang="ts">
import {ref, onMounted} from 'vue'
import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue'
import TaskSubscription from '@/components/misc/subscription.vue'
export default {
name: 'namespace-settings-dropdown',
data() {
return {
subscription: null,
}
const props = defineProps({
namespace: {
type: Object, // NamespaceModel
required: true,
},
components: {
DropdownItem,
Dropdown,
TaskSubscription,
},
props: {
namespace: {
required: true,
},
},
mounted() {
this.subscription = this.namespace.subscription
},
}
})
const subscription = ref(null)
onMounted(() => {
subscription.value = props.namespace.subscription
})
</script>

View File

@ -72,7 +72,7 @@ export default {
document.addEventListener('click', this.hidePopup)
this.interval = setInterval(this.loadNotifications, LOAD_NOTIFICATIONS_INTERVAL)
},
beforeDestroy() {
beforeUnmount() {
document.removeEventListener('click', this.hidePopup)
clearInterval(this.interval)
},
@ -93,14 +93,8 @@ export default {
closeWhenClickedOutside(e, this.$refs.popup, () => this.showNotifications = false)
}
},
loadNotifications() {
this.notificationService.getAll()
.then(r => {
this.$set(this, 'allNotifications', r)
})
.catch(e => {
this.$message.error(e)
})
async loadNotifications() {
this.allNotifications = await this.notificationService.getAll()
},
to(n, index) {
const to = {
@ -127,19 +121,125 @@ export default {
break
}
return () => {
return async () => {
if (to.name !== '') {
this.$router.push(to)
}
n.read = true
this.notificationService.update(n)
.then(r => {
this.$set(this.allNotifications, index, r)
})
.catch(e => this.$message.error(e))
this.allNotifications[index] = await this.notificationService.update(n)
}
},
},
}
</script>
<style lang="scss" scoped>
.notifications {
width: $navbar-icon-width;
.unread-indicator {
position: absolute;
top: .75rem;
right: 1.15rem;
width: .75rem;
height: .75rem;
background: var(--primary);
border-radius: 100%;
border: 2px solid var(--white);
}
.notifications-list {
position: fixed;
right: 1rem;
margin-top: 1rem;
max-height: 400px;
overflow-y: auto;
background: var(--white);
width: 350px;
max-width: calc(100vw - 2rem);
padding: .75rem .25rem;
border-radius: $radius;
box-shadow: var(--shadow-sm);
font-size: .85rem;
@media screen and (max-width: $tablet) {
max-height: calc(100vh - 1rem - #{$navbar-height});
}
.head {
font-family: $vikunja-font;
font-size: 1rem;
padding: .5rem;
}
.single-notification {
display: flex;
align-items: center;
padding: 0.25rem 0;
transition: background-color $transition;
&:hover {
background: var(--grey-100);
border-radius: $radius;
}
.read-indicator {
width: .35rem;
height: .35rem;
background: var(--primary);
border-radius: 100%;
margin-left: .5rem;
&.read {
background: transparent;
}
}
.user {
display: inline-flex;
align-items: center;
width: auto;
margin: 0 .5rem;
span {
font-family: $family-sans-serif;
}
.avatar {
height: 16px;
}
img {
margin-right: 0;
}
}
.created {
color: var(--grey-400);
}
&:last-child {
margin-bottom: .25rem;
}
a {
color: var(--grey-800);
}
}
.nothing {
text-align: center;
padding: 1rem 0;
color: var(--grey-500);
.explainer {
font-size: .75rem;
}
}
}
}
</style>

View File

@ -61,9 +61,11 @@ import TeamModel from '@/models/team'
import {CURRENT_LIST, LOADING, LOADING_MODULE, QUICK_ACTIONS_ACTIVE} from '@/store/mutation-types'
import ListModel from '@/models/list'
import createTask from '@/components/tasks/mixins/createTask'
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
import {getHistory} from '../../modules/listHistory'
import {getHistory} from '@/modules/listHistory'
import {parseTaskText, PrefixMode} from '@/modules/parseTaskText'
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
import {PREFIXES} from '@/modules/parseTaskText'
const TYPE_LIST = 'list'
const TYPE_TASK = 'task'
@ -97,9 +99,6 @@ export default {
teamService: new TeamService(),
}
},
mixins: [
createTask,
],
computed: {
active() {
const active = this.$store.state[QUICK_ACTIONS_ACTIVE]
@ -111,40 +110,33 @@ export default {
results() {
let lists = []
if (this.searchMode === SEARCH_MODE_ALL || this.searchMode === SEARCH_MODE_LISTS) {
let query = this.query
if (this.searchMode === SEARCH_MODE_LISTS) {
query = query.substr(1)
const {list} = this.parsedQuery
if (list === null) {
lists = []
} else {
const ncache = {}
const history = getHistory()
// Puts recently visited lists at the top
const allLists = [...new Set([
...history.map(l => {
return this.$store.getters['lists/getListById'](l.id)
}),
...this.$store.getters['lists/searchList'](list),
])]
lists = allLists.filter(l => {
if (typeof l === 'undefined' || l === null) {
return false
}
if (typeof ncache[l.namespaceId] === 'undefined') {
ncache[l.namespaceId] = this.$store.getters['namespaces/getNamespaceById'](l.namespaceId)
}
return !ncache[l.namespaceId].isArchived
})
}
const ncache = {}
const history = getHistory()
// Puts recently visited lists at the top
const allLists = [...new Set([
...history.map(l => {
return this.$store.getters['lists/getListById'](l.id)
}),
...Object.values(this.$store.state.lists)])]
lists = (allLists.filter(l => {
if (typeof l === 'undefined' || l === null) {
return false
}
if (l.isArchived) {
return false
}
if (typeof ncache[l.namespaceId] === 'undefined') {
ncache[l.namespaceId] = this.$store.getters['namespaces/getNamespaceById'](l.namespaceId)
}
if (ncache[l.namespaceId].isArchived) {
return false
}
return l.title.toLowerCase().includes(query.toLowerCase())
}) ?? [])
}
const cmds = this.availableCmds
@ -211,7 +203,9 @@ export default {
}
}
return this.$t('quickActions.hint')
const prefixes = PREFIXES[getQuickAddMagicMode()] ?? PREFIXES[PrefixMode.Default]
return this.$t('quickActions.hint', prefixes)
},
currentList() {
return Object.keys(this.$store.state[CURRENT_LIST]).length === 0 ? null : this.$store.state[CURRENT_LIST]
@ -240,18 +234,23 @@ export default {
return cmds
},
parsedQuery() {
return parseTaskText(this.query, getQuickAddMagicMode())
},
searchMode() {
if (this.query === '') {
return SEARCH_MODE_ALL
}
if (this.query.startsWith('#')) {
const {text, list, labels, assignees} = this.parsedQuery
if (assignees.length === 0 && text !== '') {
return SEARCH_MODE_TASKS
}
if (this.query.startsWith('*')) {
if (assignees.length === 0 && list !== null && text === '' && labels.length === 0) {
return SEARCH_MODE_LISTS
}
if (this.query.startsWith('@')) {
if (assignees.length > 0 && list === null && text === '' && labels.length === 0) {
return SEARCH_MODE_TEAMS
}
@ -272,12 +271,7 @@ export default {
return
}
let query = this.query
if (this.searchMode === SEARCH_MODE_TASKS) {
query = query.substr(1)
}
if (query === '' || this.selectedCmd !== null) {
if (this.selectedCmd !== null) {
return
}
@ -286,20 +280,44 @@ export default {
this.taskSearchTimeout = null
}
this.taskSearchTimeout = setTimeout(() => {
this.taskService.getAll({}, {s: query})
.then(r => {
r = r.map(t => {
t.type = TYPE_TASK
const list = this.$store.getters['lists/getListById'](t.listId) === null ? null : this.$store.getters['lists/getListById'](t.listId)
if (list !== null) {
t.title = `${t.title} (${list.title})`
}
const {text, list, labels} = this.parsedQuery
return t
})
this.$set(this, 'foundTasks', r)
})
const params = {
s: text,
filter_by: [],
filter_value: [],
filter_comparator: [],
}
if (list !== null) {
const l = this.$store.getters['lists/findListByExactname'](list)
if (l !== null) {
params.filter_by.push('list_id')
params.filter_value.push(l.id)
params.filter_comparator.push('equals')
}
}
if (labels.length > 0) {
const labelIds = this.$store.getters['labels/getLabelsByExactTitles'](labels).map(l => l.id)
if (labelIds.length > 0) {
params.filter_by.push('labels')
params.filter_value.push(labelIds.join())
params.filter_comparator.push('in')
}
}
this.taskSearchTimeout = setTimeout(async () => {
const r = await this.taskService.getAll({}, params)
this.foundTasks = r.map(t => {
t.type = TYPE_TASK
const list = this.$store.getters['lists/getListById'](t.listId)
if (list !== null) {
t.title = `${t.title} (${list.title})`
}
return t
})
}, 150)
},
searchTeams() {
@ -308,12 +326,7 @@ export default {
return
}
let query = this.query
if (this.searchMode === SEARCH_MODE_TEAMS) {
query = query.substr(1)
}
if (query === '' || this.selectedCmd !== null) {
if (this.query === '' || this.selectedCmd !== null) {
return
}
@ -322,15 +335,15 @@ export default {
this.teamSearchTimeout = null
}
this.teamSearchTimeout = setTimeout(() => {
this.teamService.getAll({}, {s: query})
.then(r => {
r = r.map(t => {
t.title = t.name
return t
})
this.$set(this, 'foundTeams', r)
})
const {assignees} = this.parsedQuery
this.teamSearchTimeout = setTimeout(async () => {
const teamSearchPromises = assignees.map((t) => this.teamService.getAll({}, {s: t}))
const teamsResult = await Promise.all(teamSearchPromises)
this.foundTeams = teamsResult.flatMap(team => {
team.title = team.name
return team
})
}, 150)
},
closeQuickActions() {
@ -358,7 +371,7 @@ export default {
this.doAction(this.results[0].type, this.results[0].items[0])
return
}
if (this.selectedCmd === null) {
return
}
@ -382,22 +395,20 @@ export default {
break
}
},
newTask() {
async newTask() {
if (this.currentList === null) {
return
}
this.createNewTask(this.query, 0, this.currentList.id)
.then(r => {
this.$message.success({message: this.$t('task.createSuccess')})
this.$router.push({name: 'task.detail', params: {id: r.id}})
this.closeQuickActions()
})
.catch((e) => {
this.$message.error(e)
})
const task = await this.$store.dispatch('tasks/createNewTask', {
title: this.query,
listId: this.currentList.id,
})
this.$message.success({message: this.$t('task.createSuccess')})
this.$router.push({name: 'task.detail', params: {id: task.id}})
this.closeQuickActions()
},
newList() {
async newList() {
if (this.currentList === null) {
return
}
@ -406,42 +417,27 @@ export default {
title: this.query,
namespaceId: this.currentList.namespaceId,
})
this.$store.dispatch('lists/createList', newList)
.then(r => {
this.$message.success({message: this.$t('list.create.createdSuccess')})
this.$router.push({name: 'list.index', params: {listId: r.id}})
this.closeQuickActions()
})
.catch((e) => {
this.$message.error(e)
})
const list = await this.$store.dispatch('lists/createList', newList)
this.$message.success({message: this.$t('list.create.createdSuccess')})
this.$router.push({name: 'list.index', params: {listId: list.id}})
this.closeQuickActions()
},
newNamespace() {
async newNamespace() {
const newNamespace = new NamespaceModel({title: this.query})
this.$store.dispatch('namespaces/createNamespace', newNamespace)
.then(() => {
this.$message.success({message: this.$t('namespace.create.success')})
this.closeQuickActions()
})
.catch((e) => {
this.$message.error(e)
})
await this.$store.dispatch('namespaces/createNamespace', newNamespace)
this.$message.success({message: this.$t('namespace.create.success')})
this.closeQuickActions()
},
newTeam() {
async newTeam() {
const newTeam = new TeamModel({name: this.query})
this.teamService.create(newTeam)
.then(r => {
this.$router.push({
name: 'teams.edit',
params: {id: r.id},
})
this.$message.success({message: this.$t('team.create.success')})
this.closeQuickActions()
})
.catch((e) => {
this.$message.error(e)
})
const team = await this.teamService.create(newTeam)
this.$router.push({
name: 'teams.edit',
params: {id: team.id},
})
this.$message.success({message: this.$t('team.create.success')})
this.closeQuickActions()
},
select(parentIndex, index) {
@ -490,16 +486,78 @@ export default {
<style lang="scss" scoped>
.quick-actions {
// FIXME: changed position should be an option of the modal
::v-deep.modal-content {
:deep(.modal-content) {
top: 3rem;
transform: translate(-50%, 0);
}
.action-input {
display: flex;
align-items: center;
.input {
border: 0;
font-size: 1.5rem;
}
&.has-active-cmd .input {
padding-left: .5rem;
}
.active-cmd {
font-size: 1.25rem;
margin-left: .5rem;
background-color: var(--grey-100);
color: var(--grey-800);
}
}
.results {
text-align: left;
width: 100%;
color: var(--grey-800);
.result {
&-title {
background: var(--grey-50);
padding: .5rem;
display: block;
font-size: .75rem;
}
&-items {
button {
font-size: .9rem;
width: 100%;
background: transparent;
color: var(--grey-800);
text-align: left;
box-shadow: none;
border-radius: 0;
text-transform: none;
font-family: $family-sans-serif;
font-weight: normal;
padding: .5rem .75rem;
border: none;
cursor: pointer;
&:focus, &:hover {
background: var(--grey-50);
box-shadow: none !important;
}
&:active {
background: var(--grey-100);
}
}
}
}
}
}
// HACK:
// FIXME:
.modal-container-smaller ::v-deep.hint-modal .modal-container {
.modal-container-smaller :deep(.hint-modal .modal-container) {
height: calc(100vh - 5rem);
}
</style>
</style>

View File

@ -68,7 +68,7 @@
/>
</div>
</div>
<x-button @click="add" icon="plus">
<x-button @click="add(listId)" icon="plus">
{{ $t('list.share.share') }}
</x-button>
</div>
@ -160,7 +160,7 @@
<transition name="modal">
<modal
@close="showDeleteModal = false"
@submit="remove()"
@submit="remove(listId)"
v-if="showDeleteModal"
>
<template #header>
@ -215,59 +215,41 @@ export default {
frontendUrl: (state) => state.config.frontendUrl,
}),
methods: {
load() {
async load(listId) {
// If listId == 0 the list on the calling component wasn't already loaded, so we just bail out here
if (this.listId === 0) {
if (listId === 0) {
return
}
this.linkShareService
.getAll({listId: this.listId})
.then((r) => {
this.linkShares = r
})
.catch((e) => {
this.$message.error(e)
})
this.linkShares = await this.linkShareService.getAll({listId})
},
add() {
async add(listId) {
const newLinkShare = new LinkShareModel({
right: this.selectedRight,
listId: this.listId,
listId,
name: this.name,
password: this.password,
})
this.linkShareService
.create(newLinkShare)
.then(() => {
this.selectedRight = rights.READ
this.name = ''
this.password = ''
this.showNewForm = false
this.$message.success({message: this.$t('list.share.links.createSuccess')})
this.load()
})
.catch((e) => {
this.$message.error(e)
})
await this.linkShareService.create(newLinkShare)
this.selectedRight = rights.READ
this.name = ''
this.password = ''
this.showNewForm = false
this.$message.success({message: this.$t('list.share.links.createSuccess')})
await this.load(listId)
},
remove() {
async remove(listId) {
const linkshare = new LinkShareModel({
id: this.linkIdToDelete,
listId: this.listId,
listId,
})
this.linkShareService
.delete(linkshare)
.then(() => {
this.$message.success({message: this.$t('list.share.links.deleteSuccess')})
this.load()
})
.catch((e) => {
this.$message.error(e)
})
.finally(() => {
this.showDeleteModal = false
})
try {
await this.linkShareService.delete(linkshare)
this.$message.success({message: this.$t('list.share.links.deleteSuccess')})
await this.load(listId)
} finally {
this.showDeleteModal = false
}
},
copy,
getShareLink(hash) {
@ -276,3 +258,10 @@ export default {
},
}
</script>
<style lang="scss" scoped>
// FIXME: I think this is not needed
.sharables-list:not(.card-content) {
overflow-y: auto
}
</style>

View File

@ -71,7 +71,7 @@
<div class="select">
<select
@change="toggleType(s)"
class="button mr-2"
class="mr-2"
v-model="selectedRight[s.id]"
>
<option
@ -272,45 +272,34 @@ export default {
this.load()
},
methods: {
load() {
this.stuffService
.getAll(this.stuffModel)
.then((r) => {
this.$set(this, 'sharables', r)
r.forEach((s) =>
this.$set(this.selectedRight, s.id, s.right),
)
})
.catch((e) => {
this.$message.error(e)
})
async load() {
this.sharables = await this.stuffService.getAll(this.stuffModel)
this.sharables.forEach((s) =>
this.selectedRight[s.id] = s.right,
)
},
deleteSharable() {
async deleteSharable() {
if (this.shareType === 'user') {
this.stuffModel.userId = this.sharable.username
} else if (this.shareType === 'team') {
this.stuffModel.teamId = this.sharable.id
}
this.stuffService
.delete(this.stuffModel)
.then(() => {
this.showDeleteModal = false
for (const i in this.sharables) {
if (
(this.sharables[i].username === this.stuffModel.userId && this.shareType === 'user') ||
(this.sharables[i].id === this.stuffModel.teamId && this.shareType === 'team')
) {
this.sharables.splice(i, 1)
}
}
this.$message.success({message: this.$t('list.share.userTeam.removeSuccess', {type: this.shareTypeName, sharable: this.sharableName})})
})
.catch((e) => {
this.$message.error(e)
})
await this.stuffService.delete(this.stuffModel)
this.showDeleteModal = false
for (const i in this.sharables) {
if (
(this.sharables[i].username === this.stuffModel.userId && this.shareType === 'user') ||
(this.sharables[i].id === this.stuffModel.teamId && this.shareType === 'team')
) {
this.sharables.splice(i, 1)
}
}
this.$message.success({message: this.$t('list.share.userTeam.removeSuccess', {type: this.shareTypeName, sharable: this.sharableName})})
},
add(admin) {
async add(admin) {
if (admin === null) {
admin = false
}
@ -325,17 +314,12 @@ export default {
this.stuffModel.teamId = this.sharable.id
}
this.stuffService
.create(this.stuffModel)
.then(() => {
this.$message.success({message: this.$t('list.share.userTeam.addedSuccess', {type: this.shareTypeName})})
this.load()
})
.catch((e) => {
this.$message.error(e)
})
await this.stuffService.create(this.stuffModel)
this.$message.success({message: this.$t('list.share.userTeam.addedSuccess', {type: this.shareTypeName})})
await this.load()
},
toggleType(sharable) {
async toggleType(sharable) {
if (
this.selectedRight[sharable.id] !== rights.ADMIN &&
this.selectedRight[sharable.id] !== rights.READ &&
@ -351,43 +335,32 @@ export default {
this.stuffModel.teamId = sharable.id
}
this.stuffService
.update(this.stuffModel)
.then((r) => {
for (const i in this.sharables) {
if (
(this.sharables[i].username ===
this.stuffModel.userId &&
this.shareType === 'user') ||
(this.sharables[i].id === this.stuffModel.teamId &&
this.shareType === 'team')
) {
this.$set(this.sharables[i], 'right', r.right)
}
}
this.$message.success({message: this.$t('list.share.userTeam.updatedSuccess', {type: this.shareTypeName})})
})
.catch((e) => {
this.$message.error(e)
})
const r = await this.stuffService.update(this.stuffModel)
for (const i in this.sharables) {
if (
(this.sharables[i].username ===
this.stuffModel.userId &&
this.shareType === 'user') ||
(this.sharables[i].id === this.stuffModel.teamId &&
this.shareType === 'team')
) {
this.sharables[i].right = r.right
}
}
this.$message.success({message: this.$t('list.share.userTeam.updatedSuccess', {type: this.shareTypeName})})
},
find(query) {
async find(query) {
if (query === '') {
this.$set(this, 'found', [])
this.clearAll()
return
}
this.searchService
.getAll({}, {s: query})
.then((response) => {
this.$set(this, 'found', response)
})
.catch((e) => {
this.$message.error(e)
})
this.found = await this.searchService.getAll({}, {s: query})
},
clearAll() {
this.$set(this, 'found', [])
this.found = []
},
},
}

View File

@ -3,14 +3,13 @@
<div class="field is-grouped">
<p class="control has-icons-left is-expanded">
<textarea
:disabled="taskService.loading || null"
class="input"
:disabled="taskService.loading || undefined"
class="add-task-textarea input"
:placeholder="$t('list.list.addPlaceholder')"
type="text"
rows="1"
v-focus
v-model="newTaskTitle"
ref="newTaskInput"
:style="{'height': `${textAreaHeight}px`}"
@keyup="errorMessage = ''"
@keydown.enter="handleEnter"
/>
@ -20,7 +19,8 @@
</p>
<p class="control">
<x-button
:disabled="newTaskTitle === '' || taskService.loading || null"
class="add-task-button"
:disabled="newTaskTitle === '' || taskService.loading || undefined"
@click="addTask()"
icon="plus"
:loading="taskService.loading"
@ -32,117 +32,171 @@
<p class="help is-danger" v-if="errorMessage !== ''">
{{ errorMessage }}
</p>
<quick-add-magic v-if="errorMessage === ''"/>
<quick-add-magic v-else />
</div>
</template>
<script>
import TaskService from '../../services/task'
import createTask from '@/components/tasks/mixins/createTask'
<script setup lang="ts">
import {ref, watch, unref, shallowReactive} from 'vue'
import {useI18n} from 'vue-i18n'
import {useStore} from 'vuex'
import { tryOnMounted, debouncedWatch, useWindowSize, MaybeRef } from '@vueuse/core'
import TaskService from '@/services/task'
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
const INITIAL_SCROLL_HEIGHT = 40
const cleanupTitle = title => {
function cleanupTitle(title: string) {
return title.replace(/^((\* |\+ |- )(\[ \] )?)/g, '')
}
export default {
name: 'add-task',
data() {
return {
newTaskTitle: '',
taskService: new TaskService(),
errorMessage: '',
textAreaHeight: INITIAL_SCROLL_HEIGHT,
function useAutoHeightTextarea(value: MaybeRef<string>) {
const textarea = ref<HTMLInputElement>()
const minHeight = ref(0)
// adapted from https://github.com/LeaVerou/stretchy/blob/47f5f065c733029acccb755cae793009645809e2/src/stretchy.js#L34
function resize(textareaEl: HTMLInputElement|undefined) {
if (!textareaEl) return
let empty
// the value here is the the attribute value
if (!textareaEl.value && textareaEl.placeholder) {
empty = true
textareaEl.value = textareaEl.placeholder
}
},
mixins: [
createTask,
],
components: {
QuickAddMagic,
},
props: {
defaultPosition: {
type: Number,
required: false,
const cs = getComputedStyle(textareaEl)
textareaEl.style.minHeight = ''
textareaEl.style.height = '0'
const offset = textareaEl.offsetHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom)
const height = textareaEl.scrollHeight + offset + 'px'
textareaEl.style.height = height
// calculate min-height for the first time
if (!minHeight.value) {
minHeight.value = parseFloat(height)
}
textareaEl.style.minHeight = minHeight.value.toString()
if (empty) {
textareaEl.value = ''
}
}
tryOnMounted(() => {
if (textarea.value) {
// we don't want scrollbars
textarea.value.style.overflowY = 'hidden'
}
})
const { width: windowWidth } = useWindowSize()
debouncedWatch(
windowWidth,
() => resize(textarea.value),
{ debounce: 200 },
)
// It is not possible to get notified of a change of the value attribute of a textarea without workarounds (setTimeout)
// So instead we watch the value that we bound to it.
watch(
() => [textarea.value, unref(value)],
() => resize(textarea.value),
{
immediate: true, // calculate initial size
flush: 'post', // resize after value change is rendered to DOM
},
)
return textarea
}
const props = defineProps({
defaultPosition: {
type: Number,
required: false,
},
watch: {
newTaskTitle(newVal) {
let scrollHeight = this.$refs.newTaskInput.scrollHeight
if (scrollHeight < INITIAL_SCROLL_HEIGHT || newVal === '') {
scrollHeight = INITIAL_SCROLL_HEIGHT
}
})
this.textAreaHeight = scrollHeight
},
},
methods: {
addTask() {
if (this.newTaskTitle === '') {
this.errorMessage = this.$t('list.create.addTitleRequired')
return
}
this.errorMessage = ''
const emit = defineEmits(['taskAdded'])
if (this.taskService.loading) {
return
}
const newTaskTitle = ref('')
const newTaskInput = useAutoHeightTextarea(newTaskTitle)
const newTasks = []
this.newTaskTitle.split(/[\r\n]+/).forEach(t => {
const title = cleanupTitle(t)
if (title === '') {
return
}
newTasks.push(
this.createNewTask(title, 0, this.$store.state.auth.settings.defaultListId, this.defaultPosition)
.then(task => {
this.$emit('taskAdded', task)
return task
}),
)
})
const { t } = useI18n()
const store = useStore()
Promise.all(newTasks)
.then(() => {
this.newTaskTitle = ''
})
.catch(e => {
if (e === 'NO_LIST') {
this.errorMessage = this.$t('list.create.addListRequired')
return
}
this.$message.error(e)
})
},
handleEnter(e) {
// when pressing shift + enter we want to continue as we normally would. Otherwise, we want to create
// the new task(s). The vue event modifier don't allow this, hence this method.
if (e.shiftKey) {
return
}
const taskService = shallowReactive(new TaskService())
const errorMessage = ref('')
e.preventDefault()
this.addTask()
},
},
async function addTask() {
if (newTaskTitle.value === '') {
errorMessage.value = t('list.create.addTitleRequired')
return
}
errorMessage.value = ''
if (taskService.loading) {
return
}
const taskTitleBackup = newTaskTitle.value
const newTasks = newTaskTitle.value.split(/[\r\n]+/).map(async uncleanedTitle => {
const title = cleanupTitle(uncleanedTitle)
if (title === '') {
return
}
const task = await store.dispatch('tasks/createNewTask', {
title,
listId: store.state.auth.settings.defaultListId,
position: props.defaultPosition,
})
emit('taskAdded', task)
return task
})
try {
newTaskTitle.value = ''
await Promise.all(newTasks)
} catch (e: any) {
newTaskTitle.value = taskTitleBackup
if (e?.message === 'NO_LIST') {
errorMessage.value = t('list.create.addListRequired')
return
}
throw e
}
}
function handleEnter(e: KeyboardEvent) {
// when pressing shift + enter we want to continue as we normally would. Otherwise, we want to create
// the new task(s). The vue event modifier don't allow this, hence this method.
if (e.shiftKey) {
return
}
e.preventDefault()
addTask()
}
</script>
<style lang="scss" scoped>
.task-add {
margin-bottom: 0;
.button {
height: 2.5rem;
}
}
.input, .textarea {
.add-task-button {
height: 2.5rem;
}
.add-task-textarea {
transition: border-color $transition;
resize: none;
}
</style>

View File

@ -1,4 +1,10 @@
<template>
<card
class="taskedit"
:title="$t('list.list.editTask')"
@close="$emit('close')"
:has-close="true"
>
<form @submit.prevent="editTaskSubmit()">
<div class="field">
<label class="label" for="tasktext">{{ $t('task.attributes.title') }}</label>
@ -66,31 +72,27 @@
{{ $t('task.openDetail') }}
</router-link>
</form>
</card>
</template>
<script>
import ListService from '../../services/list'
import AsyncEditor from '@/components/input/AsyncEditor'
import TaskService from '../../services/task'
import TaskModel from '../../models/task'
import priorities from '../../models/constants/priorities'
import EditLabels from './partials/editLabels'
import Reminders from './partials/reminders'
import ColorPicker from '../input/colorPicker'
import LoadingComponent from '../misc/loading'
import ErrorComponent from '../misc/error'
export default {
name: 'edit-task',
data() {
return {
listId: this.$route.params.id,
listService: new ListService(),
taskService: new TaskService(),
priorities: priorities,
list: {},
editorActive: false,
newTask: new TaskModel(),
isTaskEdit: false,
taskEditTask: TaskModel,
}
@ -99,12 +101,7 @@ export default {
ColorPicker,
Reminders,
EditLabels,
editor: () => ({
component: import('../../components/input/editor'),
loading: LoadingComponent,
error: ErrorComponent,
timeout: 60000,
}),
editor: AsyncEditor,
},
props: {
task: {
@ -139,18 +136,44 @@ export default {
this.editorActive = false
this.$nextTick(() => (this.editorActive = true))
},
editTaskSubmit() {
this.taskService
.update(this.taskEditTask)
.then((r) => {
this.$set(this, 'taskEditTask', r)
this.initTaskFields()
this.$message.success({message: this.$t('task.detail.updateSuccess')})
})
.catch((e) => {
this.$message.error(e)
})
async editTaskSubmit() {
this.taskEditTask = await this.taskService.update(this.taskEditTask)
this.initTaskFields()
this.$message.success({message: this.$t('task.detail.updateSuccess')})
},
},
}
</script>
<style lang="scss" scoped>
.priority-select {
.select,
select {
width: 100%;
}
}
ul.assingees {
list-style: none;
margin: 0;
li {
padding: 0.5rem 0.5rem 0;
a {
float: right;
color: var(--danger);
transition: all $transition;
}
}
}
.tag {
margin-right: 0.5rem;
margin-bottom: 0.5rem;
&:last-child {
margin-right: 0;
}
}
</style>

View File

@ -2,23 +2,15 @@
<div class="gantt-chart">
<div class="filter-container">
<div class="items">
<x-button
@click.prevent.stop="showTaskFilter = !showTaskFilter"
type="secondary"
icon="filter"
>
{{ $t('filters.title') }}
</x-button>
<filter-popup
v-model="params"
@update:modelValue="loadTasks()"
/>
</div>
<filter-popup
@change="loadTasks()"
:visible="showTaskFilter"
v-model="params"
/>
</div>
<div class="dates">
<template v-for="(y, yk) in days">
<div :key="yk + 'year'" class="months">
<template v-for="(y, yk) in days" :key="yk + 'year'">
<div class="months">
<div
:key="mk + 'month'"
class="month"
@ -86,9 +78,8 @@
:w="t.durationDays * dayWidth"
:x="t.offsetDays * dayWidth - 6"
:y="0"
@clicked="setTaskDragged(t)"
@dragstop="resizeTask"
@resizestop="resizeTask"
@dragstop="(e) => resizeTask(t, e)"
@resizestop="(e) => resizeTask(t, e)"
axis="x"
class="task"
>
@ -136,9 +127,8 @@
:sticks="['mr', 'ml']"
:x="dayOffsetUntilToday * dayWidth - 6"
:y="0"
@clicked="setTaskDragged(t)"
@dragstop="resizeTask"
@resizestop="resizeTask"
@dragstop="(e) => resizeTask(t, e)"
@resizestop="(e) => resizeTask(t, e)"
axis="x"
class="task nodate"
v-tooltip="$t('list.gantt.noDates')"
@ -169,15 +159,13 @@
</x-button>
</form>
<transition name="fade">
<card
<edit-task
v-if="isTaskEdit"
class="taskedit"
:title="$t('list.list.editTask')"
@close="() => {isTaskEdit = false;taskToEdit = null}"
:has-close="true"
>
<edit-task :task="taskToEdit"/>
</card>
:task="taskToEdit"
/>
</transition>
</div>
</template>
@ -195,6 +183,8 @@ import {mapState} from 'vuex'
import Rights from '../../models/constants/rights.json'
import FilterPopup from '@/components/list/partials/filter-popup.vue'
import {colorIsDark} from '@/helpers/color/colorIsDark'
export default {
name: 'GanttChart',
components: {
@ -213,10 +203,10 @@ export default {
default: false,
},
dateFrom: {
default: new Date(new Date().setDate(new Date().getDate() - 15)),
default: () => new Date(new Date().setDate(new Date().getDate() - 15)),
},
dateTo: {
default: new Date(new Date().setDate(new Date().getDate() + 30)),
default: () => new Date(new Date().setDate(new Date().getDate() + 30)),
},
// The width of a day in pixels, used to calculate all sorts of things.
dayWidth: {
@ -232,7 +222,6 @@ export default {
theTasks: [], // Pretty much a copy of the prop, since we cant mutate the prop directly
tasksWithoutDates: [],
taskService: new TaskService(),
taskDragged: null, // Saves to currently dragged task to be able to update it
fullWidth: 0,
now: new Date(),
dayOffsetUntilToday: 0,
@ -242,7 +231,6 @@ export default {
newTaskFieldActive: false,
priorities: priorities,
taskCollectionService: new TaskCollectionService(),
showTaskFilter: false,
params: {
sort_by: ['done', 'id'],
@ -266,6 +254,7 @@ export default {
canWrite: (state) => state.currentList.maxRight > Rights.READ,
}),
methods: {
colorIsDark,
buildTheGanttChart() {
this.setDates()
this.prepareGanttDays()
@ -298,56 +287,43 @@ export default {
this.fullWidth += this.dayWidth
}
console.debug('prepareGanttDays; years:', years)
this.$set(this, 'days', years)
this.days = years
},
parseTasks() {
this.setDates()
this.loadTasks()
},
loadTasks() {
this.$set(this, 'theTasks', [])
this.$set(this, 'tasksWithoutDates', [])
const getAllTasks = (page = 1) => {
return this.taskCollectionService
.getAll({listId: this.listId}, this.params, page)
.then((tasks) => {
if (page < this.taskCollectionService.totalPages) {
return getAllTasks(page + 1).then((nextTasks) => {
return tasks.concat(nextTasks)
})
} else {
return tasks
}
})
.catch((e) => {
return Promise.reject(e)
})
async loadTasks() {
this.theTasks = []
this.tasksWithoutDates = []
const getAllTasks = async (page = 1) => {
const tasks = await this.taskCollectionService.getAll({listId: this.listId}, this.params, page)
if (page < this.taskCollectionService.totalPages) {
const nextTasks = await getAllTasks(page + 1)
return tasks.concat(nextTasks)
}
return tasks
}
getAllTasks()
.then((tasks) => {
this.theTasks = tasks
.filter((t) => {
if (t.startDate === null && !t.done) {
this.tasksWithoutDates.push(t)
}
return (
t.startDate >= this.startDate &&
t.endDate <= this.endDate
)
})
.map((t) => {
return this.addGantAttributes(t)
})
.sort(function (a, b) {
if (a.startDate < b.startDate) return -1
if (a.startDate > b.startDate) return 1
return 0
})
const tasks = await getAllTasks()
this.theTasks = tasks
.filter((t) => {
if (t.startDate === null && !t.done) {
this.tasksWithoutDates.push(t)
}
return (
t.startDate >= this.startDate &&
t.endDate <= this.endDate
)
})
.catch((e) => {
this.$message.error(e)
.map((t) => this.addGantAttributes(t))
.sort(function (a, b) {
if (a.startDate < b.startDate) return -1
if (a.startDate > b.startDate) return 1
return 0
})
},
addGantAttributes(t) {
@ -360,15 +336,14 @@ export default {
t.offsetDays = Math.floor((t.startDate - this.startDate) / 1000 / 60 / 60 / 24)
return t
},
setTaskDragged(t) {
this.taskDragged = t
},
resizeTask(newRect) {
async resizeTask(taskDragged, newRect) {
if (this.isTaskEdit) {
return
}
const didntHaveDates = this.taskDragged.startDate === null ? true : false
let newTask = {...taskDragged}
const didntHaveDates = newTask.startDate === null ? true : false
let startDate = new Date(this.startDate)
startDate.setDate(
@ -378,62 +353,52 @@ export default {
startDate.setUTCMinutes(0)
startDate.setUTCSeconds(0)
startDate.setUTCMilliseconds(0)
this.taskDragged.startDate = startDate
newTask.startDate = startDate
let endDate = new Date(startDate)
endDate.setDate(
startDate.getDate() + newRect.width / this.dayWidth,
)
this.taskDragged.startDate = startDate
this.taskDragged.endDate = endDate
newTask.startDate = startDate
newTask.endDate = endDate
// We take the task from the overall tasks array because the one in it has bad data after it was updated once.
// FIXME: This is a workaround. We should use a better mechanism to get the task or, even better,
// prevent it from containing outdated Data in the first place.
for (const tt in this.theTasks) {
if (this.theTasks[tt].id === this.taskDragged.id) {
this.$set(this, 'taskDragged', this.theTasks[tt])
if (this.theTasks[tt].id === newTask.id) {
newTask = this.theTasks[tt]
break
}
}
const ganttData = {
endDate: this.taskDragged.endDate,
durationDays: this.taskDragged.durationDays,
offsetDays: this.taskDragged.offsetDays,
endDate: newTask.endDate,
durationDays: newTask.durationDays,
offsetDays: newTask.offsetDays,
}
this.taskService
.update(this.taskDragged)
.then(r => {
r.endDate = ganttData.endDate
r.durationDays = ganttData.durationDays
r.offsetDays = ganttData.offsetDays
const r = await this.taskService.update(newTask)
r.endDate = ganttData.endDate
r.durationDays = ganttData.durationDays
r.offsetDays = ganttData.offsetDays
// If the task didn't have dates before, we'll update the list
if (didntHaveDates) {
for (const t in this.tasksWithoutDates) {
if (this.tasksWithoutDates[t].id === r.id) {
this.tasksWithoutDates.splice(t, 1)
break
}
}
this.theTasks.push(this.addGantAttributes(r))
} else {
for (const tt in this.theTasks) {
if (this.theTasks[tt].id === r.id) {
this.$set(
this.theTasks,
tt,
this.addGantAttributes(r),
)
break
}
}
// If the task didn't have dates before, we'll update the list
if (didntHaveDates) {
for (const t in this.tasksWithoutDates) {
if (this.tasksWithoutDates[t].id === r.id) {
this.tasksWithoutDates.splice(t, 1)
break
}
})
.catch((e) => {
this.$message.error(e)
})
}
this.theTasks.push(this.addGantAttributes(r))
} else {
for (const tt in this.theTasks) {
if (this.theTasks[tt].id === r.id) {
this.theTasks[tt] = this.addGantAttributes(r)
break
}
}
}
},
editTask(task) {
this.taskToEdit = task
@ -453,7 +418,7 @@ export default {
this.$nextTick(() => (this.newTaskFieldActive = false))
}
},
addNewTask() {
async addNewTask() {
if (!this.newTaskFieldActive) {
return
}
@ -461,16 +426,10 @@ export default {
title: this.newTaskTitle,
listId: this.listId,
})
this.taskService
.create(task)
.then((r) => {
this.tasksWithoutDates.push(this.addGantAttributes(r))
this.newTaskTitle = ''
this.hideCrateNewTask()
})
.catch((e) => {
this.$message.error(e)
})
const r = await this.taskService.create(task)
this.tasksWithoutDates.push(this.addGantAttributes(r))
this.newTaskTitle = ''
this.hideCrateNewTask()
},
formatYear(date) {
return this.format(date, 'MMMM, yyyy')
@ -478,3 +437,196 @@ export default {
},
}
</script>
<style lang="scss" scoped>
$gantt-border: 1px solid var(--grey-200);
$gantt-vertical-border-color: var(--grey-100);
.gantt-chart {
overflow-x: auto;
border-top: 1px solid var(--grey-200);
.dates {
display: flex;
text-align: center;
.months {
display: flex;
.month {
padding: 0.5rem 0 0;
border-right: $gantt-border;
font-family: $vikunja-font;
font-weight: bold;
&:last-child {
border-right: none;
}
.days {
display: flex;
.day {
padding: 0.5rem 0;
font-weight: normal;
&.today {
background: var(--primary);
color: var(--white);
border-radius: 5px 5px 0 0;
font-weight: bold;
}
.theday {
padding: 0 .5rem;
width: 100%;
display: block;
}
.weekday {
font-size: 0.8rem;
}
}
}
}
}
}
.tasks {
max-width: unset !important;
border-top: $gantt-border;
.row {
height: 45px;
.task {
display: inline-block;
border: 2px solid var(--primary);
font-size: 0.85rem;
margin: 0.5rem;
border-radius: 6px;
padding: 0.25rem 0.5rem;
cursor: grab;
position: relative;
height: 31px !important;
-webkit-touch-callout: none; // iOS Safari
user-select: none; // Non-prefixed version
&.is-current-edit {
border-color: var(--warning) !important;
}
&.has-light-text {
color: var(--light);
&.done span:after {
border-top: 1px solid var(--light);
}
.edit-toggle {
color: var(--light);
}
}
&.has-dark-text {
color: var(--text);
&.done span:after {
border-top: 1px solid var(--dark);
}
.edit-toggle {
color: var(--text);
}
}
&.done span {
position: relative;
&::after {
content: '';
position: absolute;
right: 0;
left: 0;
top: 57%;
}
}
span:not(.high-priority) {
max-width: calc(100% - 20px);
display: inline-block;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
&.has-high-priority {
max-width: calc(100% - 90px);
}
&.has-not-so-high-priority {
max-width: calc(100% - 70px);
}
&.has-super-high-priority {
max-width: calc(100% - 111px);
}
&.icon {
width: 10px;
text-align: center;
}
}
.high-priority {
margin: 0 0 0 .5rem;
vertical-align: bottom;
}
.edit-toggle {
float: right;
cursor: pointer;
margin-right: 4px;
}
&.nodate {
border: 2px dashed var(--grey-300);
background: var(--grey-100);
}
&:active {
cursor: grabbing;
}
}
}
}
.taskedit {
position: fixed;
top: 10vh;
right: 10vw;
z-index: 5;
// FIXME: should be an option of the card, e.g. overflow
:deep(.card-content) {
max-height: 60vh;
overflow-y: auto;
}
}
.add-new-task {
padding: 1rem .7rem .4rem .7rem;
display: flex;
max-width: 450px;
.input {
margin-right: .7rem;
font-size: .8rem;
}
.button {
font-size: .68rem;
}
}
}
</style>

View File

@ -1,141 +0,0 @@
import {parseTaskText} from '@/modules/parseTaskText'
import TaskModel from '@/models/task'
import {formatISO} from 'date-fns'
import LabelTask from '@/models/labelTask'
import LabelModel from '@/models/label'
import LabelTaskService from '@/services/labelTask'
import {mapState} from 'vuex'
import UserService from '@/services/user'
import TaskService from '@/services/task'
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
export default {
data() {
return {
taskService: TaskService,
labelTaskService: LabelTaskService,
userService: UserService,
}
},
created() {
this.labelTaskService = new LabelTaskService()
this.userService = new UserService()
this.taskService = new TaskService()
},
computed: mapState({
labels: state => state.labels.labels,
}),
methods: {
createNewTask(newTaskTitle, bucketId = 0, lId = 0, position = 0) {
const parsedTask = parseTaskText(newTaskTitle, getQuickAddMagicMode())
const assignees = []
// Uses the following ways to get the list id of the new task:
// 1. If specified in quick add magic, look in store if it exists and use it if it does
// 2. Else check if a list was passed as parameter
// 3. Otherwise use the id from the route parameter
// 4. If none of the above worked, reject the promise with an error.
let listId = null
if (parsedTask.list !== null) {
const list = this.$store.getters['lists/findListByExactname'](parsedTask.list)
listId = list === null ? null : list.id
}
if (lId !== 0) {
listId = lId
}
if (typeof this.$route.params.listId !== 'undefined') {
listId = parseInt(this.$route.params.listId)
}
if (typeof listId === 'undefined' || listId === null) {
return Promise.reject('NO_LIST')
}
// Separate closure because we need to wait for the results of the user search if users were entered in the
// task create request. Because _that_ happens in a promise, we'll need something to call when it resolves.
const createTask = () => {
const task = new TaskModel({
title: parsedTask.text,
listId: listId,
dueDate: parsedTask.date !== null ? formatISO(parsedTask.date) : null, // I don't know why, but it all goes up in flames when I just pass in the date normally.
priority: parsedTask.priority,
assignees: assignees,
bucketId: bucketId,
position: position,
})
return this.taskService.create(task)
.then(task => {
if (parsedTask.labels.length > 0) {
const labelAddsToWaitFor = []
const addLabelToTask = label => {
const labelTask = new LabelTask({
taskId: task.id,
labelId: label.id,
})
return this.labelTaskService.create(labelTask)
.then(result => {
task.labels.push(label)
return Promise.resolve(result)
})
.catch(e => Promise.reject(e))
}
// Then do everything that is involved in finding, creating and adding the label to the task
parsedTask.labels.forEach(labelTitle => {
// Check if the label exists
const label = Object.values(this.labels).find(l => {
return l.title.toLowerCase() === labelTitle.toLowerCase()
})
// Label found, use it
if (typeof label !== 'undefined') {
labelAddsToWaitFor.push(addLabelToTask(label))
} else {
// label not found, create it
const label = new LabelModel({title: labelTitle})
labelAddsToWaitFor.push(this.$store.dispatch('labels/createLabel', label)
.then(res => {
return addLabelToTask(res)
})
.catch(e => Promise.reject(e)),
)
}
})
// This waits until all labels are created and added to the task
return Promise.all(labelAddsToWaitFor)
.then(() => {
return Promise.resolve(task)
})
}
return Promise.resolve(task)
})
.catch(e => Promise.reject(e))
}
if (parsedTask.assignees.length > 0) {
const searches = []
parsedTask.assignees.forEach(a => {
searches.push(this.userService.getAll({}, {s: a})
.then(users => {
const user = users.find(u => u.username.toLowerCase() === a.toLowerCase())
if (typeof user !== 'undefined') {
assignees.push(user)
}
return Promise.resolve(users)
}),
)
})
return Promise.all(searches)
.then(() => createTask())
}
return createTask()
},
},
}

View File

@ -1,14 +1,14 @@
import TaskCollectionService from '@/services/taskCollection'
// FIXME: merge with DEFAULT_PARAMS in filters.vue
const DEFAULT_PARAMS = {
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.
@ -26,7 +26,7 @@ export default {
searchTerm: '',
showTaskFilter: false,
params: DEFAULT_PARAMS,
params: {...getDefaultParams()},
}
},
watch: {
@ -38,13 +38,12 @@ export default {
'$route.path': 'loadTasksOnSavedFilter',
},
methods: {
loadTasks(
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 (
@ -76,17 +75,9 @@ export default {
}
this.tasks = []
this.taskCollectionService.getAll(list, params, page)
.then(r => {
this.tasks = r
this.currentPage = page
this.loadedList = JSON.parse(JSON.stringify(currentList))
})
.catch(e => {
this.$message.error(e)
})
this.tasks = await this.taskCollectionService.getAll(list, params, page)
this.currentPage = page
this.loadedList = JSON.parse(JSON.stringify(currentList))
},
loadTasksForPage(e) {
@ -102,7 +93,7 @@ export default {
this.initTasks(page, search)
},
loadTasksOnSavedFilter() {
if(typeof this.$route.params.listId !== 'undefined' && parseInt(this.$route.params.listId) < 0) {
if (typeof this.$route.params.listId !== 'undefined' && parseInt(this.$route.params.listId) < 0) {
this.loadTasks(1, '', null, true)
}
},

View File

@ -35,7 +35,7 @@
<div class="filename">{{ a.file.name }}</div>
<div class="info">
<p class="collapses">
<i18n path="task.attachment.createdBy">
<i18n-t keypath="task.attachment.createdBy">
<span v-tooltip="formatDate(a.created)">
{{ formatDateSince(a.created) }}
</span>
@ -44,7 +44,7 @@
:user="a.createdBy"
:is-inline="true"
/>
</i18n>
</i18n-t>
<span>
{{ a.file.getHumanSize() }}
</span>
@ -83,7 +83,7 @@
@click="$refs.files.click()"
class="mb-4"
icon="cloud-upload-alt"
type="secondary"
variant="secondary"
:shadow="false"
>
{{ $t('task.attachment.upload') }}
@ -218,24 +218,19 @@ export default {
uploadFiles(files) {
uploadFiles(this.attachmentService, this.taskId, files)
},
deleteAttachment() {
this.attachmentService
.delete(this.attachmentToDelete)
.then((r) => {
this.$store.commit(
'attachments/removeById',
this.attachmentToDelete.id,
)
this.$message.success(r)
})
.catch((e) => {
this.$message.error(e)
})
.finally(() => {
this.showDeleteModal = false
})
async deleteAttachment() {
try {
const r = await this.attachmentService.delete(this.attachmentToDelete)
this.$store.commit(
'attachments/removeById',
this.attachmentToDelete.id,
)
this.$message.success(r)
} finally{
this.showDeleteModal = false
}
},
viewOrDownload(attachment) {
async viewOrDownload(attachment) {
if (
attachment.file.name.endsWith('.jpg') ||
attachment.file.name.endsWith('.png') ||
@ -243,9 +238,7 @@ export default {
attachment.file.name.endsWith('.gif')
) {
this.showImageModal = true
this.attachmentService.getBlobUrl(attachment).then((url) => {
this.attachmentImageBlobUrl = url
})
this.attachmentImageBlobUrl = await this.attachmentService.getBlobUrl(attachment)
} else {
this.downloadAttachment(attachment)
}
@ -256,3 +249,137 @@ export default {
},
}
</script>
<style lang="scss" scoped>
.attachments {
input[type=file] {
display: none;
}
.files {
margin-bottom: 1rem;
.attachment {
margin-bottom: .5rem;
display: block;
transition: background-color $transition;
border-radius: $radius;
padding: .5rem;
&:hover {
background-color: var(--grey-200);
}
.filename {
font-weight: bold;
margin-bottom: .25rem;
color: var(--text);
}
.info {
color: var(--grey-500);
font-size: .9rem;
p {
margin-bottom: 0;
display: flex;
> span:not(:last-child):after,
> a:not(:last-child):after {
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;
}
}
}
}
}
}
}
@media screen and (max-width: $tablet) {
.button {
width: 100%;
}
}
.dropzone {
position: fixed;
background: rgba(250, 250, 250, 0.8);
top: 0;
left: 0;
bottom: 0;
right: 0;
z-index: 100;
text-align: center;
&.hidden {
display: none;
}
.drop-hint {
position: absolute;
bottom: 0;
left: 0;
right: 0;
.icon {
width: 100%;
font-size: 5rem;
height: auto;
text-shadow: var(--shadow-md);
animation: bounce 2s infinite;
}
.hint {
margin: .5rem auto 2rem;
border-radius: 2px;
box-shadow: var(--shadow-md);
background: var(--primary);
padding: 1rem;
color: var(--white);
width: 100%;
max-width: 300px;
}
}
}
}
@keyframes bounce {
from,
20%,
53%,
80%,
to {
animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
transform: translate3d(0, 0, 0);
}
40%,
43% {
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
transform: translate3d(0, -30px, 0);
}
70% {
animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06);
transform: translate3d(0, -15px, 0);
}
90% {
transform: translate3d(0, -4px, 0);
}
}
</style>

View File

@ -39,7 +39,7 @@ export default {
<style scoped lang="scss">
.checklist-summary {
color: $grey-500;
color: var(--grey-500);
display: inline-flex;
align-items: center;
@ -49,10 +49,10 @@ export default {
margin-right: .25rem;
circle {
stroke: $grey-400;
stroke: var(--grey-400);
&:last-child {
stroke: $primary;
stroke: var(--primary);
}
}
}

View File

@ -66,7 +66,7 @@
</transition>
</div>
<editor
:has-preview="true"
:hasPreview="true"
:is-edit-enabled="canWrite"
:upload-callback="attachmentUpload"
:upload-enabled="true"
@ -110,7 +110,7 @@
taskCommentService.loading &&
!isCommentEdit,
}"
:has-preview="false"
:hasPreview="false"
:upload-callback="attachmentUpload"
:upload-enabled="true"
:placeholder="$t('task.comment.placeholder')"
@ -134,9 +134,9 @@
<transition name="modal">
<modal
@close="showDeleteModal = false"
@submit="deleteComment()"
v-if="showDeleteModal"
@close="showDeleteModal = false"
@submit="() => deleteComment(commentToDelete)"
>
<template #header><span>{{ $t('task.comment.delete') }}</span></template>
@ -152,22 +152,17 @@
</template>
<script>
import AsyncEditor from '@/components/input/AsyncEditor'
import TaskCommentService from '../../../services/taskComment'
import TaskCommentModel from '../../../models/taskComment'
import LoadingComponent from '../../misc/loading'
import ErrorComponent from '../../misc/error'
import {uploadFile} from '@/helpers/attachments'
import {mapState} from 'vuex'
export default {
name: 'comments',
components: {
editor: () => ({
component: import('../../input/editor'),
loading: LoadingComponent,
error: ErrorComponent,
timeout: 60000,
}),
editor: AsyncEditor,
},
props: {
taskId: {
@ -191,7 +186,6 @@ export default {
taskCommentService: new TaskCommentService(),
newComment: new TaskCommentModel(),
editorActive: true,
actions: {},
saved: null,
saving: null,
@ -200,43 +194,46 @@ export default {
},
watch: {
taskId: {
handler(taskId) {
if (!this.enabled) {
return
}
this.loadComments()
this.newComment.taskId = taskId
this.commentEdit.taskId = taskId
this.commentToDelete.taskId = taskId
},
handler: 'loadComments',
immediate: true,
},
canWrite() {
this.makeActions()
},
computed: {
...mapState({
userAvatar: state => state.auth.info.getAvatarUrl(48),
enabled: state => state.config.taskCommentsEnabled,
}),
actions() {
if (!this.canWrite) {
return {}
}
return Object.fromEntries(this.comments.map((c) => ([
c.id,
[{
action: () => this.toggleDelete(c.id),
title: this.$t('misc.delete'),
}],
])))
},
},
computed: mapState({
userAvatar: state => state.auth.info.getAvatarUrl(48),
enabled: state => state.config.taskCommentsEnabled,
}),
methods: {
attachmentUpload(...args) {
return uploadFile(this.taskId, ...args)
},
loadComments() {
this.taskCommentService
.getAll({taskId: this.taskId})
.then(r => {
this.$set(this, 'comments', r)
this.makeActions()
})
.catch((e) => {
this.$message.error(e)
})
async loadComments(taskId) {
if (!this.enabled) {
return
}
this.newComment.taskId = taskId
this.commentEdit.taskId = taskId
this.commentToDelete.taskId = taskId
this.comments = await this.taskCommentService.getAll({taskId})
},
addComment() {
async addComment() {
if (this.newComment.comment === '') {
return
}
@ -250,30 +247,27 @@ export default {
this.$nextTick(() => (this.editorActive = true))
this.creating = true
this.taskCommentService
.create(this.newComment)
.then((r) => {
this.comments.push(r)
this.newComment.comment = ''
this.$message.success({message: this.$t('task.comment.addedSuccess')})
this.makeActions()
})
.catch((e) => {
this.$message.error(e)
})
.finally(() => {
this.creating = false
})
try {
const comment = await this.taskCommentService.create(this.newComment)
this.comments.push(comment)
this.newComment.comment = ''
this.$message.success({message: this.$t('task.comment.addedSuccess')})
} finally {
this.creating = false
}
},
toggleEdit(comment) {
this.isCommentEdit = !this.isCommentEdit
this.commentEdit = comment
},
toggleDelete(commentId) {
this.showDeleteModal = !this.showDeleteModal
this.commentToDelete.id = commentId
},
editComment() {
async editComment() {
if (this.commentEdit.comment === '') {
return
}
@ -281,56 +275,68 @@ export default {
this.saving = this.commentEdit.id
this.commentEdit.taskId = this.taskId
this.taskCommentService
.update(this.commentEdit)
.then((r) => {
for (const c in this.comments) {
if (this.comments[c].id === this.commentEdit.id) {
this.$set(this.comments, c, r)
}
try {
const comment = await this.taskCommentService.update(this.commentEdit)
for (const c in this.comments) {
if (this.comments[c].id === this.commentEdit.id) {
this.comments[c] = comment
}
this.saved = this.commentEdit.id
setTimeout(() => {
this.saved = null
}, 2000)
})
.catch((e) => {
this.$message.error(e)
})
.finally(() => {
this.isCommentEdit = false
this.saving = null
})
}
this.saved = this.commentEdit.id
setTimeout(() => {
this.saved = null
}, 2000)
} finally {
this.isCommentEdit = false
this.saving = null
}
},
deleteComment() {
this.taskCommentService
.delete(this.commentToDelete)
.then(() => {
for (const a in this.comments) {
if (this.comments[a].id === this.commentToDelete.id) {
this.comments.splice(a, 1)
}
}
})
.catch((e) => {
this.$message.error(e)
})
.finally(() => {
this.showDeleteModal = false
})
},
makeActions() {
if (this.canWrite) {
this.comments.forEach((c) => {
this.$set(this.actions, c.id, [
{
action: () => this.toggleDelete(c.id),
title: this.$t('misc.delete'),
},
])
})
async deleteComment(commentToDelete) {
try {
await this.taskCommentService.delete(commentToDelete)
const index = this.comments.findIndex(({id}) => id === commentToDelete.id)
this.comments.splice(index, 1)
} finally {
this.showDeleteModal = false
}
},
},
}
</script>
<style lang="scss" scoped>
.media-left {
margin: 0 1rem;
}
.comment-info {
display: flex;
align-items: center;
gap: .5rem;
img {
@media screen and (max-width: $tablet) {
display: block;
width: 20px;
height: 20px;
padding-right: 0;
margin-right: .5rem;
}
@media screen and (min-width: $tablet) {
display: none;
}
}
span {
font-size: .75rem;
line-height: 1;
}
}
.media-content {
width: calc(100% - 48px - 2rem);
}
</style>

View File

@ -8,21 +8,21 @@
<x-button
@click.prevent.stop="() => deferDays(1)"
:shadow="false"
type="secondary"
variant="secondary"
>
{{ $t('task.deferDueDate.1day') }}
</x-button>
<x-button
@click.prevent.stop="() => deferDays(3)"
:shadow="false"
type="secondary"
variant="secondary"
>
{{ $t('task.deferDueDate.3days') }}
</x-button>
<x-button
@click.prevent.stop="() => deferDays(7)"
:shadow="false"
type="secondary"
variant="secondary"
>
{{ $t('task.deferDueDate.1week') }}
</x-button>
@ -57,12 +57,14 @@ export default {
flatPickr,
},
props: {
value: {
modelValue: {
required: true,
},
},
emits: ['update:modelValue'],
watch: {
value: {
modelValue: {
handler(value) {
this.task = value
this.dueDate = value.dueDate
@ -83,7 +85,7 @@ export default {
this.changeInterval = setInterval(this.updateDueDate, 1000)
},
beforeDestroy() {
beforeUnmount() {
if (this.changeInterval) {
clearInterval(this.changeInterval)
}
@ -110,7 +112,8 @@ export default {
this.dueDate = this.dueDate.setDate(this.dueDate.getDate() + days)
this.updateDueDate()
},
updateDueDate() {
async updateDueDate() {
if (!this.dueDate) {
return
}
@ -120,17 +123,70 @@ export default {
}
this.task.dueDate = new Date(this.dueDate)
this.taskService
.update(this.task)
.then((r) => {
this.lastValue = r.dueDate
this.task = r
this.$emit('input', r)
})
.catch((e) => {
this.$message.error(e)
})
const task = await this.taskService.update(this.task)
this.lastValue = task.dueDate
this.task = task
this.$emit('update:modelValue', task)
},
},
}
</script>
<style lang="scss" scoped>
// 100px is roughly the size the pane is pulled to the right
$defer-task-max-width: 350px + 100px;
.defer-task {
position: absolute;
width: 100%;
max-width: $defer-task-max-width;
border-radius: $radius;
border: 1px solid var(--grey-200);
padding: 1rem;
margin: 1rem;
background: var(--white);
color: var(--text);
cursor: default;
z-index: 10;
box-shadow: var(--shadow-lg);
@media screen and (max-width: ($defer-task-max-width)) {
left: .5rem;
right: .5rem;
max-width: 100%;
width: calc(100vw - 1rem - 2rem);
}
}
.defer-days {
justify-content: space-between;
display: flex;
margin: .5rem 0;
}
:deep() {
input.input {
display: none;
}
.flatpickr-calendar {
margin: 0 auto;
box-shadow: none;
@media screen and (max-width: ($defer-task-max-width)) {
max-width: 100%;
}
span {
width: auto !important;
}
}
.flatpickr-innerContainer {
@media screen and (max-width: ($defer-task-max-width)) {
overflow: scroll;
}
}
}
</style>

View File

@ -30,8 +30,7 @@
</template>
<script>
import LoadingComponent from '@/components/misc/loading.vue'
import ErrorComponent from '@/components/misc/error.vue'
import AsyncEditor from '@/components/input/AsyncEditor'
import {LOADING} from '@/store/mutation-types'
import {mapState} from 'vuex'
@ -39,12 +38,7 @@ import {mapState} from 'vuex'
export default {
name: 'description',
components: {
editor: () => ({
component: import('@/components/input/editor.vue'),
loading: LoadingComponent,
error: ErrorComponent,
timeout: 60000,
}),
editor: AsyncEditor,
},
data() {
return {
@ -57,7 +51,7 @@ export default {
loading: LOADING,
}),
props: {
value: {
modelValue: {
required: true,
},
attachmentUpload: {
@ -67,8 +61,9 @@ export default {
required: true,
},
},
emits: ['update:modelValue'],
watch: {
value: {
modelValue: {
handler(value) {
this.task = value
},
@ -76,24 +71,19 @@ export default {
},
},
methods: {
save() {
async save() {
this.saving = true
this.$store.dispatch('tasks/update', this.task)
.then(t => {
this.task = t
this.$emit('input', t)
this.saved = true
setTimeout(() => {
this.saved = false
}, 2000)
})
.catch(e => {
this.$message.error(e)
})
.finally(() => {
this.saving = false
})
try {
this.task = await this.$store.dispatch('tasks/update', this.task)
this.$emit('update:modelValue', this.task)
this.saved = true
setTimeout(() => {
this.saved = false
}, 2000)
} finally {
this.saving = false
}
},
},
}

View File

@ -6,7 +6,6 @@
<multiselect
:loading="listUserService.loading"
:placeholder="$t('task.assignee.placeholder')"
:disabled="disabled"
:multiple="true"
@search="findUser"
:search-results="foundUsers"
@ -54,10 +53,11 @@ export default {
disabled: {
default: false,
},
value: {
modelValue: {
type: Array,
},
},
emits: ['update:modelValue'],
data() {
return {
newAssignee: new UserModel(),
@ -68,62 +68,82 @@ export default {
}
},
watch: {
value: {
modelValue: {
handler(value) {
this.assignees = value
},
immediate: true,
deep: true,
},
},
methods: {
addAssignee(user) {
this.$store.dispatch('tasks/addAssignee', {user: user, taskId: this.taskId})
.then(() => {
this.$emit('input', this.assignees)
this.$message.success({message: this.$t('task.assignee.assignSuccess')})
})
.catch(e => {
this.$message.error(e)
})
async addAssignee(user) {
await this.$store.dispatch('tasks/addAssignee', {user: user, taskId: this.taskId})
this.$emit('update:modelValue', this.assignees)
this.$message.success({message: this.$t('task.assignee.assignSuccess')})
},
removeAssignee(user) {
this.$store.dispatch('tasks/removeAssignee', {user: user, taskId: this.taskId})
.then(() => {
// Remove the assignee from the list
for (const a in this.assignees) {
if (this.assignees[a].id === user.id) {
this.assignees.splice(a, 1)
}
}
this.$message.success({message: this.$t('task.assignee.assignSuccess')})
})
.catch(e => {
this.$message.error(e)
})
async removeAssignee(user) {
await this.$store.dispatch('tasks/removeAssignee', {user: user, taskId: this.taskId})
// Remove the assignee from the list
for (const a in this.assignees) {
if (this.assignees[a].id === user.id) {
this.assignees.splice(a, 1)
}
}
this.$message.success({message: this.$t('task.assignee.unassignSuccess')})
},
findUser(query) {
async findUser(query) {
if (query === '') {
this.clearAllFoundUsers()
return
}
this.listUserService.getAll({listId: this.listId}, {s: query})
.then(response => {
// Filter the results to not include users who are already assigned
const filteredResponse = response.filter(({id}) => !includesById(this.assignees, id))
const response = await this.listUserService.getAll({listId: this.listId}, {s: query})
this.$set(this, 'foundUsers', filteredResponse)
})
.catch(e => {
this.$message.error(e)
})
// Filter the results to not include users who are already assigned
this.foundUsers = response.filter(({id}) => !includesById(this.assignees, id))
},
clearAllFoundUsers() {
this.$set(this, 'foundUsers', [])
this.foundUsers = []
},
focus() {
this.$refs.multiselect.focus()
},
},
}
</script>
<style lang="scss" scoped>
.assignee {
position: relative;
&:not(:first-child) {
margin-left: -1.5rem;
}
:deep(.user img) {
border: 2px solid var(--white);
margin-right: 0;
}
.remove-assignee {
position: absolute;
top: 4px;
left: 2px;
color: var(--danger);
background: var(--white);
padding: 0 4px;
display: block;
border-radius: 100%;
font-size: .75rem;
width: 18px;
height: 18px;
z-index: 100;
}
}
</style>

View File

@ -12,6 +12,7 @@
:create-placeholder="$t('task.label.createPlaceholder')"
v-model="labels"
:search-delay="10"
:close-after-select="false"
>
<template #tag="props">
<span
@ -47,7 +48,7 @@ import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
export default {
name: 'edit-labels',
props: {
value: {
modelValue: {
default: () => [],
type: Array,
},
@ -60,6 +61,7 @@ export default {
default: false,
},
},
emits: ['update:modelValue', 'change'],
data() {
return {
labelTaskService: new LabelTaskService(),
@ -72,11 +74,12 @@ export default {
Multiselect,
},
watch: {
value: {
modelValue: {
handler(value) {
this.labels = value
},
immediate: true,
deep: true,
},
},
computed: {
@ -91,9 +94,10 @@ export default {
findLabel(query) {
this.query = query
},
addLabel(label, showNotification = true) {
async addLabel(label, showNotification = true) {
const bubble = () => {
this.$emit('input', this.labels)
this.$emit('update:modelValue', this.labels)
this.$emit('change', this.labels)
}
@ -102,25 +106,21 @@ export default {
return
}
this.$store.dispatch('tasks/addLabel', {label: label, taskId: this.taskId})
.then(() => {
bubble()
if (showNotification) {
this.$message.success({message: this.$t('task.label.addSuccess')})
}
})
.catch(e => {
this.$message.error(e)
})
await this.$store.dispatch('tasks/addLabel', {label: label, taskId: this.taskId})
bubble()
if (showNotification) {
this.$message.success({message: this.$t('task.label.addSuccess')})
}
},
removeLabel(label) {
async removeLabel(label) {
const removeFromState = () => {
for (const l in this.labels) {
if (this.labels[l].id === label.id) {
this.labels.splice(l, 1)
}
}
this.$emit('input', this.labels)
this.$emit('update:modelValue', this.labels)
this.$emit('change', this.labels)
}
@ -129,32 +129,29 @@ export default {
return
}
this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
.then(() => {
removeFromState()
this.$message.success({message: this.$t('task.label.removeSuccess')})
})
.catch(e => {
this.$message.error(e)
})
await this.$store.dispatch('tasks/removeLabel', {label: label, taskId: this.taskId})
removeFromState()
this.$message.success({message: this.$t('task.label.removeSuccess')})
},
createAndAddLabel(title) {
async createAndAddLabel(title) {
if (this.taskId === 0) {
return
}
const newLabel = new LabelModel({title: title})
this.$store.dispatch('labels/createLabel', newLabel)
.then(r => {
this.addLabel(r, false)
this.labels.push(r)
this.$message.success({message: this.$t('task.label.addCreateSuccess')})
})
.catch(e => {
this.$message.error(e)
})
const label = await this.$store.dispatch('labels/createLabel', newLabel)
this.addLabel(label, false)
this.labels.push(label)
this.$message.success({message: this.$t('task.label.addCreateSuccess')})
},
},
}
</script>
<style lang="scss" scoped>
.tag {
margin: .5rem 0 0 .5rem;
}
</style>

View File

@ -1,14 +1,14 @@
<template>
<div class="heading">
<h1 class="title task-id">{{ textIdentifier }}</h1>
<div class="is-done" v-if="task.done">Done</div>
<Done class="heading__done" :is-done="task.done" />
<h1
class="title input"
:class="{'disabled': !canWrite}"
@blur="save($event.target.textContent)"
@keydown.enter.prevent.stop="$event.target.blur()"
:contenteditable="canWrite ? 'true' : 'false'"
spellcheck="false"
:contenteditable="canWrite ? true : undefined"
:spellcheck="false"
>
{{ task.title.trim() }}
</h1>
@ -28,8 +28,13 @@
<script>
import {mapState} from 'vuex'
import Done from '@/components/misc/Done.vue'
export default {
name: 'heading',
components: {
Done,
},
data() {
return {
showSavedMessage: false,
@ -39,14 +44,14 @@ export default {
computed: {
...mapState(['loading']),
task() {
return this.value
return this.modelValue
},
textIdentifier() {
return this.task?.getTextIdentifier() || ''
},
},
props: {
value: {
modelValue: {
required: true,
},
canWrite: {
@ -54,8 +59,11 @@ export default {
default: false,
},
},
emits: ['update:modelValue'],
methods: {
save(title) {
async save(title) {
// We only want to save if the title was actually changed.
// Because the contenteditable does not have a change event
// we're building it ourselves and only continue
@ -71,22 +79,24 @@ export default {
title,
}
this.$store.dispatch('tasks/update', newTask)
.then((task) => {
this.$emit('input', task)
this.showSavedMessage = true
setTimeout(() => {
this.showSavedMessage = false
}, 2000)
})
.catch(e => {
this.$message.error(e)
})
.finally(() => {
this.saving = false
})
try {
const task = await this.$store.dispatch('tasks/update', newTask)
this.$emit('update:modelValue', task)
this.showSavedMessage = true
setTimeout(() => {
this.showSavedMessage = false
}, 2000)
}
finally {
this.saving = false
}
},
},
}
</script>
<style lang="scss" scoped>
.heading__done {
margin-left: .5rem;
}
</style>

View File

@ -1,18 +1,18 @@
<template>
<div
class="task loader-container draggable"
:class="{
'is-loading': loadingInternal || loading,
'draggable': !(loadingInternal || loading),
'has-light-text': !colorIsDark(task.hexColor) && task.hexColor !== `#${task.defaultColor}` && task.hexColor !== task.defaultColor,
}"
:style="{'background-color': task.hexColor !== '#' && task.hexColor !== `#${task.defaultColor}` ? task.hexColor : false}"
@click.ctrl="() => markTaskAsDone(task)"
@click.ctrl="() => toggleTaskDone(task)"
@click.exact="() => $router.push({ name: 'task.kanban.detail', params: { id: task.id } })"
@click.meta="() => markTaskAsDone(task)"
class="task loader-container draggable"
@click.meta="() => toggleTaskDone(task)"
>
<span class="task-id">
<span class="is-done" v-if="task.done">Done</span>
<Done class="kanban-card__done" :is-done="task.done" variant="small" />
<template v-if="task.identifier === ''">
#{{ task.index }}
</template>
@ -58,6 +58,9 @@
<span v-if="task.description" class="icon">
<icon icon="align-left"/>
</span>
<span class="icon" v-if="task.repeatAfter.amount > 0">
<icon icon="history"/>
</span>
</div>
</div>
</template>
@ -66,13 +69,17 @@
import {playPop} from '../../../helpers/playPop'
import PriorityLabel from '../../../components/tasks/partials/priorityLabel'
import User from '../../../components/misc/user'
import Done from '@/components/misc/Done.vue'
import Labels from '../../../components/tasks/partials/labels'
import ChecklistSummary from './checklist-summary'
import {colorIsDark} from '@/helpers/color/colorIsDark'
export default {
name: 'kanban-card',
components: {
ChecklistSummary,
Done,
PriorityLabel,
User,
Labels,
@ -93,24 +100,174 @@ export default {
},
},
methods: {
markTaskAsDone(task) {
colorIsDark,
async toggleTaskDone(task) {
this.loadingInternal = true
this.$store.dispatch('tasks/update', {
...task,
done: !task.done,
})
.then(() => {
if (task.done) {
playPop()
}
})
.catch(e => {
this.$message.error(e)
})
.finally(() => {
this.loadingInternal = false
try {
await this.$store.dispatch('tasks/update', {
...task,
done: !task.done,
})
if (task.done) {
playPop()
}
} finally {
this.loadingInternal = false
}
},
},
}
</script>
<style lang="scss" scoped>
$task-background: var(--white);
.task {
-webkit-touch-callout: none; // iOS Safari
user-select: none;
cursor: pointer;
box-shadow: var(--shadow-xs);
display: block;
border: 3px solid transparent;
font-size: .9rem;
margin: .5rem;
padding: .4rem;
border-radius: $radius;
background: $task-background;
&.loader-container.is-loading::after {
width: 1.5rem;
height: 1.5rem;
top: calc(50% - .75rem);
left: calc(50% - .75rem);
border-width: 2px;
}
h3 {
font-family: $family-sans-serif;
font-size: .85rem;
word-break: break-word;
}
.progress {
margin: 8px 0 0 0;
width: 100%;
height: 0.5rem;
}
.due-date {
float: right;
display: flex;
align-items: center;
.icon {
margin-right: .25rem;
}
&.overdue {
color: var(--danger);
}
}
.label-wrapper .tag {
margin: .5rem .5rem 0 0;
}
.footer {
background: transparent;
padding: 0;
display: flex;
flex-wrap: wrap;
align-items: center;
:deep(.tag),
.assignees,
.icon,
.priority-label {
margin-top: .25rem;
margin-right: .25rem;
}
.assignees {
display: flex;
.user {
display: inline;
margin: 0;
img {
margin: 0;
}
}
}
// FIXME: should be in labels.vue
:deep(.tag) {
margin-left: 0;
}
.priority-label {
font-size: .75rem;
height: 2rem;
.icon {
height: 1rem;
padding: 0 .25rem;
margin-top: 0;
}
}
}
.footer .icon,
.due-date,
.priority-label {
background: var(--grey-100);
border-radius: $radius;
padding: 0 .5rem;
}
.due-date {
padding: 0 .25rem;
}
.task-id {
color: var(--grey-500);
font-size: .8rem;
margin-bottom: .25rem;
display: flex;
}
&.is-moving {
opacity: .5;
}
span {
width: auto;
}
&.has-light-text {
color: var(--white);
.task-id {
color: var(--grey-200);
}
.footer .icon,
.due-date,
.priority-label {
background: var(--grey-800);
}
.footer {
.icon svg {
fill: var(--white);
}
}
}
}
.kanban-card__done {
margin-right: .25rem;
}
</style>

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