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
1
src/assets/llama-cool.svg
Normal file
After Width: | Height: | Size: 8.3 KiB |
BIN
src/assets/llama-nightscape.jpg
Normal file
After Width: | Height: | Size: 49 KiB |
1
src/assets/llama.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="145.4" height="204.5" xml:space="preserve"><defs><clipPath id="a" clipPathUnits="userSpaceOnUse"><path d="M5210 5148.5c-111.4 0-201.8 62.7-201.8 140s90.4 140 201.8 140c111.5 0 201.9-62.7 201.9-140s-90.4-140-201.9-140"/></clipPath></defs><path fill="#fff" d="M145 129.8c-1.5 11.7-6.3 15-6.3 15 8.2 4.3 6.9 16.5 4.5 21-1.5 3-3.6 3-3.6 3 7.4 11-1.4 18.7-1.4 18.7a39 39 0 0 1 1.7 17h-109c-.7-1.7-1.4-4.8-.5-10.3 1.8-11.5 5.5-16.1 6.3-17 0 0-7-10.2-6.3-19.4 1-14.6 4.9-16.7 4.9-16.7-7.4-10-2.4-20.4.4-23.8 0-1.3-3.9-14.8-1.1-27 1-4 3-15.3 3.5-15.9-.5-1.4-3-6.4 1-16.3 1.8-4.2 4.2-7.6 5.9-9.7l1.2-2.8C39.2 23 43-.3 46.9 0c7.2.6 17.2 27.3 18 29.1.2.7 7.8 1.3 16.2 1.9 7.8-.6 15.8-1 20-.4 5.2.9 9.6 2.4 13.3 4.1 5 .7 6.9.9 7.2.3 1.6-3.3 6.9-26.7 16.3-32.5 5.1-3 11.5 36.4-1 48.7 0 0-1 3.7-1.5 6.7 3.6 8 .3 18.7.3 18.7 15 6.2 1.7 28.3 1.7 28.3 3.3 3.6 9.7 8 7.5 25"/><path fill="#fef2e2" d="M136.5 11.6c3.1-5.3 9.9 20-3.2 32.3-3.2 3-3.3-2.1-1-17 .7-5.2 2-11.4 4.2-15.3M50 9c-3.4-5.1-8.8 20.5 5 32.1C58.2 44 58 38.8 55 24c-1-5.1-2.7-11.3-5-15M53.3 60.4c8.4-8.7 33.6-6.6 33.6-6.6s25-3 33.7 5.5c0 0 27.6 61.4-32.7 62.8-60.2.5-34.6-61.7-34.6-61.7"/><g clip-path="url(#a)" transform="matrix(.04413 0 0 .04413 -169.3 -148)"><path fill="#fee8de" d="M5008.2 5148.5h403.7v280h-403.7Z"/></g><path fill="#fee8de" d="M112.6 80.2c-4.9 0-8.9 2.8-8.9 6.2 0 3.4 4 6.2 9 6.2 4.9 0 8.9-2.8 8.9-6.2 0-3.4-4-6.2-9-6.2"/><path fill="#231f20" d="M68.3 74.4a3 3 0 1 0 6-.1c0-1.7-1.4-4.2-3-4.2-1.7 0-3 2.6-3 4.3M100.4 75a3 3 0 1 0 5.9 0c0-1.7-1.4-4.2-3-4.2-1.7 0-3 2.6-3 4.3"/><path fill="#eedbcc" d="M61.5 100.2c-.1-7.7 11.7-16.7 24.2-16.9 12.6-.2 24 8.2 24 15.9.1 4-3.5 6.1-7.9 9.1-4.3 3-9 9.8-15.9 10-7 0-11.7-6.7-16.2-9.6-4.4-2.7-8.1-4.5-8.2-8.5"/><path fill="#fff" d="M48.8 44.7c.3-2.6 2.3-4.8 5-4.7-1.4-2.2-2.3-7 2.2-9.3 2.8-1.3 6 1.1 6 1.1s.6-5.6 6.7-6.7c6.4-1.1 10 5.2 10 5.2-.2-5 8.5-8 13.2-7.2 5.9 1 8.7 5.3 8.7 5.3 11.6-7 16 4 16 4 2.5-6.2 11.7-4.3 14.6-.6 3.7 4.9-.7 9-.7 9s8 5.4 4.5 11.6c-4 7.1-11.5 2.7-11.5 2.7-3 15-14.6 2-14.6 2-.9 7-4 13-12.8 11-6-1.4-8-9.4-8-9.4-2 8.2-8.4 7-10.8 4.7a5.8 5.8 0 0 1-1.6-3.6C70 67.3 66 58.6 66 58.6c-4.9 3-9.7 2.8-12.2-.5-1.4-1.9-1.8-4-1-7.4-2.1-.6-4.4-2.3-4-6"/><path fill="#fef2e2" d="m128.1 42.8.7.3a1.8 1.8 0 0 1 .8 1l.1.6-.1 1.3a7.8 7.8 0 0 1-.5 1.3c-.4.8-.9 1.5-1.6 2l-.6.2h-.7c-.2 0-.4 0-.7-.2l-.6-.3-.6-.4.1.7.2 1.1-.1 1.2-.3 1-.5 1-.6.7-.4.4-.4.3-.8.5-1 .3c-.3 0-.6 0-1 .2h-2l-1.1-.2c-.4 0-.8-.2-1.1-.3-.4 0-.7-.2-1.1-.3l-1.1-.4-1.1-.5-.1.2.8.9 1 .8 1 .7 1.2.5 1.2.4 1.3.3h1.3l1.4-.3 1.3-.5 1.1-.8.5-.5.4-.6c.3-.3.5-.8.7-1.2l.3-1.3v-1.3c0-.4 0-.8-.2-1.2L125 49l-.5.4 1.5.8 1 .2h1c.6-.2 1.1-.5 1.6-1a5.2 5.2 0 0 0 1.1-1.5 6.2 6.2 0 0 0 .5-2.7l-.1-.9-.5-.9-.7-.7-1-.3h-.4l-.4.1v.2M89.5 33.5l.3-.4.2-.4v-1c0-.3 0-.6-.2-.9L89 30l-.7-.5-.9-.4-1.8-.3-1 .1-.8.3c-.6.3-1.2.7-1.5 1.2l-.5.9-.2.9.1 1.7.5-.3-1-.7-1-.7-1.2-.5-1.4-.2h-1.3l-.7.2-.7.2-1.2.7-1 1-.8 1-.6 1.3-.3 1.3-.1 1.3v1.3l.2 1.2.3 1.2.5 1.2h.2V42l.1-1.2c0-.3 0-.7.2-1 0-.5 0-.8.2-1.2l.3-1 .4-1 .4-1c.3-.2.4-.5.6-.8l.7-.7.8-.6.4-.2.5-.2 1-.2 1-.1c.4 0 .7 0 1.1.2.4 0 .7.2 1 .4l1 .6.6.3v-.6c-.1-.3-.2-.5-.1-.8v-.7l.4-.6.4-.4c.7-.5 1.6-.6 2.5-.7a6 6 0 0 1 1.4.1l1.2.5c.2 0 .4.2.5.3l.4.5.2.6v.8h.1M63.3 54.2l-.5.1a2 2 0 0 1-.6 0l-.3.1h-.4l-.9-.3c-.3 0-.6-.2-.9-.4-.3-.2-.6-.3-.8-.6l-.8-.7c-.2-.3-.5-.5-.6-.8l-.2-.5-.2-.4-.4-.8-.3-.7V49l-.1-.3v-1.2l-.2-.1-.5.4c-.2.1-.3.3-.4.6l-.4 1v1.1l.4 1.2.4.6.4.5 1 .9 1.1.7 1.2.5 1.1.2H62l.4-.1c.3 0 .5-.2.6-.3l.5-.2v-.2M111.8 40.1l.1-.5.2-.6v-.7c0-.3 0-.6-.2-1l-.3-.8-.5-1-.7-.8c-.2-.2-.5-.5-.8-.6l-.4-.3-.4-.2-.7-.5c-.3 0-.5-.2-.7-.3l-.3-.1h-.3l-.6-.2h-.6v-.2s0-.2.4-.5l.6-.3c.3-.2.6-.3 1-.3l1.1.1 1.2.5.5.4.5.5.8 1 .6 1.2.4 1.2.2 1.2-.1 1-.1.5-.2.4-.2.5-.4.5h-.1"/><path fill="#231f20" d="M85.3 88.8c4.2 0 12.3 0 10.8 3.3-.8 1.7-5.1 4-10.7 4.2-5.6 0-10-2.3-10.8-4C73 89 81 88.8 85.3 88.8"/><path fill="#231f20" d="M83.8 95.5c-.4 3.7.5 7.2 3.8 9.2 3.6 2.2 8.2.8 11.6-1 1.3-.7.1-2.6-1.1-2-2.7 1.5-5.6 2.4-8.6 1.4-3.3-1-3.8-4.6-3.5-7.6.2-1.4-2-1.3-2.2 0"/><path fill="#231f20" d="M83.3 96.3c0 2.5-1 5-3 6.4-3.2 2.2-6.3-.3-8.2-2.7-.9-1.1-2.4.4-1.6 1.5 2.3 3 6 5.5 9.9 3.6a9.5 9.5 0 0 0 5-8.8c0-1.4-2.2-1.4-2.1 0"/></svg>
|
After Width: | Height: | Size: 4.1 KiB |
1
src/assets/logo-full-pride.svg
Normal file
After Width: | Height: | Size: 6.5 KiB |
1
src/assets/logo-full.svg
Normal file
After Width: | Height: | Size: 5.9 KiB |
12
src/assets/logo.svg
Normal file
@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xml:space="preserve" 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)"/>
|
||||
<path d="M1716.5 1787.9c-.1 73.8-9.3 103.6-50.4 139.7-25.8 22.6-55.9 31.2-103.8 30-47.9 1.2-82.4-13.4-107.3-39.2-37.5-39-47.4-62-47.5-135.9 0-39.9 43-128.1 55.7-148.5 21.3-36 60.6-48.9 99.1-46.2 38.6-2.7 77.9 10.3 99.1 46.2 12.8 20.4 55.1 107 55 153.9" style="fill:#f1e6d3;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
||||
<path d="M1226.6 2316c-9.6 86.2-38.6 240 61.5 331.3 11 10.1 14-24.2 15.8-38 2.6-19 0-73.5.4-92.6.7-36.1 8.3-55 4.7-71.5-9.6-45-17.3-42.2-26.5-69.6-18.3-54.4-53.3-83-55.9-59.5M1851.7 2333c10.3-18.2 37 80.3 45.4 123.2 8 40.3 18 93.8 4 133.9-7.4 21.5-53 84.5-58.4 62.9-2-8.5-3.2-71.1-8.3-101.1-6.4-37.1-18-73.8-18-111.6-.2-84.5 25.3-88 35.3-107.2" style="fill:#f1d7d4;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
||||
<path d="M1522 1319.7c-2.2-6.5-18.6-11.4-24.8-13.3-14.9-4.9-28.1 6.9-36.4 16.8-11.6 13.7-11.3 35.6-16.2 51.6-2.9 9.7-19.5 11-24.5 2-16.6-29.8-81.1 26.4-66.1 45.2 9.9 12.3-13.8 23.2-23.6 11-29-36.1 49-103.4 93.6-85.2 2-9 4-18 8-26.6 7.4-16.9 23.9-27.8 41-37 23.1-12.4 68.2 9.5 75 30.3 4.9 14.5-21.2 19.7-26 5.2M1727.6 1538.2c2.4-10 2.8-44-16-25.4-7.5 7.5-22.6 3-23.2-7-1.4-23.4-24.9-24-45.1-16.9-16 5.6-24.6-16.6-8.6-22.1 29.7-10.4 62-4.6 74.7 17.8 10.1-4.7 21.5-6 30.7 2.6 16 15 18.4 36.2 13.7 55.7-3.5 14.8-29.7 10.1-26.2-4.7M1775 1049.2c-7-14.3-19.8-13.4-33.6-7.4-10.1 4.4-22.6-2.8-19.6-13 6.2-20.6-19.7-26.6-37.3-19.3-15.4 6.5-28.8-13.8-13.2-20.3 31.6-13.2 71.7-1.6 77.5 26.2 20.4-3.3 39.8 2.4 49.4 22.3 6.7 13.6-16.4 25.4-23.2 11.5M1569.8 2153.3c-3.3-20.2-41.1 3.3-50.5 9.7-8.3 5.5-19 2.1-20-7.3-1.4-12.7-18.5-9-26.3-7.4-14.8 3-27.4 12.2-27.7 26-.4 13.6 8.2 27.7 12.6 40.4 2.9 8-8.7 17-17.2 11.5-15.2-9.7-88.7-18.5-59.4 13.6 9.3 10.2-7.1 24.8-16.6 14.5-13.5-14.8-22.6-48.7 6.6-56 15.5-3.7 37.8-3.5 56.8.8-8-25.5-9.6-48.8 23.2-65.1 22.1-11.1 52.5-11 65.4 6 27.2-14.5 69.7-28.7 75.6 7.8 2.1 13-20.4 18.5-22.5 5.5" style="fill:#faeee0;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
||||
<path d="M1443 1685.6c39.4-3.4 78.8-12.3 118.5-10.9 25.4 1 51.7 4.5 76.8 8.2 18.2 2.7 40.5 6 52.7 19.4 1-45-92.6-59.1-128.9-60-42.1-1-89.5 17.2-119 43.3" style="fill:#494949;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
||||
<path d="M1549.4 1779.5a353.5 353.5 0 0 1-2.7-87.3c.7-7.6-1.3-25.7 8.8-29.5 8.2-3 18.3 2.7 19.7 10.1 2.2 12.5-3 28.2-3.5 41-.5 14.9 0 29.8 1.6 44.7 1 8.8 5.9 20.7-4.2 27-7.4 4.5-18.3 2.8-19.7-6" style="fill:#494949;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
||||
<path d="M1626 1849.7c-23.7-1-45.7-14.2-63.4-27-16.1 10.7-40.5 20.5-60.7 14.8-12-3.4-1.1-7.1 4-10.3 9.2-6.2 16.8-14.2 23.7-22.4 10.3-12.6 19.6-25.8 30.7-38 7.6 5.6 15 11.1 21.6 17.6 3.1 3 28.5 37 32.4 42.7 2.4 3.6 5 7.4 7.8 10.8 2.9 3.5 11 9 3.9 11.8" style="fill:#494949;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
||||
<path d="M1326.5 2010c11.7 30.3 24.3 68.4 56.3 62.4 24.2-5.2 56.7-86.2 36-78.2-11.3 4.4-20.3 41.1-41.4 46-13.4 3-32-43.6-50-48.4-8.7-2.3-4.3 10.4-.9 18.2M1670.6 2010c11.7 30.3 24.2 68.4 56.3 62.4 24.2-5.2 56.7-86.2 35.9-78.2-11.3 4.4-20.2 41.1-41.3 46-13.5 3-32-43.6-50-48.4-8.7-2.3-4.4 10.4-1 18.2" style="fill:#2c3844;fill-opacity:1;fill-rule:nonzero;stroke:none;stroke-width:.36633128" transform="matrix(.13333 0 0 -.13333 -75.5 381.8)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 5.4 KiB |
44
src/assets/migration/microsoft-todo.svg
Normal file
@ -0,0 +1,44 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1007.9 821.8">
|
||||
<defs>
|
||||
<radialGradient id="c" cx="410.2" cy="853.3" r="85" gradientTransform="rotate(45 546.8 785.4)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset=".5" stop-opacity=".1"/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="e" cx="1051.1" cy="1265.9" r="85" gradientTransform="rotate(-135 769.6 767.5)" xlink:href="#c"/>
|
||||
<radialGradient id="h" cx="27.6" cy="2001.4" r="85" gradientTransform="scale(1 -1) rotate(45 2979.2 860.2)" xlink:href="#c"/>
|
||||
<linearGradient id="a" x1="700.8" y1="597" x2="749.8" y2="597" gradientTransform="matrix(.867 0 0 1.307 86.6 -142.3)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-opacity=".1"/>
|
||||
<stop offset="1" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="f" x1="1880.8" y1="34.3" x2="1929.8" y2="34.3" gradientTransform="matrix(.867 0 0 -.796 -1446 767.1)" xlink:href="#a"/>
|
||||
<linearGradient id="i" x1="308.4" y1="811.6" x2="919.3" y2="200.7" gradientTransform="rotate(-45 613.8 506.2)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#2987e6"/>
|
||||
<stop offset="1" stop-color="#58c1f5"/>
|
||||
</linearGradient>
|
||||
<mask id="b" x="317.1" y="651.8" width="170" height="205.2" maskUnits="userSpaceOnUse">
|
||||
<path class="a" transform="rotate(45 546.8 845.5)" d="M367.7 871h85v85h-85z"/>
|
||||
</mask>
|
||||
<mask id="d" x="837.9" y="95.8" width="205.2" height="205.2" maskUnits="userSpaceOnUse">
|
||||
<path class="a" transform="rotate(-135 932.9 246)" d="M876 260h170v85H876z"/>
|
||||
</mask>
|
||||
<mask id="g" x="-35.2" y="299.5" width="205.2" height="205.2" maskUnits="userSpaceOnUse">
|
||||
<path class="a" transform="rotate(-45 -81.7 457.6)" d="M-22 463.7h170v85H-22z"/>
|
||||
</mask>
|
||||
<style>
|
||||
.a{fill:#fff}
|
||||
</style>
|
||||
</defs>
|
||||
<path transform="rotate(45 852.3 570)" style="fill:url(#a)" d="M694.4 269.8h42.5v736.5h-42.5z"/>
|
||||
<g style="mask:url(#b)">
|
||||
<circle cx="402.1" cy="736.8" r="85" style="fill:url(#c)"/>
|
||||
</g>
|
||||
<g style="mask:url(#d)">
|
||||
<circle cx="922.9" cy="216" r="85" style="fill:url(#e)"/>
|
||||
</g>
|
||||
<path transform="rotate(135 226.7 680)" style="fill:url(#f)" d="M185.3 515.6h42.5v448.5h-42.5z"/>
|
||||
<g style="mask:url(#g)">
|
||||
<circle cx="85" cy="419.7" r="85" style="fill:url(#h)"/>
|
||||
</g>
|
||||
<rect x="164.4" y="320" width="288" height="576" rx="42.5" transform="rotate(-45 163.7 559.5)" style="fill:#195abd"/>
|
||||
<rect x="469.8" y="74.2" width="288" height="864" rx="42.5" transform="rotate(45 750.5 438.2)" style="fill:url(#i)"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.6 KiB |
6
src/assets/migration/todoist.svg
Normal file
@ -0,0 +1,6 @@
|
||||
<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
|
||||
<path d="M224 0H32A32 32 0 0 0 0 32v192a32 32 0 0 0 32 32h192a32 32 0 0 0 32-32V32a32 32 0 0 0-32-32" fill="#E44332"/>
|
||||
<path d="m54.1 120.8 102.6-59.6c2.2-1.3 2.3-5.2-.2-6.6l-8.8-5.1a8 8 0 0 0-8 0c-1.2.8-83.1 48.3-85.8 50-3.3 1.8-7.4 1.8-10.6 0L0 74v21.6l43 25.2c3.8 2.2 7.5 2.1 11.1 0" fill="#FFF"/>
|
||||
<path d="M54.1 161.6 156.7 102c2.2-1.3 2.3-5.2-.2-6.6l-8.8-5.1a8 8 0 0 0-8 0l-85.8 50c-3.3 1.8-7.4 1.8-10.6 0L0 114.7v21.6l43 25.2c3.8 2.2 7.5 2.1 11.1 0" fill="#FFF"/>
|
||||
<path d="m54.1 205 102.6-59.6c2.2-1.3 2.3-5.2-.2-6.7l-8.8-5a8 8 0 0 0-8 0L54 183.6c-3.3 1.9-7.4 1.9-10.6 0L0 158.2v21.6L43 205c3.8 2.1 7.5 2 11.1 0" fill="#FFF"/>
|
||||
</svg>
|
After Width: | Height: | Size: 745 B |
11
src/assets/migration/trello.svg
Normal file
@ -0,0 +1,11 @@
|
||||
<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid">
|
||||
<defs>
|
||||
<linearGradient x1="50%" y1="0%" x2="50%" y2="100%" id="a">
|
||||
<stop stop-color="#0091E6" offset="0%"/>
|
||||
<stop stop-color="#0079BF" offset="100%"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect fill="url(#a)" width="256" height="256" rx="25"/>
|
||||
<rect fill="#FFF" x="144.6" y="33.3" width="78.1" height="112" rx="12"/>
|
||||
<rect fill="#FFF" x="33.3" y="33.3" width="78.1" height="176" rx="12"/>
|
||||
</svg>
|
After Width: | Height: | Size: 512 B |
BIN
src/assets/migration/vikunja-file.png
Normal file
After Width: | Height: | Size: 6.0 KiB |
BIN
src/assets/migration/wunderlist.jpg
Normal file
After Width: | Height: | Size: 6.9 KiB |
@ -6,7 +6,7 @@
|
||||
>
|
||||
<div class="container has-text-centered link-share-view">
|
||||
<div class="column is-10 is-offset-1">
|
||||
<img alt="Vikunja" class="logo" src="/images/logo-full.svg"/>
|
||||
<img alt="Vikunja" class="logo" :src="logoUrl" />
|
||||
<h1
|
||||
:style="{ 'opacity': currentList.title === '' ? '0': '1' }"
|
||||
class="title">
|
||||
@ -27,8 +27,15 @@
|
||||
import {mapState} from 'vuex'
|
||||
import {CURRENT_LIST} from '@/store/mutation-types'
|
||||
|
||||
import logoUrl from '@/assets/logo-full.svg'
|
||||
|
||||
export default {
|
||||
name: 'contentLinkShare',
|
||||
data() {
|
||||
return {
|
||||
logoUrl,
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
currentList: CURRENT_LIST,
|
||||
background: 'background',
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="no-auth-wrapper">
|
||||
<div class="noauth-container">
|
||||
<img alt="Vikunja" src="/images/logo-full.svg" width="400" height="117"/>
|
||||
<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>
|
||||
@ -18,8 +18,15 @@
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
|
||||
import logoUrl from '@/assets/logo-full.svg'
|
||||
|
||||
export default {
|
||||
name: 'contentNoAuth',
|
||||
data() {
|
||||
return {
|
||||
logoUrl,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.redirectToHome()
|
||||
},
|
||||
|
@ -2,7 +2,7 @@
|
||||
<div :class="{'is-active': menuActive}" class="namespace-container">
|
||||
<div class="menu top-menu">
|
||||
<router-link :to="{name: 'home'}" class="logo">
|
||||
<img alt="Vikunja" src="/images/logo-full.svg" width="164" height="48"/>
|
||||
<img alt="Vikunja" :src="logoUrl" width="164" height="48"/>
|
||||
</router-link>
|
||||
<ul class="menu-list">
|
||||
<li>
|
||||
@ -162,6 +162,8 @@ import NamespaceSettingsDropdown from '@/components/namespace/namespace-settings
|
||||
import draggable from 'vuedraggable'
|
||||
import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
||||
|
||||
import logoUrl from '@/assets/logo-full.svg'
|
||||
|
||||
export default {
|
||||
name: 'navigation',
|
||||
data() {
|
||||
@ -173,6 +175,7 @@ export default {
|
||||
ghostClass: 'ghost',
|
||||
},
|
||||
listUpdating: {},
|
||||
logoUrl,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
|
@ -7,8 +7,7 @@
|
||||
>
|
||||
<div class="navbar-brand">
|
||||
<router-link :to="{name: 'home'}" class="navbar-item logo">
|
||||
<img width="164" height="48" alt="Vikunja" src="/images/logo-full-pride.svg" v-if="(new Date()).getMonth() === 5"/>
|
||||
<img width="164" height="48" alt="Vikunja" src="/images/logo-full.svg" v-else/>
|
||||
<img width="164" height="48" alt="Vikunja" :src="logoUrl" />
|
||||
</router-link>
|
||||
<a
|
||||
@click="$store.commit('toggleMenu')"
|
||||
@ -103,6 +102,9 @@ 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'
|
||||
|
||||
export default {
|
||||
name: 'topNavigation',
|
||||
components: {
|
||||
@ -111,16 +113,21 @@ export default {
|
||||
ListSettingsDropdown,
|
||||
Update,
|
||||
},
|
||||
computed: mapState({
|
||||
userInfo: state => state.auth.info,
|
||||
userAvatar: state => state.auth.avatarUrl,
|
||||
userAuthenticated: state => state.auth.authenticated,
|
||||
currentList: CURRENT_LIST,
|
||||
background: 'background',
|
||||
imprintUrl: state => state.config.legal.imprintUrl,
|
||||
privacyPolicyUrl: state => state.config.legal.privacyPolicyUrl,
|
||||
canWriteCurrentList: state => state.currentList.maxRight > Rights.READ,
|
||||
}),
|
||||
computed: {
|
||||
logoUrl() {
|
||||
return (new Date()).getMonth() === 5 ? logoFullPrideUrl : logoUrl
|
||||
},
|
||||
...mapState({
|
||||
userInfo: state => state.auth.info,
|
||||
userAvatar: state => state.auth.avatarUrl,
|
||||
userAuthenticated: state => state.auth.authenticated,
|
||||
currentList: CURRENT_LIST,
|
||||
background: 'background',
|
||||
imprintUrl: state => state.config.legal.imprintUrl,
|
||||
privacyPolicyUrl: state => state.config.legal.privacyPolicyUrl,
|
||||
canWriteCurrentList: state => state.currentList.maxRight > Rights.READ,
|
||||
}),
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => {
|
||||
if (typeof this.$refs.usernameDropdown === 'undefined' || typeof this.$refs.listTitle === 'undefined') {
|
||||
|
@ -13,7 +13,7 @@
|
||||
<div class="preview content" v-html="preview" v-if="isPreviewActive && text !== ''">
|
||||
</div>
|
||||
|
||||
<p class="has-text-centered has-text-grey is-italic" v-if="showPreviewText">
|
||||
<p class="has-text-centered has-text-grey is-italic my-5" v-if="showPreviewText">
|
||||
{{ emptyText }}
|
||||
<template v-if="isEditEnabled">
|
||||
<a @click="toggleEdit">{{ $t('input.editor.edit') }}</a>.
|
||||
@ -22,20 +22,20 @@
|
||||
|
||||
<ul class="actions" v-if="bottomActions.length > 0">
|
||||
<li v-if="isEditEnabled && !showPreviewText && showSave">
|
||||
<a v-if="!isEditActive" @click="toggleEdit">{{ $t('input.editor.edit') }}</a>
|
||||
<a v-else @click="toggleEdit" class="done-edit">{{ $t('misc.save') }}</a>
|
||||
<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>
|
||||
</ul>
|
||||
<template v-else-if="isEditEnabled && showSave">
|
||||
<ul v-if="!isEditActive" class="actions">
|
||||
<ul v-if="showEditButton" class="actions">
|
||||
<li>
|
||||
<a @click="toggleEdit">{{ $t('input.editor.edit') }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
<x-button v-else @click="toggleEdit" type="secondary" :shadow="false">
|
||||
<x-button v-else-if="isEditActive" @click="toggleEdit" type="secondary" :shadow="false">
|
||||
{{ $t('misc.save') }}
|
||||
</x-button>
|
||||
</template>
|
||||
@ -103,6 +103,9 @@ export default {
|
||||
showPreviewText() {
|
||||
return this.isPreviewActive && this.text === '' && this.emptyText !== ''
|
||||
},
|
||||
showEditButton() {
|
||||
return !this.isEditActive && this.text !== ''
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -178,9 +178,8 @@ import Fancycheckbox from '../../input/fancycheckbox'
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
import 'flatpickr/dist/flatpickr.css'
|
||||
|
||||
import {includesById} from '@/helpers/utils'
|
||||
import {formatISO} from 'date-fns'
|
||||
import differenceWith from 'lodash/differenceWith'
|
||||
|
||||
import PrioritySelect from '@/components/tasks/partials/prioritySelect.vue'
|
||||
import PercentDoneSelect from '@/components/tasks/partials/percentDoneSelect.vue'
|
||||
import Multiselect from '@/components/input/multiselect.vue'
|
||||
@ -270,13 +269,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
foundLabels() {
|
||||
const labels = (Object.values(this.$store.state.labels.labels).filter(l => {
|
||||
return l.title.toLowerCase().includes(this.labelQuery.toLowerCase())
|
||||
}) ?? [])
|
||||
|
||||
return differenceWith(labels, this.labels, (first, second) => {
|
||||
return first.id === second.id
|
||||
})
|
||||
return this.$store.getters['labels/filterLabelsByQuery'](this.labels, this.query)
|
||||
},
|
||||
flatPickerConfig() {
|
||||
return {
|
||||
@ -310,8 +303,13 @@ export default {
|
||||
this.prepareRelatedObjectFilter('namespace')
|
||||
|
||||
this.prepareSingleValue('labels')
|
||||
const labelIds = (typeof this.filters.labels === 'string' ? this.filters.labels : '').split(',').map(i => parseInt(i))
|
||||
this.labels = (Object.values(this.$store.state.labels.labels).filter(l => labelIds.includes(l.id)) ?? [])
|
||||
|
||||
const labels = typeof this.filters.labels === 'string'
|
||||
? this.filters.labels
|
||||
: ''
|
||||
const labelIds = labels.split(',').map(i => parseInt(i))
|
||||
|
||||
this.labels = this.$store.getters['labels/getLabelsByIds'](labelIds)
|
||||
},
|
||||
removePropertyFromFilter(propertyName) {
|
||||
// Because of the way arrays work, we can only ever remove one element at once.
|
||||
@ -534,10 +532,8 @@ export default {
|
||||
|
||||
this[`${kind}Service`].getAll({}, {s: query})
|
||||
.then(response => {
|
||||
// Filter the results to not include users who are already assigneid
|
||||
this[`found${kind}`] = differenceWith(response, this[kind], (first, second) => {
|
||||
return first.id === second.id
|
||||
})
|
||||
// Filter users from the results who are already assigned
|
||||
this[`found${kind}`] = response.filter(({id}) => !includesById(this[kind], id))
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
|
@ -34,11 +34,11 @@
|
||||
class="migration-in-progress-container"
|
||||
v-else-if="isMigrating === true && message === '' && lastMigrationDate === null">
|
||||
<div class="migration-in-progress">
|
||||
<img :alt="name" :src="`/images/migration/${identifier}.png`"/>
|
||||
<img :alt="name" :src="serviceIconSource"/>
|
||||
<div class="progress-dots">
|
||||
<span v-for="i in progressDotsCount" :key="i" />
|
||||
</div>
|
||||
<img alt="Vikunja" src="/images/logo.svg">
|
||||
<img alt="Vikunja" :src="logoUrl">
|
||||
</div>
|
||||
<p>{{ $t('migrate.inProgress') }}</p>
|
||||
</div>
|
||||
@ -66,6 +66,9 @@
|
||||
<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
|
||||
|
||||
@ -80,6 +83,7 @@ export default {
|
||||
message: '',
|
||||
migratorAuthCode: '',
|
||||
migrationService: null,
|
||||
logoUrl,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@ -96,6 +100,11 @@ export default {
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
serviceIconSource() {
|
||||
return SERVICE_ICONS[this.identifier]()
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.message = ''
|
||||
|
||||
|
@ -102,7 +102,7 @@
|
||||
>
|
||||
{{ t.title }}
|
||||
</span>
|
||||
<priority-label :priority="t.priority"/>
|
||||
<priority-label :priority="t.priority" :done="t.done"/>
|
||||
<!-- using the key here forces vue to use the updated version model and not the response returned by the api -->
|
||||
<a @click="editTask(theTasks[k])" class="edit-toggle">
|
||||
<icon icon="pen"/>
|
||||
@ -192,7 +192,6 @@ import TaskCollectionService from '../../services/taskCollection'
|
||||
import {mapState} from 'vuex'
|
||||
import Rights from '../../models/constants/rights.json'
|
||||
import FilterPopup from '@/components/list/partials/filter-popup.vue'
|
||||
import {format} from 'date-fns'
|
||||
|
||||
export default {
|
||||
name: 'GanttChart',
|
||||
@ -466,7 +465,7 @@ export default {
|
||||
})
|
||||
},
|
||||
formatYear(date) {
|
||||
return format(date, 'MMMM, yyyy')
|
||||
return this.format(date, 'MMMM, yyyy')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import TaskCollectionService from '@/services/taskCollection'
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
// FIXME: merge with DEFAULT_PARAMS in filters.vue
|
||||
const DEFAULT_PARAMS = {
|
||||
@ -83,7 +82,7 @@ export default {
|
||||
this.tasks = r
|
||||
this.currentPage = page
|
||||
|
||||
this.loadedList = cloneDeep(currentList)
|
||||
this.loadedList = JSON.parse(JSON.stringify(currentList))
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
|
@ -29,8 +29,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import differenceWith from 'lodash/differenceWith'
|
||||
|
||||
import {includesById} from '@/helpers/utils'
|
||||
import UserModel from '../../../models/user'
|
||||
import ListUserService from '../../../services/listUsers'
|
||||
import TaskAssigneeService from '../../../services/taskAssignee'
|
||||
@ -112,9 +111,7 @@ export default {
|
||||
this.listUserService.getAll({listId: this.listId}, {s: query})
|
||||
.then(response => {
|
||||
// Filter the results to not include users who are already assigned
|
||||
this.foundUsers = differenceWith(response, this.assignees, (first, second) => {
|
||||
return first.id === second.id
|
||||
})
|
||||
this.foundUsers = response.filter(({id}) => !includesById(this.assignees, id))
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
|
@ -38,8 +38,6 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import differenceWith from 'lodash/differenceWith'
|
||||
|
||||
import LabelModel from '../../../models/label'
|
||||
import LabelTaskService from '../../../services/labelTask'
|
||||
|
||||
@ -84,13 +82,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
foundLabels() {
|
||||
const labels = (Object.values(this.$store.state.labels.labels).filter(l => {
|
||||
return l.title.toLowerCase().includes(this.query.toLowerCase())
|
||||
}) ?? [])
|
||||
|
||||
return differenceWith(labels, this.labels, (first, second) => {
|
||||
return first.id === second.id
|
||||
})
|
||||
return this.$store.getters['labels/filterLabelsByQuery'](this.labels, this.query)
|
||||
},
|
||||
loading() {
|
||||
return this.labelTaskService.loading || (this.$store.state[LOADING] && this.$store.state[LOADING_MODULE] === 'labels')
|
||||
@ -157,7 +149,7 @@ export default {
|
||||
.then(r => {
|
||||
this.addLabel(r, false)
|
||||
this.labels.push(r)
|
||||
this.$message.success({message: this.$t('task.label.removeSuccess')})
|
||||
this.$message.success({message: this.$t('task.label.addCreateSuccess')})
|
||||
})
|
||||
.catch(e => {
|
||||
this.$message.error(e)
|
||||
|
@ -41,7 +41,7 @@
|
||||
</progress>
|
||||
<div class="footer">
|
||||
<labels :labels="task.labels"/>
|
||||
<priority-label :priority="task.priority"/>
|
||||
<priority-label :priority="task.priority" :done="task.done"/>
|
||||
<div class="assignees" v-if="task.assignees.length > 0">
|
||||
<user
|
||||
:avatar-size="24"
|
||||
@ -51,6 +51,7 @@
|
||||
v-for="u in task.assignees"
|
||||
/>
|
||||
</div>
|
||||
<checklist-summary :task="task"/>
|
||||
<span class="icon" v-if="task.attachments.length > 0">
|
||||
<icon icon="paperclip"/>
|
||||
</span>
|
||||
@ -66,10 +67,12 @@ import {playPop} from '../../../helpers/playPop'
|
||||
import PriorityLabel from '../../../components/tasks/partials/priorityLabel'
|
||||
import User from '../../../components/misc/user'
|
||||
import Labels from '../../../components/tasks/partials/labels'
|
||||
import ChecklistSummary from './checklist-summary'
|
||||
|
||||
export default {
|
||||
name: 'kanban-card',
|
||||
components: {
|
||||
ChecklistSummary,
|
||||
PriorityLabel,
|
||||
User,
|
||||
Labels,
|
||||
|
@ -2,7 +2,7 @@
|
||||
<span
|
||||
:class="{'not-so-high': priority === priorities.HIGH, 'high-priority': priority >= priorities.HIGH}"
|
||||
class="priority-label"
|
||||
v-if="showAll || priority >= priorities.HIGH">
|
||||
v-if="!done && (showAll || priority >= priorities.HIGH)">
|
||||
<span class="icon" v-if="priority >= priorities.HIGH">
|
||||
<icon icon="exclamation"/>
|
||||
</span>
|
||||
@ -39,6 +39,10 @@ export default {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
done: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -56,9 +56,9 @@
|
||||
<div class="control is-expanded">
|
||||
<div class="select is-fullwidth has-defaults">
|
||||
<select v-model="newTaskRelationKind">
|
||||
<option value="unset">Select a relation kind</option>
|
||||
<option :key="rk" :value="rk" v-for="(label, rk) in relationKinds">
|
||||
{{ label[0] }}
|
||||
<option value="unset">{{ $t('task.relation.select') }}</option>
|
||||
<option :key="rk" :value="rk" v-for="rk in relationKinds">
|
||||
{{ $tc(`task.relation.kinds.${rk}`, 1) }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
@ -256,10 +256,7 @@ export default {
|
||||
})
|
||||
},
|
||||
relationKindTitle(kind, length) {
|
||||
if (length > 1) {
|
||||
return relationKinds[kind][1]
|
||||
}
|
||||
return relationKinds[kind][0]
|
||||
return this.$tc(`task.relation.kinds.${kind}`, length)
|
||||
},
|
||||
},
|
||||
}
|
||||
|
@ -50,7 +50,7 @@
|
||||
<transition name="fade">
|
||||
<defer-task v-if="+new Date(task.dueDate) > 0 && showDefer" v-model="task" ref="deferDueDate"/>
|
||||
</transition>
|
||||
<priority-label :priority="task.priority"/>
|
||||
<priority-label :priority="task.priority" :done="task.done"/>
|
||||
<span>
|
||||
<span class="list-task-icon" v-if="task.attachments.length > 0">
|
||||
<icon icon="paperclip"/>
|
||||
|
@ -1,7 +0,0 @@
|
||||
export function findIndexById(array : [], id : string | number) {
|
||||
return array.findIndex(({id: currentId}) => currentId === id)
|
||||
}
|
||||
|
||||
export function findById(array : [], id : string | number) {
|
||||
return array.find(({id: currentId}) => currentId === id)
|
||||
}
|
@ -35,4 +35,14 @@ export const getMigratorFromSlug = (slug: string): Migrator => {
|
||||
default:
|
||||
throw Error('Unknown migrator slug ' + slug)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// NOTE: we list the imports individually for better build time optimisation
|
||||
export const SERVICE_ICONS = {
|
||||
'vikunja-file': () => import('@/assets/migration/vikunja-file.png'),
|
||||
'microsoft-todo': () => import('@/assets/migration/microsoft-todo.svg'),
|
||||
'todoist': () => import('@/assets/migration/todoist.svg'),
|
||||
'trello': () => import('@/assets/migration/trello.svg'),
|
||||
'wunderlist': () => import('@/assets/migration/wunderlist.jpg'),
|
||||
}
|
@ -6,6 +6,6 @@ export const playPop = () => {
|
||||
return
|
||||
}
|
||||
|
||||
const popSound = new Audio('/audio/pop.wav')
|
||||
const popSound = new Audio('/audio/pop.mp3')
|
||||
popSound.play()
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import {createDateFromString} from '@/helpers/time/createDateFromString'
|
||||
import {format, formatDistance} from 'date-fns'
|
||||
import {format, formatDistanceToNow} from 'date-fns'
|
||||
import {enGB, de, fr, ru} from 'date-fns/locale'
|
||||
|
||||
import {i18n} from '@/i18n'
|
||||
@ -39,12 +39,8 @@ export const formatDateSince = (date) => {
|
||||
|
||||
date = createDateFromString(date)
|
||||
|
||||
const currentDate = new Date()
|
||||
const distance = formatDistance(date, currentDate, {locale: locales[i18n.global.t('date.locale')]})
|
||||
|
||||
if (date > currentDate) {
|
||||
return i18n.global.t('date.in', {date: distance})
|
||||
}
|
||||
|
||||
return i18n.global.t('date.ago', {date: distance})
|
||||
return formatDistanceToNow(date, {
|
||||
locale: locales[i18n.global.t('date.locale')],
|
||||
addSuffix: true,
|
||||
})
|
||||
}
|
||||
|
26
src/helpers/utils.ts
Normal file
@ -0,0 +1,26 @@
|
||||
export function findIndexById(array : [], id : string | number) {
|
||||
return array.findIndex(({id: currentId}) => currentId === id)
|
||||
}
|
||||
|
||||
export function findById(array : [], id : string | number) {
|
||||
return array.find(({id: currentId}) => currentId === id)
|
||||
}
|
||||
|
||||
export function includesById(array: [], id: string | number) {
|
||||
return array.some(({id: currentId}) => currentId === id)
|
||||
}
|
||||
|
||||
// https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_isnil
|
||||
export function isNil(value: any) {
|
||||
return value == null
|
||||
}
|
||||
|
||||
export function omitBy(obj: {}, check: (value: any) => Boolean): {} {
|
||||
if (isNil(obj)) {
|
||||
return {}
|
||||
}
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(obj).filter(([, value]) => !check(value)),
|
||||
)
|
||||
}
|
@ -72,7 +72,7 @@
|
||||
"totp": {
|
||||
"title": "Zwei-Faktor-Authentifizierung",
|
||||
"enroll": "Aktivieren",
|
||||
"finishSetupPart1": "Um die Aktivierung zu vollenden, benutze diesen Token in deiner TOTP App (sowie OTP oder ähnlich):",
|
||||
"finishSetupPart1": "Um die Aktivierung zu vollenden, benutze diesen Token in deiner TOTP App (Google Authenticator oder ähnlich):",
|
||||
"finishSetupPart2": "Gib anschließend unten einen Code aus deiner Anwendung ein.",
|
||||
"scanQR": "Alternativ kannst du auch diesen QR-Code scannen:",
|
||||
"passcode": "Code",
|
||||
@ -625,7 +625,8 @@
|
||||
"createPlaceholder": "Dies als neues Label hinzufügen",
|
||||
"addSuccess": "Das Label wurde erfolgreich hinzugefügt.",
|
||||
"createSuccess": "Das Label wurde erfolgreich erstellt.",
|
||||
"removeSuccess": "Das Label wurde erfolgreich entfernt."
|
||||
"removeSuccess": "Das Label wurde erfolgreich entfernt.",
|
||||
"addCreateSuccess": "The label has been created and added successfully."
|
||||
},
|
||||
"priority": {
|
||||
"unset": "Nicht eingestellt",
|
||||
@ -644,7 +645,21 @@
|
||||
"noneYet": "Keine Aufgabenbeziehung vorhanden.",
|
||||
"delete": "Aufgabenbeziehung entfernen",
|
||||
"deleteText1": "Willst du diese Aufgabenbeziehung wirklich entfernen?",
|
||||
"deleteText2": "Dies kann nicht rückgängig gemacht werden!"
|
||||
"deleteText2": "Dies kann nicht rückgängig gemacht werden!",
|
||||
"select": "Select a relation kind",
|
||||
"kinds": {
|
||||
"subtask": "Subtask | Subtasks",
|
||||
"parenttask": "Parent Task | Parent Tasks",
|
||||
"related": "Related Task | Related Tasks",
|
||||
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||
"duplicates": "Duplicates | Duplicates",
|
||||
"blocking": "Blocking | Blocking",
|
||||
"blocked": "Blocked By | Blocked By",
|
||||
"precedes": "Precedes | Precedes",
|
||||
"follows": "Follows | Follows",
|
||||
"copiedfrom": "Copied From | Copied From",
|
||||
"copiedto": "Copied To | Copied To"
|
||||
}
|
||||
},
|
||||
"repeat": {
|
||||
"everyDay": "Jeden Tag",
|
||||
@ -795,8 +810,6 @@
|
||||
},
|
||||
"date": {
|
||||
"locale": "de",
|
||||
"in": "in {date}",
|
||||
"ago": "vor {date}",
|
||||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
|
@ -625,7 +625,8 @@
|
||||
"createPlaceholder": "Das als neues Label hinzuefüege",
|
||||
"addSuccess": "Das Label isch erfolgriich hinzuegfüegt worde.",
|
||||
"createSuccess": "Das Label isch erfolgriich erstellt worde.",
|
||||
"removeSuccess": "Das Label isch erfolgriich glöscht."
|
||||
"removeSuccess": "Das Label isch erfolgriich glöscht.",
|
||||
"addCreateSuccess": "The label has been created and added successfully."
|
||||
},
|
||||
"priority": {
|
||||
"unset": "Nid iihgstellt",
|
||||
@ -644,7 +645,21 @@
|
||||
"noneYet": "S'git kei Uufgabe Beziehige.",
|
||||
"delete": "Uufgabe Beziehig chüble",
|
||||
"deleteText1": "Bisch du dir sicher, dass du die Zueghörigkeit chüblä wetsch?",
|
||||
"deleteText2": "Das chan nid rückgängig gmacht werde!"
|
||||
"deleteText2": "Das chan nid rückgängig gmacht werde!",
|
||||
"select": "Select a relation kind",
|
||||
"kinds": {
|
||||
"subtask": "Subtask | Subtasks",
|
||||
"parenttask": "Parent Task | Parent Tasks",
|
||||
"related": "Related Task | Related Tasks",
|
||||
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||
"duplicates": "Duplicates | Duplicates",
|
||||
"blocking": "Blocking | Blocking",
|
||||
"blocked": "Blocked By | Blocked By",
|
||||
"precedes": "Precedes | Precedes",
|
||||
"follows": "Follows | Follows",
|
||||
"copiedfrom": "Copied From | Copied From",
|
||||
"copiedto": "Copied To | Copied To"
|
||||
}
|
||||
},
|
||||
"repeat": {
|
||||
"everyDay": "Jedä Tag",
|
||||
@ -795,8 +810,6 @@
|
||||
},
|
||||
"date": {
|
||||
"locale": "ch",
|
||||
"in": "in {date}",
|
||||
"ago": "vor {date}",
|
||||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
|
@ -625,7 +625,8 @@
|
||||
"createPlaceholder": "Add this as new label",
|
||||
"addSuccess": "The label has been added successfully.",
|
||||
"createSuccess": "The label has been created successfully.",
|
||||
"removeSuccess": "The label has been removed successfully."
|
||||
"removeSuccess": "The label has been removed successfully.",
|
||||
"addCreateSuccess": "The label has been created and added successfully."
|
||||
},
|
||||
"priority": {
|
||||
"unset": "Unset",
|
||||
@ -644,7 +645,21 @@
|
||||
"noneYet": "No task relations yet.",
|
||||
"delete": "Delete Task Relation",
|
||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||
"deleteText2": "This cannot be undone!"
|
||||
"deleteText2": "This cannot be undone!",
|
||||
"select": "Select a relation kind",
|
||||
"kinds": {
|
||||
"subtask": "Subtask | Subtasks",
|
||||
"parenttask": "Parent Task | Parent Tasks",
|
||||
"related": "Related Task | Related Tasks",
|
||||
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||
"duplicates": "Duplicates | Duplicates",
|
||||
"blocking": "Blocking | Blocking",
|
||||
"blocked": "Blocked By | Blocked By",
|
||||
"precedes": "Precedes | Precedes",
|
||||
"follows": "Follows | Follows",
|
||||
"copiedfrom": "Copied From | Copied From",
|
||||
"copiedto": "Copied To | Copied To"
|
||||
}
|
||||
},
|
||||
"repeat": {
|
||||
"everyDay": "Every Day",
|
||||
@ -795,8 +810,6 @@
|
||||
},
|
||||
"date": {
|
||||
"locale": "en",
|
||||
"in": "in {date}",
|
||||
"ago": "{date} ago",
|
||||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
|
@ -625,7 +625,8 @@
|
||||
"createPlaceholder": "Add this as new label",
|
||||
"addSuccess": "The label has been added successfully.",
|
||||
"createSuccess": "The label has been created successfully.",
|
||||
"removeSuccess": "The label has been removed successfully."
|
||||
"removeSuccess": "The label has been removed successfully.",
|
||||
"addCreateSuccess": "The label has been created and added successfully."
|
||||
},
|
||||
"priority": {
|
||||
"unset": "Unset",
|
||||
@ -644,7 +645,21 @@
|
||||
"noneYet": "No task relations yet.",
|
||||
"delete": "Delete Task Relation",
|
||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||
"deleteText2": "This cannot be undone!"
|
||||
"deleteText2": "This cannot be undone!",
|
||||
"select": "Select a relation kind",
|
||||
"kinds": {
|
||||
"subtask": "Subtask | Subtasks",
|
||||
"parenttask": "Parent Task | Parent Tasks",
|
||||
"related": "Related Task | Related Tasks",
|
||||
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||
"duplicates": "Duplicates | Duplicates",
|
||||
"blocking": "Blocking | Blocking",
|
||||
"blocked": "Blocked By | Blocked By",
|
||||
"precedes": "Precedes | Precedes",
|
||||
"follows": "Follows | Follows",
|
||||
"copiedfrom": "Copied From | Copied From",
|
||||
"copiedto": "Copied To | Copied To"
|
||||
}
|
||||
},
|
||||
"repeat": {
|
||||
"everyDay": "Every Day",
|
||||
@ -795,8 +810,6 @@
|
||||
},
|
||||
"date": {
|
||||
"locale": "en",
|
||||
"in": "in {date}",
|
||||
"ago": "{date} ago",
|
||||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
|
@ -625,7 +625,8 @@
|
||||
"createPlaceholder": "Ajouter ceci comme nouvelle étiquette",
|
||||
"addSuccess": "Étiquette ajoutée.",
|
||||
"createSuccess": "Étiquette créée.",
|
||||
"removeSuccess": "Étiquette retirée."
|
||||
"removeSuccess": "Étiquette retirée.",
|
||||
"addCreateSuccess": "The label has been created and added successfully."
|
||||
},
|
||||
"priority": {
|
||||
"unset": "Non définie",
|
||||
@ -644,7 +645,21 @@
|
||||
"noneYet": "Pas encore de relations de tâches.",
|
||||
"delete": "Supprimer la relation de tâche",
|
||||
"deleteText1": "Supprimer cette relation de tâche ?",
|
||||
"deleteText2": "Ceci ne peut pas être annulé !"
|
||||
"deleteText2": "Ceci ne peut pas être annulé !",
|
||||
"select": "Select a relation kind",
|
||||
"kinds": {
|
||||
"subtask": "Subtask | Subtasks",
|
||||
"parenttask": "Parent Task | Parent Tasks",
|
||||
"related": "Related Task | Related Tasks",
|
||||
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||
"duplicates": "Duplicates | Duplicates",
|
||||
"blocking": "Blocking | Blocking",
|
||||
"blocked": "Blocked By | Blocked By",
|
||||
"precedes": "Precedes | Precedes",
|
||||
"follows": "Follows | Follows",
|
||||
"copiedfrom": "Copied From | Copied From",
|
||||
"copiedto": "Copied To | Copied To"
|
||||
}
|
||||
},
|
||||
"repeat": {
|
||||
"everyDay": "Chaque jour",
|
||||
@ -795,8 +810,6 @@
|
||||
},
|
||||
"date": {
|
||||
"locale": "fr",
|
||||
"in": "en {date}",
|
||||
"ago": "il y a {date}",
|
||||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
|
@ -625,7 +625,8 @@
|
||||
"createPlaceholder": "Add this as new label",
|
||||
"addSuccess": "The label has been added successfully.",
|
||||
"createSuccess": "The label has been created successfully.",
|
||||
"removeSuccess": "The label has been removed successfully."
|
||||
"removeSuccess": "The label has been removed successfully.",
|
||||
"addCreateSuccess": "The label has been created and added successfully."
|
||||
},
|
||||
"priority": {
|
||||
"unset": "Unset",
|
||||
@ -644,7 +645,21 @@
|
||||
"noneYet": "No task relations yet.",
|
||||
"delete": "Delete Task Relation",
|
||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||
"deleteText2": "This cannot be undone!"
|
||||
"deleteText2": "This cannot be undone!",
|
||||
"select": "Select a relation kind",
|
||||
"kinds": {
|
||||
"subtask": "Subtask | Subtasks",
|
||||
"parenttask": "Parent Task | Parent Tasks",
|
||||
"related": "Related Task | Related Tasks",
|
||||
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||
"duplicates": "Duplicates | Duplicates",
|
||||
"blocking": "Blocking | Blocking",
|
||||
"blocked": "Blocked By | Blocked By",
|
||||
"precedes": "Precedes | Precedes",
|
||||
"follows": "Follows | Follows",
|
||||
"copiedfrom": "Copied From | Copied From",
|
||||
"copiedto": "Copied To | Copied To"
|
||||
}
|
||||
},
|
||||
"repeat": {
|
||||
"everyDay": "Every Day",
|
||||
@ -795,8 +810,6 @@
|
||||
},
|
||||
"date": {
|
||||
"locale": "en",
|
||||
"in": "in {date}",
|
||||
"ago": "{date} ago",
|
||||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
|
@ -625,7 +625,8 @@
|
||||
"createPlaceholder": "Add this as new label",
|
||||
"addSuccess": "The label has been added successfully.",
|
||||
"createSuccess": "The label has been created successfully.",
|
||||
"removeSuccess": "The label has been removed successfully."
|
||||
"removeSuccess": "The label has been removed successfully.",
|
||||
"addCreateSuccess": "The label has been created and added successfully."
|
||||
},
|
||||
"priority": {
|
||||
"unset": "Unset",
|
||||
@ -644,7 +645,21 @@
|
||||
"noneYet": "No task relations yet.",
|
||||
"delete": "Delete Task Relation",
|
||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||
"deleteText2": "This cannot be undone!"
|
||||
"deleteText2": "This cannot be undone!",
|
||||
"select": "Select a relation kind",
|
||||
"kinds": {
|
||||
"subtask": "Subtask | Subtasks",
|
||||
"parenttask": "Parent Task | Parent Tasks",
|
||||
"related": "Related Task | Related Tasks",
|
||||
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||
"duplicates": "Duplicates | Duplicates",
|
||||
"blocking": "Blocking | Blocking",
|
||||
"blocked": "Blocked By | Blocked By",
|
||||
"precedes": "Precedes | Precedes",
|
||||
"follows": "Follows | Follows",
|
||||
"copiedfrom": "Copied From | Copied From",
|
||||
"copiedto": "Copied To | Copied To"
|
||||
}
|
||||
},
|
||||
"repeat": {
|
||||
"everyDay": "Every Day",
|
||||
@ -795,8 +810,6 @@
|
||||
},
|
||||
"date": {
|
||||
"locale": "en",
|
||||
"in": "in {date}",
|
||||
"ago": "{date} ago",
|
||||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
|
@ -625,7 +625,8 @@
|
||||
"createPlaceholder": "Add this as new label",
|
||||
"addSuccess": "The label has been added successfully.",
|
||||
"createSuccess": "The label has been created successfully.",
|
||||
"removeSuccess": "The label has been removed successfully."
|
||||
"removeSuccess": "The label has been removed successfully.",
|
||||
"addCreateSuccess": "The label has been created and added successfully."
|
||||
},
|
||||
"priority": {
|
||||
"unset": "Unset",
|
||||
@ -644,7 +645,21 @@
|
||||
"noneYet": "No task relations yet.",
|
||||
"delete": "Delete Task Relation",
|
||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||
"deleteText2": "This cannot be undone!"
|
||||
"deleteText2": "This cannot be undone!",
|
||||
"select": "Select a relation kind",
|
||||
"kinds": {
|
||||
"subtask": "Subtask | Subtasks",
|
||||
"parenttask": "Parent Task | Parent Tasks",
|
||||
"related": "Related Task | Related Tasks",
|
||||
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||
"duplicates": "Duplicates | Duplicates",
|
||||
"blocking": "Blocking | Blocking",
|
||||
"blocked": "Blocked By | Blocked By",
|
||||
"precedes": "Precedes | Precedes",
|
||||
"follows": "Follows | Follows",
|
||||
"copiedfrom": "Copied From | Copied From",
|
||||
"copiedto": "Copied To | Copied To"
|
||||
}
|
||||
},
|
||||
"repeat": {
|
||||
"everyDay": "Every Day",
|
||||
@ -795,8 +810,6 @@
|
||||
},
|
||||
"date": {
|
||||
"locale": "en",
|
||||
"in": "in {date}",
|
||||
"ago": "{date} ago",
|
||||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
|
@ -625,7 +625,8 @@
|
||||
"createPlaceholder": "Add this as new label",
|
||||
"addSuccess": "The label has been added successfully.",
|
||||
"createSuccess": "The label has been created successfully.",
|
||||
"removeSuccess": "The label has been removed successfully."
|
||||
"removeSuccess": "The label has been removed successfully.",
|
||||
"addCreateSuccess": "The label has been created and added successfully."
|
||||
},
|
||||
"priority": {
|
||||
"unset": "Unset",
|
||||
@ -644,7 +645,21 @@
|
||||
"noneYet": "No task relations yet.",
|
||||
"delete": "Delete Task Relation",
|
||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||
"deleteText2": "This cannot be undone!"
|
||||
"deleteText2": "This cannot be undone!",
|
||||
"select": "Select a relation kind",
|
||||
"kinds": {
|
||||
"subtask": "Subtask | Subtasks",
|
||||
"parenttask": "Parent Task | Parent Tasks",
|
||||
"related": "Related Task | Related Tasks",
|
||||
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||
"duplicates": "Duplicates | Duplicates",
|
||||
"blocking": "Blocking | Blocking",
|
||||
"blocked": "Blocked By | Blocked By",
|
||||
"precedes": "Precedes | Precedes",
|
||||
"follows": "Follows | Follows",
|
||||
"copiedfrom": "Copied From | Copied From",
|
||||
"copiedto": "Copied To | Copied To"
|
||||
}
|
||||
},
|
||||
"repeat": {
|
||||
"everyDay": "Every Day",
|
||||
@ -795,8 +810,6 @@
|
||||
},
|
||||
"date": {
|
||||
"locale": "en",
|
||||
"in": "in {date}",
|
||||
"ago": "{date} ago",
|
||||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
|
@ -625,7 +625,8 @@
|
||||
"createPlaceholder": "Add this as new label",
|
||||
"addSuccess": "The label has been added successfully.",
|
||||
"createSuccess": "The label has been created successfully.",
|
||||
"removeSuccess": "The label has been removed successfully."
|
||||
"removeSuccess": "The label has been removed successfully.",
|
||||
"addCreateSuccess": "The label has been created and added successfully."
|
||||
},
|
||||
"priority": {
|
||||
"unset": "Unset",
|
||||
@ -644,7 +645,21 @@
|
||||
"noneYet": "No task relations yet.",
|
||||
"delete": "Delete Task Relation",
|
||||
"deleteText1": "Are you sure you want to delete this task relation?",
|
||||
"deleteText2": "This cannot be undone!"
|
||||
"deleteText2": "This cannot be undone!",
|
||||
"select": "Select a relation kind",
|
||||
"kinds": {
|
||||
"subtask": "Subtask | Subtasks",
|
||||
"parenttask": "Parent Task | Parent Tasks",
|
||||
"related": "Related Task | Related Tasks",
|
||||
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||
"duplicates": "Duplicates | Duplicates",
|
||||
"blocking": "Blocking | Blocking",
|
||||
"blocked": "Blocked By | Blocked By",
|
||||
"precedes": "Precedes | Precedes",
|
||||
"follows": "Follows | Follows",
|
||||
"copiedfrom": "Copied From | Copied From",
|
||||
"copiedto": "Copied To | Copied To"
|
||||
}
|
||||
},
|
||||
"repeat": {
|
||||
"everyDay": "Every Day",
|
||||
@ -795,8 +810,6 @@
|
||||
},
|
||||
"date": {
|
||||
"locale": "en",
|
||||
"in": "in {date}",
|
||||
"ago": "{date} ago",
|
||||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
|
@ -625,7 +625,8 @@
|
||||
"createPlaceholder": "Добавить как новую метку",
|
||||
"addSuccess": "Метка добавлена.",
|
||||
"createSuccess": "Метка создана.",
|
||||
"removeSuccess": "Метка удалена."
|
||||
"removeSuccess": "Метка удалена.",
|
||||
"addCreateSuccess": "The label has been created and added successfully."
|
||||
},
|
||||
"priority": {
|
||||
"unset": "Не указан",
|
||||
@ -644,7 +645,21 @@
|
||||
"noneYet": "Ещё нет связанных задач.",
|
||||
"delete": "Удалить связь",
|
||||
"deleteText1": "Удалить эту связь с задачей?",
|
||||
"deleteText2": "Это действие отменить нельзя!"
|
||||
"deleteText2": "Это действие отменить нельзя!",
|
||||
"select": "Select a relation kind",
|
||||
"kinds": {
|
||||
"subtask": "Subtask | Subtasks",
|
||||
"parenttask": "Parent Task | Parent Tasks",
|
||||
"related": "Related Task | Related Tasks",
|
||||
"duplicateof": "Duplicate Of | Duplicates Of",
|
||||
"duplicates": "Duplicates | Duplicates",
|
||||
"blocking": "Blocking | Blocking",
|
||||
"blocked": "Blocked By | Blocked By",
|
||||
"precedes": "Precedes | Precedes",
|
||||
"follows": "Follows | Follows",
|
||||
"copiedfrom": "Copied From | Copied From",
|
||||
"copiedto": "Copied To | Copied To"
|
||||
}
|
||||
},
|
||||
"repeat": {
|
||||
"everyDay": "Каждый день",
|
||||
@ -795,8 +810,6 @@
|
||||
},
|
||||
"date": {
|
||||
"locale": "en",
|
||||
"in": "в {date}",
|
||||
"ago": "{date} назад",
|
||||
"altFormatLong": "j M Y H:i",
|
||||
"altFormatShort": "j M Y"
|
||||
},
|
||||
|
@ -18,7 +18,7 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
import {formatDateShort, formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
||||
import {formatDate, formatDateShort, formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
||||
// @ts-ignore
|
||||
import {VERSION} from './version.json'
|
||||
|
||||
@ -82,6 +82,7 @@ import {setTitle} from './helpers/setTitle'
|
||||
app.mixin({
|
||||
methods: {
|
||||
formatDateSince,
|
||||
format: formatDate,
|
||||
formatDate: formatDateLong,
|
||||
formatDateShort: formatDateShort,
|
||||
getNamespaceTitle,
|
||||
|
@ -1,7 +1,5 @@
|
||||
import defaults from 'lodash/defaults'
|
||||
import isNil from 'lodash/isNil'
|
||||
import omitBy from 'lodash/omitBy'
|
||||
import {objectToCamelCase} from '@/helpers/case'
|
||||
import {omitBy, isNil} from '@/helpers/utils'
|
||||
|
||||
export default class AbstractModel {
|
||||
|
||||
@ -16,11 +14,14 @@ export default class AbstractModel {
|
||||
* @param data
|
||||
*/
|
||||
constructor(data) {
|
||||
|
||||
data = objectToCamelCase(data)
|
||||
|
||||
// Put all data in our model while overriding those with a value of null or undefined with their defaults
|
||||
defaults(this, omitBy(data, isNil), this.defaults())
|
||||
Object.assign(
|
||||
this,
|
||||
this.defaults(),
|
||||
omitBy(data, isNil),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,46 +1,12 @@
|
||||
{
|
||||
"subtask": [
|
||||
"Subtask",
|
||||
"Subtasks"
|
||||
],
|
||||
"parenttask": [
|
||||
"Parent Task",
|
||||
"Parent Tasks"
|
||||
],
|
||||
"related": [
|
||||
"Related Task",
|
||||
"Related Tasks"
|
||||
],
|
||||
"duplicateof": [
|
||||
"Duplicate Of",
|
||||
"Duplicates Of"
|
||||
],
|
||||
"duplicates": [
|
||||
"Duplicates",
|
||||
"Duplicates"
|
||||
],
|
||||
"blocking": [
|
||||
"Blocking",
|
||||
"Blocking"
|
||||
],
|
||||
"blocked": [
|
||||
"Blocked By",
|
||||
"Blocked By"
|
||||
],
|
||||
"precedes": [
|
||||
"Precedes",
|
||||
"Precedes"
|
||||
],
|
||||
"follows": [
|
||||
"Follows",
|
||||
"Follows"
|
||||
],
|
||||
"copiedfrom": [
|
||||
"Copied From",
|
||||
"Copied From"
|
||||
],
|
||||
"copiedto": [
|
||||
"Copied To",
|
||||
"Copied To"
|
||||
]
|
||||
}
|
||||
[
|
||||
"subtask",
|
||||
"parenttask",
|
||||
"related",
|
||||
"duplicates",
|
||||
"blocking",
|
||||
"blocked",
|
||||
"precedes",
|
||||
"follows",
|
||||
"copiedfrom",
|
||||
"copiedto"
|
||||
]
|
@ -1,13 +1,10 @@
|
||||
import TeamShareBaseModel from './teamShareBase'
|
||||
import merge from 'lodash/merge'
|
||||
|
||||
export default class TeamListModel extends TeamShareBaseModel {
|
||||
defaults() {
|
||||
return merge(
|
||||
super.defaults(),
|
||||
{
|
||||
listId: 0,
|
||||
},
|
||||
)
|
||||
return {
|
||||
...super.defaults(),
|
||||
listId: 0,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,11 @@
|
||||
import UserModel from './user'
|
||||
import merge from 'lodash/merge'
|
||||
|
||||
export default class TeamMemberModel extends UserModel {
|
||||
defaults() {
|
||||
return merge(
|
||||
super.defaults(),
|
||||
{
|
||||
admin: false,
|
||||
teamId: 0,
|
||||
},
|
||||
)
|
||||
return {
|
||||
...super.defaults(),
|
||||
admin: false,
|
||||
teamId: 0,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,13 +1,10 @@
|
||||
import TeamShareBaseModel from './teamShareBase'
|
||||
import merge from 'lodash/merge'
|
||||
|
||||
export default class TeamNamespaceModel extends TeamShareBaseModel {
|
||||
defaults() {
|
||||
return merge(
|
||||
super.defaults(),
|
||||
{
|
||||
namespaceId: 0,
|
||||
},
|
||||
)
|
||||
return {
|
||||
...super.defaults(),
|
||||
namespaceId: 0,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,11 @@
|
||||
import UserShareBaseModel from './userShareBase'
|
||||
import merge from 'lodash/merge'
|
||||
|
||||
// This class extends the user share model with a 'rights' parameter which is used in sharing
|
||||
export default class UserListModel extends UserShareBaseModel {
|
||||
defaults() {
|
||||
return merge(
|
||||
super.defaults(),
|
||||
{
|
||||
listId: 0,
|
||||
},
|
||||
)
|
||||
return {
|
||||
...super.defaults(),
|
||||
listId: 0,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,14 +1,11 @@
|
||||
import UserShareBaseModel from './userShareBase'
|
||||
import merge from 'lodash/merge'
|
||||
|
||||
// This class extends the user share model with a 'rights' parameter which is used in sharing
|
||||
export default class UserNamespaceModel extends UserShareBaseModel {
|
||||
defaults() {
|
||||
return merge(
|
||||
super.defaults(),
|
||||
{
|
||||
namespaceId: 0,
|
||||
},
|
||||
)
|
||||
return {
|
||||
...super.defaults(),
|
||||
namespaceId: 0,
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
import axios from 'axios'
|
||||
import reduce from 'lodash/reduce'
|
||||
import replace from 'lodash/replace'
|
||||
import {objectToSnakeCase} from '@/helpers/case'
|
||||
import {getToken} from '@/helpers/auth'
|
||||
|
||||
@ -157,9 +155,10 @@ export default class AbstractService {
|
||||
*/
|
||||
getReplacedRoute(path, pathparams) {
|
||||
let replacements = this.getRouteReplacements(path, pathparams)
|
||||
return reduce(replacements, function (result, value, parameter) {
|
||||
return replace(result, parameter, value)
|
||||
}, path)
|
||||
return Object.entries(replacements).reduce(
|
||||
(result, [parameter, value]) => result.replace(parameter, value),
|
||||
path,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,4 @@
|
||||
import {findIndexById} from '@/helpers/find'
|
||||
import {findIndexById} from '@/helpers/utils'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
|
@ -1,6 +1,4 @@
|
||||
import cloneDeep from 'lodash/cloneDeep'
|
||||
|
||||
import {findById, findIndexById} from '@/helpers/find'
|
||||
import {findById, findIndexById} from '@/helpers/utils'
|
||||
import BucketService from '../../services/bucket'
|
||||
import {setLoading} from '../helper'
|
||||
import TaskCollectionService from '@/services/taskCollection'
|
||||
@ -253,7 +251,7 @@ export default {
|
||||
const cancel = setLoading(ctx, 'kanban')
|
||||
ctx.commit('setBucketLoading', {bucketId: bucketId, loading: true})
|
||||
|
||||
const params = cloneDeep(ps)
|
||||
const params = JSON.parse(JSON.stringify(ps))
|
||||
|
||||
params.sort_by = 'kanban_position'
|
||||
params.order_by = 'asc'
|
||||
|
@ -1,10 +1,37 @@
|
||||
import LabelService from '@/services/label'
|
||||
import {setLoading} from '@/store/helper'
|
||||
|
||||
/**
|
||||
* Returns the labels by id if found
|
||||
* @param {Object} state
|
||||
* @param {Array} ids
|
||||
* @returns {Array}
|
||||
*/
|
||||
function getLabelsByIds(state, ids) {
|
||||
return Object.values(state.labels).filter(({id}) => ids.includes(id))
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a list of labels is available in the store and filters them then query
|
||||
* @param {Object} state
|
||||
* @param {Array} labels
|
||||
* @param {String} query
|
||||
* @returns {Array}
|
||||
*/
|
||||
function filterLabelsByQuery(state, labels, query) {
|
||||
const labelIds = labels.map(({id}) => id)
|
||||
const foundLabels = getLabelsByIds(state, labelIds)
|
||||
const labelQuery = query.toLowerCase()
|
||||
|
||||
return foundLabels.filter(({title}) => {
|
||||
return !title.toLowerCase().includes(labelQuery)
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
// The state is an object which has the label ids as keys.
|
||||
state: () => ({
|
||||
// The labels are stored as an object which has the label ids as keys.
|
||||
labels: {},
|
||||
loaded: false,
|
||||
}),
|
||||
@ -24,6 +51,14 @@ export default {
|
||||
state.loaded = loaded
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
getLabelsByIds(state) {
|
||||
return (ids) => getLabelsByIds(state, ids)
|
||||
},
|
||||
filterLabelsByQuery(state) {
|
||||
return (...arr) => filterLabelsByQuery(state, ...arr)
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
loadAllLabels(ctx, {forceLoad} = {}) {
|
||||
if (ctx.state.loaded && !forceLoad) {
|
||||
|
@ -117,11 +117,16 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
margin-top: .25rem;
|
||||
|
||||
.tag, .assignees, .icon, .priority-label {
|
||||
margin-top: .25rem;
|
||||
.tag, .assignees, .icon, .priority-label, .checklist-summary {
|
||||
margin-top: 0;
|
||||
margin-right: .25rem;
|
||||
}
|
||||
|
||||
.checklist-summary {
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.assignees {
|
||||
display: flex;
|
||||
@ -142,7 +147,7 @@ $filter-container-height: '1rem - #{$switch-view-height}';
|
||||
|
||||
.priority-label {
|
||||
font-size: .75rem;
|
||||
height: 2rem;
|
||||
height: 1.5rem;
|
||||
|
||||
.icon {
|
||||
height: 1rem;
|
||||
|
@ -35,7 +35,7 @@
|
||||
}
|
||||
|
||||
.no-auth-wrapper {
|
||||
background: url('/images/llama.svg') no-repeat bottom left fixed $light-background;
|
||||
background: url('@/assets/llama.svg') no-repeat bottom left fixed $light-background;
|
||||
min-height: 100vh;
|
||||
|
||||
.noauth-container {
|
||||
|
@ -1,6 +1,5 @@
|
||||
.offline {
|
||||
background: url('/images/llama-nightscape.png') no-repeat center;
|
||||
-webkit-background-size: cover;
|
||||
background: url('@/assets/llama-nightscape.jpg') no-repeat center;
|
||||
background-size: cover;
|
||||
height: 100vh;
|
||||
|
||||
|
@ -112,7 +112,7 @@ export default {
|
||||
this.filterService.update(this.filter)
|
||||
.then(r => {
|
||||
this.$store.dispatch('namespaces/loadNamespaces')
|
||||
this.$message.success({message: this.$t('filters.attributes.edit.success')})
|
||||
this.$message.success({message: this.$t('filters.edit.success')})
|
||||
this.filter = r
|
||||
this.filters = objectToSnakeCase(this.filter.filters)
|
||||
this.$router.back()
|
||||
|
@ -236,7 +236,7 @@
|
||||
import draggable from 'vuedraggable'
|
||||
|
||||
import BucketModel from '../../../models/bucket'
|
||||
import {findById} from '@/helpers/find'
|
||||
import {findById} from '@/helpers/utils'
|
||||
import {mapState} from 'vuex'
|
||||
import {saveListView} from '@/helpers/saveListView'
|
||||
import Rights from '../../../models/constants/rights.json'
|
||||
|
@ -99,16 +99,18 @@
|
||||
@taskUpdated="updateTasks"
|
||||
task-detail-route="task.detail"
|
||||
>
|
||||
<span class="icon handle">
|
||||
<icon icon="grip-lines"/>
|
||||
</span>
|
||||
<div
|
||||
@click="editTask(t.id)"
|
||||
class="icon settings"
|
||||
v-if="!list.isArchived && canWrite"
|
||||
>
|
||||
<icon icon="pencil-alt"/>
|
||||
</div>
|
||||
<template v-if="canWrite">
|
||||
<span class="icon handle">
|
||||
<icon icon="grip-lines"/>
|
||||
</span>
|
||||
<div
|
||||
@click="editTask(t.id)"
|
||||
class="icon settings"
|
||||
v-if="!list.isArchived"
|
||||
>
|
||||
<icon icon="pencil-alt"/>
|
||||
</div>
|
||||
</template>
|
||||
</single-task-in-list>
|
||||
</template>
|
||||
</draggable>
|
||||
@ -152,7 +154,6 @@ import {HAS_TASKS} from '@/store/mutation-types'
|
||||
import Nothing from '@/components/misc/nothing.vue'
|
||||
import Pagination from '@/components/misc/pagination.vue'
|
||||
|
||||
import {mapState} from 'vuex'
|
||||
import draggable from 'vuedraggable'
|
||||
import {calculateItemPosition} from '../../../helpers/calculateItemPosition'
|
||||
|
||||
@ -216,10 +217,12 @@ export default {
|
||||
|
||||
return calculateItemPosition(null, this.tasks[0].position)
|
||||
},
|
||||
...mapState({
|
||||
canWrite: state => state.currentList.maxRight > Rights.READ,
|
||||
list: state => state.currentList,
|
||||
}),
|
||||
canWrite() {
|
||||
return this.list.maxRight > Rights.READ && this.list.id > 0
|
||||
},
|
||||
list() {
|
||||
return this.$store.state.currentList
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.$nextTick(() => (this.ctaVisible = true))
|
||||
|
@ -140,7 +140,7 @@
|
||||
<router-link :to="{name: 'task.detail', params: { id: t.id }}">{{ t.title }}</router-link>
|
||||
</td>
|
||||
<td v-if="activeColumns.priority">
|
||||
<priority-label :priority="t.priority" :show-all="true"/>
|
||||
<priority-label :priority="t.priority" :done="t.done" :show-all="true"/>
|
||||
</td>
|
||||
<td v-if="activeColumns.labels">
|
||||
<labels :labels="t.labels"/>
|
||||
|
@ -4,18 +4,20 @@
|
||||
<p>{{ $t('migrate.description') }}</p>
|
||||
<div class="migration-services-overview">
|
||||
<router-link
|
||||
:key="m.identifier"
|
||||
:to="{name: 'migrate.service', params: {service: m.identifier}}"
|
||||
v-for="m in availableMigrators">
|
||||
<img :alt="m.name" :src="`/images/migration/${m.identifier}.png`"/>
|
||||
{{ m.name }}
|
||||
v-for="{name, identifier} in availableMigrators"
|
||||
:key="identifier"
|
||||
:to="{name: 'migrate.service', params: {service: identifier}}"
|
||||
>
|
||||
<img :alt="name" :src="serviceIconSources[identifier]"/>
|
||||
{{ name }}
|
||||
</router-link>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {getMigratorFromSlug} from '../../helpers/migrator'
|
||||
import {getMigratorFromSlug, SERVICE_ICONS} from '../../helpers/migrator'
|
||||
|
||||
|
||||
export default {
|
||||
name: 'migrate.service',
|
||||
@ -26,6 +28,9 @@ export default {
|
||||
availableMigrators() {
|
||||
return this.$store.state.config.availableMigrators.map(getMigratorFromSlug)
|
||||
},
|
||||
serviceIconSources() {
|
||||
return this.availableMigrators.map(({identifier}) => SERVICE_ICONS[identifier]())
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
@ -77,7 +77,7 @@ export default {
|
||||
}
|
||||
|
||||
// TODO: Put this logic in a global errorMessage handler method which checks all auth codes
|
||||
let errorMessage = this.$t('sharing.errorMessage')
|
||||
let errorMessage = this.$t('sharing.error')
|
||||
if (e.response && e.response.data && e.response.data.message) {
|
||||
errorMessage = e.response.data.message
|
||||
}
|
||||
|
@ -38,7 +38,7 @@
|
||||
</div>
|
||||
<template v-if="!loading && (!tasks || tasks.length === 0) && showNothingToDo">
|
||||
<h3 class="nothing">{{ $t('task.show.noTasks') }}</h3>
|
||||
<img alt="" src="/images/cool.svg"/>
|
||||
<img alt="" :src="llamaCoolUrl" />
|
||||
</template>
|
||||
<div :class="{ 'is-loading': loading}" class="spinner"></div>
|
||||
|
||||
@ -64,6 +64,8 @@ import 'flatpickr/dist/flatpickr.css'
|
||||
import Fancycheckbox from '../../components/input/fancycheckbox'
|
||||
import {LOADING, LOADING_MODULE} from '../../store/mutation-types'
|
||||
|
||||
import llamaCoolUrl from '@/assets/llama-cool.svg'
|
||||
|
||||
export default {
|
||||
name: 'ShowTasks',
|
||||
components: {
|
||||
@ -81,6 +83,7 @@ export default {
|
||||
cEndDate: null,
|
||||
|
||||
showNothingToDo: false,
|
||||
llamaCoolUrl,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
|
@ -423,7 +423,6 @@
|
||||
<script>
|
||||
import TaskService from '../../services/task'
|
||||
import TaskModel from '../../models/task'
|
||||
import relationKinds from '../../models/constants/relationKinds.json'
|
||||
|
||||
import priorites from '../../models/constants/priorities.json'
|
||||
import rights from '../../models/constants/rights.json'
|
||||
@ -473,7 +472,6 @@ export default {
|
||||
return {
|
||||
taskService: new TaskService(),
|
||||
task: new TaskModel(),
|
||||
relationKinds: relationKinds,
|
||||
// We doubled the task color property here because verte does not have a real change property, leading
|
||||
// to the color property change being triggered when the # is removed from it, leading to an update,
|
||||
// which leads in turn to a change... This creates an infinite loop in which the task is updated, changed,
|
||||
|