feat: add-task usability improvements (#2767)
Co-authored-by: Dominik Pschenitschni <mail@celement.de> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/2767 Reviewed-by: konrad <k@knt.li> Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de> Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
This commit is contained in:
parent
5a89bc0183
commit
4be53b098c
@ -49,6 +49,7 @@
|
|||||||
"flatpickr": "4.6.13",
|
"flatpickr": "4.6.13",
|
||||||
"flexsearch": "0.7.21",
|
"flexsearch": "0.7.21",
|
||||||
"floating-vue": "2.0.0-beta.20",
|
"floating-vue": "2.0.0-beta.20",
|
||||||
|
"focus-within": "3.0.2",
|
||||||
"highlight.js": "11.7.0",
|
"highlight.js": "11.7.0",
|
||||||
"is-touch-device": "1.0.1",
|
"is-touch-device": "1.0.1",
|
||||||
"lodash.clonedeep": "4.5.0",
|
"lodash.clonedeep": "4.5.0",
|
||||||
@ -77,6 +78,7 @@
|
|||||||
"@types/codemirror": "5.60.6",
|
"@types/codemirror": "5.60.6",
|
||||||
"@types/dompurify": "2.4.0",
|
"@types/dompurify": "2.4.0",
|
||||||
"@types/flexsearch": "0.7.3",
|
"@types/flexsearch": "0.7.3",
|
||||||
|
"@types/focus-within": "1.0.1",
|
||||||
"@types/lodash.debounce": "4.0.7",
|
"@types/lodash.debounce": "4.0.7",
|
||||||
"@types/marked": "4.0.8",
|
"@types/marked": "4.0.8",
|
||||||
"@types/node": "18.11.18",
|
"@types/node": "18.11.18",
|
||||||
@ -99,6 +101,8 @@
|
|||||||
"happy-dom": "8.1.1",
|
"happy-dom": "8.1.1",
|
||||||
"netlify-cli": "12.5.0",
|
"netlify-cli": "12.5.0",
|
||||||
"postcss": "8.4.20",
|
"postcss": "8.4.20",
|
||||||
|
"postcss-easing-gradients": "3.0.1",
|
||||||
|
"postcss-easings": "3.0.1",
|
||||||
"postcss-preset-env": "7.8.3",
|
"postcss-preset-env": "7.8.3",
|
||||||
"rollup": "3.9.1",
|
"rollup": "3.9.1",
|
||||||
"rollup-plugin-visualizer": "5.9.0",
|
"rollup-plugin-visualizer": "5.9.0",
|
||||||
|
62
pnpm-lock.yaml
generated
62
pnpm-lock.yaml
generated
@ -19,6 +19,7 @@ specifiers:
|
|||||||
'@types/codemirror': 5.60.6
|
'@types/codemirror': 5.60.6
|
||||||
'@types/dompurify': 2.4.0
|
'@types/dompurify': 2.4.0
|
||||||
'@types/flexsearch': 0.7.3
|
'@types/flexsearch': 0.7.3
|
||||||
|
'@types/focus-within': 1.0.1
|
||||||
'@types/is-touch-device': 1.0.0
|
'@types/is-touch-device': 1.0.0
|
||||||
'@types/lodash.clonedeep': 4.5.7
|
'@types/lodash.clonedeep': 4.5.7
|
||||||
'@types/lodash.debounce': 4.0.7
|
'@types/lodash.debounce': 4.0.7
|
||||||
@ -55,6 +56,7 @@ specifiers:
|
|||||||
flatpickr: 4.6.13
|
flatpickr: 4.6.13
|
||||||
flexsearch: 0.7.21
|
flexsearch: 0.7.21
|
||||||
floating-vue: 2.0.0-beta.20
|
floating-vue: 2.0.0-beta.20
|
||||||
|
focus-within: 3.0.2
|
||||||
happy-dom: 8.1.1
|
happy-dom: 8.1.1
|
||||||
highlight.js: 11.7.0
|
highlight.js: 11.7.0
|
||||||
is-touch-device: 1.0.1
|
is-touch-device: 1.0.1
|
||||||
@ -65,6 +67,8 @@ specifiers:
|
|||||||
netlify-cli: 12.5.0
|
netlify-cli: 12.5.0
|
||||||
pinia: 2.0.28
|
pinia: 2.0.28
|
||||||
postcss: 8.4.20
|
postcss: 8.4.20
|
||||||
|
postcss-easing-gradients: 3.0.1
|
||||||
|
postcss-easings: 3.0.1
|
||||||
postcss-preset-env: 7.8.3
|
postcss-preset-env: 7.8.3
|
||||||
register-service-worker: 1.7.2
|
register-service-worker: 1.7.2
|
||||||
rollup: 3.9.1
|
rollup: 3.9.1
|
||||||
@ -118,6 +122,7 @@ dependencies:
|
|||||||
flatpickr: 4.6.13
|
flatpickr: 4.6.13
|
||||||
flexsearch: 0.7.21
|
flexsearch: 0.7.21
|
||||||
floating-vue: 2.0.0-beta.20_vue@3.2.45
|
floating-vue: 2.0.0-beta.20_vue@3.2.45
|
||||||
|
focus-within: 3.0.2
|
||||||
highlight.js: 11.7.0
|
highlight.js: 11.7.0
|
||||||
is-touch-device: 1.0.1
|
is-touch-device: 1.0.1
|
||||||
lodash.clonedeep: 4.5.0
|
lodash.clonedeep: 4.5.0
|
||||||
@ -146,6 +151,7 @@ devDependencies:
|
|||||||
'@types/codemirror': 5.60.6
|
'@types/codemirror': 5.60.6
|
||||||
'@types/dompurify': 2.4.0
|
'@types/dompurify': 2.4.0
|
||||||
'@types/flexsearch': 0.7.3
|
'@types/flexsearch': 0.7.3
|
||||||
|
'@types/focus-within': 1.0.1
|
||||||
'@types/lodash.debounce': 4.0.7
|
'@types/lodash.debounce': 4.0.7
|
||||||
'@types/marked': 4.0.8
|
'@types/marked': 4.0.8
|
||||||
'@types/node': 18.11.18
|
'@types/node': 18.11.18
|
||||||
@ -168,6 +174,8 @@ devDependencies:
|
|||||||
happy-dom: 8.1.1
|
happy-dom: 8.1.1
|
||||||
netlify-cli: 12.5.0_@types+node@18.11.18
|
netlify-cli: 12.5.0_@types+node@18.11.18
|
||||||
postcss: 8.4.20
|
postcss: 8.4.20
|
||||||
|
postcss-easing-gradients: 3.0.1
|
||||||
|
postcss-easings: 3.0.1_postcss@8.4.20
|
||||||
postcss-preset-env: 7.8.3_postcss@8.4.20
|
postcss-preset-env: 7.8.3_postcss@8.4.20
|
||||||
rollup: 3.9.1
|
rollup: 3.9.1
|
||||||
rollup-plugin-visualizer: 5.9.0_rollup@3.9.1
|
rollup-plugin-visualizer: 5.9.0_rollup@3.9.1
|
||||||
@ -3253,6 +3261,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-HXwADeHEP4exXkCIwy2n1+i0f1ilP1ETQOH5KDOugjkTFZPntWo0Gr8stZOaebkxsdx+k0X/K6obU/+it07ocg==}
|
resolution: {integrity: sha512-HXwADeHEP4exXkCIwy2n1+i0f1ilP1ETQOH5KDOugjkTFZPntWo0Gr8stZOaebkxsdx+k0X/K6obU/+it07ocg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/focus-within/1.0.1:
|
||||||
|
resolution: {integrity: sha512-ClIiYA9fOJUcZzb8nQXlvwta5obDHd5aQw4I2L/b1lvFSPXXImgiN7ueRVfMlIEkpAvc22hMjbu3g3RiPzZEUQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/glob/7.2.0:
|
/@types/glob/7.2.0:
|
||||||
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
|
resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -4869,6 +4881,10 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/chroma-js/1.4.1:
|
||||||
|
resolution: {integrity: sha512-jTwQiT859RTFN/vIf7s+Vl/Z2LcMrvMv3WUFmd/4u76AdlFC0NTNgqEEFPcRiHmAswPsMiQEDZLM8vX8qXpZNQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/ci-info/2.0.0:
|
/ci-info/2.0.0:
|
||||||
resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==}
|
resolution: {integrity: sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -6105,6 +6121,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==}
|
resolution: {integrity: sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/easing-coordinates/2.0.2:
|
||||||
|
resolution: {integrity: sha512-uQpJaLQX2CKVnN27YvN4sL4pXyEFGAv00y4zgrC46H0EBHrDhJc/8OsT2CQ5/6yz6+d+u8ACGd9bo4v83FNVlg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/eastasianwidth/0.2.0:
|
/eastasianwidth/0.2.0:
|
||||||
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -7069,6 +7089,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
|
resolution: {integrity: sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/focus-within/3.0.2:
|
||||||
|
resolution: {integrity: sha512-TMn2sLUwi02dSPElXFPyzBkiQN+Xo1oYta5Jd2zC54MQoBOCMht6xar7gJgw5a9+GtFxkG9c2k4ptI6cU37soA==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
postcss: 7.0.39
|
||||||
|
dev: false
|
||||||
|
|
||||||
/folder-walker/3.2.0:
|
/folder-walker/3.2.0:
|
||||||
resolution: {integrity: sha512-VjAQdSLsl6AkpZNyrQJfO7BXLo4chnStqb055bumZMbRUPpVuPN3a4ktsnRCmrFZjtMlYLkyXiR5rAs4WOpC4Q==}
|
resolution: {integrity: sha512-VjAQdSLsl6AkpZNyrQJfO7BXLo4chnStqb055bumZMbRUPpVuPN3a4ktsnRCmrFZjtMlYLkyXiR5rAs4WOpC4Q==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@ -10489,6 +10517,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
|
resolution: {integrity: sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/picocolors/0.2.1:
|
||||||
|
resolution: {integrity: sha512-cMlDqaLEqfSaW8Z7N5Jw+lyIW869EzT73/F5lhtY9cLGoVxSXznfgfXMO0Z5K0o0Q2TkTXq+0KFsdnSe3jDViA==}
|
||||||
|
|
||||||
/picocolors/1.0.0:
|
/picocolors/1.0.0:
|
||||||
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==}
|
||||||
|
|
||||||
@ -10662,6 +10693,26 @@ packages:
|
|||||||
postcss-value-parser: 4.2.0
|
postcss-value-parser: 4.2.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/postcss-easing-gradients/3.0.1:
|
||||||
|
resolution: {integrity: sha512-UrOKb4cenjGmMmrheETw7Cjnn/IKn3xgTvHs92b0sSwMhKgeZKxJpduGRjYZ8wgpu3zOzzgQpRwOLhhtMofayA==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
dependencies:
|
||||||
|
chroma-js: 1.4.1
|
||||||
|
easing-coordinates: 2.0.2
|
||||||
|
postcss: 7.0.39
|
||||||
|
postcss-value-parser: 3.3.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/postcss-easings/3.0.1_postcss@8.4.20:
|
||||||
|
resolution: {integrity: sha512-n3bG/X3iB0m8d845vhFg/62/ECeT8jY8gE8F2A41z8Mty41spYA4vzMLezha7icVjtGjqlxgO3QE+uOzpDqeww==}
|
||||||
|
engines: {node: '>=10.0'}
|
||||||
|
peerDependencies:
|
||||||
|
postcss: ^8.1.0
|
||||||
|
dependencies:
|
||||||
|
postcss: 8.4.20
|
||||||
|
postcss-value-parser: 4.2.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/postcss-env-function/4.0.6_postcss@8.4.20:
|
/postcss-env-function/4.0.6_postcss@8.4.20:
|
||||||
resolution: {integrity: sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==}
|
resolution: {integrity: sha512-kpA6FsLra+NqcFnL81TnsU+Z7orGtDTxcOhl6pwXeEq1yFPpRMkCDpHhrz8CFQDr/Wfm0jLiNQ1OsGGPjlqPwA==}
|
||||||
engines: {node: ^12 || ^14 || >=16}
|
engines: {node: ^12 || ^14 || >=16}
|
||||||
@ -10894,6 +10945,10 @@ packages:
|
|||||||
util-deprecate: 1.0.2
|
util-deprecate: 1.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/postcss-value-parser/3.3.1:
|
||||||
|
resolution: {integrity: sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/postcss-value-parser/4.2.0:
|
/postcss-value-parser/4.2.0:
|
||||||
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@ -10910,6 +10965,13 @@ packages:
|
|||||||
quote-unquote: 1.0.0
|
quote-unquote: 1.0.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/postcss/7.0.39:
|
||||||
|
resolution: {integrity: sha512-yioayjNbHn6z1/Bywyb2Y4s3yvDAeXGOyxqD+LnVOinq6Mdmd++SW2wUNVzavyyHxd6+DxzWGIuosg6P1Rj8uA==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
dependencies:
|
||||||
|
picocolors: 0.2.1
|
||||||
|
source-map: 0.6.1
|
||||||
|
|
||||||
/postcss/8.4.20:
|
/postcss/8.4.20:
|
||||||
resolution: {integrity: sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==}
|
resolution: {integrity: sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==}
|
||||||
engines: {node: ^10 || ^12 || >=14}
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
179
src/components/base/Expandable.vue
Normal file
179
src/components/base/Expandable.vue
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
<template>
|
||||||
|
<transition
|
||||||
|
name="expandable-slide"
|
||||||
|
@before-enter="beforeEnter"
|
||||||
|
@enter="enter"
|
||||||
|
@after-enter="afterEnter"
|
||||||
|
@enter-cancelled="enterCancelled"
|
||||||
|
@before-leave="beforeLeave"
|
||||||
|
@leave="leave"
|
||||||
|
@after-leave="afterLeave"
|
||||||
|
@leave-cancelled="leaveCancelled"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-if="initialHeight"
|
||||||
|
class="expandable-initial-height"
|
||||||
|
:style="{ maxHeight: `${initialHeight}px` }"
|
||||||
|
:class="{ 'expandable-initial-height--expanded': open }"
|
||||||
|
>
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="open" class="expandable">
|
||||||
|
<slot />
|
||||||
|
</div>
|
||||||
|
</transition>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// the logic of this component is loosly based on this article
|
||||||
|
// https://gomakethings.com/how-to-add-transition-animations-to-vanilla-javascript-show-and-hide-methods/#putting-it-all-together
|
||||||
|
|
||||||
|
import {computed, ref} from 'vue'
|
||||||
|
import {getInheritedBackgroundColor} from '@/helpers/getInheritedBackgroundColor'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
/** Wheather the Expandable is open or not */
|
||||||
|
open: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
/** If there is too much content, content will be cut of here. */
|
||||||
|
initialHeight: {
|
||||||
|
type: Number,
|
||||||
|
default: undefined,
|
||||||
|
},
|
||||||
|
/** The hidden content is indicated by a gradient. This is the color that the gradient fades to.
|
||||||
|
* Makes only sense if `initialHeight` is set. */
|
||||||
|
backgroundColor: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const wrapper = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
|
const computedBackgroundColor = computed(() => {
|
||||||
|
if (wrapper.value === null) {
|
||||||
|
return props.backgroundColor || '#fff'
|
||||||
|
}
|
||||||
|
return props.backgroundColor || getInheritedBackgroundColor(wrapper.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the natural height of the element
|
||||||
|
*/
|
||||||
|
function getHeight(el: HTMLElement) {
|
||||||
|
const { display } = el.style // save display property
|
||||||
|
el.style.display = 'block' // Make it visible
|
||||||
|
const height = `${el.scrollHeight}px` // Get its height
|
||||||
|
el.style.display = display // revert to original display property
|
||||||
|
return height
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* force layout of element changes
|
||||||
|
* https://gist.github.com/paulirish/5d52fb081b3570c81e3a
|
||||||
|
*/
|
||||||
|
function forceLayout(el: HTMLElement) {
|
||||||
|
el.offsetTop
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ######################################################################
|
||||||
|
# The following functions are called by the js hooks of the transitions.
|
||||||
|
# They follow the orignal hook order of the vue transition component
|
||||||
|
# see: https://vuejs.org/guide/built-ins/transition.html#javascript-hooks
|
||||||
|
###################################################################### */
|
||||||
|
|
||||||
|
function beforeEnter(el: HTMLElement) {
|
||||||
|
el.style.height = '0'
|
||||||
|
el.style.willChange = 'height'
|
||||||
|
el.style.backfaceVisibility = 'hidden'
|
||||||
|
forceLayout(el)
|
||||||
|
}
|
||||||
|
|
||||||
|
// the done callback is optional when
|
||||||
|
// used in combination with CSS
|
||||||
|
function enter(el: HTMLElement) {
|
||||||
|
const height = getHeight(el) // Get the natural height
|
||||||
|
el.style.height = height // Update the height
|
||||||
|
}
|
||||||
|
|
||||||
|
function afterEnter(el: HTMLElement) {
|
||||||
|
removeHeight(el)
|
||||||
|
}
|
||||||
|
|
||||||
|
function enterCancelled(el: HTMLElement) {
|
||||||
|
removeHeight(el)
|
||||||
|
}
|
||||||
|
|
||||||
|
function beforeLeave(el: HTMLElement) {
|
||||||
|
// Give the element a height to change from
|
||||||
|
el.style.height = `${el.scrollHeight}px`
|
||||||
|
forceLayout(el)
|
||||||
|
}
|
||||||
|
|
||||||
|
function leave(el: HTMLElement) {
|
||||||
|
// Set the height back to 0
|
||||||
|
el.style.height = '0'
|
||||||
|
el.style.willChange = ''
|
||||||
|
el.style.backfaceVisibility = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
function afterLeave(el: HTMLElement) {
|
||||||
|
removeHeight(el)
|
||||||
|
}
|
||||||
|
|
||||||
|
function leaveCancelled(el: HTMLElement) {
|
||||||
|
removeHeight(el)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeHeight(el: HTMLElement) {
|
||||||
|
el.style.height = ''
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
$transition-time: 300ms;
|
||||||
|
|
||||||
|
.expandable-slide-enter-active,
|
||||||
|
.expandable-slide-leave-active {
|
||||||
|
transition:
|
||||||
|
opacity $transition-time ease-in-quint,
|
||||||
|
height $transition-time ease-in-out-quint;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandable-slide-enter,
|
||||||
|
.expandable-slide-leave-to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandable-initial-height {
|
||||||
|
padding: 5px;
|
||||||
|
margin: -5px;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
background-image: linear-gradient(
|
||||||
|
to bottom,
|
||||||
|
transparent,
|
||||||
|
ease-in-out
|
||||||
|
v-bind(computedBackgroundColor)
|
||||||
|
);
|
||||||
|
position: absolute;
|
||||||
|
height: 40px;
|
||||||
|
width: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.expandable-initial-height--expanded {
|
||||||
|
height: 100% !important;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,9 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="task-add">
|
<div class="task-add" ref="taskAdd">
|
||||||
<div class="field is-grouped">
|
<div class="add-task__field field is-grouped">
|
||||||
<p class="control has-icons-left is-expanded">
|
<p class="control has-icons-left is-expanded">
|
||||||
<textarea
|
<textarea
|
||||||
:disabled="loading || undefined"
|
|
||||||
class="add-task-textarea input"
|
class="add-task-textarea input"
|
||||||
:class="{'textarea-empty': newTaskTitle === ''}"
|
:class="{'textarea-empty': newTaskTitle === ''}"
|
||||||
:placeholder="$t('list.list.addPlaceholder')"
|
:placeholder="$t('list.list.addPlaceholder')"
|
||||||
@ -33,27 +32,34 @@
|
|||||||
</x-button>
|
</x-button>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="help is-danger" v-if="errorMessage !== ''">
|
<Expandable :open="errorMessage !== '' || taskAddFocused || taskAddHovered && debouncedTaskAddHovered">
|
||||||
{{ errorMessage }}
|
<p class="pt-3 mt-0 help is-danger" v-if="errorMessage !== ''">
|
||||||
</p>
|
{{ errorMessage }}
|
||||||
<quick-add-magic v-else/>
|
</p>
|
||||||
|
<quick-add-magic v-else class="quick-add-magic" />
|
||||||
|
</Expandable>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed, ref} from 'vue'
|
import {computed, ref} from 'vue'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
import {refDebounced, useElementHover, useFocusWithin} from '@vueuse/core'
|
||||||
|
|
||||||
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
import {RELATION_KIND} from '@/types/IRelationKind'
|
||||||
import type {ITask} from '@/modelTypes/ITask'
|
import type {ITask} from '@/modelTypes/ITask'
|
||||||
|
|
||||||
|
import Expandable from '@/components/base/Expandable.vue'
|
||||||
|
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
||||||
import {parseSubtasksViaIndention} from '@/helpers/parseSubtasksViaIndention'
|
import {parseSubtasksViaIndention} from '@/helpers/parseSubtasksViaIndention'
|
||||||
import TaskRelationService from '@/services/taskRelation'
|
import TaskRelationService from '@/services/taskRelation'
|
||||||
import TaskRelationModel from '@/models/taskRelation'
|
import TaskRelationModel from '@/models/taskRelation'
|
||||||
import {RELATION_KIND} from '@/types/IRelationKind'
|
import {getLabelsFromPrefix} from '@/modules/parseTaskText'
|
||||||
|
|
||||||
import {useAuthStore} from '@/stores/auth'
|
import {useAuthStore} from '@/stores/auth'
|
||||||
import {useTaskStore} from '@/stores/tasks'
|
import {useTaskStore} from '@/stores/tasks'
|
||||||
|
|
||||||
import {useAutoHeightTextarea} from '@/composables/useAutoHeightTextarea'
|
import {useAutoHeightTextarea} from '@/composables/useAutoHeightTextarea'
|
||||||
import {getLabelsFromPrefix} from '@/modules/parseTaskText'
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
defaultPosition: {
|
defaultPosition: {
|
||||||
@ -71,9 +77,24 @@ const {t} = useI18n({useScope: 'global'})
|
|||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
const taskStore = useTaskStore()
|
const taskStore = useTaskStore()
|
||||||
|
|
||||||
|
const taskAdd = ref<HTMLTextAreaElement | null>(null)
|
||||||
|
|
||||||
|
// enable only if we don't have a modal
|
||||||
|
// onStartTyping(() => {
|
||||||
|
// if (newTaskInput.value === null || document.activeElement === newTaskInput.value) {
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
// newTaskInput.value.focus()
|
||||||
|
// })
|
||||||
|
|
||||||
|
const { focused: taskAddFocused } = useFocusWithin(taskAdd)
|
||||||
|
|
||||||
|
const taskAddHovered = useElementHover(taskAdd)
|
||||||
|
const debouncedTaskAddHovered = refDebounced(taskAddHovered, 500)
|
||||||
|
|
||||||
const errorMessage = ref('')
|
const errorMessage = ref('')
|
||||||
|
|
||||||
function resetEmptyTitleError(e) {
|
function resetEmptyTitleError(e: KeyboardEvent) {
|
||||||
if (
|
if (
|
||||||
(e.which <= 90 && e.which >= 48 || e.which >= 96 && e.which <= 105)
|
(e.which <= 90 && e.which >= 48 || e.which >= 96 && e.which <= 105)
|
||||||
&& newTaskTitle.value !== ''
|
&& newTaskTitle.value !== ''
|
||||||
@ -192,7 +213,9 @@ defineExpose({
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.task-add {
|
.task-add,
|
||||||
|
// overwrite bulma styles
|
||||||
|
.task-add .add-task__field {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,4 +243,8 @@ defineExpose({
|
|||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.quick-add-magic {
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="available">
|
<div v-if="mode !== 'disabled' && prefixes !== undefined">
|
||||||
<p class="help has-text-grey">
|
<p class="help has-text-grey">
|
||||||
{{ $t('task.quickAddMagic.hint') }}.
|
{{ $t('task.quickAddMagic.hint') }}.
|
||||||
<ButtonLink @click="() => visible = true">{{ $t('task.quickAddMagic.what') }}</ButtonLink>
|
<ButtonLink @click="() => visible = true">{{ $t('task.quickAddMagic.what') }}</ButtonLink>
|
||||||
@ -100,6 +100,5 @@ import {PREFIXES} from '@/modules/parseTaskText'
|
|||||||
const visible = ref(false)
|
const visible = ref(false)
|
||||||
const mode = ref(getQuickAddMagicMode())
|
const mode = ref(getQuickAddMagicMode())
|
||||||
|
|
||||||
const available = computed(() => mode.value !== 'disabled')
|
|
||||||
const prefixes = computed(() => PREFIXES[mode.value])
|
const prefixes = computed(() => PREFIXES[mode.value])
|
||||||
</script>
|
</script>
|
||||||
|
@ -4,11 +4,11 @@ import {debouncedWatch, tryOnMounted, useWindowSize, type MaybeRef} from '@vueus
|
|||||||
// TODO: also add related styles
|
// TODO: also add related styles
|
||||||
// OR: replace with vueuse function
|
// OR: replace with vueuse function
|
||||||
export function useAutoHeightTextarea(value: MaybeRef<string>) {
|
export function useAutoHeightTextarea(value: MaybeRef<string>) {
|
||||||
const textarea = ref<HTMLInputElement>()
|
const textarea = ref<HTMLTextAreaElement | null>(null)
|
||||||
const minHeight = ref(0)
|
const minHeight = ref(0)
|
||||||
|
|
||||||
// adapted from https://github.com/LeaVerou/stretchy/blob/47f5f065c733029acccb755cae793009645809e2/src/stretchy.js#L34
|
// adapted from https://github.com/LeaVerou/stretchy/blob/47f5f065c733029acccb755cae793009645809e2/src/stretchy.js#L34
|
||||||
function resize(textareaEl: HTMLInputElement | undefined) {
|
function resize(textareaEl: HTMLTextAreaElement | null) {
|
||||||
if (!textareaEl) return
|
if (!textareaEl) return
|
||||||
|
|
||||||
let empty
|
let empty
|
||||||
|
24
src/helpers/getInheritedBackgroundColor.ts
Normal file
24
src/helpers/getInheritedBackgroundColor.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
function getDefaultBackground() {
|
||||||
|
const div = document.createElement('div')
|
||||||
|
document.head.appendChild(div)
|
||||||
|
const bg = window.getComputedStyle(div).backgroundColor
|
||||||
|
document.head.removeChild(div)
|
||||||
|
return bg
|
||||||
|
}
|
||||||
|
|
||||||
|
// get default style for current browser
|
||||||
|
const defaultStyle = getDefaultBackground() // typically "rgba(0, 0, 0, 0)"
|
||||||
|
|
||||||
|
// based on https://stackoverflow.com/a/62630563/15522256
|
||||||
|
export function getInheritedBackgroundColor(el: HTMLElement): string {
|
||||||
|
const backgroundColor = window.getComputedStyle(el).backgroundColor
|
||||||
|
|
||||||
|
if (backgroundColor !== defaultStyle) return backgroundColor
|
||||||
|
|
||||||
|
if (!el.parentElement) {
|
||||||
|
// we reached the top parent el without getting an explicit color
|
||||||
|
return defaultStyle
|
||||||
|
}
|
||||||
|
|
||||||
|
return getInheritedBackgroundColor(el.parentElement)
|
||||||
|
}
|
@ -1,3 +1,4 @@
|
|||||||
|
import './polyfills'
|
||||||
import {createApp} from 'vue'
|
import {createApp} from 'vue'
|
||||||
|
|
||||||
import pinia from './pinia'
|
import pinia from './pinia'
|
||||||
|
4
src/polyfills.ts
Normal file
4
src/polyfills.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
// in order to use postcss-preset-env correctly we need some client side plugins
|
||||||
|
import focusWithin from 'focus-within'
|
||||||
|
|
||||||
|
focusWithin(document)
|
@ -10,6 +10,8 @@ import {VitePWA} from 'vite-plugin-pwa'
|
|||||||
import {visualizer} from 'rollup-plugin-visualizer'
|
import {visualizer} from 'rollup-plugin-visualizer'
|
||||||
import svgLoader from 'vite-svg-loader'
|
import svgLoader from 'vite-svg-loader'
|
||||||
import postcssPresetEnv from 'postcss-preset-env'
|
import postcssPresetEnv from 'postcss-preset-env'
|
||||||
|
import postcssEasings from 'postcss-easings'
|
||||||
|
import postcssEasingGradients from 'postcss-easing-gradients'
|
||||||
|
|
||||||
|
|
||||||
const pathSrc = fileURLToPath(new URL('./src', import.meta.url))
|
const pathSrc = fileURLToPath(new URL('./src', import.meta.url))
|
||||||
@ -46,7 +48,19 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
postcss: {
|
postcss: {
|
||||||
plugins: [
|
plugins: [
|
||||||
postcssPresetEnv(),
|
postcssEasings(),
|
||||||
|
postcssEasingGradients(),
|
||||||
|
postcssPresetEnv({
|
||||||
|
// These plugins are enabled by default but require
|
||||||
|
// a polyfill that we don't include
|
||||||
|
// see also './src/polyfills.ts'
|
||||||
|
features: {
|
||||||
|
'blank-pseudo-class': false,
|
||||||
|
'focus-visible-pseudo-class': false,
|
||||||
|
'has-pseudo-class': false,
|
||||||
|
'prefers-color-scheme-query': false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user