Merge branch 'main' into feature/recurring-nlp
continuous-integration/drone/pr Build is failing
Details
continuous-integration/drone/pr Build is failing
Details
This commit is contained in:
commit
903d02be61
34
package.json
34
package.json
|
@ -27,7 +27,7 @@
|
|||
"camel-case": "4.1.2",
|
||||
"codemirror": "5.64.0",
|
||||
"copy-to-clipboard": "3.3.1",
|
||||
"date-fns": "2.26.0",
|
||||
"date-fns": "2.27.0",
|
||||
"dompurify": "2.3.3",
|
||||
"easymde": "2.15.0",
|
||||
"flatpickr": "4.6.9",
|
||||
|
@ -36,7 +36,7 @@
|
|||
"is-touch-device": "1.0.1",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"lodash.debounce": "4.0.8",
|
||||
"marked": "4.0.5",
|
||||
"marked": "4.0.6",
|
||||
"register-service-worker": "1.7.2",
|
||||
"snake-case": "3.0.4",
|
||||
"ufo": "0.7.9",
|
||||
|
@ -45,11 +45,11 @@
|
|||
"vue-advanced-cropper": "2.7.0",
|
||||
"vue-drag-resize": "2.0.3",
|
||||
"vue-flatpickr-component": "9.0.5",
|
||||
"vue-i18n": "9.2.0-beta.22",
|
||||
"vue-i18n": "9.2.0-beta.23",
|
||||
"vue-router": "4.0.12",
|
||||
"vuedraggable": "4.1.0",
|
||||
"vuex": "4.0.2",
|
||||
"workbox-precaching": "6.4.1"
|
||||
"workbox-precaching": "6.4.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@4tw/cypress-drag-drop": "2.1.0",
|
||||
|
@ -59,8 +59,8 @@
|
|||
"@fortawesome/vue-fontawesome": "3.0.0-5",
|
||||
"@types/flexsearch": "0.7.2",
|
||||
"@types/jest": "27.0.3",
|
||||
"@typescript-eslint/eslint-plugin": "5.4.0",
|
||||
"@typescript-eslint/parser": "5.4.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.5.0",
|
||||
"@typescript-eslint/parser": "5.5.0",
|
||||
"@vitejs/plugin-legacy": "1.6.3",
|
||||
"@vitejs/plugin-vue": "1.10.1",
|
||||
"@vue/eslint-config-typescript": "9.1.0",
|
||||
|
@ -69,34 +69,35 @@
|
|||
"browserslist": "4.18.1",
|
||||
"cypress": "8.7.0",
|
||||
"cypress-file-upload": "5.0.8",
|
||||
"esbuild": "0.14.0",
|
||||
"eslint": "8.3.0",
|
||||
"esbuild": "0.14.2",
|
||||
"eslint": "8.4.0",
|
||||
"eslint-plugin-vue": "8.1.1",
|
||||
"express": "4.17.1",
|
||||
"faker": "5.5.3",
|
||||
"jest": "27.3.1",
|
||||
"netlify-cli": "8.0.3",
|
||||
"jest": "27.4.3",
|
||||
"netlify-cli": "8.0.15",
|
||||
"postcss": "8.4.4",
|
||||
"postcss-preset-env": "7.0.1",
|
||||
"rollup": "2.60.1",
|
||||
"rollup": "2.60.2",
|
||||
"rollup-plugin-visualizer": "5.5.2",
|
||||
"sass": "1.43.5",
|
||||
"sass": "1.44.0",
|
||||
"slugify": "1.6.3",
|
||||
"ts-jest": "27.0.7",
|
||||
"typescript": "4.5.2",
|
||||
"vite": "2.6.14",
|
||||
"vite-plugin-pwa": "0.11.8",
|
||||
"vite-plugin-pwa": "0.11.10",
|
||||
"vite-svg-loader": "3.1.0",
|
||||
"vue-tsc": "0.29.6",
|
||||
"vue-tsc": "0.29.8",
|
||||
"wait-on": "6.0.0",
|
||||
"workbox-cli": "6.4.1"
|
||||
"workbox-cli": "6.4.2"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"root": true,
|
||||
"env": {
|
||||
"browser": true,
|
||||
"es2021": true,
|
||||
"node": true
|
||||
"node": true,
|
||||
"vue/setup-compiler-macros": true
|
||||
},
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
|
@ -120,6 +121,7 @@
|
|||
"error",
|
||||
"never"
|
||||
],
|
||||
"vue/script-setup-uses-vars": "error",
|
||||
"vue/multi-word-component-names": 0
|
||||
},
|
||||
"parser": "vue-eslint-parser",
|
||||
|
|
|
@ -20,64 +20,59 @@
|
|||
:class="{'is-favorite': list.isFavorite, 'is-archived': list.isArchived}"
|
||||
@click.stop="toggleFavoriteList(list)"
|
||||
class="favorite">
|
||||
<icon icon="star" v-if="list.isFavorite"/>
|
||||
<icon :icon="['far', 'star']" v-else/>
|
||||
<icon :icon="list.isFavorite ? 'star' : ['far', 'star']" />
|
||||
</span>
|
||||
</div>
|
||||
<div class="title">{{ list.title }}</div>
|
||||
</router-link>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
import {ref, watch} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
|
||||
import ListService from '@/services/list'
|
||||
|
||||
export default {
|
||||
name: 'list-card',
|
||||
data() {
|
||||
return {
|
||||
background: null,
|
||||
backgroundLoading: false,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
list: {
|
||||
required: true,
|
||||
},
|
||||
showArchived: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
list: {
|
||||
handler: 'loadBackground',
|
||||
immediate: true,
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
async loadBackground() {
|
||||
if (this.list === null || !this.list.backgroundInformation || this.backgroundLoading) {
|
||||
return
|
||||
}
|
||||
const background = ref(null)
|
||||
const backgroundLoading = ref(false)
|
||||
|
||||
this.backgroundLoading = true
|
||||
|
||||
const listService = new ListService()
|
||||
try {
|
||||
this.background = await listService.background(this.list)
|
||||
} finally {
|
||||
this.backgroundLoading = false
|
||||
}
|
||||
},
|
||||
toggleFavoriteList(list) {
|
||||
// The favorites pseudo list is always favorite
|
||||
// Archived lists cannot be marked favorite
|
||||
if (list.id === -1 || list.isArchived) {
|
||||
return
|
||||
}
|
||||
this.$store.dispatch('lists/toggleListFavorite', list)
|
||||
},
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
showArchived: {
|
||||
default: false,
|
||||
type: Boolean,
|
||||
},
|
||||
})
|
||||
|
||||
watch(props.list, loadBackground, { immediate: true })
|
||||
|
||||
async function loadBackground() {
|
||||
if (props.list === null || !props.list.backgroundInformation || backgroundLoading.value) {
|
||||
return
|
||||
}
|
||||
|
||||
backgroundLoading.value = true
|
||||
|
||||
const listService = new ListService()
|
||||
try {
|
||||
background.value = await listService.background(props.list)
|
||||
} finally {
|
||||
backgroundLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const store = useStore()
|
||||
|
||||
function toggleFavoriteList(list) {
|
||||
// The favorites pseudo list is always favorite
|
||||
// Archived lists cannot be marked favorite
|
||||
if (list.id === -1 || list.isArchived) {
|
||||
return
|
||||
}
|
||||
store.dispatch('lists/toggleListFavorite', list)
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,40 +1,27 @@
|
|||
<template>
|
||||
<div
|
||||
v-if="isDone"
|
||||
class="is-done"
|
||||
:class="{ 'is-done--small': variant === variants.SMALL }"
|
||||
>
|
||||
{{ $t('task.attributes.done') }}
|
||||
</div>
|
||||
v-if="isDone"
|
||||
class="is-done"
|
||||
:class="{ 'is-done--small': variant === 'small' }"
|
||||
>
|
||||
{{ $t('task.attributes.done') }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const VARIANTS = {
|
||||
DEFAULT: 'default',
|
||||
SMALL: 'small',
|
||||
}
|
||||
<script lang="ts" setup>
|
||||
import {PropType} from 'vue'
|
||||
type Variants = 'default' | 'small'
|
||||
|
||||
export default {
|
||||
name: 'Done',
|
||||
|
||||
data() {
|
||||
return {
|
||||
variants: VARIANTS,
|
||||
}
|
||||
defineProps({
|
||||
isDone: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
|
||||
props: {
|
||||
isDone: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
variant: {
|
||||
type: String,
|
||||
default: VARIANTS.DEFAULT,
|
||||
validator: (variant) => Object.values(VARIANTS).includes(variant),
|
||||
},
|
||||
variant: {
|
||||
type: String as PropType<Variants>,
|
||||
default: 'default',
|
||||
},
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
|
|
|
@ -24,41 +24,39 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'card',
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
padding: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hasClose: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
closeIcon: {
|
||||
type: String,
|
||||
default: 'times',
|
||||
},
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hasContent: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
<script setup lang="ts">
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
emits: ['close'],
|
||||
}
|
||||
padding: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hasClose: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
closeIcon: {
|
||||
type: String,
|
||||
default: 'times',
|
||||
},
|
||||
shadow: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
hasContent: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
|
||||
defineEmits(['close'])
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -11,18 +11,15 @@
|
|||
</router-link>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'dropdown-item',
|
||||
props: {
|
||||
to: {
|
||||
required: true,
|
||||
},
|
||||
icon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
to: {
|
||||
required: true,
|
||||
},
|
||||
}
|
||||
icon: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -7,16 +7,10 @@
|
|||
</message>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import Message from '@/components/misc/message'
|
||||
<script lang="ts" setup>
|
||||
import Message from '@/components/misc/message.vue'
|
||||
|
||||
export default {
|
||||
name: 'error',
|
||||
components: {Message},
|
||||
methods: {
|
||||
reload() {
|
||||
window.location.reload()
|
||||
},
|
||||
},
|
||||
function reload() {
|
||||
window.location.reload()
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -6,16 +6,14 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
<script lang="ts" setup>
|
||||
import {computed} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'legal',
|
||||
computed: mapState({
|
||||
imprintUrl: state => state.config.legal.imprintUrl,
|
||||
privacyPolicyUrl: state => state.config.legal.privacyPolicyUrl,
|
||||
}),
|
||||
}
|
||||
const store = useStore()
|
||||
|
||||
const imprintUrl = computed(() => store.state.config.legal.imprintUrl)
|
||||
const privacyPolicyUrl = computed(() => store.state.config.legal.privacyPolicyUrl)
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -2,12 +2,6 @@
|
|||
<div class="loader-container is-loading"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'loading',
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.loader-container {
|
||||
height: 100%;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
variant: {
|
||||
type: String,
|
||||
|
|
|
@ -2,17 +2,17 @@
|
|||
<div class="no-auth-wrapper">
|
||||
<div class="noauth-container">
|
||||
<Logo class="logo" width="400" height="117" />
|
||||
<message v-if="motd !== ''" class="my-2">
|
||||
<Message v-if="motd !== ''" class="my-2">
|
||||
{{ motd }}
|
||||
</message>
|
||||
</Message>
|
||||
<slot/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
<script lang="ts" setup>
|
||||
import Logo from '@/components/home/Logo.vue'
|
||||
import message from '@/components/misc/message'
|
||||
import Message from '@/components/misc/message.vue'
|
||||
import {useStore} from 'vuex'
|
||||
import {computed} from 'vue'
|
||||
|
||||
|
@ -22,7 +22,7 @@ const motd = computed(() => store.state.config.motd)
|
|||
|
||||
<style lang="scss" scoped>
|
||||
.no-auth-wrapper {
|
||||
background: url('@/assets/llama.svg') no-repeat bottom left fixed var(--site-background);
|
||||
background: url('@/assets/llama.svg?url') no-repeat bottom left fixed var(--site-background);
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
|
|
|
@ -34,8 +34,10 @@
|
|||
</nav>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
function createPagination(totalPages, currentPage) {
|
||||
<script lang="ts" setup>
|
||||
import {computed} from 'vue'
|
||||
|
||||
function createPagination(totalPages: number, currentPage: number) {
|
||||
const pages = []
|
||||
for (let i = 0; i < totalPages; i++) {
|
||||
|
||||
|
@ -79,30 +81,18 @@ function getRouteForPagination(page = 1, type = 'list') {
|
|||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'Pagination',
|
||||
|
||||
props: {
|
||||
totalPages: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
const props = defineProps({
|
||||
totalPages: {
|
||||
type: Number,
|
||||
required: true,
|
||||
},
|
||||
|
||||
computed: {
|
||||
pages() {
|
||||
return createPagination(this.totalPages, this.currentPage)
|
||||
},
|
||||
currentPage: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
})
|
||||
|
||||
methods: {
|
||||
getRouteForPagination,
|
||||
},
|
||||
}
|
||||
const pages = computed(() => createPagination(props.totalPages, props.currentPage))
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<section v-else-if="error !== ''">
|
||||
<no-auth-wrapper>
|
||||
<card>
|
||||
<p v-if="error === errorNoApiUrl">
|
||||
<p v-if="error === ERROR_NO_API_URL">
|
||||
{{ $t('ready.noApiUrlConfigured') }}
|
||||
</p>
|
||||
<message variant="danger" v-else>
|
||||
|
@ -40,51 +40,34 @@
|
|||
</transition>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
import {ref, computed} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
|
||||
import Logo from '@/assets/logo.svg?component'
|
||||
import ApiConfig from '@/components/misc/api-config'
|
||||
import Message from '@/components/misc/message'
|
||||
import NoAuthWrapper from '@/components/misc/no-auth-wrapper'
|
||||
import {mapState} from 'vuex'
|
||||
import ApiConfig from '@/components/misc/api-config.vue'
|
||||
import Message from '@/components/misc/message.vue'
|
||||
import NoAuthWrapper from '@/components/misc/no-auth-wrapper.vue'
|
||||
|
||||
import {ERROR_NO_API_URL} from '@/helpers/checkAndSetApiUrl'
|
||||
|
||||
export default {
|
||||
name: 'ready',
|
||||
components: {
|
||||
Message,
|
||||
Logo,
|
||||
NoAuthWrapper,
|
||||
ApiConfig,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
error: '',
|
||||
errorNoApiUrl: ERROR_NO_API_URL,
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.load()
|
||||
},
|
||||
computed: {
|
||||
ready() {
|
||||
return this.$store.state.vikunjaReady
|
||||
},
|
||||
showLoading() {
|
||||
return !this.ready && this.error === ''
|
||||
},
|
||||
...mapState([
|
||||
'online',
|
||||
]),
|
||||
},
|
||||
methods: {
|
||||
load() {
|
||||
this.$store.dispatch('loadApp')
|
||||
.catch(e => {
|
||||
this.error = e
|
||||
})
|
||||
},
|
||||
},
|
||||
const store = useStore()
|
||||
|
||||
const ready = computed(() => store.state.vikunjaReady)
|
||||
const online = computed(() => store.state.online)
|
||||
|
||||
const error = ref('')
|
||||
const showLoading = computed(() => !ready.value && error.value === '')
|
||||
|
||||
async function load() {
|
||||
try {
|
||||
await store.dispatch('loadApp')
|
||||
} catch(e: any) {
|
||||
error.value = e
|
||||
}
|
||||
}
|
||||
|
||||
load()
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -7,24 +7,21 @@
|
|||
</component>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'shortcut',
|
||||
props: {
|
||||
keys: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
combination: {
|
||||
type: String,
|
||||
default: '+',
|
||||
},
|
||||
is: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
keys: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
}
|
||||
combination: {
|
||||
type: String,
|
||||
default: '+',
|
||||
},
|
||||
is: {
|
||||
type: String,
|
||||
default: 'div',
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -22,91 +22,89 @@
|
|||
</a>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
import {computed, shallowRef} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import SubscriptionService from '@/services/subscription'
|
||||
import SubscriptionModel from '@/models/subscription'
|
||||
|
||||
export default {
|
||||
name: 'task-subscription',
|
||||
data() {
|
||||
return {
|
||||
subscriptionService: new SubscriptionService(),
|
||||
}
|
||||
},
|
||||
props: {
|
||||
entity: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
subscription: {
|
||||
required: true,
|
||||
},
|
||||
entityId: {
|
||||
required: true,
|
||||
},
|
||||
isButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
},
|
||||
emits: ['change'],
|
||||
computed: {
|
||||
tooltipText() {
|
||||
if (this.disabled) {
|
||||
return this.$t('task.subscription.subscribedThroughParent', {
|
||||
entity: this.entity,
|
||||
parent: this.subscription.entity,
|
||||
})
|
||||
}
|
||||
import {success} from '@/message'
|
||||
|
||||
return this.subscription !== null ?
|
||||
this.$t('task.subscription.subscribed', {entity: this.entity}) :
|
||||
this.$t('task.subscription.notSubscribed', {entity: this.entity})
|
||||
},
|
||||
buttonText() {
|
||||
return this.subscription !== null ? this.$t('task.subscription.unsubscribe') : this.$t('task.subscription.subscribe')
|
||||
},
|
||||
icon() {
|
||||
return this.subscription !== null ? ['far', 'bell-slash'] : 'bell'
|
||||
},
|
||||
disabled() {
|
||||
if (this.subscription === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return this.subscription.entity !== this.entity
|
||||
},
|
||||
const props = defineProps({
|
||||
entity: {
|
||||
required: true,
|
||||
type: String,
|
||||
},
|
||||
methods: {
|
||||
changeSubscription() {
|
||||
if (this.disabled) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.subscription === null) {
|
||||
this.subscribe()
|
||||
} else {
|
||||
this.unsubscribe()
|
||||
}
|
||||
},
|
||||
async subscribe() {
|
||||
const subscription = new SubscriptionModel({
|
||||
entity: this.entity,
|
||||
entityId: this.entityId,
|
||||
})
|
||||
await this.subscriptionService.create(subscription)
|
||||
this.$emit('change', subscription)
|
||||
this.$message.success({message: this.$t('task.subscription.subscribeSuccess', {entity: this.entity})})
|
||||
},
|
||||
async unsubscribe() {
|
||||
const subscription = new SubscriptionModel({
|
||||
entity: this.entity,
|
||||
entityId: this.entityId,
|
||||
})
|
||||
await this.subscriptionService.delete(subscription)
|
||||
this.$emit('change', null)
|
||||
this.$message.success({message: this.$t('task.subscription.unsubscribeSuccess', {entity: this.entity})})
|
||||
},
|
||||
subscription: {
|
||||
required: true,
|
||||
},
|
||||
entityId: {
|
||||
required: true,
|
||||
},
|
||||
isButton: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
})
|
||||
|
||||
const emit = defineEmits(['change'])
|
||||
|
||||
const subscriptionService = shallowRef(new SubscriptionService())
|
||||
|
||||
const {t} = useI18n()
|
||||
const tooltipText = computed(() => {
|
||||
if (disabled.value) {
|
||||
return t('task.subscription.subscribedThroughParent', {
|
||||
entity: props.entity,
|
||||
parent: props.subscription.entity,
|
||||
})
|
||||
}
|
||||
|
||||
return props.subscription !== null ?
|
||||
t('task.subscription.subscribed', {entity: props.entity}) :
|
||||
t('task.subscription.notSubscribed', {entity: props.entity})
|
||||
})
|
||||
|
||||
const buttonText = computed(() => props.subscription !== null ? t('task.subscription.unsubscribe') : t('task.subscription.subscribe'))
|
||||
const icon = computed(() => props.subscription !== null ? ['far', 'bell-slash'] : 'bell')
|
||||
const disabled = computed(() => {
|
||||
if (props.subscription === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return props.subscription.entity !== props.entity
|
||||
})
|
||||
|
||||
function changeSubscription() {
|
||||
if (disabled.value) {
|
||||
return
|
||||
}
|
||||
|
||||
if (props.subscription === null) {
|
||||
subscribe()
|
||||
} else {
|
||||
unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
async function subscribe() {
|
||||
const subscription = new SubscriptionModel({
|
||||
entity: props.entity,
|
||||
entityId: props.entityId,
|
||||
})
|
||||
await subscriptionService.value.create(subscription)
|
||||
emit('change', subscription)
|
||||
success({message: t('task.subscription.subscribeSuccess', {entity: props.entity})})
|
||||
}
|
||||
|
||||
async function unsubscribe() {
|
||||
const subscription = new SubscriptionModel({
|
||||
entity: props.entity,
|
||||
entityId: props.entityId,
|
||||
})
|
||||
await subscriptionService.value.delete(subscription)
|
||||
emit('change', null)
|
||||
success({message: t('task.subscription.unsubscribeSuccess', {entity: props.entity})})
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -11,31 +11,28 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'user',
|
||||
props: {
|
||||
user: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
showUsername: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
avatarSize: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 50,
|
||||
},
|
||||
isInline: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
<script lang="ts" setup>
|
||||
defineProps({
|
||||
user: {
|
||||
required: true,
|
||||
type: Object,
|
||||
},
|
||||
}
|
||||
showUsername: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
avatarSize: {
|
||||
required: false,
|
||||
type: Number,
|
||||
default: 50,
|
||||
},
|
||||
isInline: {
|
||||
required: false,
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
|
|
@ -9,32 +9,23 @@
|
|||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script lang="ts" setup>
|
||||
import {ref, computed} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
import Multiselect from '@/components/input/multiselect.vue'
|
||||
|
||||
export default {
|
||||
name: 'namespace-search',
|
||||
emits: ['selected'],
|
||||
data() {
|
||||
return {
|
||||
query: '',
|
||||
}
|
||||
},
|
||||
components: {
|
||||
Multiselect,
|
||||
},
|
||||
computed: {
|
||||
namespaces() {
|
||||
return this.$store.getters['namespaces/searchNamespace'](this.query)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
findNamespaces(query) {
|
||||
this.query = query
|
||||
},
|
||||
select(namespace) {
|
||||
this.$emit('selected', namespace)
|
||||
},
|
||||
},
|
||||
const emit = defineEmits(['selected'])
|
||||
|
||||
const query = ref('')
|
||||
|
||||
const store = useStore()
|
||||
const namespaces = computed(() => store.getters['namespaces/searchNamespace'](query.value))
|
||||
|
||||
function findNamespaces(newQuery: string) {
|
||||
query.value = newQuery
|
||||
}
|
||||
|
||||
function select(namespace) {
|
||||
emit('selected', namespace)
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -52,30 +52,22 @@
|
|||
</dropdown>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import {ref, onMounted} from 'vue'
|
||||
|
||||
import Dropdown from '@/components/misc/dropdown.vue'
|
||||
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||
import TaskSubscription from '@/components/misc/subscription.vue'
|
||||
|
||||
export default {
|
||||
name: 'namespace-settings-dropdown',
|
||||
data() {
|
||||
return {
|
||||
subscription: null,
|
||||
}
|
||||
const props = defineProps({
|
||||
namespace: {
|
||||
type: Object, // NamespaceModel
|
||||
required: true,
|
||||
},
|
||||
components: {
|
||||
DropdownItem,
|
||||
Dropdown,
|
||||
TaskSubscription,
|
||||
},
|
||||
props: {
|
||||
namespace: {
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.subscription = this.namespace.subscription
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
const subscription = ref(null)
|
||||
onMounted(() => {
|
||||
subscription.value = props.namespace.subscription
|
||||
})
|
||||
</script>
|
||||
|
|
|
@ -3,17 +3,13 @@
|
|||
<div class="field is-grouped">
|
||||
<p class="control has-icons-left is-expanded">
|
||||
<textarea
|
||||
:disabled="taskService.loading || null"
|
||||
class="input"
|
||||
:disabled="taskService.loading || undefined"
|
||||
class="add-task-textarea input"
|
||||
:placeholder="$t('list.list.addPlaceholder')"
|
||||
cols="1"
|
||||
rows="1"
|
||||
v-focus
|
||||
v-model="newTaskTitle"
|
||||
ref="newTaskInput"
|
||||
:style="{
|
||||
'minHeight': `${initialTextAreaHeight}px`,
|
||||
'height': `calc(${textAreaHeight}px - 2px + 1rem)`
|
||||
}"
|
||||
@keyup="errorMessage = ''"
|
||||
@keydown.enter="handleEnter"
|
||||
/>
|
||||
|
@ -23,7 +19,8 @@
|
|||
</p>
|
||||
<p class="control">
|
||||
<x-button
|
||||
:disabled="newTaskTitle === '' || taskService.loading || null"
|
||||
class="add-task-button"
|
||||
:disabled="newTaskTitle === '' || taskService.loading || undefined"
|
||||
@click="addTask()"
|
||||
icon="plus"
|
||||
:loading="taskService.loading"
|
||||
|
@ -35,121 +32,172 @@
|
|||
<p class="help is-danger" v-if="errorMessage !== ''">
|
||||
{{ errorMessage }}
|
||||
</p>
|
||||
<quick-add-magic v-if="errorMessage === ''"/>
|
||||
<quick-add-magic v-else />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup lang="ts">
|
||||
import {ref, watch, unref, shallowReactive} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import {useStore} from 'vuex'
|
||||
import { tryOnMounted, debouncedWatch, useWindowSize, MaybeRef } from '@vueuse/core'
|
||||
|
||||
import TaskService from '../../services/task'
|
||||
import QuickAddMagic from '@/components/tasks/partials/quick-add-magic.vue'
|
||||
|
||||
const INPUT_BORDER_PX = 2
|
||||
const LINE_HEIGHT = 1.5 // using getComputedStyles().lineHeight returns an (wrong) absolute pixel value, we need the factor to do calculations with it.
|
||||
|
||||
const cleanupTitle = title => {
|
||||
function cleanupTitle(title: string) {
|
||||
return title.replace(/^((\* |\+ |- )(\[ \] )?)/g, '')
|
||||
}
|
||||
|
||||
export default {
|
||||
name: 'add-task',
|
||||
emits: ['taskAdded'],
|
||||
data() {
|
||||
return {
|
||||
newTaskTitle: '',
|
||||
taskService: new TaskService(),
|
||||
errorMessage: '',
|
||||
textAreaHeight: null,
|
||||
initialTextAreaHeight: null,
|
||||
function useAutoHeightTextarea(value: MaybeRef<string>) {
|
||||
const textarea = ref<HTMLInputElement>()
|
||||
const minHeight = ref(0)
|
||||
|
||||
// adapted from https://github.com/LeaVerou/stretchy/blob/47f5f065c733029acccb755cae793009645809e2/src/stretchy.js#L34
|
||||
function resize(textareaEl: HTMLInputElement|undefined) {
|
||||
if (!textareaEl) return
|
||||
|
||||
let empty
|
||||
|
||||
// the value here is the the attribute value
|
||||
if (!textareaEl.value && textareaEl.placeholder) {
|
||||
empty = true
|
||||
textareaEl.value = textareaEl.placeholder
|
||||
}
|
||||
},
|
||||
components: {
|
||||
QuickAddMagic,
|
||||
},
|
||||
props: {
|
||||
defaultPosition: {
|
||||
type: Number,
|
||||
required: false,
|
||||
|
||||
const cs = getComputedStyle(textareaEl)
|
||||
|
||||
textareaEl.style.minHeight = ''
|
||||
textareaEl.style.height = '0'
|
||||
const offset = textareaEl.offsetHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom)
|
||||
const height = textareaEl.scrollHeight + offset + 'px'
|
||||
|
||||
textareaEl.style.height = height
|
||||
|
||||
// calculate min-height for the first time
|
||||
if (!minHeight.value) {
|
||||
minHeight.value = parseFloat(height)
|
||||
}
|
||||
|
||||
textareaEl.style.minHeight = minHeight.value.toString()
|
||||
|
||||
|
||||
if (empty) {
|
||||
textareaEl.value = ''
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
tryOnMounted(() => {
|
||||
if (textarea.value) {
|
||||
// we don't want scrollbars
|
||||
textarea.value.style.overflowY = 'hidden'
|
||||
}
|
||||
})
|
||||
|
||||
const { width: windowWidth } = useWindowSize()
|
||||
|
||||
debouncedWatch(
|
||||
windowWidth,
|
||||
() => resize(textarea.value),
|
||||
{ debounce: 200 },
|
||||
)
|
||||
|
||||
// It is not possible to get notified of a change of the value attribute of a textarea without workarounds (setTimeout)
|
||||
// So instead we watch the value that we bound to it.
|
||||
watch(
|
||||
() => [textarea.value, unref(value)],
|
||||
() => resize(textarea.value),
|
||||
{
|
||||
immediate: true, // calculate initial size
|
||||
flush: 'post', // resize after value change is rendered to DOM
|
||||
},
|
||||
)
|
||||
|
||||
return textarea
|
||||
}
|
||||
|
||||
const emit = defineEmits(['taskAdded'])
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
defaultPosition: {
|
||||
type: Number,
|
||||
required: false,
|
||||
},
|
||||
watch: {
|
||||
newTaskTitle(newVal) {
|
||||
// Calculating the textarea height based on lines of input in it.
|
||||
// That is more reliable when removing a line from the input.
|
||||
const numberOfLines = newVal.split(/\r\n|\r|\n/).length
|
||||
const fontSize = parseFloat(window.getComputedStyle(this.$refs.newTaskInput, null).getPropertyValue('font-size'))
|
||||
})
|
||||
|
||||
this.textAreaHeight = numberOfLines * fontSize * LINE_HEIGHT + INPUT_BORDER_PX
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.initialTextAreaHeight = this.$refs.newTaskInput.scrollHeight + INPUT_BORDER_PX
|
||||
},
|
||||
methods: {
|
||||
async addTask() {
|
||||
if (this.newTaskTitle === '') {
|
||||
this.errorMessage = this.$t('list.create.addTitleRequired')
|
||||
return
|
||||
}
|
||||
this.errorMessage = ''
|
||||
const taskService = shallowReactive(new TaskService())
|
||||
const errorMessage = ref('')
|
||||
|
||||
if (this.taskService.loading) {
|
||||
return
|
||||
}
|
||||
const newTaskTitle = ref('')
|
||||
const newTaskInput = useAutoHeightTextarea(newTaskTitle)
|
||||
|
||||
const newTasks = this.newTaskTitle.split(/[\r\n]+/).map(async t => {
|
||||
const title = cleanupTitle(t)
|
||||
if (title === '') {
|
||||
return
|
||||
}
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
|
||||
const task = await this.$store.dispatch('tasks/createNewTask', {
|
||||
title,
|
||||
listId: this.$store.state.auth.settings.defaultListId,
|
||||
position: this.defaultPosition,
|
||||
})
|
||||
this.$emit('taskAdded', task)
|
||||
return task
|
||||
})
|
||||
async function addTask() {
|
||||
if (newTaskTitle.value === '') {
|
||||
errorMessage.value = t('list.create.addTitleRequired')
|
||||
return
|
||||
}
|
||||
errorMessage.value = ''
|
||||
|
||||
try {
|
||||
await Promise.all(newTasks)
|
||||
this.newTaskTitle = ''
|
||||
} catch (e) {
|
||||
if (e.message === 'NO_LIST') {
|
||||
this.errorMessage = this.$t('list.create.addListRequired')
|
||||
return
|
||||
}
|
||||
throw e
|
||||
}
|
||||
},
|
||||
handleEnter(e) {
|
||||
// when pressing shift + enter we want to continue as we normally would. Otherwise, we want to create
|
||||
// the new task(s). The vue event modifier don't allow this, hence this method.
|
||||
if (e.shiftKey) {
|
||||
return
|
||||
}
|
||||
if (taskService.loading) {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
this.addTask()
|
||||
},
|
||||
},
|
||||
const taskTitleBackup = newTaskTitle.value
|
||||
const newTasks = newTaskTitle.value.split(/[\r\n]+/).map(async uncleanedTitle => {
|
||||
const title = cleanupTitle(uncleanedTitle)
|
||||
if (title === '') {
|
||||
return
|
||||
}
|
||||
|
||||
const task = await store.dispatch('tasks/createNewTask', {
|
||||
title,
|
||||
listId: store.state.auth.settings.defaultListId,
|
||||
position: props.defaultPosition,
|
||||
})
|
||||
emit('taskAdded', task)
|
||||
return task
|
||||
})
|
||||
|
||||
try {
|
||||
newTaskTitle.value = ''
|
||||
await Promise.all(newTasks)
|
||||
} catch (e: any) {
|
||||
newTaskTitle.value = taskTitleBackup
|
||||
if (e?.message === 'NO_LIST') {
|
||||
errorMessage.value = t('list.create.addListRequired')
|
||||
return
|
||||
}
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
function handleEnter(e: KeyboardEvent) {
|
||||
// when pressing shift + enter we want to continue as we normally would. Otherwise, we want to create
|
||||
// the new task(s). The vue event modifier don't allow this, hence this method.
|
||||
if (e.shiftKey) {
|
||||
return
|
||||
}
|
||||
|
||||
e.preventDefault()
|
||||
addTask()
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.task-add {
|
||||
margin-bottom: 0;
|
||||
|
||||
.button {
|
||||
height: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.input, .textarea {
|
||||
.add-task-button {
|
||||
height: 2.5rem;
|
||||
}
|
||||
.add-task-textarea {
|
||||
transition: border-color $transition;
|
||||
}
|
||||
|
||||
.input {
|
||||
resize: vertical;
|
||||
resize: none;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -2,15 +2,7 @@
|
|||
<div class="gantt-chart">
|
||||
<div class="filter-container">
|
||||
<div class="items">
|
||||
<x-button
|
||||
@click.prevent.stop="showTaskFilter = !showTaskFilter"
|
||||
type="secondary"
|
||||
icon="filter"
|
||||
>
|
||||
{{ $t('filters.title') }}
|
||||
</x-button>
|
||||
<filter-popup
|
||||
:visible="showTaskFilter"
|
||||
v-model="params"
|
||||
@update:modelValue="loadTasks()"
|
||||
/>
|
||||
|
@ -237,7 +229,6 @@ export default {
|
|||
newTaskFieldActive: false,
|
||||
priorities: priorities,
|
||||
taskCollectionService: new TaskCollectionService(),
|
||||
showTaskFilter: false,
|
||||
|
||||
params: {
|
||||
sort_by: ['done', 'id'],
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import {computed, watch, readonly} from 'vue'
|
||||
import {useStorage, createSharedComposable, ColorSchemes, usePreferredColorScheme, tryOnMounted} from '@vueuse/core'
|
||||
import {useStorage, createSharedComposable, ColorSchema, usePreferredColorScheme, tryOnMounted} from '@vueuse/core'
|
||||
|
||||
const STORAGE_KEY = 'color-scheme'
|
||||
|
||||
const DEFAULT_COLOR_SCHEME_SETTING: ColorSchemes = 'light'
|
||||
const DEFAULT_COLOR_SCHEME_SETTING: ColorSchema = 'light'
|
||||
|
||||
const CLASS_DARK = 'dark'
|
||||
const CLASS_LIGHT = 'light'
|
||||
|
@ -16,7 +16,7 @@ const CLASS_LIGHT = 'light'
|
|||
// - value is synced via `createSharedComposable`
|
||||
// https://github.com/vueuse/vueuse/blob/main/packages/core/useDark/index.ts
|
||||
export const useColorScheme = createSharedComposable(() => {
|
||||
const store = useStorage<ColorSchemes>(STORAGE_KEY, DEFAULT_COLOR_SCHEME_SETTING)
|
||||
const store = useStorage<ColorSchema>(STORAGE_KEY, DEFAULT_COLOR_SCHEME_SETTING)
|
||||
|
||||
const preferredColorScheme = usePreferredColorScheme()
|
||||
|
||||
|
|
|
@ -297,7 +297,8 @@ const getDayFromText = (text: string) => {
|
|||
}
|
||||
}
|
||||
|
||||
const date = new Date()
|
||||
const now = new Date()
|
||||
const date = new Date(now)
|
||||
const day = parseInt(results[0])
|
||||
date.setDate(day)
|
||||
|
||||
|
@ -309,7 +310,7 @@ const getDayFromText = (text: string) => {
|
|||
date.setDate(day)
|
||||
}
|
||||
|
||||
if (date < new Date()) {
|
||||
if (date < now) {
|
||||
date.setMonth(date.getMonth() + 1)
|
||||
}
|
||||
|
||||
|
|
|
@ -208,7 +208,11 @@ describe('Parse Task Text', () => {
|
|||
|
||||
expect(result.text).toBe('Lorem Ipsum')
|
||||
expect(result.date.getDate()).toBe(date.getDate())
|
||||
expect(result.date.getMonth()).toBe(result.date.getDate() === 31 ? date.getMonth() + 2 : date.getMonth() + 1)
|
||||
|
||||
const nextMonthWithDate = result.date.getDate() === 31
|
||||
? (date.getMonth() + 2) % 12
|
||||
: (date.getMonth() + 1) % 12
|
||||
expect(result.date.getMonth()).toBe(nextMonthWithDate)
|
||||
})
|
||||
it('should recognize dates of the month in the future', () => {
|
||||
const nextDay = new Date(+new Date() + 60 * 60 * 24 * 1000)
|
||||
|
|
|
@ -19,14 +19,12 @@ $mobile: math.div($tablet, 2);
|
|||
$family-sans-serif: 'Open Sans', Helvetica, Arial, sans-serif;
|
||||
$vikunja-font: 'Quicksand', sans-serif;
|
||||
|
||||
$thickness: 1px;
|
||||
$pagination-current-border: var(--primary);
|
||||
$navbar-item-active-color: var(--primary);
|
||||
|
||||
$dropdown-content-shadow: none;
|
||||
$dropdown-item-hover-background-color: var(--grey-100);
|
||||
|
||||
$bulmaswatch-import-font: false !default;
|
||||
$site-background: var(--grey-100);
|
||||
|
||||
$transition-duration: 150ms;
|
||||
|
|
|
@ -40,6 +40,7 @@
|
|||
}
|
||||
|
||||
.select select {
|
||||
$thickness: 1px;
|
||||
border-width: $thickness;
|
||||
|
||||
&:not([multiple]) {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* eslint-disable no-console */
|
||||
/* eslint-disable no-undef */
|
||||
|
||||
const workboxVersion = 'v6.4.1'
|
||||
const workboxVersion = 'v6.4.2'
|
||||
importScripts( `/workbox-${workboxVersion}/workbox-sw.js`)
|
||||
workbox.setConfig({
|
||||
modulePathPrefix: `/workbox-${workboxVersion}`,
|
||||
|
|
|
@ -47,7 +47,6 @@
|
|||
v-for="(l, k) in listHistory"
|
||||
:key="`l${k}`"
|
||||
:list="l"
|
||||
:background-resolver="() => null"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -55,92 +54,66 @@
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {mapState} from 'vuex'
|
||||
import Message from '@/components/misc/message'
|
||||
import ShowTasks from './tasks/ShowTasks.vue'
|
||||
import {getHistory} from '../modules/listHistory'
|
||||
<script lang="ts" setup>
|
||||
import {ref, computed} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
import {useNow} from '@vueuse/core'
|
||||
|
||||
import Message from '@/components/misc/message.vue'
|
||||
import ShowTasks from '@/views/tasks/ShowTasks.vue'
|
||||
import ListCard from '@/components/list/partials/list-card.vue'
|
||||
import AddTask from '../components/tasks/add-task.vue'
|
||||
import {LOADING, LOADING_MODULE} from '../store/mutation-types'
|
||||
import {parseDateOrNull} from '../helpers/parseDateOrNull'
|
||||
import AddTask from '@/components/tasks/add-task.vue'
|
||||
|
||||
export default {
|
||||
name: 'Home',
|
||||
components: {
|
||||
Message,
|
||||
ListCard,
|
||||
ShowTasks,
|
||||
AddTask,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
currentDate: new Date(),
|
||||
tasks: [],
|
||||
showTasksKey: 0,
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
welcome() {
|
||||
const now = new Date()
|
||||
import {getHistory} from '@/modules/listHistory'
|
||||
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
||||
import {formatDateShort, formatDateSince} from '@/helpers/time/formatDate'
|
||||
|
||||
if (now.getHours() < 5) {
|
||||
return 'Night'
|
||||
}
|
||||
const now = useNow()
|
||||
const welcome = computed(() => {
|
||||
const hours = new Date(now.value).getHours()
|
||||
|
||||
if (now.getHours() < 11) {
|
||||
return 'Morning'
|
||||
}
|
||||
if (hours < 5) {
|
||||
return 'Night'
|
||||
}
|
||||
|
||||
if (now.getHours() < 18) {
|
||||
return 'Day'
|
||||
}
|
||||
if (hours < 11) {
|
||||
return 'Morning'
|
||||
}
|
||||
|
||||
if (now.getHours() < 23) {
|
||||
return 'Evening'
|
||||
}
|
||||
if (hours < 18) {
|
||||
return 'Day'
|
||||
}
|
||||
|
||||
return 'Night'
|
||||
},
|
||||
listHistory() {
|
||||
const history = getHistory()
|
||||
return history.map(l => {
|
||||
return this.$store.getters['lists/getListById'](l.id)
|
||||
}).filter(l => l !== null)
|
||||
},
|
||||
...mapState({
|
||||
migratorsEnabled: state =>
|
||||
state.config.availableMigrators !== null &&
|
||||
state.config.availableMigrators.length > 0,
|
||||
authenticated: state => state.auth.authenticated,
|
||||
userInfo: state => state.auth.info,
|
||||
hasTasks: state => state.hasTasks,
|
||||
defaultListId: state => state.auth.defaultListId,
|
||||
defaultNamespaceId: state => {
|
||||
if (state.namespaces.namespaces.length === 0) {
|
||||
return 0
|
||||
}
|
||||
if (hours < 23) {
|
||||
return 'Evening'
|
||||
}
|
||||
|
||||
return state.namespaces.namespaces[0].id
|
||||
},
|
||||
hasLists: state => {
|
||||
if (state.namespaces.namespaces.length === 0) {
|
||||
return false
|
||||
}
|
||||
return 'Night'
|
||||
})
|
||||
|
||||
return state.namespaces.namespaces[0].lists.length > 0
|
||||
},
|
||||
loading: state => state[LOADING] && state[LOADING_MODULE] === 'tasks',
|
||||
deletionScheduledAt: state => parseDateOrNull(state.auth.info?.deletionScheduledAt),
|
||||
}),
|
||||
},
|
||||
methods: {
|
||||
// This is to reload the tasks list after adding a new task through the global task add.
|
||||
// FIXME: Should use vuex (somehow?)
|
||||
updateTaskList() {
|
||||
this.showTasksKey++
|
||||
},
|
||||
},
|
||||
const store = useStore()
|
||||
const listHistory = computed(() => {
|
||||
const history = getHistory()
|
||||
return history.map(l => {
|
||||
return store.getters['lists/getListById'](l.id)
|
||||
}).filter(l => l !== null)
|
||||
})
|
||||
|
||||
|
||||
const migratorsEnabled = computed(() => store.state.config.availableMigrators?.length > 0)
|
||||
const userInfo = computed(() => store.state.auth.info)
|
||||
const hasTasks = computed(() => store.state.hasTasks)
|
||||
const defaultListId = computed(() => store.state.auth.defaultListId)
|
||||
const defaultNamespaceId = computed(() => store.state.namespaces.namespaces?.[0]?.id || 0)
|
||||
const hasLists = computed (() => store.state.namespaces.namespaces?.[0]?.lists.length > 0)
|
||||
const loading = computed(() => store.state.loading && store.state.loadingModule === 'tasks')
|
||||
const deletionScheduledAt = computed(() => parseDateOrNull(store.state.auth.info?.deletionScheduledAt))
|
||||
|
||||
// This is to reload the tasks list after adding a new task through the global task add.
|
||||
// FIXME: Should use vuex (somehow?)
|
||||
const showTasksKey = ref(0)
|
||||
function updateTaskList() {
|
||||
showTasksKey.value++
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
Reference in New Issue