Compare commits

..

40 Commits

Author SHA1 Message Date
kolaente 51c806c12b
feat: color the task color button when the task has a color set
continuous-integration/drone/pr Build is passing Details
2022-09-14 18:43:56 +02:00
konrad a6e9b36bd6 feat(link shares): allows switching the initial view by passing a query parameter (#2335)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #2335
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2022-09-14 16:37:54 +00:00
renovate 854068fff9 chore(deps): update dependency cypress to v10.8.0 (#2359)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #2359
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-09-14 14:46:03 +00:00
kolaente 6576b6148c
feat: move the url link to the bottom of the items
continuous-integration/drone/pr Build is passing Details
2022-09-14 16:31:05 +02:00
kolaente 224cea33ce
feat: make share link name italic
continuous-integration/drone/pr Build was killed Details
2022-09-14 16:27:57 +02:00
kolaente 7e7fa807fd
chore: set more expressive variable names for available views dropdowns
continuous-integration/drone/pr Build was killed Details
2022-09-14 16:17:31 +02:00
Dominik Pschenitschni f083f181e2 fix: only warn once if triggeredNotifications are not supported (#2344)
continuous-integration/drone/push Build is passing Details
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: #2344
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2022-09-14 12:45:21 +00:00
kolaente 2b82df5dbd
Merge branch 'main' into feature/redirect-to-specific-view
continuous-integration/drone/pr Build is passing Details
# Conflicts:
#	src/components/sharing/linkSharing.vue
2022-09-13 22:07:50 +02:00
kolaente e67fc7fb7e
fix: use proper computed for available views list
continuous-integration/drone/pr Build is failing Details
2022-09-13 22:04:24 +02:00
Dominik Pschenitschni db8b8812af feat: use v-model more consequent (#2356)
continuous-integration/drone/push Build is passing Details
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: #2356
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2022-09-13 15:30:33 +00:00
renovate 2013924949 chore(deps): update dependency autoprefixer to v10.4.10 (#2355)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #2355
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-09-13 15:17:25 +00:00
Dominik Pschenitschni 1a11b43ca8 feat: improve models
continuous-integration/drone/push Build is passing Details
2022-09-13 14:59:02 +00:00
renovate 61427987c2 fix(deps): update dependency date-fns to v2.29.3 (#2354)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #2354
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-09-13 13:26:48 +00:00
Dominik Pschenitschni 7b398f73f6 feat: add fallback for useCopyToClipboard (#2343)
continuous-integration/drone/push Build is passing Details
Co-authored-by: Dominik Pschenitschni <mail@celement.de>
Reviewed-on: #2343
Reviewed-by: konrad <k@knt.li>
Co-authored-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
Co-committed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2022-09-13 12:56:29 +00:00
renovate 64726a6421 fix(deps): update dependency blurhash to v2 (#2351)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #2351
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-09-13 12:54:46 +00:00
renovate 53858e0c31 chore(deps): update typescript-eslint monorepo to v5.37.0
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-09-12 18:02:50 +00:00
renovate 2f0f648d28 chore(deps): update dependency eslint to v8.23.1
continuous-integration/drone/push Build is passing Details
2022-09-12 06:14:59 +00:00
renovate 6e026cc7cc chore(deps): update dependency netlify-cli to v11.7.1
continuous-integration/drone/push Build is passing Details
2022-09-12 06:13:48 +00:00
renovate d0fefd3c08 chore(deps): update dependency caniuse-lite to v1.0.30001397
continuous-integration/drone/push Build is passing Details
2022-09-12 06:12:28 +00:00
renovate 4dd397e3d2 chore(deps): update dependency autoprefixer to v10.4.9
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-09-12 00:03:06 +00:00
drone 2a41ccb980 [skip ci] Updated translations via Crowdin 2022-09-11 00:31:16 +00:00
renovate 38d72b59df chore(deps): update dependency vitest to v0.23.2
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is failing Details
2022-09-10 08:02:50 +00:00
drone add080d214 [skip ci] Updated translations via Crowdin 2022-09-10 00:31:03 +00:00
renovate 65f9def438 chore(deps): update dependency typescript to v4.8.3 (#2341)
continuous-integration/drone/push Build is failing Details
Reviewed-on: #2341
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-09-09 06:57:05 +00:00
drone f1a726550e [skip ci] Updated translations via Crowdin 2022-09-09 00:31:14 +00:00
kolaente 26d02d5593
feat: programmatically generate list of available views
continuous-integration/drone/pr Build is passing Details
2022-09-08 16:29:29 +02:00
kolaente 5f678e2449
chore: remove unused id
continuous-integration/drone/pr Build is passing Details
2022-09-08 16:26:51 +02:00
kolaente 23598dd2ee
fix: introduce a ListView type to properly type all available list views
continuous-integration/drone/pr Build is passing Details
2022-09-08 14:11:19 +02:00
kolaente d91d1fecf1
chore: remove &nbsp;
continuous-integration/drone/pr Build is passing Details
2022-09-08 13:58:49 +02:00
kolaente 7a457eb161
feat(link shares): cleanup link share table
continuous-integration/drone/pr Build is passing Details
2022-09-08 13:56:52 +02:00
kolaente d3171b59be
feat(link shares): allows switching the initial view by passing a query parameter 2022-09-08 13:56:50 +02:00
kolaente 63f5f446fd feat(link shares): hide the logo if a query parameter was passed
continuous-integration/drone/push Build is passing Details
2022-09-08 09:56:09 +00:00
kolaente b8d77a617b
chore: rearrange non-dev dependencies
continuous-integration/drone/push Build is passing Details
2022-09-08 11:34:48 +02:00
kolaente d822709991
chore: automerge renovate dev dependency updates
continuous-integration/drone/push Build is passing Details
2022-09-08 11:31:51 +02:00
renovate 86a04da470 fix(deps): update dependency vue to v3.2.39
continuous-integration/drone/push Build is failing Details
2022-09-08 08:25:34 +00:00
renovate e6fbf1cb50 chore(deps): update dependency vue-tsc to v0.40.13
continuous-integration/drone/pr Build is passing Details
continuous-integration/drone/push Build is passing Details
2022-09-08 04:03:13 +00:00
renovate 4fe6186ee6 chore(deps): update dependency sass to v1.54.9 (#2336)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #2336
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-09-07 22:09:57 +00:00
kolaente 6bf5f6efd4
fix: dragging a list on mobile Safari
continuous-integration/drone/push Build is failing Details
2022-09-07 23:11:44 +02:00
renovate 03f448457a chore(deps): update dependency vue-tsc to v0.40.11 (#2333)
continuous-integration/drone/push Build is passing Details
Reviewed-on: #2333
Co-authored-by: renovate <renovatebot@kolaente.de>
Co-committed-by: renovate <renovatebot@kolaente.de>
2022-09-07 18:23:17 +00:00
konrad 7f6f8963e7 feat: add keyboard shortcut to toggle task description edit (#2332)
continuous-integration/drone/push Build is failing Details
Co-authored-by: kolaente <k@knt.li>
Reviewed-on: #2332
Reviewed-by: Dominik Pschenitschni <dpschen@noreply.kolaente.de>
2022-09-07 17:55:59 +00:00
136 changed files with 817 additions and 528 deletions

View File

@ -18,6 +18,10 @@
"browserslist:update": "npx browserslist@latest --update-db" "browserslist:update": "npx browserslist@latest --update-db"
}, },
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "6.2.0",
"@fortawesome/free-regular-svg-icons": "6.2.0",
"@fortawesome/free-solid-svg-icons": "6.2.0",
"@fortawesome/vue-fontawesome": "3.0.1",
"@github/hotkey": "2.0.1", "@github/hotkey": "2.0.1",
"@kyvg/vue3-notification": "2.4.1", "@kyvg/vue3-notification": "2.4.1",
"@sentry/tracing": "7.12.1", "@sentry/tracing": "7.12.1",
@ -27,10 +31,11 @@
"@types/sortablejs": "1.13.0", "@types/sortablejs": "1.13.0",
"@vueuse/core": "9.2.0", "@vueuse/core": "9.2.0",
"@vueuse/router": "9.2.0", "@vueuse/router": "9.2.0",
"blurhash": "1.1.5", "axios": "0.27.2",
"blurhash": "2.0.0",
"bulma-css-variables": "0.9.33", "bulma-css-variables": "0.9.33",
"camel-case": "4.1.2", "camel-case": "4.1.2",
"date-fns": "2.29.2", "date-fns": "2.29.3",
"dompurify": "2.4.0", "dompurify": "2.4.0",
"easymde": "2.17.0", "easymde": "2.17.0",
"flatpickr": "4.6.13", "flatpickr": "4.6.13",
@ -45,7 +50,7 @@
"snake-case": "3.0.4", "snake-case": "3.0.4",
"ufo": "0.8.5", "ufo": "0.8.5",
"v-tooltip": "4.0.0-beta.17", "v-tooltip": "4.0.0-beta.17",
"vue": "3.2.38", "vue": "3.2.39",
"vue-advanced-cropper": "2.8.3", "vue-advanced-cropper": "2.8.3",
"vue-drag-resize": "2.0.3", "vue-drag-resize": "2.0.3",
"vue-flatpickr-component": "9.0.6", "vue-flatpickr-component": "9.0.6",
@ -60,40 +65,35 @@
"@cypress/vite-dev-server": "3.1.1", "@cypress/vite-dev-server": "3.1.1",
"@cypress/vue": "4.2.0", "@cypress/vue": "4.2.0",
"@faker-js/faker": "7.5.0", "@faker-js/faker": "7.5.0",
"@fortawesome/fontawesome-svg-core": "6.2.0",
"@fortawesome/free-regular-svg-icons": "6.2.0",
"@fortawesome/free-solid-svg-icons": "6.2.0",
"@fortawesome/vue-fontawesome": "3.0.1",
"@types/flexsearch": "0.7.3", "@types/flexsearch": "0.7.3",
"@typescript-eslint/eslint-plugin": "5.36.2", "@typescript-eslint/eslint-plugin": "5.37.0",
"@typescript-eslint/parser": "5.36.2", "@typescript-eslint/parser": "5.37.0",
"@vitejs/plugin-legacy": "2.1.0", "@vitejs/plugin-legacy": "2.1.0",
"@vitejs/plugin-vue": "3.1.0", "@vitejs/plugin-vue": "3.1.0",
"@vue/eslint-config-typescript": "11.0.1", "@vue/eslint-config-typescript": "11.0.1",
"@vue/test-utils": "2.0.2", "@vue/test-utils": "2.0.2",
"@vue/tsconfig": "0.1.3", "@vue/tsconfig": "0.1.3",
"autoprefixer": "10.4.8", "autoprefixer": "10.4.10",
"axios": "0.27.2",
"browserslist": "4.21.3", "browserslist": "4.21.3",
"caniuse-lite": "1.0.30001390", "caniuse-lite": "1.0.30001397",
"cypress": "10.7.0", "cypress": "10.8.0",
"esbuild": "0.15.7", "esbuild": "0.15.7",
"eslint": "8.23.0", "eslint": "8.23.1",
"eslint-plugin-vue": "9.4.0", "eslint-plugin-vue": "9.4.0",
"express": "4.18.1", "express": "4.18.1",
"happy-dom": "6.0.4", "happy-dom": "6.0.4",
"netlify-cli": "11.5.1", "netlify-cli": "11.7.1",
"postcss": "8.4.16", "postcss": "8.4.16",
"postcss-preset-env": "7.8.1", "postcss-preset-env": "7.8.1",
"rollup": "2.79.0", "rollup": "2.79.0",
"rollup-plugin-visualizer": "5.8.1", "rollup-plugin-visualizer": "5.8.1",
"sass": "1.54.8", "sass": "1.54.9",
"typescript": "4.8.2", "typescript": "4.8.3",
"vite": "3.1.0", "vite": "3.1.0",
"vite-plugin-pwa": "0.12.7", "vite-plugin-pwa": "0.12.7",
"vite-svg-loader": "3.6.0", "vite-svg-loader": "3.6.0",
"vitest": "0.23.1", "vitest": "0.23.2",
"vue-tsc": "0.40.10", "vue-tsc": "0.40.13",
"wait-on": "6.0.1", "wait-on": "6.0.1",
"workbox-cli": "6.5.4" "workbox-cli": "6.5.4"
}, },

View File

@ -19,6 +19,12 @@
"matchPackagePrefixes": [ "matchPackagePrefixes": [
"@vueuse/" "@vueuse/"
] ]
},
{
"matchDepTypes": ["devDependencies"],
"automerge": true,
"automergeStrategy": "squash",
"automergeType": "pr"
} }
] ]
} }

View File

@ -85,7 +85,7 @@ import DatemathHelp from '@/components/date/datemathHelp.vue'
const store = useStore() const store = useStore()
const {t} = useI18n({useScope: 'global'}) const {t} = useI18n({useScope: 'global'})
const emit = defineEmits(['dateChanged', 'update:modelValue']) const emit = defineEmits(['update:modelValue'])
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
required: false, required: false,
@ -127,7 +127,6 @@ function emitChanged() {
dateFrom: from.value === '' ? null : from.value, dateFrom: from.value === '' ? null : from.value,
dateTo: to.value === '' ? null : to.value, dateTo: to.value === '' ? null : to.value,
} }
emit('dateChanged', args)
emit('update:modelValue', args) emit('update:modelValue', args)
} }

View File

@ -6,8 +6,9 @@
> >
<div class="container has-text-centered link-share-view"> <div class="container has-text-centered link-share-view">
<div class="column is-10 is-offset-1"> <div class="column is-10 is-offset-1">
<Logo class="logo"/> <Logo class="logo" v-if="logoVisible"/>
<h1 <h1
:class="{'m-0': !logoVisible}"
:style="{ 'opacity': currentList.title === '' ? '0': '1' }" :style="{ 'opacity': currentList.title === '' ? '0': '1' }"
class="title"> class="title">
{{ currentList.title === '' ? $t('misc.loading') : currentList.title }} {{ currentList.title === '' ? $t('misc.loading') : currentList.title }}
@ -31,6 +32,7 @@ import PoweredByLink from './PoweredByLink.vue'
const store = useStore() const store = useStore()
const currentList = computed(() => store.state.currentList) const currentList = computed(() => store.state.currentList)
const background = computed(() => store.state.background) const background = computed(() => store.state.background)
const logoVisible = computed(() => store.state.logoVisible)
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>

View File

@ -156,8 +156,8 @@ import {calculateItemPosition} from '@/helpers/calculateItemPosition'
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle' import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
import {getListTitle} from '@/helpers/getListTitle' import {getListTitle} from '@/helpers/getListTitle'
import {useEventListener} from '@vueuse/core' import {useEventListener} from '@vueuse/core'
import type { IList } from '@/models/list' import type {IList} from '@/modelTypes/IList'
import type { INamespace } from '@/models/namespace' import type {INamespace} from '@/modelTypes/INamespace'
const drag = ref(false) const drag = ref(false)
const dragOptions = { const dragOptions = {
@ -250,9 +250,14 @@ async function saveListPosition(e: SortableEvent) {
const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex as string) const newNamespaceIndex = parseInt(e.to.dataset.namespaceIndex as string)
const listsActive = activeLists.value[newNamespaceIndex] const listsActive = activeLists.value[newNamespaceIndex]
const list = listsActive[e.newIndex] // If the list was dragged to the last position, Safari will report e.newIndex as the size of the listsActive
const listBefore = listsActive[e.newIndex - 1] ?? null // array instead of using the position. Because the index is wrong in that case, dragging the list will fail.
const listAfter = listsActive[e.newIndex + 1] ?? null // To work around that we're explicitly checking that case here and decrease the index.
const newIndex = e.newIndex === listsActive.length ? e.newIndex - 1 : e.newIndex
const list = listsActive[newIndex]
const listBefore = listsActive[newIndex - 1] ?? null
const listAfter = listsActive[newIndex + 1] ?? null
listUpdating.value[list.id] = true listUpdating.value[list.id] = true
const position = calculateItemPosition( const position = calculateItemPosition(

View File

@ -68,7 +68,7 @@ export default defineComponent({
default: 'top', default: 'top',
}, },
}, },
emits: ['update:modelValue', 'change'], emits: ['update:modelValue'],
watch: { watch: {
modelValue: { modelValue: {
handler(modelValue) { handler(modelValue) {
@ -98,7 +98,6 @@ export default defineComponent({
this.lastChangeTimeout = setTimeout(() => { this.lastChangeTimeout = setTimeout(() => {
this.$emit('update:modelValue', this.color) this.$emit('update:modelValue', this.color)
this.$emit('change')
}, 500) }, 500)
}, },
reset() { reset() {

View File

@ -131,7 +131,7 @@ export default defineComponent({
default: false, default: false,
}, },
}, },
emits: ['update:modelValue', 'change', 'close', 'close-on-change'], emits: ['update:modelValue', 'close', 'close-on-change'],
mounted() { mounted() {
document.addEventListener('click', this.hideDatePopup) document.addEventListener('click', this.hideDatePopup)
}, },
@ -186,7 +186,6 @@ export default defineComponent({
updateData() { updateData() {
this.changed = true this.changed = true
this.$emit('update:modelValue', this.date) this.$emit('update:modelValue', this.date)
this.$emit('change', this.date)
}, },
toggleDatePopup() { toggleDatePopup() {
if (this.disabled) { if (this.disabled) {

View File

@ -16,14 +16,29 @@
<p class="has-text-centered has-text-grey is-italic my-5" v-if="showPreviewText"> <p class="has-text-centered has-text-grey is-italic my-5" v-if="showPreviewText">
{{ emptyText }} {{ emptyText }}
<template v-if="isEditEnabled"> <template v-if="isEditEnabled">
<ButtonLink @click="toggleEdit" class="d-print-none">{{ $t('input.editor.edit') }}</ButtonLink>. <ButtonLink
@click="toggleEdit"
v-shortcut="editShortcut"
class="d-print-none">
{{ $t('input.editor.edit') }}
</ButtonLink>.
</template> </template>
</p> </p>
<ul class="actions d-print-none" v-if="bottomActions.length > 0"> <ul class="actions d-print-none" v-if="bottomActions.length > 0">
<li v-if="isEditEnabled && !showPreviewText && showSave"> <li v-if="isEditEnabled && !showPreviewText && showSave">
<BaseButton v-if="showEditButton" @click="toggleEdit">{{ $t('input.editor.edit') }}</BaseButton> <BaseButton
<BaseButton v-else-if="isEditActive" @click="toggleEdit" class="done-edit">{{ $t('misc.save') }}</BaseButton> v-if="showEditButton"
@click="toggleEdit"
v-shortcut="editShortcut">
{{ $t('input.editor.edit') }}
</BaseButton>
<BaseButton
v-else-if="isEditActive"
@click="toggleEdit"
class="done-edit">
{{ $t('misc.save') }}
</BaseButton>
</li> </li>
<li v-for="(action, k) in bottomActions" :key="k"> <li v-for="(action, k) in bottomActions" :key="k">
<BaseButton @click="action.action">{{ action.title }}</BaseButton> <BaseButton @click="action.action">{{ action.title }}</BaseButton>
@ -32,7 +47,11 @@
<template v-else-if="isEditEnabled && showSave"> <template v-else-if="isEditEnabled && showSave">
<ul v-if="showEditButton" class="actions d-print-none"> <ul v-if="showEditButton" class="actions d-print-none">
<li> <li>
<BaseButton @click="toggleEdit">{{ $t('input.editor.edit') }}</BaseButton> <BaseButton
@click="toggleEdit"
v-shortcut="editShortcut">
{{ $t('input.editor.edit') }}
</BaseButton>
</li> </li>
</ul> </ul>
<x-button <x-button
@ -110,8 +129,14 @@ export default defineComponent({
type: Boolean, type: Boolean,
default: false, default: false,
}, },
// If a key is passed the editor will go in "edit" mode when the key is pressed.
// Disabled if an empty string is passed.
editShortcut: {
type: String,
default: '',
},
}, },
emits: ['update:modelValue', 'change'], emits: ['update:modelValue'],
computed: { computed: {
showPreviewText() { showPreviewText() {
return this.isPreviewActive && this.text === '' && this.emptyText !== '' return this.isPreviewActive && this.text === '' && this.emptyText !== ''
@ -187,7 +212,6 @@ export default defineComponent({
this.changeTimeout = setTimeout(() => { this.changeTimeout = setTimeout(() => {
this.$emit('update:modelValue', this.text) this.$emit('update:modelValue', this.text)
this.$emit('change', this.text)
}, timeout) }, timeout)
}, },
replaceAt(str, index, replacement) { replaceAt(str, index, replacement) {

View File

@ -60,8 +60,8 @@
:is-button="false" :is-button="false"
entity="list" entity="list"
:entity-id="list.id" :entity-id="list.id"
:subscription="list.subscription" :model-value="list.subscription"
@change="sub => subscription = sub" @update:model-value="sub => subscription = sub"
type="dropdown" type="dropdown"
/> />
<dropdown-item <dropdown-item
@ -83,8 +83,8 @@ import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
import Dropdown from '@/components/misc/dropdown.vue' import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue' import DropdownItem from '@/components/misc/dropdown-item.vue'
import TaskSubscription from '@/components/misc/subscription.vue' import TaskSubscription from '@/components/misc/subscription.vue'
import type {IList} from '@/models/list' import type {IList} from '@/modelTypes/IList'
import type { ISubscription } from '@/models/subscription' import type {ISubscription} from '@/modelTypes/ISubscription'
const props = defineProps({ const props = defineProps({
list: { list: {

View File

@ -38,7 +38,7 @@
<priority-select <priority-select
:disabled="!filters.usePriority || undefined" :disabled="!filters.usePriority || undefined"
v-model.number="filters.priority" v-model.number="filters.priority"
@change="setPriority" @update:model-value="setPriority"
/> />
<fancycheckbox <fancycheckbox
v-model="filters.usePriority" v-model="filters.usePriority"
@ -53,7 +53,7 @@
<div class="control single-value-control"> <div class="control single-value-control">
<percent-done-select <percent-done-select
v-model.number="filters.percentDone" v-model.number="filters.percentDone"
@change="setPercentDoneFilter" @update:model-value="setPercentDoneFilter"
:disabled="!filters.usePercentDone || undefined" :disabled="!filters.usePercentDone || undefined"
/> />
<fancycheckbox <fancycheckbox
@ -68,8 +68,9 @@
<label class="label">{{ $t('task.attributes.dueDate') }}</label> <label class="label">{{ $t('task.attributes.dueDate') }}</label>
<div class="control"> <div class="control">
<datepicker-with-range <datepicker-with-range
@dateChanged="values => setDateFilter('due_date', values)" v-model="filters.dueDate"
v-model="filters.dueDate"> @update:model-value="values => setDateFilter('due_date', values)"
>
<template #trigger="{toggle, buttonText}"> <template #trigger="{toggle, buttonText}">
<x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2"> <x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2">
{{ buttonText }} {{ buttonText }}
@ -82,8 +83,9 @@
<label class="label">{{ $t('task.attributes.startDate') }}</label> <label class="label">{{ $t('task.attributes.startDate') }}</label>
<div class="control"> <div class="control">
<datepicker-with-range <datepicker-with-range
@dateChanged="values => setDateFilter('start_date', values)" v-model="filters.startDate"
v-model="filters.startDate"> @update:model-value="values => setDateFilter('start_date', values)"
>
<template #trigger="{toggle, buttonText}"> <template #trigger="{toggle, buttonText}">
<x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2"> <x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2">
{{ buttonText }} {{ buttonText }}
@ -96,8 +98,9 @@
<label class="label">{{ $t('task.attributes.endDate') }}</label> <label class="label">{{ $t('task.attributes.endDate') }}</label>
<div class="control"> <div class="control">
<datepicker-with-range <datepicker-with-range
@dateChanged="values => setDateFilter('end_date', values)" v-model="filters.endDate"
v-model="filters.endDate"> @update:model-value="values => setDateFilter('end_date', values)"
>
<template #trigger="{toggle, buttonText}"> <template #trigger="{toggle, buttonText}">
<x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2"> <x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2">
{{ buttonText }} {{ buttonText }}
@ -110,8 +113,9 @@
<label class="label">{{ $t('task.attributes.reminders') }}</label> <label class="label">{{ $t('task.attributes.reminders') }}</label>
<div class="control"> <div class="control">
<datepicker-with-range <datepicker-with-range
@dateChanged="values => setDateFilter('reminders', values)" v-model="filters.reminders"
v-model="filters.reminders"> @update:model-value="values => setDateFilter('reminders', values)"
>
<template #trigger="{toggle, buttonText}"> <template #trigger="{toggle, buttonText}">
<x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2"> <x-button @click.prevent.stop="toggle()" variant="secondary" :shadow="false" class="mb-2">
{{ buttonText }} {{ buttonText }}
@ -141,7 +145,7 @@
<div class="field"> <div class="field">
<label class="label">{{ $t('task.attributes.labels') }}</label> <label class="label">{{ $t('task.attributes.labels') }}</label>
<div class="control labels-list"> <div class="control labels-list">
<edit-labels v-model="labels" @change="changeLabelFilter"/> <edit-labels v-model="labels" @update:model-value="changeLabelFilter"/>
</div> </div>
</div> </div>
@ -278,7 +282,7 @@ export default defineComponent({
default: false, default: false,
}, },
}, },
emits: ['update:modelValue', 'change'], emits: ['update:modelValue'],
watch: { watch: {
modelValue: { modelValue: {
handler(value) { handler(value) {
@ -312,7 +316,6 @@ export default defineComponent({
const params = {...this.params} const params = {...this.params}
params.filter_value = params.filter_value.map(v => v instanceof Date ? v.toISOString() : v) params.filter_value = params.filter_value.map(v => v instanceof Date ? v.toISOString() : v)
this.$emit('update:modelValue', params) this.$emit('update:modelValue', params)
this.$emit('change', params)
}, },
prepareFilters() { prepareFilters() {
this.prepareDone() this.prepareDone()

View File

@ -45,7 +45,7 @@ import {getBlobFromBlurHash} from '@/helpers/getBlobFromBlurHash'
import {colorIsDark} from '@/helpers/color/colorIsDark' import {colorIsDark} from '@/helpers/color/colorIsDark'
import BaseButton from '@/components/base/BaseButton.vue' import BaseButton from '@/components/base/BaseButton.vue'
import type { IList } from '@/models/list' import type {IList} from '@/modelTypes/IList'
const background = ref<string | null>(null) const background = ref<string | null>(null)
const backgroundLoading = ref(false) const backgroundLoading = ref(false)

View File

@ -136,6 +136,10 @@ export const KEYBOARD_SHORTCUTS : ShortcutGroup[] = [
title: 'keyboardShortcuts.task.reminder', title: 'keyboardShortcuts.task.reminder',
keys: ['alt', 'r'], keys: ['alt', 'r'],
}, },
{
title: 'keyboardShortcuts.task.description',
keys: ['e'],
},
], ],
}, },
] ]

View File

@ -5,7 +5,7 @@
:icon="iconName" :icon="iconName"
v-tooltip="tooltipText" v-tooltip="tooltipText"
@click="changeSubscription" @click="changeSubscription"
:disabled="disabled || undefined" :disabled="disabled"
> >
{{ buttonText }} {{ buttonText }}
</x-button> </x-button>
@ -23,6 +23,7 @@
v-tooltip="tooltipText" v-tooltip="tooltipText"
@click="changeSubscription" @click="changeSubscription"
:class="{'is-disabled': disabled}" :class="{'is-disabled': disabled}"
:disabled="disabled"
> >
<span class="icon"> <span class="icon">
<icon :icon="iconName"/> <icon :icon="iconName"/>
@ -39,7 +40,8 @@ import BaseButton from '@/components/base/BaseButton.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue' import DropdownItem from '@/components/misc/dropdown-item.vue'
import SubscriptionService from '@/services/subscription' import SubscriptionService from '@/services/subscription'
import SubscriptionModel, { type ISubscription } from '@/models/subscription' import SubscriptionModel from '@/models/subscription'
import type {ISubscription} from '@/modelTypes/ISubscription'
import {success} from '@/message' import {success} from '@/message'
@ -50,7 +52,7 @@ const props = defineProps({
type: Boolean, type: Boolean,
default: true, default: true,
}, },
subscription: { modelValue: {
type: Object as PropType<ISubscription>, type: Object as PropType<ISubscription>,
default: null, default: null,
}, },
@ -60,9 +62,9 @@ const props = defineProps({
}, },
}) })
const subscriptionEntity = computed<string | null>(() => props.subscription?.entity ?? null) const subscriptionEntity = computed<string | null>(() => props.modelValue?.entity ?? null)
const emit = defineEmits(['change']) const emit = defineEmits(['update:modelValue'])
const subscriptionService = shallowRef(new SubscriptionService()) const subscriptionService = shallowRef(new SubscriptionService())
@ -75,27 +77,21 @@ const tooltipText = computed(() => {
}) })
} }
return props.subscription !== null ? return props.modelValue !== null ?
t('task.subscription.subscribed', {entity: props.entity}) : t('task.subscription.subscribed', {entity: props.entity}) :
t('task.subscription.notSubscribed', {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 buttonText = computed(() => props.modelValue ? t('task.subscription.unsubscribe') : t('task.subscription.subscribe'))
const iconName = computed(() => props.subscription !== null ? ['far', 'bell-slash'] : 'bell') const iconName = computed(() => props.modelValue ? ['far', 'bell-slash'] : 'bell')
const disabled = computed(() => { const disabled = computed(() => props.modelValue && subscriptionEntity.value !== props.entity)
if (props.subscription === null) {
return false
}
return subscriptionEntity.value !== props.entity
})
function changeSubscription() { function changeSubscription() {
if (disabled.value) { if (disabled.value) {
return return
} }
if (props.subscription === null) { if (props.modelValue === null) {
subscribe() subscribe()
} else { } else {
unsubscribe() unsubscribe()
@ -108,7 +104,7 @@ async function subscribe() {
entityId: props.entityId, entityId: props.entityId,
}) })
await subscriptionService.value.create(subscription) await subscriptionService.value.create(subscription)
emit('change', subscription) emit('update:modelValue', subscription)
success({message: t('task.subscription.subscribeSuccess', {entity: props.entity})}) success({message: t('task.subscription.subscribeSuccess', {entity: props.entity})})
} }
@ -118,7 +114,7 @@ async function unsubscribe() {
entityId: props.entityId, entityId: props.entityId,
}) })
await subscriptionService.value.delete(subscription) await subscriptionService.value.delete(subscription)
emit('change', null) emit('update:modelValue', null)
success({message: t('task.subscription.unsubscribeSuccess', {entity: props.entity})}) success({message: t('task.subscription.unsubscribeSuccess', {entity: props.entity})})
} }
</script> </script>

View File

@ -34,12 +34,13 @@
{{ $t('menu.archive') }} {{ $t('menu.archive') }}
</dropdown-item> </dropdown-item>
<task-subscription <task-subscription
v-if="subscription"
class="has-no-shadow" class="has-no-shadow"
:is-button="false" :is-button="false"
entity="namespace" entity="namespace"
:entity-id="namespace.id" :entity-id="namespace.id"
:subscription="subscription" :model-value="subscription"
@change="sub => subscription = sub" @update:model-value="sub => subscription = sub"
type="dropdown" type="dropdown"
/> />
<dropdown-item <dropdown-item
@ -59,7 +60,8 @@ import {ref, onMounted, type PropType} from 'vue'
import Dropdown from '@/components/misc/dropdown.vue' import Dropdown from '@/components/misc/dropdown.vue'
import DropdownItem from '@/components/misc/dropdown-item.vue' import DropdownItem from '@/components/misc/dropdown-item.vue'
import TaskSubscription from '@/components/misc/subscription.vue' import TaskSubscription from '@/components/misc/subscription.vue'
import type { INamespace } from '@/models/namespace' import type {INamespace} from '@/modelTypes/INamespace'
import type {ISubscription} from '@/modelTypes/ISubscription'
const props = defineProps({ const props = defineProps({
namespace: { namespace: {
@ -68,7 +70,7 @@ const props = defineProps({
}, },
}) })
const subscription = ref(null) const subscription = ref<ISubscription | null>(null)
onMounted(() => { onMounted(() => {
subscription.value = props.namespace.subscription subscription.value = props.namespace.subscription
}) })

View File

@ -79,30 +79,59 @@
> >
<thead> <thead>
<tr> <tr>
<th>{{ $t('list.share.attributes.link') }}</th> <th></th>
<th>{{ $t('list.share.attributes.name') }}</th> <th>{{ $t('list.share.links.view') }}</th>
<th>{{ $t('list.share.attributes.sharedBy') }}</th>
<th>{{ $t('list.share.attributes.right') }}</th>
<th>{{ $t('list.share.attributes.delete') }}</th> <th>{{ $t('list.share.attributes.delete') }}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr :key="s.id" v-for="s in linkShares"> <tr :key="s.id" v-for="s in linkShares">
<td> <td>
<p class="mb-2 is-italic" v-if="s.name !== ''">
{{ s.name }}
</p>
<p class="mb-2">
<i18n-t keypath="list.share.links.sharedBy">
<strong>{{ s.sharedBy.getDisplayName() }}</strong>
</i18n-t>
</p>
<p class="mb-2">
<template v-if="s.right === RIGHTS.ADMIN">
<span class="icon is-small">
<icon icon="lock"/>
</span>&nbsp;
{{ $t('list.share.right.admin') }}
</template>
<template v-else-if="s.right === RIGHTS.READ_WRITE">
<span class="icon is-small">
<icon icon="pen"/>
</span>&nbsp;
{{ $t('list.share.right.readWrite') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="users"/>
</span>&nbsp;
{{ $t('list.share.right.read') }}
</template>
</p>
<div class="field has-addons no-input-mobile"> <div class="field has-addons no-input-mobile">
<div class="control"> <div class="control">
<input <input
:value="getShareLink(s.hash)" :value="getShareLink(s.hash, selectedView[s.id])"
class="input" class="input"
readonly readonly
type="text" type="text"
/> />
</div> </div>
<div class="control"> <div class="control">
<x-button <x-button
@click="copy(getShareLink(s.hash))" @click="copy(getShareLink(s.hash, selectedView[s.id]))"
:shadow="false" :shadow="false"
v-tooltip="$t('misc.copy')" v-tooltip="$t('misc.copy')"
> >
<span class="icon"> <span class="icon">
<icon icon="paste"/> <icon icon="paste"/>
@ -112,33 +141,16 @@
</div> </div>
</td> </td>
<td> <td>
<template v-if="s.name !== ''"> <div class="select">
{{ s.name }} <select v-model="selectedView[s.id]">
</template> <option
<i v-else>{{ $t('list.share.links.noName') }}</i> v-for="(title, key) in availableViews"
</td> :value="key"
<td> :key="key">
{{ s.sharedBy.getDisplayName() }} {{ title }}
</td> </option>
<td class="type"> </select>
<template v-if="s.right === RIGHTS.ADMIN"> </div>
<span class="icon is-small">
<icon icon="lock"/>
</span>&nbsp;
{{ $t('list.share.right.admin') }}
</template>
<template v-else-if="s.right === RIGHTS.READ_WRITE">
<span class="icon is-small">
<icon icon="pen"/>
</span>&nbsp;
{{ $t('list.share.right.readWrite') }}
</template>
<template v-else>
<span class="icon is-small">
<icon icon="users"/>
</span>&nbsp;
{{ $t('list.share.right.read') }}
</template>
</td> </td>
<td class="actions"> <td class="actions">
<x-button <x-button
@ -166,7 +178,7 @@
<template #header> <template #header>
<span>{{ $t('list.share.links.remove') }}</span> <span>{{ $t('list.share.links.remove') }}</span>
</template> </template>
<template #text> <template #text>
<p>{{ $t('list.share.links.removeText') }}</p> <p>{{ $t('list.share.links.removeText') }}</p>
</template> </template>
@ -181,13 +193,17 @@ import {useStore} from '@/store'
import {useI18n} from 'vue-i18n' import {useI18n} from 'vue-i18n'
import {RIGHTS} from '@/constants/rights' import {RIGHTS} from '@/constants/rights'
import LinkShareModel, { type ILinkShare } from '@/models/linkShare' import LinkShareModel from '@/models/linkShare'
import type {ILinkShare} from '@/modelTypes/ILinkShare'
import type {IList} from '@/modelTypes/IList'
import LinkShareService from '@/services/linkShare' import LinkShareService from '@/services/linkShare'
import {useCopyToClipboard} from '@/composables/useCopyToClipboard' import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
import {success} from '@/message' import {success} from '@/message'
import type { IList } from '@/models/list' import type {ListView} from '@/types/ListView'
import {LIST_VIEWS} from '@/types/ListView'
const props = defineProps({ const props = defineProps({
listId: { listId: {
@ -207,6 +223,17 @@ const showDeleteModal = ref(false)
const linkIdToDelete = ref(0) const linkIdToDelete = ref(0)
const showNewForm = ref(false) const showNewForm = ref(false)
type SelectedViewMapper = Record<IList['id'], ListView>
const selectedView = ref<SelectedViewMapper>({})
const availableViews = computed<Record<ListView, string>>(() => ({
list: t('list.list.title'),
gantt: t('list.gantt.title'),
table: t('list.table.title'),
kanban: t('list.kanban.title'),
}))
const copy = useCopyToClipboard() const copy = useCopyToClipboard()
watch( watch(
() => props.listId, () => props.listId,
@ -223,7 +250,11 @@ async function load(listId: IList['id']) {
return return
} }
linkShares.value = await linkShareService.getAll({listId}) const links = await linkShareService.getAll({listId})
links.forEach((l: ILinkShare) => {
selectedView.value[l.id] = 'list'
})
linkShares.value = links
} }
async function add(listId: IList['id']) { async function add(listId: IList['id']) {
@ -255,15 +286,15 @@ async function remove(listId: IList['id']) {
} }
} }
function getShareLink(hash: string) { function getShareLink(hash: string, view: ListView = LIST_VIEWS.LIST) {
return frontendUrl.value + 'share/' + hash + '/auth' return frontendUrl.value + 'share/' + hash + '/auth?view=' + view
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
// FIXME: I think this is not needed // FIXME: I think this is not needed
.sharables-list:not(.card-content) { .sharables-list:not(.card-content) {
overflow-y: auto overflow-y: auto
} }
@include modal-transition(); @include modal-transition();

View File

@ -143,22 +143,29 @@ import {useStore} from '@/store'
import {useI18n} from 'vue-i18n' import {useI18n} from 'vue-i18n'
import UserNamespaceService from '@/services/userNamespace' import UserNamespaceService from '@/services/userNamespace'
import UserNamespaceModel, { type IUserNamespace } from '@/models/userNamespace' import UserNamespaceModel from '@/models/userNamespace'
import type {IUserNamespace} from '@/modelTypes/IUserNamespace'
import UserListService from '@/services/userList' import UserListService from '@/services/userList'
import UserListModel, { type IUserList } from '@/models/userList' import UserListModel from '@/models/userList'
import type {IUserList} from '@/modelTypes/IUserList'
import UserService from '@/services/user' import UserService from '@/services/user'
import UserModel, { type IUser } from '@/models/user' import UserModel from '@/models/user'
import type {IUser} from '@/modelTypes/IUser'
import TeamNamespaceService from '@/services/teamNamespace' import TeamNamespaceService from '@/services/teamNamespace'
import TeamNamespaceModel, { type ITeamNamespace } from '@/models/teamNamespace' import TeamNamespaceModel from '@/models/teamNamespace'
import type { ITeamNamespace } from '@/modelTypes/ITeamNamespace'
import TeamListService from '@/services/teamList' import TeamListService from '@/services/teamList'
import TeamListModel, { type ITeamList } from '@/models/teamList' import TeamListModel from '@/models/teamList'
import type { ITeamList } from '@/modelTypes/ITeamList'
import TeamService from '@/services/team' import TeamService from '@/services/team'
import TeamModel, { type ITeam } from '@/models/team' import TeamModel from '@/models/team'
import type {ITeam} from '@/modelTypes/ITeam'
import {RIGHTS} from '@/constants/rights' import {RIGHTS} from '@/constants/rights'
import Multiselect from '@/components/input/multiselect.vue' import Multiselect from '@/components/input/multiselect.vue'

View File

@ -36,8 +36,8 @@
<strong>{{ $t('task.attributes.reminders') }}</strong> <strong>{{ $t('task.attributes.reminders') }}</strong>
<reminders <reminders
@change="editTaskSubmit()"
v-model="taskEditTask.reminderDates" v-model="taskEditTask.reminderDates"
@update:model-value="editTaskSubmit()"
/> />
<div class="field"> <div class="field">
@ -83,7 +83,8 @@ import {useI18n} from 'vue-i18n'
import Editor from '@/components/input/AsyncEditor' import Editor from '@/components/input/AsyncEditor'
import TaskService from '@/services/task' import TaskService from '@/services/task'
import TaskModel, { type ITask } from '@/models/task' import TaskModel from '@/models/task'
import type {ITask} from '@/modelTypes/ITask'
import EditLabels from './partials/editLabels.vue' import EditLabels from './partials/editLabels.vue'
import Reminders from './partials/reminders.vue' import Reminders from './partials/reminders.vue'
import ColorPicker from '../input/colorPicker.vue' import ColorPicker from '../input/colorPicker.vue'

View File

@ -147,7 +147,8 @@
import {defineComponent} from 'vue' import {defineComponent} from 'vue'
import AttachmentService from '../../../services/attachment' import AttachmentService from '../../../services/attachment'
import AttachmentModel, { type IAttachment } from '@/models/attachment' import AttachmentModel from '@/models/attachment'
import type {IAttachment} from '@/modelTypes/IAttachment'
import User from '@/components/misc/user.vue' import User from '@/components/misc/user.vue'
import {mapState} from 'vuex' import {mapState} from 'vuex'
@ -155,8 +156,8 @@ import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
import { uploadFiles, generateAttachmentUrl } from '@/helpers/attachments' import { uploadFiles, generateAttachmentUrl } from '@/helpers/attachments'
import {formatDate, formatDateSince, formatDateLong} from '@/helpers/time/formatDate' import {formatDate, formatDateSince, formatDateLong} from '@/helpers/time/formatDate'
import BaseButton from '@/components/base/BaseButton' import BaseButton from '@/components/base/BaseButton.vue'
import type { IFile } from '@/models/file' import type { IFile } from '@/modelTypes/IFile'
import { getHumanSize } from '@/helpers/getHumanSize' import { getHumanSize } from '@/helpers/getHumanSize'
export default defineComponent({ export default defineComponent({

View File

@ -14,7 +14,7 @@ import {computed, type PropType} from 'vue'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import {getChecklistStatistics} from '@/helpers/checklistFromText' import {getChecklistStatistics} from '@/helpers/checklistFromText'
import type {ITask} from '@/models/task' import type {ITask} from '@/modelTypes/ITask'
const props = defineProps({ const props = defineProps({
task: { task: {

View File

@ -70,13 +70,13 @@
:is-edit-enabled="canWrite && c.author.id === currentUserId" :is-edit-enabled="canWrite && c.author.id === currentUserId"
:upload-callback="attachmentUpload" :upload-callback="attachmentUpload"
:upload-enabled="true" :upload-enabled="true"
@change=" v-model="c.comment"
@update:model-value="
() => { () => {
toggleEdit(c) toggleEdit(c)
editComment() editComment()
} }
" "
v-model="c.comment"
:bottom-actions="actions[c.id]" :bottom-actions="actions[c.id]"
:show-save="true" :show-save="true"
/> />
@ -159,12 +159,15 @@ import {useI18n} from 'vue-i18n'
import Editor from '@/components/input/AsyncEditor' import Editor from '@/components/input/AsyncEditor'
import TaskCommentService from '@/services/taskComment' import TaskCommentService from '@/services/taskComment'
import TaskCommentModel, { type ITaskComment } from '@/models/taskComment' import TaskCommentModel from '@/models/taskComment'
import type {ITaskComment} from '@/modelTypes/ITaskComment'
import type {ITask} from '@/modelTypes/ITask'
import {uploadFile} from '@/helpers/attachments' import {uploadFile} from '@/helpers/attachments'
import {success} from '@/message' import {success} from '@/message'
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate' import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
import type { ITask } from '@/models/task'
const props = defineProps({ const props = defineProps({
taskId: { taskId: {
type: Number, type: Number,

View File

@ -28,7 +28,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import {computed, toRefs, type PropType} from 'vue' import {computed, toRefs, type PropType} from 'vue'
import type { ITask } from '@/models/task' import type {ITask} from '@/modelTypes/ITask'
import {formatISO, formatDateLong, formatDateSince} from '@/helpers/time/formatDate' import {formatISO, formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
const props = defineProps({ const props = defineProps({

View File

@ -44,7 +44,7 @@ import {useI18n} from 'vue-i18n'
import flatPickr from 'vue-flatpickr-component' import flatPickr from 'vue-flatpickr-component'
import TaskService from '@/services/task' import TaskService from '@/services/task'
import { type ITask } from '@/models/task' import type {ITask} from '@/modelTypes/ITask'
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {

View File

@ -20,11 +20,12 @@
:is-edit-enabled="canWrite" :is-edit-enabled="canWrite"
:upload-callback="attachmentUpload" :upload-callback="attachmentUpload"
:upload-enabled="true" :upload-enabled="true"
@change="save"
:placeholder="$t('task.description.placeholder')" :placeholder="$t('task.description.placeholder')"
:empty-text="$t('task.description.empty')" :empty-text="$t('task.description.empty')"
:show-save="true" :show-save="true"
edit-shortcut="e"
v-model="task.description" v-model="task.description"
@update:model-value="save"
/> />
</div> </div>
</template> </template>
@ -35,7 +36,7 @@ import {useStore} from '@/store'
import Editor from '@/components/input/AsyncEditor' import Editor from '@/components/input/AsyncEditor'
import type { ITask } from '@/models/task' import type {ITask} from '@/modelTypes/ITask'
const props = defineProps({ const props = defineProps({

View File

@ -66,7 +66,7 @@ const props = defineProps({
}, },
}) })
const emit = defineEmits(['update:modelValue', 'change']) const emit = defineEmits(['update:modelValue'])
const store = useStore() const store = useStore()
const {t} = useI18n({useScope: 'global'}) const {t} = useI18n({useScope: 'global'})
@ -96,7 +96,6 @@ function findLabel(newQuery: string) {
async function addLabel(label: ILabel, showNotification = true) { async function addLabel(label: ILabel, showNotification = true) {
const bubble = () => { const bubble = () => {
emit('update:modelValue', labels.value) emit('update:modelValue', labels.value)
emit('change', labels.value)
} }
if (props.taskId === 0) { if (props.taskId === 0) {
@ -122,7 +121,6 @@ async function removeLabel(label: ILabel) {
} }
} }
emit('update:modelValue', labels.value) emit('update:modelValue', labels.value)
emit('change', labels.value)
success({message: t('task.label.removeSuccess')}) success({message: t('task.label.removeSuccess')})
} }

View File

@ -34,12 +34,14 @@
<script setup lang="ts"> <script setup lang="ts">
import {ref, computed, type PropType} from 'vue' import {ref, computed, type PropType} from 'vue'
import {useStore} from '@/store' import {useStore} from '@/store'
import {useRouter} from 'vue-router'
import BaseButton from '@/components/base/BaseButton.vue' import BaseButton from '@/components/base/BaseButton.vue'
import Done from '@/components/misc/Done.vue' import Done from '@/components/misc/Done.vue'
import type {ITask} from '@/models/task'
import { useRouter } from 'vue-router' import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
import type {ITask} from '@/modelTypes/ITask'
const props = defineProps({ const props = defineProps({
task: { task: {

View File

@ -74,7 +74,8 @@ import User from '../../../components/misc/user.vue'
import Done from '@/components/misc/Done.vue' import Done from '@/components/misc/Done.vue'
import Labels from '../../../components/tasks/partials/labels.vue' import Labels from '../../../components/tasks/partials/labels.vue'
import ChecklistSummary from './checklist-summary.vue' import ChecklistSummary from './checklist-summary.vue'
import {TASK_DEFAULT_COLOR, type ITask} from '@/models/task' import {TASK_DEFAULT_COLOR} from '@/models/task'
import type {ITask} from '@/modelTypes/ITask'
import {formatDateLong, formatISO, formatDateSince} from '@/helpers/time/formatDate' import {formatDateLong, formatISO, formatDateSince} from '@/helpers/time/formatDate'
import {colorIsDark} from '@/helpers/color/colorIsDark' import {colorIsDark} from '@/helpers/color/colorIsDark'

View File

@ -11,8 +11,8 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import type { PropType } from 'vue' import type {PropType} from 'vue'
import type { ILabel } from '@/models/label' import type {ILabel} from '@/modelTypes/ILabel'
defineProps({ defineProps({
labels: { labels: {

View File

@ -21,7 +21,8 @@ import {reactive, ref, watch} from 'vue'
import type {PropType} from 'vue' import type {PropType} from 'vue'
import {useStore} from '@/store' import {useStore} from '@/store'
import {useI18n} from 'vue-i18n' import {useI18n} from 'vue-i18n'
import ListModel, { type IList } from '@/models/list' import ListModel from '@/models/list'
import type {IList} from '@/modelTypes/IList'
import Multiselect from '@/components/input/multiselect.vue' import Multiselect from '@/components/input/multiselect.vue'
const props = defineProps({ const props = defineProps({

View File

@ -32,13 +32,12 @@ const props = defineProps({
}, },
}) })
const emit = defineEmits(['update:modelValue', 'change']) const emit = defineEmits(['update:modelValue'])
const percentDone = computed({ const percentDone = computed({
get: () => props.modelValue, get: () => props.modelValue,
set(percentDone) { set(percentDone) {
emit('update:modelValue', percentDone) emit('update:modelValue', percentDone)
emit('change')
}, },
}) })
</script> </script>

View File

@ -19,19 +19,19 @@
import {ref, watch} from 'vue' import {ref, watch} from 'vue'
import {PRIORITIES} from '@/constants/priorities' import {PRIORITIES} from '@/constants/priorities'
const priority = ref(0)
const props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
default: 0,
type: Number, type: Number,
default: 0,
}, },
disabled: { disabled: {
default: false, default: false,
}, },
}) })
const emit = defineEmits(['update:modelValue', 'change']) const emit = defineEmits(['update:modelValue'])
const priority = ref(0)
// FIXME: store value outside // FIXME: store value outside
// Set the priority to the :value every time it changes from the outside // Set the priority to the :value every time it changes from the outside
@ -45,6 +45,5 @@ watch(
function updateData() { function updateData() {
emit('update:modelValue', priority.value) emit('update:modelValue', priority.value)
emit('change')
} }
</script> </script>

View File

@ -63,7 +63,7 @@ const props = defineProps({
}, },
}) })
const emit = defineEmits(['update:modelValue', 'change']) const emit = defineEmits(['update:modelValue'])
const reminders = ref<Reminder[]>([]) const reminders = ref<Reminder[]>([])
@ -86,7 +86,6 @@ watch(
function updateData() { function updateData() {
emit('update:modelValue', reminders.value) emit('update:modelValue', reminders.value)
emit('change')
} }
const newReminder = ref(null) const newReminder = ref(null)

View File

@ -85,7 +85,7 @@ const props = defineProps({
const {t} = useI18n({useScope: 'global'}) const {t} = useI18n({useScope: 'global'})
const emit = defineEmits(['update:modelValue', 'change']) const emit = defineEmits(['update:modelValue'])
const task = ref<ITask>() const task = ref<ITask>()
const repeatAfter = reactive({ const repeatAfter = reactive({
@ -116,7 +116,6 @@ function updateData() {
Object.assign(task.value.repeatAfter, repeatAfter) Object.assign(task.value.repeatAfter, repeatAfter)
emit('update:modelValue', task.value) emit('update:modelValue', task.value)
emit('change')
} }
function setRepeatAfter(amount: number, type: IRepeatAfter['type']) { function setRepeatAfter(amount: number, type: IRepeatAfter['type']) {

View File

@ -98,7 +98,8 @@
<script lang="ts"> <script lang="ts">
import {defineComponent, type PropType} from 'vue' import {defineComponent, type PropType} from 'vue'
import TaskModel, { type ITask } from '../../../models/task' import TaskModel from '@/models/task'
import type {ITask} from '@/modelTypes/ITask'
import PriorityLabel from './priorityLabel.vue' import PriorityLabel from './priorityLabel.vue'
import TaskService from '../../../services/task' import TaskService from '../../../services/task'
import Labels from '@/components/tasks/partials/labels.vue' import Labels from '@/components/tasks/partials/labels.vue'

View File

@ -3,11 +3,42 @@ import {useI18n} from 'vue-i18n'
export function useCopyToClipboard() { export function useCopyToClipboard() {
const {t} = useI18n({useScope: 'global'}) const {t} = useI18n({useScope: 'global'})
function fallbackCopyTextToClipboard(text: string) {
const textArea = document.createElement('textarea')
textArea.value = text
// Avoid scrolling to bottom
textArea.style.top = '0'
textArea.style.left = '0'
textArea.style.position = 'fixed'
document.body.appendChild(textArea)
textArea.focus()
textArea.select()
try {
// NOTE: the execCommand is deprecated but as of 2022_09
// widely supported and works without https
const successful = document.execCommand('copy')
if (!successful) {
throw new Error()
}
} catch (err) {
error(t('misc.copyError'))
}
document.body.removeChild(textArea)
}
return async (text: string) => { return async (text: string) => {
if (!navigator.clipboard) {
fallbackCopyTextToClipboard(text)
return
}
try { try {
await navigator.clipboard.writeText(text) await navigator.clipboard.writeText(text)
} catch { } catch(e) {
error(t('misc.copyError')) error(t('misc.copyError'))
} }
} }

View File

@ -4,6 +4,9 @@ import {isAppleDevice} from '@/helpers/isAppleDevice'
const directive: Directive = { const directive: Directive = {
mounted(el, {value}) { mounted(el, {value}) {
if(value === '') {
return
}
if (isAppleDevice() && value.includes('Control')) { if (isAppleDevice() && value.includes('Control')) {
value = value.replace('Control', 'Meta') value = value.replace('Control', 'Meta')
} }

View File

@ -1,5 +1,6 @@
import AttachmentModel, { type IAttachment } from '@/models/attachment' import AttachmentModel from '@/models/attachment'
import type {IFile} from '@/models/file' import type {IAttachment} from '@/modelTypes/IAttachment'
import type {IFile} from '@/modelTypes/IFile'
import AttachmentService from '@/services/attachment' import AttachmentService from '@/services/attachment'
import { store } from '@/store' import { store } from '@/store'

View File

@ -1,5 +1,5 @@
import {i18n} from '@/i18n' import {i18n} from '@/i18n'
import type { IList } from '@/models/list' import type {IList} from '@/modelTypes/IList'
export function getListTitle(l: IList) { export function getListTitle(l: IList) {
if (l.id === -1) { if (l.id === -1) {

View File

@ -1,5 +1,5 @@
import {i18n} from '@/i18n' import {i18n} from '@/i18n'
import type {INamespace} from '@/models/namespace' import type {INamespace} from '@/modelTypes/INamespace'
export const getNamespaceTitle = (n: INamespace) => { export const getNamespaceTitle = (n: INamespace) => {
if (n.id === -1) { if (n.id === -1) {

View File

@ -1,7 +1,7 @@
import {createNewIndexer} from '../indexes' import {createNewIndexer} from '../indexes'
import type {LabelState} from '@/store/types' import type {LabelState} from '@/store/types'
import type {ILabel} from '@/models/label' import type {ILabel} from '@/modelTypes/ILabel'
const {search} = createNewIndexer('labels', ['title', 'description']) const {search} = createNewIndexer('labels', ['title', 'description'])

View File

@ -1,4 +1,4 @@
import type {IList} from '@/models/list' import type {IList} from '@/modelTypes/IList'
const key = 'collapsedBuckets' const key = 'collapsedBuckets'

View File

@ -1,4 +1,4 @@
import type {IList} from '@/models/list' import type {IList} from '@/modelTypes/IList'
export function getSavedFilterIdFromListId(listId: IList['id']) { export function getSavedFilterIdFromListId(listId: IList['id']) {
let filterId = listId * -1 - 1 let filterId = listId * -1 - 1

View File

@ -870,7 +870,8 @@
"related": "Modify related tasks of this task", "related": "Modify related tasks of this task",
"color": "Change the color of this task", "color": "Change the color of this task",
"move": "Move this task to another list", "move": "Move this task to another list",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "List Views", "title": "List Views",

View File

@ -870,7 +870,8 @@
"related": "Upravit související úkoly tohoto úkolu", "related": "Upravit související úkoly tohoto úkolu",
"color": "Změnit barvu tohoto úkolu", "color": "Změnit barvu tohoto úkolu",
"move": "Přesunout tento úkol do jiného seznamu", "move": "Přesunout tento úkol do jiného seznamu",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "Zobrazení seznamů", "title": "Zobrazení seznamů",

View File

@ -870,7 +870,8 @@
"related": "Ändere die Abhängigen Aufgaben dieser Aufgabe", "related": "Ändere die Abhängigen Aufgaben dieser Aufgabe",
"color": "Die Farbe dieser Aufgabe ändern", "color": "Die Farbe dieser Aufgabe ändern",
"move": "Diese Aufgabe in eine andere Liste verschieben", "move": "Diese Aufgabe in eine andere Liste verschieben",
"reminder": "Erinnerungen für diese Aufgabe verwalten" "reminder": "Erinnerungen für diese Aufgabe verwalten",
"description": "Aufgabenbeschreibung bearbeiten"
}, },
"list": { "list": {
"title": "Listenansicht", "title": "Listenansicht",

View File

@ -870,7 +870,8 @@
"related": "Beziehige vo dere Uufgab bearbeite", "related": "Beziehige vo dere Uufgab bearbeite",
"color": "Die Farbe dieser Aufgabe ändern", "color": "Die Farbe dieser Aufgabe ändern",
"move": "Diese Aufgabe in eine andere Liste verschieben", "move": "Diese Aufgabe in eine andere Liste verschieben",
"reminder": "Erinnerungen für diese Aufgabe verwalten" "reminder": "Erinnerungen für diese Aufgabe verwalten",
"description": "Aufgabenbeschreibung bearbeiten"
}, },
"list": { "list": {
"title": "Listenansicht", "title": "Listenansicht",

View File

@ -242,7 +242,9 @@
"remove": "Remove a link share", "remove": "Remove a link share",
"removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this list with this link share. This cannot be undone!", "removeText": "Are you sure you want to remove this link share? It will no longer be possible to access this list with this link share. This cannot be undone!",
"createSuccess": "The link share was successfully created.", "createSuccess": "The link share was successfully created.",
"deleteSuccess": "The link share was successfully deleted" "deleteSuccess": "The link share was successfully deleted",
"view": "View",
"sharedBy": "Shared by {0}"
}, },
"userTeam": { "userTeam": {
"typeUser": "user | users", "typeUser": "user | users",
@ -264,9 +266,6 @@
}, },
"attributes": { "attributes": {
"link": "Link", "link": "Link",
"name": "Name",
"sharedBy": "Shared by",
"right": "Right",
"delete": "Delete" "delete": "Delete"
} }
}, },
@ -873,7 +872,8 @@
"related": "Modify related tasks of this task", "related": "Modify related tasks of this task",
"color": "Change the color of this task", "color": "Change the color of this task",
"move": "Move this task to another list", "move": "Move this task to another list",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "List Views", "title": "List Views",

View File

@ -870,7 +870,8 @@
"related": "Modify related tasks of this task", "related": "Modify related tasks of this task",
"color": "Change the color of this task", "color": "Change the color of this task",
"move": "Move this task to another list", "move": "Move this task to another list",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "List Views", "title": "List Views",

View File

@ -870,7 +870,8 @@
"related": "Modifier les tâches connexes de cette tâche", "related": "Modifier les tâches connexes de cette tâche",
"color": "Changer la couleur de cette tâche", "color": "Changer la couleur de cette tâche",
"move": "Déplacer cette tâche dans une autre liste", "move": "Déplacer cette tâche dans une autre liste",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "Vues en liste", "title": "Vues en liste",

View File

@ -87,7 +87,7 @@
"language": "Lingua", "language": "Lingua",
"defaultList": "Lista predefinita", "defaultList": "Lista predefinita",
"timezone": "Fuso Orario", "timezone": "Fuso Orario",
"overdueTasksRemindersTime": "Orario email del promemoria attività in ritardo" "overdueTasksRemindersTime": "Orario email attività in scadute"
}, },
"totp": { "totp": {
"title": "Autenticazione a due fattori", "title": "Autenticazione a due fattori",
@ -562,12 +562,12 @@
} }
}, },
"datemathHelp": { "datemathHelp": {
"canuse": "You can use date math to filter for relative dates.", "canuse": "Puoi usare le date calcolate per filtrare per date relative.",
"learnhow": "Scopri come funziona", "learnhow": "Scopri come funziona",
"title": "Date Math", "title": "Date Calcolate",
"intro": "Date Math allows you to specify relative dates which are resolved on the fly by Vikunja when applying the filter.", "intro": "Le Date Calcolate ti permettono di specificare date relative che vengono calcolate al volo da Vikunja quando viene applicato il filtro.",
"expression": "Each Date Math expression starts with an anchor date, which can either be {0}, or a date string ending with {1}. This anchor date can optionally be followed by one or more maths expressions.", "expression": "Ogni Data Calcolata inizia con una data base, che può essere {0}, o una data con {1} alla fine. Questa data base può essere seguita da una o più espressioni matematiche.",
"similar": "These expressions are similar to the ones provided by {0} and {1}.", "similar": "Queste espressioni sono simili a quelle fornite da {0} e {1}.",
"add1Day": "Aggiungi un giorno", "add1Day": "Aggiungi un giorno",
"minus1Day": "Sottrai un giorno", "minus1Day": "Sottrai un giorno",
"roundDay": "Arrotonda per difetto al giorno più vicino", "roundDay": "Arrotonda per difetto al giorno più vicino",
@ -781,7 +781,7 @@
"weeks": "Settimane", "weeks": "Settimane",
"months": "Mesi", "months": "Mesi",
"years": "Anni", "years": "Anni",
"invalidAmount": "Please enter more than 0." "invalidAmount": "Inserisci più di 0."
}, },
"quickAddMagic": { "quickAddMagic": {
"hint": "Puoi usare l'Aggiunta Rapida Magica", "hint": "Puoi usare l'Aggiunta Rapida Magica",
@ -791,15 +791,15 @@
"multiple": "Puoi usarlo più volte.", "multiple": "Puoi usarlo più volte.",
"label1": "Per aggiungere un'etichetta, basta aggiungere il nome dell'etichetta preceduto da {prefix}.", "label1": "Per aggiungere un'etichetta, basta aggiungere il nome dell'etichetta preceduto da {prefix}.",
"label2": "Vikunja controllerà prima se l'etichetta esiste già e nel caso la creerà.", "label2": "Vikunja controllerà prima se l'etichetta esiste già e nel caso la creerà.",
"label3": "To use spaces, simply add a \" or ' around the label name.", "label3": "Per usare gli spazi, basta aggiungere un \" o ' prima e dopo il nome dell'etichetta.",
"label4": "Per esempio: {prefix}\"Etichetta con spazi\".", "label4": "Per esempio: {prefix}\"Etichetta con spazi\".",
"priority1": "Per impostare la priorità di un'attività, aggiungi un numero 1-5, preceduto da {prefix}.", "priority1": "Per impostare la priorità di un'attività, aggiungi un numero 1-5, preceduto da {prefix}.",
"priority2": "Più alto è il numero, più alta è la priorità.", "priority2": "Più alto è il numero, più alta è la priorità.",
"assignees": "Per assegnare direttamente l'attività a un utente, aggiungere il suo nome utente preceduto da {prefix} all'attività.", "assignees": "Per assegnare direttamente l'attività a un utente, aggiungere il suo nome utente preceduto da {prefix} all'attività.",
"list1": "Per impostare una lista di appartenenza all'attività, inserisci il suo nome prefisso con {prefix}.", "list1": "Per impostare una lista di appartenenza all'attività, inserisci il suo nome prefisso con {prefix}.",
"list2": "Ciò restituirà un errore se la lista non esiste.", "list2": "Ciò restituirà un errore se la lista non esiste.",
"list3": "To use spaces, simply add a \" or ' around the list name.", "list3": "Per usare gli spazi, basta aggiungere un \" o ' prima e dopo il nome della lista.",
"list4": "For example: {prefix}\"List with spaces\".", "list4": "Per esempio: {prefix}\"Etichetta con spazi\".",
"dateAndTime": "Data e ora", "dateAndTime": "Data e ora",
"date": "Qualsiasi data verrà utilizzata come data di scadenza della nuova attività. È possibile utilizzare le date in uno qualsiasi di questi formati:", "date": "Qualsiasi data verrà utilizzata come data di scadenza della nuova attività. È possibile utilizzare le date in uno qualsiasi di questi formati:",
"dateWeekday": "qualsiasi giorno della settimana, userà la data più vicina", "dateWeekday": "qualsiasi giorno della settimana, userà la data più vicina",
@ -870,7 +870,8 @@
"related": "Modifica le attività collegate a questa", "related": "Modifica le attività collegate a questa",
"color": "Cambia il colore di questa attività", "color": "Cambia il colore di questa attività",
"move": "Sposta questa attività in un altro elenco", "move": "Sposta questa attività in un altro elenco",
"reminder": "Gestisci i promemoria di questa attività" "reminder": "Gestisci i promemoria di questa attività",
"description": "Attiva/Disattiva modifica della descrizione dell'attività"
}, },
"list": { "list": {
"title": "Viste Liste", "title": "Viste Liste",

View File

@ -870,7 +870,8 @@
"related": "Modify related tasks of this task", "related": "Modify related tasks of this task",
"color": "Change the color of this task", "color": "Change the color of this task",
"move": "Move this task to another list", "move": "Move this task to another list",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "List Views", "title": "List Views",

View File

@ -870,7 +870,8 @@
"related": "Zmodyfikuj zadania powiązane z tym zadaniem", "related": "Zmodyfikuj zadania powiązane z tym zadaniem",
"color": "Zmień kolor tego zadania", "color": "Zmień kolor tego zadania",
"move": "Przenieś to zadanie do innej listy", "move": "Przenieś to zadanie do innej listy",
"reminder": "Zarządzaj przypomnieniami o tym zadaniu" "reminder": "Zarządzaj przypomnieniami o tym zadaniu",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "Widoki listy", "title": "Widoki listy",

View File

@ -870,7 +870,8 @@
"related": "Modify related tasks of this task", "related": "Modify related tasks of this task",
"color": "Change the color of this task", "color": "Change the color of this task",
"move": "Move this task to another list", "move": "Move this task to another list",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "List Views", "title": "List Views",

View File

@ -870,7 +870,8 @@
"related": "Modificar as tarefas relacionadas desta tarefa", "related": "Modificar as tarefas relacionadas desta tarefa",
"color": "Alterar a cor desta tarefa", "color": "Alterar a cor desta tarefa",
"move": "Mover esta tarefa para outra lista", "move": "Mover esta tarefa para outra lista",
"reminder": "Gerir lembretes desta tarefa" "reminder": "Gerir lembretes desta tarefa",
"description": "Alternar edição da descrição da tarefa"
}, },
"list": { "list": {
"title": "Visualização em Lista", "title": "Visualização em Lista",

View File

@ -870,7 +870,8 @@
"related": "Modify related tasks of this task", "related": "Modify related tasks of this task",
"color": "Change the color of this task", "color": "Change the color of this task",
"move": "Move this task to another list", "move": "Move this task to another list",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "List Views", "title": "List Views",

View File

@ -870,7 +870,8 @@
"related": "Изменить связанные задачи", "related": "Изменить связанные задачи",
"color": "Change the color of this task", "color": "Change the color of this task",
"move": "Move this task to another list", "move": "Move this task to another list",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "List Views", "title": "List Views",

View File

@ -870,7 +870,8 @@
"related": "Modify related tasks of this task", "related": "Modify related tasks of this task",
"color": "Change the color of this task", "color": "Change the color of this task",
"move": "Move this task to another list", "move": "Move this task to another list",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "List Views", "title": "List Views",

View File

@ -870,7 +870,8 @@
"related": "Modify related tasks of this task", "related": "Modify related tasks of this task",
"color": "Change the color of this task", "color": "Change the color of this task",
"move": "Move this task to another list", "move": "Move this task to another list",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "List Views", "title": "List Views",

View File

@ -870,7 +870,8 @@
"related": "Modify related tasks of this task", "related": "Modify related tasks of this task",
"color": "Change the color of this task", "color": "Change the color of this task",
"move": "Move this task to another list", "move": "Move this task to another list",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "List Views", "title": "List Views",

View File

@ -870,7 +870,8 @@
"related": "Sửa đổi các công việc liên kết", "related": "Sửa đổi các công việc liên kết",
"color": "Thay đổi màu công việc này", "color": "Thay đổi màu công việc này",
"move": "Dời công việc này sang danh sách khác", "move": "Dời công việc này sang danh sách khác",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "Xem danh sách", "title": "Xem danh sách",

View File

@ -870,7 +870,8 @@
"related": "Modify related tasks of this task", "related": "Modify related tasks of this task",
"color": "Change the color of this task", "color": "Change the color of this task",
"move": "Move this task to another list", "move": "Move this task to another list",
"reminder": "Manage reminders of this task" "reminder": "Manage reminders of this task",
"description": "Toggle editing of the task description"
}, },
"list": { "list": {
"title": "List Views", "title": "List Views",

View File

@ -5,7 +5,7 @@ import type { IUser } from '@/modelTypes/IUser'
import type { IFile } from '@/modelTypes/IFile' import type { IFile } from '@/modelTypes/IFile'
import type { IAttachment } from '@/modelTypes/IAttachment' import type { IAttachment } from '@/modelTypes/IAttachment'
export default class AttachmentModel extends AbstractModel implements IAttachment { export default class AttachmentModel extends AbstractModel<IAttachment> implements IAttachment {
id = 0 id = 0
taskId = 0 taskId = 0
createdBy: IUser = UserModel createdBy: IUser = UserModel

View File

@ -1,7 +1,7 @@
import AbstractModel from './abstractModel' import AbstractModel from './abstractModel'
import type { IAvatar } from '@/modelTypes/IAvatar' import type { IAvatar } from '@/modelTypes/IAvatar'
export default class AvatarModel extends AbstractModel implements IAvatar { export default class AvatarModel extends AbstractModel<IAvatar> implements IAvatar {
avatarProvider: IAvatar['avatarProvider'] = 'default' avatarProvider: IAvatar['avatarProvider'] = 'default'
constructor(data: Partial<IAvatar>) { constructor(data: Partial<IAvatar>) {

View File

@ -1,7 +1,7 @@
import AbstractModel from './abstractModel' import AbstractModel from './abstractModel'
import type {IBackgroundImage} from '@/modelTypes/IBackgroundImage' import type {IBackgroundImage} from '@/modelTypes/IBackgroundImage'
export default class BackgroundImageModel extends AbstractModel implements IBackgroundImage { export default class BackgroundImageModel extends AbstractModel<IBackgroundImage> implements IBackgroundImage {
id = 0 id = 0
url = '' url = ''
thumb = '' thumb = ''

View File

@ -6,14 +6,14 @@ import type {IBucket} from '@/modelTypes/IBucket'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
export default class BucketModel extends AbstractModel implements IBucket { export default class BucketModel extends AbstractModel<IBucket> implements IBucket {
id = 0 id = 0
title = '' title = ''
listId = '' listId = ''
limit = 0 limit = 0
tasks: ITask[] = [] tasks: ITask[] = []
isDoneBucket: false isDoneBucket = false
position: 0 position = 0
createdBy: IUser = null createdBy: IUser = null
created: Date = null created: Date = null

View File

@ -2,7 +2,7 @@ import AbstractModel from './abstractModel'
import type {ICaldavToken} from '@/modelTypes/ICaldavToken' import type {ICaldavToken} from '@/modelTypes/ICaldavToken'
export default class CaldavTokenModel extends AbstractModel implements ICaldavToken { export default class CaldavTokenModel extends AbstractModel<ICaldavToken> implements ICaldavToken {
id: number id: number
created: Date created: Date

View File

@ -2,7 +2,7 @@ import AbstractModel from './abstractModel'
import type {IEmailUpdate} from '@/modelTypes/IEmailUpdate' import type {IEmailUpdate} from '@/modelTypes/IEmailUpdate'
export default class EmailUpdateModel extends AbstractModel implements IEmailUpdate { export default class EmailUpdateModel extends AbstractModel<IEmailUpdate> implements IEmailUpdate {
newEmail = '' newEmail = ''
password = '' password = ''

View File

@ -1,7 +1,7 @@
import AbstractModel from './abstractModel' import AbstractModel from './abstractModel'
import type {IFile} from '@/modelTypes/IFile' import type {IFile} from '@/modelTypes/IFile'
export default class FileModel extends AbstractModel implements IFile { export default class FileModel extends AbstractModel<IFile> implements IFile {
id = 0 id = 0
mime = '' mime = ''
name = '' name = ''

View File

@ -8,7 +8,7 @@ import {colorIsDark} from '@/helpers/color/colorIsDark'
const DEFAULT_LABEL_BACKGROUND_COLOR = 'e8e8e8' const DEFAULT_LABEL_BACKGROUND_COLOR = 'e8e8e8'
export default class LabelModel extends AbstractModel implements ILabel { export default class LabelModel extends AbstractModel<ILabel> implements ILabel {
id = 0 id = 0
title = '' title = ''
// FIXME: this should be empty and be definied in the client. // FIXME: this should be empty and be definied in the client.

View File

@ -2,7 +2,7 @@ import AbstractModel from './abstractModel'
import type { ILabelTask } from '@/modelTypes/ILabelTask' import type { ILabelTask } from '@/modelTypes/ILabelTask'
export default class LabelTask extends AbstractModel implements ILabelTask { export default class LabelTask extends AbstractModel<ILabelTask> implements ILabelTask {
id = 0 id = 0
taskId = 0 taskId = 0
labelId = 0 labelId = 0

View File

@ -5,7 +5,7 @@ import {RIGHTS, type Right} from '@/constants/rights'
import type {ILinkShare} from '@/modelTypes/ILinkShare' import type {ILinkShare} from '@/modelTypes/ILinkShare'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
export default class LinkShareModel extends AbstractModel implements ILinkShare { export default class LinkShareModel extends AbstractModel<ILinkShare> implements ILinkShare {
id = 0 id = 0
hash = '' hash = ''
right: Right = RIGHTS.READ right: Right = RIGHTS.READ

View File

@ -11,7 +11,7 @@ import type {ISubscription} from '@/modelTypes/ISubscription'
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter' import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
export default class ListModel extends AbstractModel implements IList { export default class ListModel extends AbstractModel<IList> implements IList {
id = 0 id = 0
title = '' title = ''
description = '' description = ''
@ -30,7 +30,7 @@ export default class ListModel extends AbstractModel implements IList {
created: Date = null created: Date = null
updated: Date = null updated: Date = null
constructor(data: Partial<IList>) { constructor(data: Partial<IList> = {}) {
super() super()
this.assignData(data) this.assignData(data)

View File

@ -5,7 +5,7 @@ import type {IListDuplicate} from '@/modelTypes/IListDuplicate'
import type {INamespace} from '@/modelTypes/INamespace' import type {INamespace} from '@/modelTypes/INamespace'
import type {IList} from '@/modelTypes/IList' import type {IList} from '@/modelTypes/IList'
export default class ListDuplicateModel extends AbstractModel implements IListDuplicate { export default class ListDuplicateModel extends AbstractModel<IListDuplicate> implements IListDuplicate {
listId = 0 listId = 0
namespaceId: INamespace['id'] = 0 namespaceId: INamespace['id'] = 0
list: IList = ListModel list: IList = ListModel

View File

@ -8,7 +8,7 @@ import type {IUser} from '@/modelTypes/IUser'
import type {IList} from '@/modelTypes/IList' import type {IList} from '@/modelTypes/IList'
import type {ISubscription} from '@/modelTypes/ISubscription' import type {ISubscription} from '@/modelTypes/ISubscription'
export default class NamespaceModel extends AbstractModel implements INamespace { export default class NamespaceModel extends AbstractModel<INamespace> implements INamespace {
id = 0 id = 0
title = '' title = ''
description = '' description = ''
@ -21,7 +21,7 @@ export default class NamespaceModel extends AbstractModel implements INamespace
created: Date = null created: Date = null
updated: Date = null updated: Date = null
constructor(data: Partial<INamespace>) { constructor(data: Partial<INamespace> = {}) {
super() super()
this.assignData(data) this.assignData(data)

View File

@ -8,7 +8,7 @@ import TeamModel from '@/models/team'
import {NOTIFICATION_NAMES, type INotification} from '@/modelTypes/INotification' import {NOTIFICATION_NAMES, type INotification} from '@/modelTypes/INotification'
export default class NotificationModel extends AbstractModel implements INotification { export default class NotificationModel extends AbstractModel<INotification> implements INotification {
id = 0 id = 0
name = '' name = ''
notification: INotification['notification'] = null notification: INotification['notification'] = null

View File

@ -2,7 +2,7 @@ import AbstractModel from './abstractModel'
import type {IPasswordReset} from '@/modelTypes/IPasswordReset' import type {IPasswordReset} from '@/modelTypes/IPasswordReset'
export default class PasswordResetModel extends AbstractModel implements IPasswordReset { export default class PasswordResetModel extends AbstractModel<IPasswordReset> implements IPasswordReset {
token = '' token = ''
newPassword = '' newPassword = ''
email = '' email = ''

View File

@ -2,7 +2,7 @@ import AbstractModel from './abstractModel'
import type {IPasswordUpdate} from '@/modelTypes/IPasswordUpdate' import type {IPasswordUpdate} from '@/modelTypes/IPasswordUpdate'
export default class PasswordUpdateModel extends AbstractModel implements IPasswordUpdate { export default class PasswordUpdateModel extends AbstractModel<IPasswordUpdate> implements IPasswordUpdate {
newPassword = '' newPassword = ''
oldPassword = '' oldPassword = ''

View File

@ -4,7 +4,7 @@ import UserModel from '@/models/user'
import type {ISavedFilter} from '@/modelTypes/ISavedFilter' import type {ISavedFilter} from '@/modelTypes/ISavedFilter'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
export default class SavedFilterModel extends AbstractModel implements ISavedFilter { export default class SavedFilterModel extends AbstractModel<ISavedFilter> implements ISavedFilter {
id = 0 id = 0
title = '' title = ''
description = '' description = ''

View File

@ -4,7 +4,7 @@ import UserModel from '@/models/user'
import type {ISubscription} from '@/modelTypes/ISubscription' import type {ISubscription} from '@/modelTypes/ISubscription'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
export default class SubscriptionModel extends AbstractModel implements ISubscription { export default class SubscriptionModel extends AbstractModel<ISubscription> implements ISubscription {
id = 0 id = 0
entity = '' entity = ''
entityId = 0 entityId = 0

View File

@ -21,9 +21,13 @@ import UserModel from './user'
import AttachmentModel from './attachment' import AttachmentModel from './attachment'
import SubscriptionModel from './subscription' import SubscriptionModel from './subscription'
const SUPPORTS_TRIGGERED_NOTIFICATION = 'Notification' in window && 'showTrigger' in Notification.prototype
export const TASK_DEFAULT_COLOR = '#1973ff' export const TASK_DEFAULT_COLOR = '#1973ff'
const SUPPORTS_TRIGGERED_NOTIFICATION = 'Notification' in window && 'showTrigger' in Notification.prototype
if (!SUPPORTS_TRIGGERED_NOTIFICATION) {
console.debug('This browser does not support triggered notifications')
}
export function getHexColor(hexColor: string) { export function getHexColor(hexColor: string) {
if (hexColor === '' || hexColor === '#') { if (hexColor === '' || hexColor === '#') {
return TASK_DEFAULT_COLOR return TASK_DEFAULT_COLOR
@ -32,7 +36,7 @@ export function getHexColor(hexColor: string) {
return hexColor return hexColor
} }
export default class TaskModel extends AbstractModel implements ITask { export default class TaskModel extends AbstractModel<ITask> implements ITask {
id = 0 id = 0
title = '' title = ''
description = '' description = ''
@ -176,7 +180,6 @@ export default class TaskModel extends AbstractModel implements ITask {
async cancelScheduledNotifications() { async cancelScheduledNotifications() {
if (!SUPPORTS_TRIGGERED_NOTIFICATION) { if (!SUPPORTS_TRIGGERED_NOTIFICATION) {
console.debug('This browser does not support triggered notifications')
return return
} }
@ -211,7 +214,6 @@ export default class TaskModel extends AbstractModel implements ITask {
} }
if (!SUPPORTS_TRIGGERED_NOTIFICATION) { if (!SUPPORTS_TRIGGERED_NOTIFICATION) {
console.debug('This browser does not support triggered notifications')
return return
} }

View File

@ -4,7 +4,7 @@ import type {ITaskAssignee} from '@/modelTypes/ITaskAssignee'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
export default class TaskAssigneeModel extends AbstractModel implements ITaskAssignee { export default class TaskAssigneeModel extends AbstractModel<ITaskAssignee> implements ITaskAssignee {
created: Date = null created: Date = null
userId: IUser['id'] = 0 userId: IUser['id'] = 0
taskId: ITask['id'] = 0 taskId: ITask['id'] = 0

View File

@ -5,7 +5,7 @@ import type {ITaskComment} from '@/modelTypes/ITaskComment'
import type {ITask} from '@/modelTypes/ITask' import type {ITask} from '@/modelTypes/ITask'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
export default class TaskCommentModel extends AbstractModel implements ITaskComment { export default class TaskCommentModel extends AbstractModel<ITaskComment> implements ITaskComment {
id = 0 id = 0
taskId: ITask['id'] = 0 taskId: ITask['id'] = 0
comment = '' comment = ''

View File

@ -6,7 +6,7 @@ import type {ITask} from '@/modelTypes/ITask'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
import type {IRelationKind} from '@/types/IRelationKind' import type {IRelationKind} from '@/types/IRelationKind'
export default class TaskRelationModel extends AbstractModel implements ITaskRelation { export default class TaskRelationModel extends AbstractModel<ITaskRelation> implements ITaskRelation {
id = 0 id = 0
otherTaskId: ITask['id'] = 0 otherTaskId: ITask['id'] = 0
taskId: ITask['id'] = 0 taskId: ITask['id'] = 0

View File

@ -7,7 +7,7 @@ import type {ITeam} from '@/modelTypes/ITeam'
import type {ITeamMember} from '@/modelTypes/ITeamMember' import type {ITeamMember} from '@/modelTypes/ITeamMember'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
export default class TeamModel extends AbstractModel implements ITeam { export default class TeamModel extends AbstractModel<ITeam> implements ITeam {
id = 0 id = 0
name = '' name = ''
description = '' description = ''

View File

@ -8,7 +8,7 @@ import type {ITeam} from '@/modelTypes/ITeam'
* This class is a base class for common team sharing model. * This class is a base class for common team sharing model.
* It is extended in a way so it can be used for namespaces as well for lists. * It is extended in a way so it can be used for namespaces as well for lists.
*/ */
export default class TeamShareBaseModel extends AbstractModel implements ITeamShareBase { export default class TeamShareBaseModel extends AbstractModel<ITeamShareBase> implements ITeamShareBase {
teamId: ITeam['id'] = 0 teamId: ITeam['id'] = 0
right: Right = RIGHTS.READ right: Right = RIGHTS.READ

View File

@ -2,7 +2,7 @@ import AbstractModel from './abstractModel'
import type {ITotp} from '@/modelTypes/ITotp' import type {ITotp} from '@/modelTypes/ITotp'
export default class TotpModel extends AbstractModel implements ITotp { export default class TotpModel extends AbstractModel<ITotp> implements ITotp {
secret = '' secret = ''
enabled = false enabled = false
url = '' url = ''

View File

@ -4,7 +4,7 @@ import UserSettingsModel from '@/models/userSettings'
import type { IUser } from '@/modelTypes/IUser' import type { IUser } from '@/modelTypes/IUser'
import type { IUserSettings } from '@/modelTypes/IUserSettings' import type { IUserSettings } from '@/modelTypes/IUserSettings'
export default class UserModel extends AbstractModel implements IUser { export default class UserModel extends AbstractModel<IUser> implements IUser {
id = 0 id = 0
email = '' email = ''
username = '' username = ''

View File

@ -4,7 +4,7 @@ import AbstractModel from './abstractModel'
import type {IUserSettings} from '@/modelTypes/IUserSettings' import type {IUserSettings} from '@/modelTypes/IUserSettings'
import type {IList} from '@/modelTypes/IList' import type {IList} from '@/modelTypes/IList'
export default class UserSettingsModel extends AbstractModel implements IUserSettings { export default class UserSettingsModel extends AbstractModel<IUserSettings> implements IUserSettings {
name = '' name = ''
emailRemindersEnabled = true emailRemindersEnabled = true
discoverableByName = false discoverableByName = false

View File

@ -4,7 +4,7 @@ import {RIGHTS, type Right} from '@/constants/rights'
import type {IUserShareBase} from '@/modelTypes/IUserShareBase' import type {IUserShareBase} from '@/modelTypes/IUserShareBase'
import type {IUser} from '@/modelTypes/IUser' import type {IUser} from '@/modelTypes/IUser'
export default class UserShareBaseModel extends AbstractModel implements IUserShareBase { export default class UserShareBaseModel extends AbstractModel<IUserShareBase> implements IUserShareBase {
userId: IUser['id'] = '' userId: IUser['id'] = ''
right: Right = RIGHTS.READ right: Right = RIGHTS.READ

View File

@ -1,6 +1,6 @@
import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest' import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest'
import {parseTaskText} from './parseTaskText' import {parseTaskText, PrefixMode} from './parseTaskText'
import {getDateFromText, getDateFromTextIn} from '../helpers/time/parseDate' import {getDateFromText, getDateFromTextIn} from '../helpers/time/parseDate'
import {calculateDayInterval} from '../helpers/time/calculateDayInterval' import {calculateDayInterval} from '../helpers/time/calculateDayInterval'
import {PRIORITIES} from '@/constants/priorities' import {PRIORITIES} from '@/constants/priorities'
@ -20,13 +20,13 @@ describe('Parse Task Text', () => {
it('should not parse text when disabled', () => { it('should not parse text when disabled', () => {
const text = 'Lorem Ipsum today *label +list !2 @user' const text = 'Lorem Ipsum today *label +list !2 @user'
const result = parseTaskText(text, 'disabled') const result = parseTaskText(text, PrefixMode.Disabled)
expect(result.text).toBe(text) expect(result.text).toBe(text)
}) })
it('should parse text in todoist mode when configured', () => { it('should parse text in todoist mode when configured', () => {
const result = parseTaskText('Lorem Ipsum today @label #list !2 +user', 'todoist') const result = parseTaskText('Lorem Ipsum today @label #list !2 +user', PrefixMode.Todoist)
expect(result.text).toBe('Lorem Ipsum') expect(result.text).toBe('Lorem Ipsum')
const now = new Date() const now = new Date()
@ -84,7 +84,7 @@ describe('Parse Task Text', () => {
'at 3am': '3:0', 'at 3am': '3:0',
'at 3:12 am': '3:12', 'at 3:12 am': '3:12',
'at 3:12 pm': '15:12', 'at 3:12 pm': '15:12',
} } as const
for (const c in cases) { for (const c in cases) {
it(`should recognize today with a time ${c}`, () => { it(`should recognize today with a time ${c}`, () => {
@ -95,7 +95,7 @@ describe('Parse Task Text', () => {
expect(result.date.getFullYear()).toBe(now.getFullYear()) expect(result.date.getFullYear()).toBe(now.getFullYear())
expect(result.date.getMonth()).toBe(now.getMonth()) expect(result.date.getMonth()).toBe(now.getMonth())
expect(result.date.getDate()).toBe(now.getDate()) expect(result.date.getDate()).toBe(now.getDate())
expect(`${result.date.getHours()}:${result.date.getMinutes()}`).toBe(cases[c]) expect(`${result.date.getHours()}:${result.date.getMinutes()}`).toBe(cases[c as keyof typeof cases])
expect(result.date.getSeconds()).toBe(0) expect(result.date.getSeconds()).toBe(0)
}) })
} }

View File

@ -4,7 +4,7 @@ import type {Method} from 'axios'
import {objectToSnakeCase} from '@/helpers/case' import {objectToSnakeCase} from '@/helpers/case'
import AbstractModel, { type IAbstract } from '@/models/abstractModel' import AbstractModel, { type IAbstract } from '@/models/abstractModel'
import type { Right } from '@/constants/rights' import type { Right } from '@/constants/rights'
import type { IFile } from '@/models/file' import type {IFile} from '@/modelTypes/IFile'
interface Paths { interface Paths {
create : string create : string

View File

@ -1,8 +1,12 @@
import AbstractService from './abstractService'
import AttachmentModel, { type IAttachment } from '../models/attachment'
import {formatISO} from 'date-fns' import {formatISO} from 'date-fns'
import AbstractService from './abstractService'
import AttachmentModel from '../models/attachment'
import type { IAttachment } from '@/modelTypes/IAttachment'
import type { IFile } from '@/modelTypes/IFile'
import {downloadBlob} from '@/helpers/downloadBlob' import {downloadBlob} from '@/helpers/downloadBlob'
import type { IFile } from '@/models/file'
export default class AttachmentService extends AbstractService<AttachmentModel> { export default class AttachmentService extends AbstractService<AttachmentModel> {
constructor() { constructor() {
@ -22,7 +26,7 @@ export default class AttachmentService extends AbstractService<AttachmentModel>
return false return false
} }
modelFactory(data) { modelFactory(data: Partial<IAttachment>) {
return new AttachmentModel(data) return new AttachmentModel(data)
} }

View File

@ -1,5 +1,6 @@
import AbstractService from './abstractService' import AbstractService from './abstractService'
import AvatarModel, { type IAvatar } from '../models/avatar' import AvatarModel from '@/models/avatar'
import type { IAvatar } from '@/modelTypes/IAvatar'
export default class AvatarService extends AbstractService<IAvatar> { export default class AvatarService extends AbstractService<IAvatar> {
constructor() { constructor() {
@ -10,7 +11,7 @@ export default class AvatarService extends AbstractService<IAvatar> {
}) })
} }
modelFactory(data) { modelFactory(data: Partial<IAvatar>) {
return new AvatarModel(data) return new AvatarModel(data)
} }

View File

@ -1,6 +1,7 @@
import AbstractService from './abstractService' import AbstractService from './abstractService'
import BackgroundImageModel, { type IBackgroundImage } from '../models/backgroundImage' import BackgroundImageModel from '../models/backgroundImage'
import ListModel from '@/models/list' import ListModel from '@/models/list'
import type { IBackgroundImage } from '@/modelTypes/IBackgroundImage'
export default class BackgroundUnsplashService extends AbstractService<IBackgroundImage> { export default class BackgroundUnsplashService extends AbstractService<IBackgroundImage> {
constructor() { constructor() {
@ -10,7 +11,7 @@ export default class BackgroundUnsplashService extends AbstractService<IBackgrou
}) })
} }
modelFactory(data) { modelFactory(data: Partial<IBackgroundImage>) {
return new BackgroundImageModel(data) return new BackgroundImageModel(data)
} }

View File

@ -1,6 +1,8 @@
import AbstractService from './abstractService' import AbstractService from './abstractService'
import ListModel, { type IList } from '../models/list' import ListModel from '@/models/list'
import type { IFile } from '@/models/file'
import type { IList } from '@/modelTypes/IList'
import type { IFile } from '@/modelTypes/IFile'
export default class BackgroundUploadService extends AbstractService { export default class BackgroundUploadService extends AbstractService {
constructor() { constructor() {
@ -13,14 +15,12 @@ export default class BackgroundUploadService extends AbstractService {
return false return false
} }
modelCreateFactory(data) { modelCreateFactory(data: Partial<IList>) {
return new ListModel(data) return new ListModel(data)
} }
/** /**
* Uploads a file to the server * Uploads a file to the server
* @param file
* @returns {Promise<any|never>}
*/ */
create(listId: IList['id'], file: IFile) { create(listId: IList['id'], file: IFile) {
return this.uploadFile( return this.uploadFile(

View File

@ -1,6 +1,7 @@
import AbstractService from './abstractService' import AbstractService from './abstractService'
import BucketModel, { type IBucket } from '../models/bucket' import BucketModel from '../models/bucket'
import TaskService from '@/services/task' import TaskService from '@/services/task'
import type { IBucket } from '@/modelTypes/IBucket'
export default class BucketService extends AbstractService<IBucket> { export default class BucketService extends AbstractService<IBucket> {
constructor() { constructor() {
@ -12,7 +13,7 @@ export default class BucketService extends AbstractService<IBucket> {
}) })
} }
modelFactory(data) { modelFactory(data: Partial<IBucket>) {
return new BucketModel(data) return new BucketModel(data)
} }

View File

@ -1,5 +1,6 @@
import {formatISO} from 'date-fns' import {formatISO} from 'date-fns'
import CaldavTokenModel, {type ICaldavToken} from '../models/caldavToken' import CaldavTokenModel from '@/models/caldavToken'
import type {ICaldavToken} from '@/modelTypes/ICaldavToken'
import AbstractService from './abstractService' import AbstractService from './abstractService'
export default class CaldavTokenService extends AbstractService<ICaldavToken> { export default class CaldavTokenService extends AbstractService<ICaldavToken> {

View File

@ -1,5 +1,6 @@
import AbstractService from './abstractService' import AbstractService from './abstractService'
import LabelModel, { type ILabel } from '@/models/label' import LabelModel from '@/models/label'
import type {ILabel} from '@/modelTypes/ILabel'
import {formatISO} from 'date-fns' import {formatISO} from 'date-fns'
import {colorFromHex} from '@/helpers/color/colorFromHex' import {colorFromHex} from '@/helpers/color/colorFromHex'

View File

@ -1,5 +1,6 @@
import AbstractService from './abstractService' import AbstractService from './abstractService'
import LabelTask, {type ILabelTask} from '@/models/labelTask' import LabelTask from '@/models/labelTask'
import type {ILabelTask} from '@/modelTypes/ILabelTask'
export default class LabelTaskService extends AbstractService<ILabelTask> { export default class LabelTaskService extends AbstractService<ILabelTask> {
constructor() { constructor() {

View File

@ -1,5 +1,6 @@
import AbstractService from './abstractService' import AbstractService from './abstractService'
import LinkShareModel, { type ILinkShare } from '@/models/linkShare' import LinkShareModel from '@/models/linkShare'
import type {ILinkShare} from '@/modelTypes/ILinkShare'
import {formatISO} from 'date-fns' import {formatISO} from 'date-fns'
export default class LinkShareService extends AbstractService<ILinkShare> { export default class LinkShareService extends AbstractService<ILinkShare> {

View File

@ -1,5 +1,6 @@
import AbstractService from './abstractService' import AbstractService from './abstractService'
import ListModel, { type IList } from '@/models/list' import ListModel from '@/models/list'
import type {IList} from '@/modelTypes/IList'
import TaskService from './task' import TaskService from './task'
import {formatISO} from 'date-fns' import {formatISO} from 'date-fns'
import {colorFromHex} from '@/helpers/color/colorFromHex' import {colorFromHex} from '@/helpers/color/colorFromHex'

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