feature/convert-abstract-service-to-ts #1798
|
@ -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 NamespaceModel from '@/models/namespace'
|
import type { IList } from '@/models/list'
|
||||||
import type ListModel from '@/models/list'
|
import type { INamespace } from '@/models/namespace'
|
||||||
|
|
||||||
const drag = ref(false)
|
const drag = ref(false)
|
||||||
const dragOptions = {
|
const dragOptions = {
|
||||||
|
@ -172,7 +172,7 @@ const loading = computed(() => store.state.loading && store.state.loadingModule
|
||||||
|
|
||||||
|
|
||||||
const namespaces = computed(() => {
|
const namespaces = computed(() => {
|
||||||
return (store.state.namespaces.namespaces as NamespaceModel[]).filter(n => !n.isArchived)
|
return (store.state.namespaces.namespaces as INamespace[]).filter(n => !n.isArchived)
|
||||||
})
|
})
|
||||||
const activeLists = computed(() => {
|
const activeLists = computed(() => {
|
||||||
return namespaces.value.map(({lists}) => {
|
return namespaces.value.map(({lists}) => {
|
||||||
|
@ -195,7 +195,7 @@ useEventListener('resize', resize)
|
||||||
onMounted(() => resize())
|
onMounted(() => resize())
|
||||||
|
|
||||||
|
|
||||||
function toggleFavoriteList(list: ListModel) {
|
function toggleFavoriteList(list: IList) {
|
||||||
// The favorites pseudo list is always favorite
|
// The favorites pseudo list is always favorite
|
||||||
// Archived lists cannot be marked favorite
|
// Archived lists cannot be marked favorite
|
||||||
if (list.id === -1 || list.isArchived) {
|
if (list.id === -1 || list.isArchived) {
|
||||||
|
@ -209,14 +209,14 @@ function resize() {
|
||||||
store.commit(MENU_ACTIVE, window.innerWidth >= 770)
|
store.commit(MENU_ACTIVE, window.innerWidth >= 770)
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleLists(namespaceId: NamespaceModel['id']) {
|
function toggleLists(namespaceId: INamespace['id']) {
|
||||||
listsVisible.value[namespaceId] = !listsVisible.value[namespaceId]
|
listsVisible.value[namespaceId] = !listsVisible.value[namespaceId]
|
||||||
}
|
}
|
||||||
|
|
||||||
const listsVisible = ref<{ [id: NamespaceModel['id']]: boolean }>({})
|
const listsVisible = ref<{ [id: INamespace['id']]: boolean }>({})
|
||||||
// FIXME: async action will be unfinished when component mounts
|
// FIXME: async action will be unfinished when component mounts
|
||||||
onBeforeMount(async () => {
|
onBeforeMount(async () => {
|
||||||
const namespaces = await store.dispatch('namespaces/loadNamespaces') as NamespaceModel[]
|
const namespaces = await store.dispatch('namespaces/loadNamespaces') as INamespace[]
|
||||||
namespaces.forEach(n => {
|
namespaces.forEach(n => {
|
||||||
if (typeof listsVisible.value[n.id] === 'undefined') {
|
if (typeof listsVisible.value[n.id] === 'undefined') {
|
||||||
listsVisible.value[n.id] = true
|
listsVisible.value[n.id] = true
|
||||||
|
@ -224,7 +224,7 @@ onBeforeMount(async () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
function updateActiveLists(namespace: NamespaceModel, activeLists: ListModel[]) {
|
function updateActiveLists(namespace: INamespace, activeLists: IList[]) {
|
||||||
// This is a bit hacky: since we do have to filter out the archived items from the list
|
// This is a bit hacky: since we do have to filter out the archived items from the list
|
||||||
// for vue draggable updating it is not as simple as replacing it.
|
// for vue draggable updating it is not as simple as replacing it.
|
||||||
// To work around this, we merge the active lists with the archived ones. Doing so breaks the order
|
// To work around this, we merge the active lists with the archived ones. Doing so breaks the order
|
||||||
|
@ -241,7 +241,7 @@ function updateActiveLists(namespace: NamespaceModel, activeLists: ListModel[])
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const listUpdating = ref<{ [id: NamespaceModel['id']]: boolean }>({})
|
const listUpdating = ref<{ [id: INamespace['id']]: boolean }>({})
|
||||||
|
|
||||||
async function saveListPosition(e: SortableEvent) {
|
async function saveListPosition(e: SortableEvent) {
|
||||||
if (!e.newIndex && e.newIndex !== 0) return
|
if (!e.newIndex && e.newIndex !== 0) return
|
||||||
|
|
|
@ -76,24 +76,24 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, computed, watchEffect} from 'vue'
|
import {ref, computed, watchEffect, type PropType} from 'vue'
|
||||||
import {useStore} from 'vuex'
|
import {useStore} from 'vuex'
|
||||||
|
|
||||||
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
|
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 ListModel from '@/models/list'
|
import type {IList} from '@/models/list'
|
||||||
import type SubscriptionModel from '@/models/subscription'
|
import type { ISubscription } from '@/models/subscription'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
list: {
|
list: {
|
||||||
type: ListModel,
|
type: Object as PropType<IList>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const subscription = ref<SubscriptionModel | null>(null)
|
const subscription = ref<ISubscription | null>(null)
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
subscription.value = props.list.subscription ?? null
|
subscription.value = props.list.subscription ?? null
|
||||||
})
|
})
|
||||||
|
|
|
@ -43,9 +43,9 @@ import ListService from '@/services/list'
|
||||||
import {getBlobFromBlurHash} from '@/helpers/getBlobFromBlurHash'
|
import {getBlobFromBlurHash} from '@/helpers/getBlobFromBlurHash'
|
||||||
|
|
||||||
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||||
import type ListModel from '@/models/list'
|
|
||||||
|
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
import type { IList } from '@/models/list'
|
||||||
|
|
||||||
const background = ref<string | null>(null)
|
const background = ref<string | null>(null)
|
||||||
const backgroundLoading = ref(false)
|
const backgroundLoading = ref(false)
|
||||||
|
@ -53,7 +53,7 @@ const blurHashUrl = ref('')
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
list: {
|
list: {
|
||||||
type: Object as PropType<ListModel>,
|
type: Object as PropType<IList>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
showArchived: {
|
showArchived: {
|
||||||
|
@ -86,7 +86,7 @@ async function loadBackground() {
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
function toggleFavoriteList(list: ListModel) {
|
function toggleFavoriteList(list: IList) {
|
||||||
// The favorites pseudo list is always favorite
|
// The favorites pseudo list is always favorite
|
||||||
// Archived lists cannot be marked favorite
|
// Archived lists cannot be marked favorite
|
||||||
if (list.id === -1 || list.isArchived) {
|
if (list.id === -1 || list.isArchived) {
|
||||||
|
|
|
@ -39,7 +39,7 @@ 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 from '@/models/subscription'
|
import SubscriptionModel, { type ISubscription } from '@/models/subscription'
|
||||||
|
|
||||||
import {success} from '@/message'
|
import {success} from '@/message'
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ const props = defineProps({
|
||||||
default: true,
|
default: true,
|
||||||
},
|
},
|
||||||
subscription: {
|
subscription: {
|
||||||
type: Object as PropType<SubscriptionModel>,
|
type: Object as PropType<ISubscription>,
|
||||||
default: null,
|
default: null,
|
||||||
},
|
},
|
||||||
type: {
|
type: {
|
||||||
|
|
|
@ -54,15 +54,16 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, onMounted} from 'vue'
|
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'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
namespace: {
|
namespace: {
|
||||||
type: Object, // NamespaceModel
|
type: Object as PropType<INamespace>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -52,7 +52,7 @@ import {computed, onMounted, onUnmounted, ref} from 'vue'
|
||||||
import NotificationService from '@/services/notification'
|
import NotificationService from '@/services/notification'
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
import User from '@/components/misc/user.vue'
|
import User from '@/components/misc/user.vue'
|
||||||
import NotificationModel, { NOTIFICATION_NAMES as names} from '@/models/notification'
|
import { NOTIFICATION_NAMES as names, type INotification} from '@/models/notification'
|
||||||
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
import {closeWhenClickedOutside} from '@/helpers/closeWhenClickedOutside'
|
||||||
import {useStore} from 'vuex'
|
import {useStore} from 'vuex'
|
||||||
import {useRouter} from 'vue-router'
|
import {useRouter} from 'vue-router'
|
||||||
|
@ -63,7 +63,7 @@ const LOAD_NOTIFICATIONS_INTERVAL = 10000
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const allNotifications = ref<NotificationModel[]>([])
|
const allNotifications = ref<INotification[]>([])
|
||||||
const showNotifications = ref(false)
|
const showNotifications = ref(false)
|
||||||
const popup = ref(null)
|
const popup = ref(null)
|
||||||
|
|
||||||
|
|
|
@ -181,13 +181,13 @@ import {useStore} from 'vuex'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
import {RIGHTS} from '@/models/constants/rights'
|
import {RIGHTS} from '@/models/constants/rights'
|
||||||
import LinkShareModel from '@/models/linkShare'
|
import LinkShareModel, { type ILinkShare } from '@/models/linkShare'
|
||||||
import type ListModel from '@/models/list'
|
|
||||||
|
|
||||||
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'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
listId: {
|
listId: {
|
||||||
|
@ -198,7 +198,7 @@ const props = defineProps({
|
||||||
|
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
|
|
||||||
const linkShares = ref<LinkShareModel[]>([])
|
const linkShares = ref<ILinkShare[]>([])
|
||||||
const linkShareService = shallowReactive(new LinkShareService())
|
const linkShareService = shallowReactive(new LinkShareService())
|
||||||
const selectedRight = ref(RIGHTS.READ)
|
const selectedRight = ref(RIGHTS.READ)
|
||||||
const name = ref('')
|
const name = ref('')
|
||||||
|
@ -217,7 +217,7 @@ watch(
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const frontendUrl = computed(() => store.state.config.frontendUrl)
|
const frontendUrl = computed(() => store.state.config.frontendUrl)
|
||||||
|
|
||||||
async function load(listId: ListModel['id']) {
|
async function load(listId: IList['id']) {
|
||||||
// If listId == 0 the list on the calling component wasn't already loaded, so we just bail out here
|
// If listId == 0 the list on the calling component wasn't already loaded, so we just bail out here
|
||||||
if (listId === 0) {
|
if (listId === 0) {
|
||||||
return
|
return
|
||||||
|
@ -226,7 +226,7 @@ async function load(listId: ListModel['id']) {
|
||||||
linkShares.value = await linkShareService.getAll({listId})
|
linkShares.value = await linkShareService.getAll({listId})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function add(listId: ListModel['id']) {
|
async function add(listId: IList['id']) {
|
||||||
const newLinkShare = new LinkShareModel({
|
const newLinkShare = new LinkShareModel({
|
||||||
right: selectedRight.value,
|
right: selectedRight.value,
|
||||||
listId,
|
listId,
|
||||||
|
@ -242,7 +242,7 @@ async function add(listId: ListModel['id']) {
|
||||||
await load(listId)
|
await load(listId)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function remove(listId: ListModel['id']) {
|
async function remove(listId: IList['id']) {
|
||||||
try {
|
try {
|
||||||
await linkShareService.delete(new LinkShareModel({
|
await linkShareService.delete(new LinkShareModel({
|
||||||
id: linkIdToDelete.value,
|
id: linkIdToDelete.value,
|
||||||
|
|
|
@ -143,18 +143,22 @@ import {useStore} from 'vuex'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
import UserNamespaceService from '@/services/userNamespace'
|
import UserNamespaceService from '@/services/userNamespace'
|
||||||
import UserNamespaceModel from '@/models/userNamespace'
|
import UserNamespaceModel, { type IUserNamespace } from '@/models/userNamespace'
|
||||||
import UserListModel from '@/models/userList'
|
|
||||||
import UserListService from '@/services/userList'
|
import UserListService from '@/services/userList'
|
||||||
|
import UserListModel, { type IUserList } from '@/models/userList'
|
||||||
|
|
||||||
import UserService from '@/services/user'
|
import UserService from '@/services/user'
|
||||||
import UserModel from '@/models/user'
|
import UserModel, { type IUser } from '@/models/user'
|
||||||
|
|
||||||
import TeamNamespaceService from '@/services/teamNamespace'
|
import TeamNamespaceService from '@/services/teamNamespace'
|
||||||
import TeamNamespaceModel from '@/models/teamNamespace'
|
import TeamNamespaceModel, { type ITeamNamespace } from '@/models/teamNamespace'
|
||||||
import TeamListModel from '@/models/teamList'
|
|
||||||
import TeamListService from '@/services/teamList'
|
import TeamListService from '@/services/teamList'
|
||||||
|
import TeamListModel, { type ITeamList } from '@/models/teamList'
|
||||||
|
|
||||||
import TeamService from '@/services/team'
|
import TeamService from '@/services/team'
|
||||||
import TeamModel from '@/models/team'
|
import TeamModel, { type ITeam } from '@/models/team'
|
||||||
|
|
||||||
import {RIGHTS} from '@/models/constants/rights'
|
import {RIGHTS} from '@/models/constants/rights'
|
||||||
import Multiselect from '@/components/input/multiselect.vue'
|
import Multiselect from '@/components/input/multiselect.vue'
|
||||||
|
@ -183,10 +187,10 @@ const props = defineProps({
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
|
|
||||||
// This user service is either a userNamespaceService or a userListService, depending on the type we are using
|
// This user service is either a userNamespaceService or a userListService, depending on the type we are using
|
||||||
let stuffService: ShallowReactive<UserNamespaceService | UserListService | TeamListService | TeamNamespaceService>
|
let stuffService: UserNamespaceService | UserListService | TeamListService | TeamNamespaceService
|
||||||
let stuffModel: UserNamespaceModel | UserListModel | TeamListModel | TeamNamespaceModel
|
let stuffModel: IUserNamespace | IUserList | ITeamList | ITeamNamespace
|
||||||
let searchService: ShallowReactive<UserService | TeamService>
|
let searchService: UserService | TeamService
|
||||||
let sharable: Ref<UserModel | TeamModel>
|
let sharable: Ref<IUser | ITeam>
|
||||||
|
|
||||||
const searchLabel = ref('')
|
const searchLabel = ref('')
|
||||||
const selectedRight = ref({})
|
const selectedRight = ref({})
|
||||||
|
|
|
@ -76,14 +76,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, reactive, computed, shallowReactive, watch, nextTick} from 'vue'
|
import {ref, reactive, computed, shallowReactive, watch, nextTick, type PropType} from 'vue'
|
||||||
import {useRouter} from 'vue-router'
|
import {useRouter} from 'vue-router'
|
||||||
import {useI18n} from 'vue-i18n'
|
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 from '@/models/task'
|
import TaskModel, { type ITask } from '@/models/task'
|
||||||
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'
|
||||||
|
@ -93,14 +93,16 @@ import {success} from '@/message'
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps({
|
||||||
task?: TaskModel | null,
|
task: {
|
||||||
}>()
|
type: Object as PropType<ITask | null>,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
const taskService = shallowReactive(new TaskService())
|
const taskService = shallowReactive(new TaskService())
|
||||||
|
|
||||||
const editorActive = ref(false)
|
const editorActive = ref(false)
|
||||||
let taskEditTask: TaskModel | undefined
|
let taskEditTask: ITask | undefined
|
||||||
|
|
||||||
|
|
||||||
// FIXME: this initialization should not be necessary here
|
// FIXME: this initialization should not be necessary here
|
||||||
|
|
|
@ -147,8 +147,7 @@
|
||||||
import {defineComponent} from 'vue'
|
import {defineComponent} from 'vue'
|
||||||
|
|
||||||
import AttachmentService from '../../../services/attachment'
|
import AttachmentService from '../../../services/attachment'
|
||||||
import AttachmentModel from '../../../models/attachment'
|
import AttachmentModel, { type IAttachment } from '@/models/attachment'
|
||||||
import type FileModel from '@/models/file'
|
|
||||||
import User from '@/components/misc/user.vue'
|
import User from '@/components/misc/user.vue'
|
||||||
import {mapState} from 'vuex'
|
import {mapState} from 'vuex'
|
||||||
|
|
||||||
|
@ -157,6 +156,7 @@ 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'
|
||||||
|
import type { IFile } from '@/models/file'
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'attachments',
|
name: 'attachments',
|
||||||
|
@ -192,7 +192,7 @@ export default defineComponent({
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const copy = useCopyToClipboard()
|
const copy = useCopyToClipboard()
|
||||||
|
|
||||||
function copyUrl(attachment: AttachmentModel) {
|
function copyUrl(attachment: IAttachment) {
|
||||||
copy(generateAttachmentUrl(props.taskId, attachment.id))
|
copy(generateAttachmentUrl(props.taskId, attachment.id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ export default defineComponent({
|
||||||
formatDateSince,
|
formatDateSince,
|
||||||
formatDateLong,
|
formatDateLong,
|
||||||
|
|
||||||
downloadAttachment(attachment: AttachmentModel) {
|
downloadAttachment(attachment: IAttachment) {
|
||||||
this.attachmentService.download(attachment)
|
this.attachmentService.download(attachment)
|
||||||
},
|
},
|
||||||
uploadNewAttachment() {
|
uploadNewAttachment() {
|
||||||
|
@ -245,7 +245,7 @@ export default defineComponent({
|
||||||
|
|
||||||
this.uploadFiles(this.$refs.files.files)
|
this.uploadFiles(this.$refs.files.files)
|
||||||
},
|
},
|
||||||
uploadFiles(files: FileModel[]) {
|
uploadFiles(files: IFile[]) {
|
||||||
uploadFiles(this.attachmentService, this.taskId, files)
|
uploadFiles(this.attachmentService, this.taskId, files)
|
||||||
},
|
},
|
||||||
async deleteAttachment() {
|
async deleteAttachment() {
|
||||||
|
|
|
@ -10,15 +10,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {computed} from 'vue'
|
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 TaskModel from '@/models/task'
|
import type {ITask} from '@/models/task'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
task: {
|
task: {
|
||||||
type: TaskModel,
|
type: Object as PropType<ITask>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -159,12 +159,12 @@ 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 from '@/models/taskComment'
|
import TaskCommentModel, { type ITaskComment } from '@/models/taskComment'
|
||||||
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 TaskModel from '@/models/task'
|
import type { ITask } from '@/models/task'
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
taskId: {
|
taskId: {
|
||||||
type: Number,
|
type: Number,
|
||||||
|
@ -178,7 +178,7 @@ const props = defineProps({
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
const comments = ref<TaskCommentModel[]>([])
|
const comments = ref<ITaskComment[]>([])
|
||||||
|
|
||||||
const showDeleteModal = ref(false)
|
const showDeleteModal = ref(false)
|
||||||
const commentToDelete = reactive(new TaskCommentModel())
|
const commentToDelete = reactive(new TaskCommentModel())
|
||||||
|
@ -188,8 +188,8 @@ const commentEdit = reactive(new TaskCommentModel())
|
||||||
|
|
||||||
const newComment = reactive(new TaskCommentModel())
|
const newComment = reactive(new TaskCommentModel())
|
||||||
|
|
||||||
const saved = ref<TaskModel['id'] | null>(null)
|
const saved = ref<ITask['id'] | null>(null)
|
||||||
const saving = ref<TaskModel['id'] | null>(null)
|
const saving = ref<ITask['id'] | null>(null)
|
||||||
|
|
||||||
const userAvatar = computed(() => store.state.auth.info.getAvatarUrl(48))
|
const userAvatar = computed(() => store.state.auth.info.getAvatarUrl(48))
|
||||||
const currentUserId = computed(() => store.state.auth.info.id)
|
const currentUserId = computed(() => store.state.auth.info.id)
|
||||||
|
@ -215,7 +215,7 @@ function attachmentUpload(...args) {
|
||||||
|
|
||||||
const taskCommentService = shallowReactive(new TaskCommentService())
|
const taskCommentService = shallowReactive(new TaskCommentService())
|
||||||
|
|
||||||
async function loadComments(taskId: TaskModel['id']) {
|
async function loadComments(taskId: ITask['id']) {
|
||||||
if (!enabled.value) {
|
if (!enabled.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -259,12 +259,12 @@ async function addComment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleEdit(comment: TaskCommentModel) {
|
function toggleEdit(comment: ITaskComment) {
|
||||||
isCommentEdit.value = !isCommentEdit.value
|
isCommentEdit.value = !isCommentEdit.value
|
||||||
Object.assign(commentEdit, comment)
|
Object.assign(commentEdit, comment)
|
||||||
}
|
}
|
||||||
|
|
||||||
function toggleDelete(commentId: TaskCommentModel['id']) {
|
function toggleDelete(commentId: ITaskComment['id']) {
|
||||||
showDeleteModal.value = !showDeleteModal.value
|
showDeleteModal.value = !showDeleteModal.value
|
||||||
commentToDelete.id = commentId
|
commentToDelete.id = commentId
|
||||||
}
|
}
|
||||||
|
@ -294,7 +294,7 @@ async function editComment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteComment(commentToDelete: TaskCommentModel) {
|
async function deleteComment(commentToDelete: ITaskComment) {
|
||||||
try {
|
try {
|
||||||
await taskCommentService.delete(commentToDelete)
|
await taskCommentService.delete(commentToDelete)
|
||||||
const index = comments.value.findIndex(({id}) => id === commentToDelete.id)
|
const index = comments.value.findIndex(({id}) => id === commentToDelete.id)
|
||||||
|
|
|
@ -27,13 +27,13 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {computed, toRefs} from 'vue'
|
import {computed, toRefs, type PropType} from 'vue'
|
||||||
import TaskModel from '@/models/task'
|
import type { ITask } from '@/models/task'
|
||||||
import {formatISO, formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
import {formatISO, formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
task: {
|
task: {
|
||||||
type: TaskModel,
|
type: Object as PropType<ITask>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -38,17 +38,17 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, shallowReactive, computed, watch, onMounted, onBeforeUnmount} from 'vue'
|
import {ref, shallowReactive, computed, watch, onMounted, onBeforeUnmount, type PropType} from 'vue'
|
||||||
import {useStore} from 'vuex'
|
import {useStore} from 'vuex'
|
||||||
import {useI18n} from 'vue-i18n'
|
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 TaskModel from '@/models/task'
|
import { type ITask } from '@/models/task'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: TaskModel,
|
type: Object as PropType<ITask>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -58,7 +58,7 @@ const {t} = useI18n({useScope: 'global'})
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
const taskService = shallowReactive(new TaskService())
|
const taskService = shallowReactive(new TaskService())
|
||||||
const task = ref<TaskModel>()
|
const task = ref<ITask>()
|
||||||
|
|
||||||
// We're saving the due date seperately to prevent null errors in very short periods where the task is null.
|
// We're saving the due date seperately to prevent null errors in very short periods where the task is null.
|
||||||
const dueDate = ref<Date>()
|
const dueDate = ref<Date>()
|
||||||
|
|
|
@ -30,17 +30,17 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref,computed, watch} from 'vue'
|
import {ref,computed, watch, type PropType} from 'vue'
|
||||||
import {useStore} from 'vuex'
|
import {useStore} from 'vuex'
|
||||||
|
|
||||||
import Editor from '@/components/input/AsyncEditor'
|
import Editor from '@/components/input/AsyncEditor'
|
||||||
|
|
||||||
import TaskModel from '@/models/task'
|
import type { ITask } from '@/models/task'
|
||||||
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: TaskModel,
|
type: Object as PropType<ITask>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
attachmentUpload: {
|
attachmentUpload: {
|
||||||
|
@ -54,7 +54,7 @@ const props = defineProps({
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue'])
|
const emit = defineEmits(['update:modelValue'])
|
||||||
|
|
||||||
const task = ref<TaskModel>({description: ''})
|
const task = ref<ITask>({description: ''})
|
||||||
const saved = ref(false)
|
const saved = ref(false)
|
||||||
|
|
||||||
// Since loading is global state, this variable ensures we're only showing the saving icon when saving the description.
|
// Since loading is global state, this variable ensures we're only showing the saving icon when saving the description.
|
||||||
|
|
|
@ -37,9 +37,9 @@ import Multiselect from '@/components/input/multiselect.vue'
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
|
||||||
import {includesById} from '@/helpers/utils'
|
import {includesById} from '@/helpers/utils'
|
||||||
import type UserModel from '@/models/user'
|
|
||||||
import ListUserService from '@/services/listUsers'
|
import ListUserService from '@/services/listUsers'
|
||||||
import {success} from '@/message'
|
import {success} from '@/message'
|
||||||
|
import type { IUser } from '@/models/user'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
taskId: {
|
taskId: {
|
||||||
|
@ -54,7 +54,7 @@ const props = defineProps({
|
||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Array as PropType<UserModel[]>,
|
type: Array as PropType<IUser[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -65,7 +65,7 @@ const {t} = useI18n({useScope: 'global'})
|
||||||
|
|
||||||
const listUserService = shallowReactive(new ListUserService())
|
const listUserService = shallowReactive(new ListUserService())
|
||||||
const foundUsers = ref([])
|
const foundUsers = ref([])
|
||||||
const assignees = ref<UserModel[]>([])
|
const assignees = ref<IUser[]>([])
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
|
@ -78,13 +78,13 @@ watch(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
async function addAssignee(user: UserModel) {
|
async function addAssignee(user: IUser) {
|
||||||
await store.dispatch('tasks/addAssignee', {user: user, taskId: props.taskId})
|
await store.dispatch('tasks/addAssignee', {user: user, taskId: props.taskId})
|
||||||
emit('update:modelValue', assignees.value)
|
emit('update:modelValue', assignees.value)
|
||||||
success({message: t('task.assignee.assignSuccess')})
|
success({message: t('task.assignee.assignSuccess')})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeAssignee(user: UserModel) {
|
async function removeAssignee(user: IUser) {
|
||||||
await store.dispatch('tasks/removeAssignee', {user: user, taskId: props.taskId})
|
await store.dispatch('tasks/removeAssignee', {user: user, taskId: props.taskId})
|
||||||
|
|
||||||
// Remove the assignee from the list
|
// Remove the assignee from the list
|
||||||
|
|
|
@ -43,7 +43,7 @@ import {type PropType, ref, computed, shallowReactive, watch} from 'vue'
|
||||||
import {useStore} from 'vuex'
|
import {useStore} from 'vuex'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
import LabelModel from '@/models/label'
|
import LabelModel, { type ILabel } from '@/models/label'
|
||||||
import LabelTaskService from '@/services/labelTask'
|
import LabelTaskService from '@/services/labelTask'
|
||||||
import {success} from '@/message'
|
import {success} from '@/message'
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ import Multiselect from '@/components/input/multiselect.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Array as PropType<LabelModel[]>,
|
type: Array as PropType<ILabel[]>,
|
||||||
default: () => [],
|
default: () => [],
|
||||||
},
|
},
|
||||||
taskId: {
|
taskId: {
|
||||||
|
@ -71,7 +71,7 @@ const store = useStore()
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
|
|
||||||
const labelTaskService = shallowReactive(new LabelTaskService())
|
const labelTaskService = shallowReactive(new LabelTaskService())
|
||||||
const labels = ref<LabelModel[]>([])
|
const labels = ref<ILabel[]>([])
|
||||||
const query = ref('')
|
const query = ref('')
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
|
@ -92,7 +92,7 @@ function findLabel(newQuery: string) {
|
||||||
query.value = newQuery
|
query.value = newQuery
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addLabel(label: LabelModel, 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)
|
emit('change', labels.value)
|
||||||
|
@ -110,7 +110,7 @@ async function addLabel(label: LabelModel, showNotification = true) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function removeLabel(label: LabelModel) {
|
async function removeLabel(label: ILabel) {
|
||||||
if (props.taskId !== 0) {
|
if (props.taskId !== 0) {
|
||||||
await store.dispatch('tasks/removeLabel', {label, taskId: props.taskId})
|
await store.dispatch('tasks/removeLabel', {label, taskId: props.taskId})
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,18 +32,18 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, computed} from 'vue'
|
import {ref, computed, type PropType} from 'vue'
|
||||||
import {useStore} from 'vuex'
|
import {useStore} from 'vuex'
|
||||||
|
|
||||||
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 TaskModel from '@/models/task'
|
import type {ITask} from '@/models/task'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
task: {
|
task: {
|
||||||
type: TaskModel,
|
type: Object as PropType<ITask>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
canWrite: {
|
canWrite: {
|
||||||
|
|
|
@ -74,7 +74,7 @@ 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 TaskModel, {TASK_DEFAULT_COLOR} from '@/models/task'
|
import {TASK_DEFAULT_COLOR, type ITask} from '@/models/task'
|
||||||
|
|
||||||
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'
|
||||||
|
@ -96,7 +96,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
task: {
|
task: {
|
||||||
type: Object as PropType<TaskModel>,
|
type: Object as PropType<ITask>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
loading: {
|
loading: {
|
||||||
|
@ -117,7 +117,7 @@ export default defineComponent({
|
||||||
formatISO,
|
formatISO,
|
||||||
formatDateSince,
|
formatDateSince,
|
||||||
colorIsDark,
|
colorIsDark,
|
||||||
async toggleTaskDone(task: TaskModel) {
|
async toggleTaskDone(task: ITask) {
|
||||||
this.loadingInternal = true
|
this.loadingInternal = true
|
||||||
try {
|
try {
|
||||||
const done = !task.done
|
const done = !task.done
|
||||||
|
|
|
@ -11,12 +11,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type LabelModel from '@/models/label'
|
|
||||||
import type { PropType } from 'vue'
|
import type { PropType } from 'vue'
|
||||||
|
import type { ILabel } from '@/models/label'
|
||||||
|
|
||||||
defineProps({
|
defineProps({
|
||||||
labels: {
|
labels: {
|
||||||
type: Array as PropType<LabelModel[]>,
|
type: Array as PropType<ILabel[]>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -21,15 +21,12 @@ import {reactive, ref, watch} from 'vue'
|
||||||
import type {PropType} from 'vue'
|
import type {PropType} from 'vue'
|
||||||
import {useStore} from 'vuex'
|
import {useStore} from 'vuex'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
import ListModel from '@/models/list'
|
import ListModel, { type IList } from '@/models/list'
|
||||||
import Multiselect from '@/components/input/multiselect.vue'
|
import Multiselect from '@/components/input/multiselect.vue'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Object as PropType<ListModel>,
|
type: Object as PropType<IList>,
|
||||||
validator(value) {
|
|
||||||
return value instanceof ListModel
|
|
||||||
},
|
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
@ -38,7 +35,7 @@ const emit = defineEmits(['update:modelValue'])
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
|
|
||||||
const list: ListModel= reactive(new ListModel())
|
const list: IList = reactive(new ListModel())
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.modelValue,
|
() => props.modelValue,
|
||||||
|
@ -57,7 +54,7 @@ function findLists(query: string) {
|
||||||
foundLists.value = store.getters['lists/searchList'](query)
|
foundLists.value = store.getters['lists/searchList'](query)
|
||||||
}
|
}
|
||||||
|
|
||||||
function select(l: ListModel | null) {
|
function select(l: IList | null) {
|
||||||
Object.assign(list, l)
|
Object.assign(list, l)
|
||||||
emit('update:modelValue', list)
|
emit('update:modelValue', list)
|
||||||
}
|
}
|
||||||
|
|
|
@ -63,14 +63,14 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {ref, reactive, watch, type PropType} from 'vue'
|
import {ref, reactive, watch, type PropType} from 'vue'
|
||||||
import {error} from '@/message'
|
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
import {TASK_REPEAT_MODES, type RepeatAfter} from '@/models/task'
|
|
||||||
import type TaskModel from '@/models/task'
|
import {error} from '@/message'
|
||||||
|
import {TASK_REPEAT_MODES, type ITask, type RepeatAfter} from '@/models/task'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: Object as PropType<TaskModel>,
|
type: Object as PropType<ITask>,
|
||||||
default: () => ({}),
|
default: () => ({}),
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
@ -84,7 +84,7 @@ const {t} = useI18n({useScope: 'global'})
|
||||||
|
|
||||||
const emit = defineEmits(['update:modelValue', 'change'])
|
const emit = defineEmits(['update:modelValue', 'change'])
|
||||||
|
|
||||||
const task = ref<TaskModel>()
|
const task = ref<ITask>()
|
||||||
const repeatAfter = reactive({
|
const repeatAfter = reactive({
|
||||||
amount: 0,
|
amount: 0,
|
||||||
type: '',
|
type: '',
|
||||||
|
|
|
@ -98,7 +98,7 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import {defineComponent} from 'vue'
|
import {defineComponent} from 'vue'
|
||||||
|
|
||||||
import TaskModel from '../../../models/task'
|
import TaskModel, { type ITask } from '../../../models/task'
|
||||||
import PriorityLabel from './priorityLabel.vue'
|
import PriorityLabel from './priorityLabel.vue'
|
||||||
import TaskService from '../../../services/task'
|
import TaskService from '../../../services/task'
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
@ -129,7 +129,7 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
theTask: {
|
theTask: {
|
||||||
type: TaskModel,
|
type: Object as PropType<ITask>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
isArchived: {
|
isArchived: {
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
import AttachmentModel from '@/models/attachment'
|
import AttachmentModel, { type IAttachment } from '@/models/attachment'
|
||||||
import FileModel from '@/models/file'
|
import type {IFile} from '@/models/file'
|
||||||
|
|
||||||
import AttachmentService from '@/services/attachment'
|
import AttachmentService from '@/services/attachment'
|
||||||
import { store } from '@/store'
|
import { store } from '@/store'
|
||||||
|
|
||||||
export function uploadFile(taskId: number, file: FileModel, onSuccess: () => Function) {
|
export function uploadFile(taskId: number, file: IFile, onSuccess: () => Function) {
|
||||||
const attachmentService = new AttachmentService()
|
const attachmentService = new AttachmentService()
|
||||||
const files = [file]
|
const files = [file]
|
||||||
|
|
||||||
return uploadFiles(attachmentService, taskId, files, onSuccess)
|
return uploadFiles(attachmentService, taskId, files, onSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function uploadFiles(attachmentService: AttachmentService, taskId: number, files: FileModel[], onSuccess : Function = () => {}) {
|
export async function uploadFiles(
|
||||||
|
attachmentService: AttachmentService,
|
||||||
|
taskId: number,
|
||||||
|
files: IFile[],
|
||||||
|
onSuccess: Function = () => {},
|
||||||
|
) {
|
||||||
const attachmentModel = new AttachmentModel({taskId})
|
const attachmentModel = new AttachmentModel({taskId})
|
||||||
const response = await attachmentService.create(attachmentModel, files)
|
const response = await attachmentService.create(attachmentModel, files)
|
||||||
console.debug(`Uploaded attachments for task ${taskId}, response was`, response)
|
console.debug(`Uploaded attachments for task ${taskId}, response was`, response)
|
||||||
|
|
||||||
response.success?.map((attachment: AttachmentModel) => {
|
response.success?.map((attachment: IAttachment) => {
|
||||||
store.dispatch('tasks/addTaskAttachment', {
|
store.dispatch('tasks/addTaskAttachment', {
|
||||||
taskId,
|
taskId,
|
||||||
attachment,
|
attachment,
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import {i18n} from '@/i18n'
|
import {i18n} from '@/i18n'
|
||||||
|
import type { IList } from '@/models/list'
|
||||||
|
|
||||||
import type ListModal from '@/models/list'
|
export function getListTitle(l: IList) {
|
||||||
|
|
||||||
export function getListTitle(l: ListModal) {
|
|
||||||
if (l.id === -1) {
|
if (l.id === -1) {
|
||||||
return i18n.global.t('list.pseudo.favorites.title')
|
return i18n.global.t('list.pseudo.favorites.title')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {i18n} from '@/i18n'
|
import {i18n} from '@/i18n'
|
||||||
import NamespaceModel from '@/models/namespace'
|
import type {INamespace} from '@/models/namespace'
|
||||||
|
|
||||||
export const getNamespaceTitle = (n: NamespaceModel) => {
|
export const getNamespaceTitle = (n: INamespace) => {
|
||||||
if (n.id === -1) {
|
if (n.id === -1) {
|
||||||
return i18n.global.t('namespace.pseudo.sharedLists.title')
|
return i18n.global.t('namespace.pseudo.sharedLists.title')
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,10 @@
|
||||||
import {createNewIndexer} from '../indexes'
|
import {createNewIndexer} from '../indexes'
|
||||||
|
|
||||||
|
import type {LabelState} from '@/store/types'
|
||||||
|
import type {ILabel} from '@/models/label'
|
||||||
|
|
||||||
const {search} = createNewIndexer('labels', ['title', 'description'])
|
const {search} = createNewIndexer('labels', ['title', 'description'])
|
||||||
|
|
||||||
export interface label {
|
|
||||||
id: number,
|
|
||||||
title: string,
|
|
||||||
}
|
|
||||||
|
|
||||||
interface labelState {
|
|
||||||
labels: {
|
|
||||||
[k: number]: label,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if a list of labels is available in the store and filters them then query
|
* Checks if a list of labels is available in the store and filters them then query
|
||||||
* @param {Object} state
|
* @param {Object} state
|
||||||
|
@ -20,7 +12,7 @@ interface labelState {
|
||||||
* @param {String} query
|
* @param {String} query
|
||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
export function filterLabelsByQuery(state: labelState, labelsToHide: label[], query: string) {
|
export function filterLabelsByQuery(state: LabelState, labelsToHide: ILabel[], query: string) {
|
||||||
const labelIdsToHide: number[] = labelsToHide.map(({id}) => id)
|
const labelIdsToHide: number[] = labelsToHide.map(({id}) => id)
|
||||||
|
|
||||||
return search(query)
|
return search(query)
|
||||||
|
@ -36,6 +28,6 @@ export function filterLabelsByQuery(state: labelState, labelsToHide: label[], qu
|
||||||
* @param {Array} ids
|
* @param {Array} ids
|
||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
export function getLabelsByIds(state: labelState, ids: number[]) {
|
export function getLabelsByIds(state: LabelState, ids: ILabel['id'][]) {
|
||||||
return Object.values(state.labels).filter(({id}) => ids.includes(id))
|
return Object.values(state.labels).filter(({id}) => ids.includes(id))
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import ListModel from '@/models/list'
|
import type {IList} from '@/models/list'
|
||||||
|
|
||||||
const key = 'collapsedBuckets'
|
const key = 'collapsedBuckets'
|
||||||
|
|
||||||
|
@ -11,7 +11,10 @@ const getAllState = () => {
|
||||||
return JSON.parse(saved)
|
return JSON.parse(saved)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const saveCollapsedBucketState = (listId: ListModel['id'], collapsedBuckets) => {
|
export const saveCollapsedBucketState = (
|
||||||
|
listId: IList['id'],
|
||||||
|
collapsedBuckets,
|
||||||
|
) => {
|
||||||
const state = getAllState()
|
const state = getAllState()
|
||||||
state[listId] = collapsedBuckets
|
state[listId] = collapsedBuckets
|
||||||
for (const bucketId in state[listId]) {
|
for (const bucketId in state[listId]) {
|
||||||
|
@ -22,7 +25,7 @@ export const saveCollapsedBucketState = (listId: ListModel['id'], collapsedBucke
|
||||||
localStorage.setItem(key, JSON.stringify(state))
|
localStorage.setItem(key, JSON.stringify(state))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getCollapsedBucketState = (listId : ListModel['id']) => {
|
export const getCollapsedBucketState = (listId : IList['id']) => {
|
||||||
const state = getAllState()
|
const state = getAllState()
|
||||||
if (typeof state[listId] !== 'undefined') {
|
if (typeof state[listId] !== 'undefined') {
|
||||||
return state[listId]
|
return state[listId]
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import ListModel from '@/models/list'
|
import type {IList} from '@/models/list'
|
||||||
|
|
||||||
export function getSavedFilterIdFromListId(listId: ListModel['id']) {
|
export function getSavedFilterIdFromListId(listId: IList['id']) {
|
||||||
let filterId = listId * -1 - 1
|
let filterId = listId * -1 - 1
|
||||||
// FilterIds from listIds are always positive
|
// FilterIds from listIds are always positive
|
||||||
if (filterId < 0) {
|
if (filterId < 0) {
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
export function findIndexById(array : [], id : string | number) {
|
export function findIndexById<T extends {id: string | number}>(array : T[], id : string | number) {
|
||||||
return array.findIndex(({id: currentId}) => currentId === id)
|
return array.findIndex(({id: currentId}) => currentId === id)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function findById(array : [], id : string | number) {
|
export function findById<T extends {id: string | number}>(array : T[], id : string | number) {
|
||||||
return array.find(({id: currentId}) => currentId === id)
|
return array.find(({id: currentId}) => currentId === id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,11 @@ import {objectToCamelCase} from '@/helpers/case'
|
||||||
import {omitBy, isNil} from '@/helpers/utils'
|
import {omitBy, isNil} from '@/helpers/utils'
|
||||||
import type {Right} from '@/models/constants/rights'
|
import type {Right} from '@/models/constants/rights'
|
||||||
|
|
||||||
export default class AbstractModel {
|
export interface IAbstract {
|
||||||
|
maxRight: Right | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AbstractModel implements IAbstract {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The max right the user has on this object, as returned by the x-max-right header from the api.
|
* The max right the user has on this object, as returned by the x-max-right header from the api.
|
||||||
|
|
|
@ -1,12 +1,21 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
import UserModel from './user'
|
import UserModel, {type IUser} from './user'
|
||||||
import FileModel from './file'
|
import FileModel, {type IFile} from './file'
|
||||||
|
import type {IAbstract} from './abstractModel'
|
||||||
|
|
||||||
export default class AttachmentModel extends AbstractModel {
|
export interface IAttachment extends IAbstract {
|
||||||
id: number
|
id: number
|
||||||
taskId: number
|
taskId: number
|
||||||
createdBy: UserModel
|
createdBy: IUser
|
||||||
file: FileModel
|
file: IFile
|
||||||
|
created: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AttachmentModel extends AbstractModel implements IAttachment {
|
||||||
|
declare id: number
|
||||||
|
declare taskId: number
|
||||||
|
createdBy: IUser
|
||||||
|
file: IFile
|
||||||
konrad marked this conversation as resolved
Outdated
|
|||||||
created: Date
|
created: Date
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel, { type IAbstract } from './abstractModel'
|
||||||
|
|
||||||
export type AVATAR_PROVIDERS = 'default' | 'initials' | 'gravatar' | 'marble' | 'upload'
|
export type AvatarProvider = 'default' | 'initials' | 'gravatar' | 'marble' | 'upload'
|
||||||
|
|
||||||
export default class AvatarModel extends AbstractModel {
|
export interface IAvatar extends IAbstract {
|
||||||
avatarProvider: AVATAR_PROVIDERS
|
avatarProvider: AvatarProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class AvatarModel extends AbstractModel implements IAvatar {
|
||||||
|
declare avatarProvider: AvatarProvider
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel, { type IAbstract } from './abstractModel'
|
||||||
|
|
||||||
export default class BackgroundImageModel extends AbstractModel {
|
export interface IBackgroundImage extends IAbstract {
|
||||||
id: number
|
id: number
|
||||||
url: string
|
url: string
|
||||||
thumb: string
|
thumb: string
|
||||||
|
@ -9,6 +9,17 @@ export default class BackgroundImageModel extends AbstractModel {
|
||||||
authorName: string
|
authorName: string
|
||||||
}
|
}
|
||||||
blurHash: string
|
blurHash: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class BackgroundImageModel extends AbstractModel implements IBackgroundImage {
|
||||||
|
declare id: number
|
||||||
|
declare url: string
|
||||||
|
declare thumb: string
|
||||||
|
declare info: {
|
||||||
|
author: string
|
||||||
|
authorName: string
|
||||||
|
}
|
||||||
|
declare blurHash: string
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,17 +1,31 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel, { type IAbstract } from './abstractModel'
|
||||||
import UserModel from './user'
|
import UserModel, { type IUser } from './user'
|
||||||
import TaskModel from './task'
|
import TaskModel, { type ITask } from './task'
|
||||||
|
|
||||||
export default class BucketModel extends AbstractModel {
|
export interface IBucket extends IAbstract {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
listId: number
|
listId: number
|
||||||
limit: number
|
limit: number
|
||||||
tasks: TaskModel[]
|
tasks: ITask[]
|
||||||
isDoneBucket: boolean
|
isDoneBucket: boolean
|
||||||
position: number
|
position: number
|
||||||
|
|
||||||
createdBy: UserModel
|
createdBy: IUser
|
||||||
|
created: Date
|
||||||
|
updated: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class BucketModel extends AbstractModel implements IBucket {
|
||||||
|
declare id: number
|
||||||
|
declare title: string
|
||||||
|
declare listId: number
|
||||||
|
declare limit: number
|
||||||
|
declare tasks: ITask[]
|
||||||
|
declare isDoneBucket: boolean
|
||||||
|
declare position: number
|
||||||
|
|
||||||
|
createdBy: IUser
|
||||||
created: Date
|
created: Date
|
||||||
updated: Date
|
updated: Date
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel, { type IAbstract } from './abstractModel'
|
||||||
|
|
||||||
export default class CaldavTokenModel extends AbstractModel {
|
export interface ICaldavToken extends IAbstract {
|
||||||
id: number
|
id: number;
|
||||||
created: Date
|
created: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class CaldavTokenModel extends AbstractModel implements ICaldavToken {
|
||||||
|
declare id: number
|
||||||
|
declare created: Date
|
||||||
|
|
||||||
constructor(data? : Object) {
|
constructor(data? : Object) {
|
||||||
super(data)
|
super(data)
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
|
|
||||||
export default class EmailUpdateModel extends AbstractModel {
|
interface IEmailUpdate {
|
||||||
newEmail: string
|
newEmail: string
|
||||||
password: string
|
password: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class EmailUpdateModel extends AbstractModel implements IEmailUpdate {
|
||||||
|
declare newEmail: string
|
||||||
|
declare password: string
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,11 +1,19 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
|
|
||||||
export default class FileModel extends AbstractModel {
|
export interface IFile {
|
||||||
id: number
|
id: number
|
||||||
mime: string
|
mime: string
|
||||||
name: string
|
name: string
|
||||||
size: number
|
size: number
|
||||||
created: Date
|
created: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class FileModel extends AbstractModel implements IFile {
|
||||||
|
declare id: number
|
||||||
|
declare mime: string
|
||||||
|
declare name: string
|
||||||
|
declare size: number
|
||||||
|
created: Date
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
super(data)
|
super(data)
|
||||||
|
|
|
@ -1,19 +1,33 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
import UserModel from './user'
|
import UserModel, { type IUser } from './user'
|
||||||
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||||
|
|
||||||
const DEFAULT_LABEL_BACKGROUND_COLOR = 'e8e8e8'
|
const DEFAULT_LABEL_BACKGROUND_COLOR = 'e8e8e8'
|
||||||
export default class LabelModel extends AbstractModel {
|
|
||||||
|
export interface ILabel {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
hexColor: string
|
hexColor: string
|
||||||
description: string
|
description: string
|
||||||
createdBy: UserModel
|
createdBy: IUser
|
||||||
listId: number
|
listId: number
|
||||||
textColor: string
|
textColor: string
|
||||||
|
|
||||||
created: Date
|
created: Date
|
||||||
updated: Date
|
updated: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class LabelModel extends AbstractModel implements ILabel {
|
||||||
|
declare id: number
|
||||||
|
declare title: string
|
||||||
|
declare hexColor: string
|
||||||
|
declare description: string
|
||||||
|
declare createdBy: IUser
|
||||||
|
declare listId: number
|
||||||
|
declare textColor: string
|
||||||
|
|
||||||
|
created: Date
|
||||||
|
updated: Date
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
super(data)
|
super(data)
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
|
|
||||||
export default class LabelTask extends AbstractModel {
|
interface ILabel {
|
||||||
id: number
|
id: number
|
||||||
taskId: number
|
taskId: number
|
||||||
labelId: number
|
labelId: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class LabelTask extends AbstractModel implements ILabel {
|
||||||
|
declare id: number
|
||||||
|
declare taskId: number
|
||||||
|
declare labelId: number
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,18 +1,31 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel, { type IAbstract } from './abstractModel'
|
||||||
import UserModel from './user'
|
import UserModel, { type IUser } from './user'
|
||||||
import {RIGHTS, type Right} from '@/models/constants/rights'
|
import {RIGHTS, type Right} from '@/models/constants/rights'
|
||||||
|
|
||||||
export default class LinkShareModel extends AbstractModel {
|
export interface ILinkShare extends IAbstract {
|
||||||
id: number
|
id: number
|
||||||
hash: string
|
hash: string
|
||||||
right: Right
|
right: Right
|
||||||
sharedBy: UserModel
|
sharedBy: IUser
|
||||||
sharingType: number // FIXME: use correct numbers
|
sharingType: number // FIXME: use correct numbers
|
||||||
listId: number
|
listId: number
|
||||||
name: string
|
name: string
|
||||||
password: string
|
password: string
|
||||||
created: Date
|
created: Date
|
||||||
updated: Date
|
updated: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class LinkShareModel extends AbstractModel implements ILinkShare {
|
||||||
|
declare id: number
|
||||||
|
declare hash: string
|
||||||
|
declare right: Right
|
||||||
|
sharedBy: IUser
|
||||||
|
declare sharingType: number // FIXME: use correct numbers
|
||||||
|
declare listId: number
|
||||||
|
declare name: string
|
||||||
|
declare password: string
|
||||||
|
created: Date
|
||||||
|
updated: Date
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
// The constructor of AbstractModel handles all the default parsing.
|
// The constructor of AbstractModel handles all the default parsing.
|
||||||
|
|
|
@ -1,28 +1,49 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel, { type IAbstract } from '@/models/abstractModel'
|
||||||
import TaskModel from './task'
|
import TaskModel, { type ITask } from '@/models/task'
|
||||||
import UserModel from './user'
|
import UserModel, { type IUser } from '@/models/user'
|
||||||
import type NamespaceModel from './namespace'
|
import SubscriptionModel, { type ISubscription } from '@/models/subscription'
|
||||||
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
|
import type { INamespace } from '@/models/namespace'
|
||||||
import SubscriptionModel from '@/models/subscription'
|
|
||||||
|
|
||||||
export default class ListModel extends AbstractModel {
|
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
|
||||||
|
|
||||||
|
export interface IList extends IAbstract {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
owner: UserModel
|
owner: IUser
|
||||||
tasks: TaskModel[]
|
tasks: ITask[]
|
||||||
namespaceId: NamespaceModel['id']
|
namespaceId: INamespace['id']
|
||||||
isArchived: boolean
|
isArchived: boolean
|
||||||
hexColor: string
|
hexColor: string
|
||||||
identifier: string
|
identifier: string
|
||||||
backgroundInformation: any
|
backgroundInformation: any
|
||||||
isFavorite: boolean
|
isFavorite: boolean
|
||||||
subscription: SubscriptionModel
|
subscription: ISubscription
|
||||||
position: number
|
position: number
|
||||||
backgroundBlurHash: string
|
backgroundBlurHash: string
|
||||||
|
|
||||||
created: Date
|
created: Date
|
||||||
updated: Date
|
updated: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ListModel extends AbstractModel implements IList {
|
||||||
|
declare id: number
|
||||||
|
declare title: string
|
||||||
|
declare description: string
|
||||||
|
owner: IUser
|
||||||
|
tasks: ITask[]
|
||||||
|
declare namespaceId: INamespace['id']
|
||||||
|
declare isArchived: boolean
|
||||||
|
declare hexColor: string
|
||||||
|
declare identifier: string
|
||||||
|
declare backgroundInformation: any
|
||||||
|
declare isFavorite: boolean
|
||||||
|
declare subscription: ISubscription
|
||||||
|
declare position: number
|
||||||
|
declare backgroundBlurHash: string
|
||||||
|
|
||||||
|
created: Date
|
||||||
|
updated: Date
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
super(data)
|
super(data)
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
import ListModel from './list'
|
import ListModel, { type IList } from './list'
|
||||||
import NamespaceModel from './namespace'
|
import type { INamespace } from './namespace'
|
||||||
|
|
||||||
export default class ListDuplicateModel extends AbstractModel {
|
export interface ListDuplicate {
|
||||||
listId: number
|
listId: number
|
||||||
namespaceId: NamespaceModel['id']
|
namespaceId: INamespace['id']
|
||||||
list: ListModel
|
list: IList
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ListDuplicateModel extends AbstractModel implements ListDuplicate {
|
||||||
|
declare listId: number
|
||||||
|
declare namespaceId: INamespace['id']
|
||||||
|
list: IList
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
super(data)
|
super(data)
|
||||||
|
|
|
@ -1,17 +1,31 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel, { type IAbstract } from './abstractModel'
|
||||||
import ListModel from './list'
|
import ListModel, { type IList } from './list'
|
||||||
import UserModel from './user'
|
import UserModel, { type IUser } from './user'
|
||||||
import SubscriptionModel from '@/models/subscription'
|
import SubscriptionModel, { type ISubscription } from '@/models/subscription'
|
||||||
|
|
||||||
export default class NamespaceModel extends AbstractModel {
|
export interface INamespace extends IAbstract {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
owner: UserModel
|
owner: IUser
|
||||||
lists: ListModel[]
|
lists: IList[]
|
||||||
isArchived: boolean
|
isArchived: boolean
|
||||||
hexColor: string
|
hexColor: string
|
||||||
subscription: SubscriptionModel
|
subscription: ISubscription
|
||||||
|
|
||||||
|
created: Date
|
||||||
|
updated: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class NamespaceModel extends AbstractModel implements INamespace {
|
||||||
|
declare id: number
|
||||||
|
declare title: string
|
||||||
|
declare description: string
|
||||||
|
owner: IUser
|
||||||
|
lists: IList[]
|
||||||
|
declare isArchived: boolean
|
||||||
|
declare hexColor: string
|
||||||
|
declare subscription: ISubscription
|
||||||
|
|
||||||
created: Date
|
created: Date
|
||||||
updated: Date
|
updated: Date
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import AbstractModel from '@/models/abstractModel'
|
import AbstractModel from '@/models/abstractModel'
|
||||||
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
||||||
import UserModel from '@/models/user'
|
import UserModel, { type IUser } from '@/models/user'
|
||||||
import TaskModel from '@/models/task'
|
import TaskModel, { type ITask } from '@/models/task'
|
||||||
import TaskCommentModel from '@/models/taskComment'
|
import TaskCommentModel, { type ITaskComment } from '@/models/taskComment'
|
||||||
import ListModel from '@/models/list'
|
import ListModel from '@/models/list'
|
||||||
import TeamModel from '@/models/team'
|
import TeamModel, { type ITeam } from '@/models/team'
|
||||||
|
|
||||||
export const NOTIFICATION_NAMES = {
|
export const NOTIFICATION_NAMES = {
|
||||||
'TASK_COMMENT': 'task.comment',
|
'TASK_COMMENT': 'task.comment',
|
||||||
|
@ -15,32 +15,32 @@ export const NOTIFICATION_NAMES = {
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
interface Notification {
|
interface Notification {
|
||||||
doer: UserModel
|
doer: IUser
|
||||||
}
|
}
|
||||||
interface NotificationTask extends Notification {
|
interface NotificationTask extends Notification {
|
||||||
task: TaskModel
|
task: ITask
|
||||||
comment: TaskCommentModel
|
comment: ITaskComment
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NotificationAssigned extends Notification {
|
interface NotificationAssigned extends Notification {
|
||||||
task: TaskModel
|
task: ITask
|
||||||
assignee: UserModel
|
assignee: IUser
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NotificationDeleted extends Notification {
|
interface NotificationDeleted extends Notification {
|
||||||
task: TaskModel
|
task: ITask
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NotificationCreated extends Notification {
|
interface NotificationCreated extends Notification {
|
||||||
task: TaskModel
|
task: ITask
|
||||||
}
|
}
|
||||||
|
|
||||||
interface NotificationMemberAdded extends Notification {
|
interface NotificationMemberAdded extends Notification {
|
||||||
member: UserModel
|
member: IUser
|
||||||
team: TeamModel
|
team: ITeam
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class NotificationModel extends AbstractModel {
|
export interface INotification {
|
||||||
id: number
|
id: number
|
||||||
name: string
|
name: string
|
||||||
notification: NotificationTask | NotificationAssigned | NotificationDeleted | NotificationCreated | NotificationMemberAdded
|
notification: NotificationTask | NotificationAssigned | NotificationDeleted | NotificationCreated | NotificationMemberAdded
|
||||||
|
@ -48,6 +48,16 @@ export default class NotificationModel extends AbstractModel {
|
||||||
readAt: Date | null
|
readAt: Date | null
|
||||||
|
|
||||||
created: Date
|
created: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class NotificationModel extends AbstractModel implements INotification {
|
||||||
|
declare id: number
|
||||||
|
declare name: string
|
||||||
|
declare notification: NotificationTask | NotificationAssigned | NotificationDeleted | NotificationCreated | NotificationMemberAdded
|
||||||
|
declare read: boolean
|
||||||
|
readAt: Date | null
|
||||||
|
|
||||||
|
created: Date
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
super(data)
|
super(data)
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
|
|
||||||
export default class PasswordResetModel extends AbstractModel {
|
export interface IPasswordReset {
|
||||||
token: string
|
token: string
|
||||||
newPassword: string
|
newPassword: string
|
||||||
email: string
|
email: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class PasswordResetModel extends AbstractModel implements IPasswordReset {
|
||||||
|
token: string
|
||||||
|
declare newPassword: string
|
||||||
|
declare email: string
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
super(data)
|
super(data)
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from '@/models/abstractModel'
|
||||||
|
|
||||||
export default class PasswordUpdateModel extends AbstractModel {
|
export interface IPasswordUpdate {
|
||||||
newPassword: string
|
newPassword: string
|
||||||
oldPassword: string
|
oldPassword: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class PasswordUpdateModel extends AbstractModel implements IPasswordUpdate {
|
||||||
|
declare newPassword: string
|
||||||
|
declare oldPassword: string
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import AbstractModel from '@/models/abstractModel'
|
import AbstractModel from '@/models/abstractModel'
|
||||||
import UserModel from '@/models/user'
|
import UserModel, { type IUser } from '@/models/user'
|
||||||
|
|
||||||
export default class SavedFilterModel extends AbstractModel {
|
export interface ISavedFilter {
|
||||||
id: 0
|
id: 0
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
|
@ -15,7 +15,26 @@ export default class SavedFilterModel extends AbstractModel {
|
||||||
filterIncludeNulls: boolean
|
filterIncludeNulls: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
owner: any
|
owner: IUser
|
||||||
|
created: Date
|
||||||
|
updated: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SavedFilterModel extends AbstractModel implements ISavedFilter {
|
||||||
|
declare id: 0
|
||||||
|
declare title: string
|
||||||
|
declare description: string
|
||||||
|
declare filters: {
|
||||||
|
sortBy: ('done' | 'id')[]
|
||||||
|
orderBy: ('asc' | 'desc')[]
|
||||||
|
filterBy: 'done'[]
|
||||||
|
filterValue: 'false'[]
|
||||||
|
filterComparator: 'equals'[]
|
||||||
|
filterConcat: 'and'
|
||||||
|
filterIncludeNulls: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
owner: IUser
|
||||||
created: Date
|
created: Date
|
||||||
updated: Date
|
updated: Date
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,20 @@
|
||||||
import AbstractModel from '@/models/abstractModel'
|
import AbstractModel from '@/models/abstractModel'
|
||||||
import UserModel from '@/models/user'
|
import UserModel, { type IUser } from '@/models/user'
|
||||||
|
|
||||||
export default class SubscriptionModel extends AbstractModel {
|
export interface ISubscription {
|
||||||
id: number
|
id: number
|
||||||
entity: string // FIXME: correct type?
|
entity: string // FIXME: correct type?
|
||||||
entityId: number // FIXME: correct type?
|
entityId: number // FIXME: correct type?
|
||||||
user: UserModel
|
user: IUser
|
||||||
|
|
||||||
|
created: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class SubscriptionModel extends AbstractModel implements ISubscription {
|
||||||
|
declare id: number
|
||||||
|
declare entity: string // FIXME: correct type?
|
||||||
|
declare entityId: number // FIXME: correct type?
|
||||||
|
user: IUser
|
||||||
|
|
||||||
created: Date
|
created: Date
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import AbstractModel from './abstractModel'
|
import type { Priority } from '@/models/constants/priorities'
|
||||||
import UserModel from './user'
|
|
||||||
import LabelModel from './label'
|
import AbstractModel from '@/models/abstractModel'
|
||||||
import AttachmentModel from './attachment'
|
import UserModel, { type IUser } from '@/models/user'
|
||||||
|
import LabelModel, { type ILabel } from '@/models/label'
|
||||||
|
import AttachmentModel, {type IAttachment} from '@/models/attachment'
|
||||||
|
import SubscriptionModel, { type ISubscription } from '@/models/subscription'
|
||||||
|
import type { IList } from '@/models/list'
|
||||||
|
|
||||||
import SubscriptionModel from '@/models/subscription'
|
|
||||||
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
||||||
import type ListModel from './list'
|
import type { IBucket } from './bucket'
|
||||||
import type { Priority } from './constants/priorities'
|
|
||||||
|
|
||||||
const SUPPORTS_TRIGGERED_NOTIFICATION = 'Notification' in window && 'showTrigger' in Notification.prototype
|
const SUPPORTS_TRIGGERED_NOTIFICATION = 'Notification' in window && 'showTrigger' in Notification.prototype
|
||||||
export const TASK_DEFAULT_COLOR = '#1973ff'
|
export const TASK_DEFAULT_COLOR = '#1973ff'
|
||||||
|
@ -24,15 +26,15 @@ export interface RepeatAfter {
|
||||||
amount: number
|
amount: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class TaskModel extends AbstractModel {
|
export interface ITask {
|
||||||
id: number
|
id: number
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
done: boolean
|
done: boolean
|
||||||
doneAt: Date | null
|
doneAt: Date | null
|
||||||
priority: Priority
|
priority: Priority
|
||||||
labels: LabelModel[]
|
labels: ILabel[]
|
||||||
assignees: UserModel[]
|
assignees: IUser[]
|
||||||
|
|
||||||
dueDate: Date | null
|
dueDate: Date | null
|
||||||
startDate: Date | null
|
startDate: Date | null
|
||||||
|
@ -41,26 +43,64 @@ export default class TaskModel extends AbstractModel {
|
||||||
repeatFromCurrentDate: boolean
|
repeatFromCurrentDate: boolean
|
||||||
repeatMode: TaskRepeatMode
|
repeatMode: TaskRepeatMode
|
||||||
reminderDates: Date[]
|
reminderDates: Date[]
|
||||||
parentTaskId: TaskModel['id']
|
parentTaskId: ITask['id']
|
||||||
hexColor: string
|
hexColor: string
|
||||||
percentDone: number
|
percentDone: number
|
||||||
relatedTasks: { [relationKind: string]: TaskModel } // FIXME: use relationKinds
|
relatedTasks: { [relationKind: string]: ITask } // FIXME: use relationKinds
|
||||||
attachments: AttachmentModel[]
|
attachments: IAttachment[]
|
||||||
identifier: string
|
identifier: string
|
||||||
index: number
|
index: number
|
||||||
isFavorite: boolean
|
isFavorite: boolean
|
||||||
subscription: SubscriptionModel
|
subscription: ISubscription
|
||||||
|
|
||||||
position: number
|
position: number
|
||||||
kanbanPosition: number
|
kanbanPosition: number
|
||||||
|
|
||||||
createdBy: UserModel
|
createdBy: IUser
|
||||||
dpschen marked this conversation as resolved
Outdated
konrad
commented
Probably my lack of typescript knowledge speaking here: I've seen this a few times now, is there a special reason to use Probably my lack of typescript knowledge speaking here: I've seen this a few times now, is there a special reason to use `IList['id']` instead of `IList.id`?
dpschen
commented
IList is a type and not a namespace. Afaik you can only access types in namespaces like IList is a type and not a namespace. Afaik you can only access types in namespaces like `IList.id`. To access properties of types you need that bracket notation.
|
|||||||
created: Date
|
created: Date
|
||||||
updated: Date
|
updated: Date
|
||||||
|
|
||||||
listId: ListModel['id'] // Meta, only used when creating a new task
|
listId: IList['id'] // Meta, only used when creating a new task
|
||||||
|
bucketId: IBucket['id']
|
||||||
|
}
|
||||||
|
|
||||||
constructor(data: Partial<TaskModel>) {
|
export default class TaskModel extends AbstractModel implements ITask {
|
||||||
|
id: number
|
||||||
|
title: string
|
||||||
|
declare description: string
|
||||||
|
declare done: boolean
|
||||||
|
doneAt: Date | null
|
||||||
|
declare priority: Priority
|
||||||
|
labels: ILabel[]
|
||||||
|
assignees: IUser[]
|
||||||
|
|
||||||
|
dueDate: Date | null
|
||||||
|
startDate: Date | null
|
||||||
|
endDate: Date | null
|
||||||
|
declare repeatAfter: number | RepeatAfter
|
||||||
|
declare repeatFromCurrentDate: boolean
|
||||||
|
declare repeatMode: TaskRepeatMode
|
||||||
|
reminderDates: Date[]
|
||||||
|
declare parentTaskId: ITask['id']
|
||||||
|
declare hexColor: string
|
||||||
|
declare percentDone: number
|
||||||
|
declare relatedTasks: { [relationKind: string]: ITask } // FIXME: use relationKinds
|
||||||
|
attachments: IAttachment[]
|
||||||
|
declare identifier: string
|
||||||
|
declare index: number
|
||||||
|
declare isFavorite: boolean
|
||||||
|
declare subscription: ISubscription
|
||||||
|
|
||||||
|
declare position: number
|
||||||
|
declare kanbanPosition: number
|
||||||
|
|
||||||
|
createdBy: IUser
|
||||||
|
created: Date
|
||||||
|
updated: Date
|
||||||
|
|
||||||
|
listId: IList['id'] // Meta, only used when creating a new task
|
||||||
|
|
||||||
|
constructor(data: Partial<ITask>) {
|
||||||
super(data)
|
super(data)
|
||||||
|
|
||||||
this.id = Number(this.id)
|
this.id = Number(this.id)
|
||||||
|
@ -120,6 +160,7 @@ export default class TaskModel extends AbstractModel {
|
||||||
|
|
||||||
this.listId = Number(this.listId)
|
this.listId = Number(this.listId)
|
||||||
}
|
}
|
||||||
|
bucketId: number
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
import type UserModel from './user'
|
import type { ITask } from './task'
|
||||||
import type TaskModel from './task'
|
import type { IUser } from './user'
|
||||||
|
|
||||||
export default class TaskAssigneeModel extends AbstractModel {
|
export interface ITaskAssignee {
|
||||||
created: Date
|
created: Date
|
||||||
userId: UserModel['id']
|
userId: IUser['id']
|
||||||
taskId: TaskModel['id']
|
taskId: ITask['id']
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TaskAssigneeModel extends AbstractModel implements ITaskAssignee {
|
||||||
|
created: Date
|
||||||
|
declare userId: IUser['id']
|
||||||
|
declare taskId: ITask['id']
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
super(data)
|
super(data)
|
||||||
|
|
|
@ -1,12 +1,22 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
import UserModel from './user'
|
import UserModel, { type IUser } from './user'
|
||||||
import type TaskModel from './task'
|
import type { ITask } from './task'
|
||||||
|
|
||||||
export default class TaskCommentModel extends AbstractModel {
|
export interface ITaskComment {
|
||||||
id: number
|
id: number
|
||||||
taskId: TaskModel['id']
|
taskId: ITask['id']
|
||||||
comment: string
|
comment: string
|
||||||
author: UserModel
|
author: IUser
|
||||||
|
|
||||||
|
created: Date
|
||||||
|
updated: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TaskCommentModel extends AbstractModel implements ITaskComment {
|
||||||
|
declare id: number
|
||||||
|
declare taskId: ITask['id']
|
||||||
|
declare comment: string
|
||||||
|
author: IUser
|
||||||
|
|
||||||
created: Date
|
created: Date
|
||||||
updated: Date
|
updated: Date
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
import UserModel from './user'
|
import UserModel, { type IUser } from './user'
|
||||||
import type TaskModel from './task'
|
import type { ITask } from './task'
|
||||||
|
|
||||||
export const RELATION_KIND = {
|
export const RELATION_KIND = {
|
||||||
'SUBTASK': 'subtask',
|
'SUBTASK': 'subtask',
|
||||||
|
@ -19,13 +19,23 @@ export const RELATION_KINDS = [...Object.values(RELATION_KIND)] as const
|
||||||
|
|
||||||
export type RelationKind = typeof RELATION_KINDS[number]
|
export type RelationKind = typeof RELATION_KINDS[number]
|
||||||
|
|
||||||
export default class TaskRelationModel extends AbstractModel {
|
export interface ITaskRelationModel {
|
||||||
id: number
|
id: number
|
||||||
otherTaskId: TaskModel['id']
|
otherTaskId: ITask['id']
|
||||||
taskId: TaskModel['id']
|
taskId: ITask['id']
|
||||||
relationKind: RelationKind
|
relationKind: RelationKind
|
||||||
|
|
||||||
createdBy: UserModel
|
createdBy: IUser
|
||||||
|
created: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TaskRelationModel extends AbstractModel implements ITaskRelationModel {
|
||||||
|
declare id: number
|
||||||
|
declare otherTaskId: ITask['id']
|
||||||
|
declare taskId: ITask['id']
|
||||||
|
declare relationKind: RelationKind
|
||||||
|
|
||||||
|
createdBy: IUser
|
||||||
created: Date
|
created: Date
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
|
|
|
@ -1,16 +1,28 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
import UserModel from './user'
|
import UserModel, { type IUser } from './user'
|
||||||
import TeamMemberModel from './teamMember'
|
import TeamMemberModel, { type ITeamMember } from './teamMember'
|
||||||
import {RIGHTS, type Right} from '@/models/constants/rights'
|
import {RIGHTS, type Right} from '@/models/constants/rights'
|
||||||
|
|
||||||
export default class TeamModel extends AbstractModel {
|
export interface ITeam {
|
||||||
id: 0
|
id: number
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
members: TeamMemberModel[]
|
members: ITeamMember[]
|
||||||
right: Right
|
right: Right
|
||||||
|
|
||||||
createdBy: UserModel
|
createdBy: IUser
|
||||||
|
created: Date
|
||||||
|
updated: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TeamModel extends AbstractModel implements ITeam {
|
||||||
|
declare id: number
|
||||||
|
declare name: string
|
||||||
|
declare description: string
|
||||||
|
members: ITeamMember[]
|
||||||
|
declare right: Right
|
||||||
|
|
||||||
|
createdBy: IUser
|
||||||
created: Date
|
created: Date
|
||||||
updated: Date
|
updated: Date
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import TeamShareBaseModel from './teamShareBase'
|
import TeamShareBaseModel from './teamShareBase'
|
||||||
import type ListModel from './list'
|
import type { IList } from './list'
|
||||||
|
|
||||||
export default class TeamListModel extends TeamShareBaseModel {
|
export interface ITeamList {
|
||||||
listId: ListModel['id']
|
listId: IList['id']
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TeamListModel extends TeamShareBaseModel implements ITeamList {
|
||||||
|
declare listId: IList['id']
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,9 +1,14 @@
|
||||||
import UserModel from './user'
|
import UserModel from './user'
|
||||||
import type ListModel from './list'
|
import type { IList } from './list'
|
||||||
|
|
||||||
export default class TeamMemberModel extends UserModel {
|
export interface ITeamMember {
|
||||||
admin: boolean
|
admin: boolean
|
||||||
teamId: ListModel['id']
|
teamId: IList['id']
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TeamMemberModel extends UserModel implements ITeamMember {
|
||||||
|
declare admin: boolean
|
||||||
|
declare teamId: IList['id']
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,8 +1,12 @@
|
||||||
import TeamShareBaseModel from './teamShareBase'
|
import TeamShareBaseModel from './teamShareBase'
|
||||||
import type NamespaceModel from './namespace'
|
import type { INamespace } from './namespace'
|
||||||
|
|
||||||
export default class TeamNamespaceModel extends TeamShareBaseModel {
|
export interface ITeamNamespace {
|
||||||
namespaceId: NamespaceModel['id']
|
namespaceId: INamespace['id']
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TeamNamespaceModel extends TeamShareBaseModel implements ITeamNamespace {
|
||||||
|
declare namespaceId: INamespace['id']
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,14 +1,22 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
import type TeamModel from './team'
|
|
||||||
import {RIGHTS, type Right} from '@/models/constants/rights'
|
import {RIGHTS, type Right} from '@/models/constants/rights'
|
||||||
|
import type { ITeam } from './team'
|
||||||
|
|
||||||
|
export interface ITeamShareBase {
|
||||||
|
teamId: ITeam['id']
|
||||||
|
right: Right
|
||||||
|
|
||||||
|
created: Date
|
||||||
|
updated: Date
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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 {
|
export default class TeamShareBaseModel extends AbstractModel implements ITeamShareBase {
|
||||||
teamId: TeamModel['id']
|
declare teamId: ITeam['id']
|
||||||
right: Right
|
declare right: Right
|
||||||
|
|
||||||
created: Date
|
created: Date
|
||||||
updated: Date
|
updated: Date
|
||||||
|
|
|
@ -1,9 +1,15 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
|
|
||||||
export default class TotpModel extends AbstractModel {
|
export interface ITotp {
|
||||||
secret: string
|
secret: string
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
url: string
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class TotpModel extends AbstractModel implements ITotp{
|
||||||
|
declare secret: string
|
||||||
|
declare enabled: boolean
|
||||||
|
declare url: string
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
import UserSettingsModel from '@/models/userSettings'
|
import UserSettingsModel, { type IUserSettings } from '@/models/userSettings'
|
||||||
|
|
||||||
export default class UserModel extends AbstractModel {
|
export interface IUser {
|
||||||
id: number
|
id: number
|
||||||
email: string
|
email: string
|
||||||
username: string
|
username: string
|
||||||
|
@ -9,7 +9,18 @@ export default class UserModel extends AbstractModel {
|
||||||
|
|
||||||
created: Date
|
created: Date
|
||||||
updated: Date
|
updated: Date
|
||||||
settings: UserSettingsModel
|
settings: IUserSettings
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UserModel extends AbstractModel implements IUser {
|
||||||
|
declare id: number
|
||||||
|
declare email: string
|
||||||
|
declare username: string
|
||||||
|
declare name: string
|
||||||
|
|
||||||
|
created: Date
|
||||||
|
updated: Date
|
||||||
|
settings: IUserSettings
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
super(data)
|
super(data)
|
||||||
|
@ -28,6 +39,7 @@ export default class UserModel extends AbstractModel {
|
||||||
email: '',
|
email: '',
|
||||||
username: '',
|
username: '',
|
||||||
name: '',
|
name: '',
|
||||||
|
|
||||||
created: null,
|
created: null,
|
||||||
updated: null,
|
updated: null,
|
||||||
settings: null,
|
settings: null,
|
||||||
|
|
|
@ -1,8 +1,13 @@
|
||||||
import UserShareBaseModel from './userShareBase'
|
import UserShareBaseModel from './userShareBase'
|
||||||
import type ListModel from './list'
|
import type { IList } from './list'
|
||||||
|
|
||||||
|
export interface IUserList {
|
||||||
|
listId: IList['id']
|
||||||
|
}
|
||||||
|
|
||||||
// This class extends the user share model with a 'rights' parameter which is used in sharing
|
// This class extends the user share model with a 'rights' parameter which is used in sharing
|
||||||
export default class UserListModel extends UserShareBaseModel {
|
export default class UserListModel extends UserShareBaseModel implements IUserList {
|
||||||
listId: ListModel['id']
|
declare listId: IList['id']
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
import UserShareBaseModel from './userShareBase'
|
import UserShareBaseModel from './userShareBase'
|
||||||
import type NamespaceModel from './namespace'
|
import type { INamespace } from './namespace'
|
||||||
|
|
||||||
|
export interface IUserNamespace {
|
||||||
|
namespaceId: INamespace['id']
|
||||||
|
}
|
||||||
|
|
||||||
// This class extends the user share model with a 'rights' parameter which is used in sharing
|
// This class extends the user share model with a 'rights' parameter which is used in sharing
|
||||||
export default class UserNamespaceModel extends UserShareBaseModel {
|
export default class UserNamespaceModel extends UserShareBaseModel implements IUserNamespace {
|
||||||
namespaceId: NamespaceModel['id']
|
declare namespaceId: INamespace['id']
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,16 +1,27 @@
|
||||||
|
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
import type ListModel from './list'
|
import type { IList } from './list'
|
||||||
|
|
||||||
export default class UserSettingsModel extends AbstractModel {
|
export interface IUserSettings {
|
||||||
name: string
|
name: string
|
||||||
emailRemindersEnabled: boolean
|
emailRemindersEnabled: boolean
|
||||||
discoverableByName: boolean
|
discoverableByName: boolean
|
||||||
discoverableByEmail: boolean
|
discoverableByEmail: boolean
|
||||||
overdueTasksRemindersEnabled: boolean
|
overdueTasksRemindersEnabled: boolean
|
||||||
defaultListId: undefined | ListModel['id']
|
defaultListId: undefined | IList['id']
|
||||||
weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6
|
weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6
|
||||||
timezone: string
|
timezone: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UserSettingsModel extends AbstractModel implements IUserSettings {
|
||||||
|
declare name: string
|
||||||
|
declare emailRemindersEnabled: boolean
|
||||||
|
declare discoverableByName: boolean
|
||||||
|
declare discoverableByEmail: boolean
|
||||||
|
declare overdueTasksRemindersEnabled: boolean
|
||||||
|
declare defaultListId: undefined | IList['id']
|
||||||
|
declare weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6
|
||||||
|
declare timezone: string
|
||||||
|
|
||||||
defaults() {
|
defaults() {
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -1,13 +1,21 @@
|
||||||
import AbstractModel from './abstractModel'
|
import AbstractModel from './abstractModel'
|
||||||
import type UserModel from './user'
|
|
||||||
import {RIGHTS, type Right} from '@/models/constants/rights'
|
import {RIGHTS, type Right} from '@/models/constants/rights'
|
||||||
|
import type { IUser } from './user'
|
||||||
|
|
||||||
export default class UserShareBaseModel extends AbstractModel {
|
export interface IUserShareBase {
|
||||||
userId: UserModel['id']
|
userId: IUser['id']
|
||||||
right: Right
|
right: Right
|
||||||
|
|
||||||
created: Date
|
created: Date
|
||||||
updated: Date
|
updated: Date
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class UserShareBaseModel extends AbstractModel implements IUserShareBase {
|
||||||
|
declare userId: IUser['id']
|
||||||
|
declare right: Right
|
||||||
|
|
||||||
|
created: Date
|
||||||
|
updated: Date
|
||||||
|
|
||||||
constructor(data) {
|
constructor(data) {
|
||||||
super(data)
|
super(data)
|
||||||
|
|
|
@ -3,7 +3,7 @@ import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest'
|
||||||
import {parseTaskText} from './parseTaskText'
|
import {parseTaskText} 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 '@/models/constants/priorities.ts'
|
import {PRIORITIES} from '@/models/constants/priorities'
|
||||||
|
|
||||||
describe('Parse Task Text', () => {
|
describe('Parse Task Text', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -2,9 +2,9 @@ import {AuthenticatedHTTPFactory} from '@/http-common'
|
||||||
import type {Method} from 'axios'
|
import type {Method} from 'axios'
|
||||||
|
|
||||||
import {objectToSnakeCase} from '@/helpers/case'
|
import {objectToSnakeCase} from '@/helpers/case'
|
||||||
import AbstractModel from '@/models/abstractModel'
|
import AbstractModel, { type IAbstract } from '@/models/abstractModel'
|
||||||
import type { Right } from '@/models/constants/rights'
|
import type { Right } from '@/models/constants/rights'
|
||||||
import type FileModel from '@/models/file'
|
import type { IFile } from '@/models/file'
|
||||||
|
|
||||||
interface Paths {
|
interface Paths {
|
||||||
create : string
|
create : string
|
||||||
|
@ -12,6 +12,7 @@ interface Paths {
|
||||||
getAll : string
|
getAll : string
|
||||||
update : string
|
update : string
|
||||||
delete : string
|
delete : string
|
||||||
|
reset?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertObject(o: Record<string, unknown>) {
|
function convertObject(o: Record<string, unknown>) {
|
||||||
|
@ -39,7 +40,7 @@ function prepareParams(params: Record<string, unknown | unknown[]>) {
|
||||||
return objectToSnakeCase(params)
|
return objectToSnakeCase(params)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class AbstractService<Model extends AbstractModel = AbstractModel> {
|
export default class AbstractService<Model extends IAbstract = IAbstract> {
|
||||||
|
|
||||||
/////////////////////////////
|
/////////////////////////////
|
||||||
// Initial variable definitions
|
// Initial variable definitions
|
||||||
|
@ -269,7 +270,7 @@ export default class AbstractService<Model extends AbstractModel = AbstractModel
|
||||||
* This is a more abstract implementation which only does a get request.
|
* This is a more abstract implementation which only does a get request.
|
||||||
* Services which need more flexibility can use this.
|
* Services which need more flexibility can use this.
|
||||||
*/
|
*/
|
||||||
async getM(url : string, model = new AbstractModel({}) as Model, params: Record<string, unknown> = {}) {
|
async getM(url : string, model : Model = new AbstractModel({}), params: Record<string, unknown> = {}) {
|
||||||
const cancel = this.setLoading()
|
const cancel = this.setLoading()
|
||||||
|
|
||||||
model = this.beforeGet(model)
|
model = this.beforeGet(model)
|
||||||
|
@ -285,7 +286,7 @@ export default class AbstractService<Model extends AbstractModel = AbstractModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBlobUrl(url : string, method = 'GET' as Method, data = {}) {
|
async getBlobUrl(url : string, method : Method = 'GET', data = {}) {
|
||||||
const response = await this.http({
|
const response = await this.http({
|
||||||
url,
|
url,
|
||||||
method,
|
method,
|
||||||
|
@ -302,7 +303,7 @@ export default class AbstractService<Model extends AbstractModel = AbstractModel
|
||||||
* @param params Optional query parameters
|
* @param params Optional query parameters
|
||||||
* @param page The page to get
|
* @param page The page to get
|
||||||
*/
|
*/
|
||||||
async getAll(model : Model = new AbstractModel({}) as Model, params = {}, page = 1) {
|
async getAll(model : Model = new AbstractModel({}), params = {}, page = 1) {
|
||||||
if (this.paths.getAll === '') {
|
if (this.paths.getAll === '') {
|
||||||
throw new Error('This model is not able to get data.')
|
throw new Error('This model is not able to get data.')
|
||||||
}
|
}
|
||||||
|
@ -408,10 +409,10 @@ export default class AbstractService<Model extends AbstractModel = AbstractModel
|
||||||
/**
|
/**
|
||||||
* Uploads a file to a url.
|
* Uploads a file to a url.
|
||||||
* @param url
|
* @param url
|
||||||
* @param file {FileModel}
|
* @param file {IFile}
|
||||||
* @param fieldName The name of the field the file is uploaded to.
|
* @param fieldName The name of the field the file is uploaded to.
|
||||||
*/
|
*/
|
||||||
uploadFile(url : string, file: FileModel, fieldName : string) {
|
uploadFile(url : string, file: IFile, fieldName : string) {
|
||||||
return this.uploadBlob(url, new Blob([file]), fieldName, file.name)
|
return this.uploadBlob(url, new Blob([file]), fieldName, file.name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import AbstractService from './abstractService'
|
import AbstractService from './abstractService'
|
||||||
import AttachmentModel from '../models/attachment'
|
import AttachmentModel, { type IAttachment } from '../models/attachment'
|
||||||
import {formatISO} from 'date-fns'
|
import {formatISO} from 'date-fns'
|
||||||
import {downloadBlob} from '@/helpers/downloadBlob'
|
import {downloadBlob} from '@/helpers/downloadBlob'
|
||||||
import type FileModel from '@/models/file'
|
import type { IFile } from '@/models/file'
|
||||||
|
|
||||||
export default class AttachmentService extends AbstractService<AttachmentModel> {
|
export default class AttachmentService extends AbstractService<AttachmentModel> {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -13,7 +13,7 @@ export default class AttachmentService extends AbstractService<AttachmentModel>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
processModel(model: AttachmentModel) {
|
processModel(model: IAttachment) {
|
||||||
model.created = formatISO(new Date(model.created))
|
model.created = formatISO(new Date(model.created))
|
||||||
return model
|
return model
|
||||||
}
|
}
|
||||||
|
@ -34,11 +34,11 @@ export default class AttachmentService extends AbstractService<AttachmentModel>
|
||||||
return data
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
getBlobUrl(model: AttachmentModel) {
|
getBlobUrl(model: IAttachment) {
|
||||||
return AbstractService.prototype.getBlobUrl.call(this, '/tasks/' + model.taskId + '/attachments/' + model.id)
|
return AbstractService.prototype.getBlobUrl.call(this, '/tasks/' + model.taskId + '/attachments/' + model.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
async download(model: AttachmentModel) {
|
async download(model: IAttachment) {
|
||||||
const url = await this.getBlobUrl(model)
|
const url = await this.getBlobUrl(model)
|
||||||
return downloadBlob(url, model.file.name)
|
return downloadBlob(url, model.file.name)
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ export default class AttachmentService extends AbstractService<AttachmentModel>
|
||||||
* @param files
|
* @param files
|
||||||
* @returns {Promise<any|never>}
|
* @returns {Promise<any|never>}
|
||||||
*/
|
*/
|
||||||
create(model: AttachmentModel, files: FileModel[]) {
|
create(model: IAttachment, files: IFile[]) {
|
||||||
const data = new FormData()
|
const data = new FormData()
|
||||||
for (let i = 0; i < files.length; i++) {
|
for (let i = 0; i < files.length; i++) {
|
||||||
// TODO: Validation of file size
|
// TODO: Validation of file size
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import AbstractService from './abstractService'
|
import AbstractService from './abstractService'
|
||||||
import AvatarModel from '../models/avatar'
|
import AvatarModel, { type IAvatar } from '../models/avatar'
|
||||||
|
|
||||||
export default class AvatarService extends AbstractService {
|
export default class AvatarService extends AbstractService<IAvatar> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
get: '/user/settings/avatar',
|
get: '/user/settings/avatar',
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import AbstractService from './abstractService'
|
import AbstractService from './abstractService'
|
||||||
import BackgroundImageModel from '../models/backgroundImage'
|
import BackgroundImageModel, { type IBackgroundImage } from '../models/backgroundImage'
|
||||||
import ListModel from '../models/list'
|
import ListModel from '@/models/list'
|
||||||
|
|
||||||
export default class BackgroundUnsplashService extends AbstractService {
|
export default class BackgroundUnsplashService extends AbstractService<IBackgroundImage> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
getAll: '/backgrounds/unsplash/search',
|
getAll: '/backgrounds/unsplash/search',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import AbstractService from './abstractService'
|
import AbstractService from './abstractService'
|
||||||
import ListModel from '../models/list'
|
import ListModel, { type IList } from '../models/list'
|
||||||
import type FileModel from '@/models/file'
|
import type { IFile } from '@/models/file'
|
||||||
|
|
||||||
export default class BackgroundUploadService extends AbstractService {
|
export default class BackgroundUploadService extends AbstractService {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
@ -22,7 +22,7 @@ export default class BackgroundUploadService extends AbstractService {
|
||||||
* @param file
|
* @param file
|
||||||
* @returns {Promise<any|never>}
|
* @returns {Promise<any|never>}
|
||||||
*/
|
*/
|
||||||
create(listId: ListModel['id'], file: FileModel) {
|
create(listId: IList['id'], file: IFile) {
|
||||||
return this.uploadFile(
|
return this.uploadFile(
|
||||||
this.getReplacedRoute(this.paths.create, {listId}),
|
this.getReplacedRoute(this.paths.create, {listId}),
|
||||||
file,
|
file,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import AbstractService from './abstractService'
|
import AbstractService from './abstractService'
|
||||||
import BucketModel from '../models/bucket'
|
import BucketModel, { type IBucket } from '../models/bucket'
|
||||||
import TaskService from '@/services/task'
|
import TaskService from '@/services/task'
|
||||||
|
|
||||||
export default class BucketService extends AbstractService {
|
export default class BucketService extends AbstractService<IBucket> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
getAll: '/lists/{listId}/buckets',
|
getAll: '/lists/{listId}/buckets',
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import {formatISO} from 'date-fns'
|
import {formatISO} from 'date-fns'
|
||||||
import CaldavTokenModel from '../models/caldavToken'
|
import CaldavTokenModel, {type ICaldavToken} from '../models/caldavToken'
|
||||||
import AbstractService from './abstractService'
|
import AbstractService from './abstractService'
|
||||||
|
|
||||||
export default class CaldavTokenService extends AbstractService {
|
export default class CaldavTokenService extends AbstractService<ICaldavToken> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
getAll: '/user/settings/token/caldav',
|
getAll: '/user/settings/token/caldav',
|
||||||
|
@ -11,7 +11,7 @@ export default class CaldavTokenService extends AbstractService {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
processModel(model: Partial<CaldavTokenModel>) {
|
processModel(model: Partial<ICaldavToken>) {
|
||||||
return {
|
return {
|
||||||
...model,
|
...model,
|
||||||
created: formatISO(new Date(model.created)),
|
created: formatISO(new Date(model.created)),
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import AbstractService from './abstractService'
|
import AbstractService from './abstractService'
|
||||||
import LabelModel from '../models/label'
|
import LabelModel, { type ILabel } from '@/models/label'
|
||||||
import {formatISO} from 'date-fns'
|
import {formatISO} from 'date-fns'
|
||||||
import {colorFromHex} from '@/helpers/color/colorFromHex'
|
import {colorFromHex} from '@/helpers/color/colorFromHex'
|
||||||
|
|
||||||
export default class LabelService extends AbstractService {
|
export default class LabelService extends AbstractService<ILabel> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
create: '/labels',
|
create: '/labels',
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
import AbstractService from './abstractService'
|
import AbstractService from './abstractService'
|
||||||
import ListModel from '../models/list'
|
import ListModel, { type IList } from '@/models/list'
|
||||||
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'
|
||||||
|
|
||||||
export default class ListService extends AbstractService {
|
export default class ListService extends AbstractService<IList> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
create: '/namespaces/{namespaceId}/lists',
|
create: '/namespaces/{namespaceId}/lists',
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
import AbstractService from './abstractService'
|
import AbstractService from './abstractService'
|
||||||
import NamespaceModel from '../models/namespace'
|
import NamespaceModel, { type INamespace } from '../models/namespace'
|
||||||
import {formatISO} from 'date-fns'
|
import {formatISO} from 'date-fns'
|
||||||
import {colorFromHex} from '@/helpers/color/colorFromHex'
|
import {colorFromHex} from '@/helpers/color/colorFromHex'
|
||||||
|
|
||||||
export default class NamespaceService extends AbstractService {
|
export default class NamespaceService extends AbstractService<INamespace> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
create: '/namespaces',
|
create: '/namespaces',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import AbstractService from './abstractService'
|
import AbstractService from './abstractService'
|
||||||
import TaskModel from '../models/task'
|
import TaskModel, { type ITask } from '../models/task'
|
||||||
import AttachmentService from './attachment'
|
import AttachmentService from './attachment'
|
||||||
import LabelService from './label'
|
import LabelService from './label'
|
||||||
|
|
||||||
|
@ -113,7 +113,7 @@ export default class TaskService extends AbstractService {
|
||||||
model.labels = model.labels.map(l => labelService.processModel(l))
|
model.labels = model.labels.map(l => labelService.processModel(l))
|
||||||
}
|
}
|
||||||
|
|
||||||
return model as TaskModel
|
return model as ITask
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import AbstractService from './abstractService'
|
import AbstractService from './abstractService'
|
||||||
import TaskCommentModel from '../models/taskComment'
|
import TaskCommentModel, { type ITaskComment } from '../models/taskComment'
|
||||||
import {formatISO} from 'date-fns'
|
import {formatISO} from 'date-fns'
|
||||||
|
|
||||||
export default class TaskCommentService extends AbstractService {
|
export default class TaskCommentService extends AbstractService<ITaskComment> {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({
|
super({
|
||||||
create: '/tasks/{taskId}/comments',
|
create: '/tasks/{taskId}/comments',
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
import type { ActionContext } from 'vuex'
|
||||||
import {LOADING, LOADING_MODULE} from './mutation-types'
|
import {LOADING, LOADING_MODULE} from './mutation-types'
|
||||||
|
import type { RootStoreState } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This helper sets the loading state with a 100ms delay to avoid flickering.
|
* This helper sets the loading state with a 100ms delay to avoid flickering.
|
||||||
|
@ -7,7 +9,11 @@ import {LOADING, LOADING_MODULE} from './mutation-types'
|
||||||
* @param {null|String} module The module that is loading. This parameter allows components to listen for specific parts of the application loading.
|
* @param {null|String} module The module that is loading. This parameter allows components to listen for specific parts of the application loading.
|
||||||
* @param {null|function} loadFunc If not null, this function will be executed instead of the default setting loading.
|
* @param {null|function} loadFunc If not null, this function will be executed instead of the default setting loading.
|
||||||
*/
|
*/
|
||||||
export const setLoading = (context, module = null, loadFunc = null) => {
|
export function setLoading<State>(
|
||||||
|
context : ActionContext<State, RootStoreState>,
|
||||||
|
module : string | null = null,
|
||||||
|
loadFunc : (() => void) | null = null,
|
||||||
|
) {
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
if (loadFunc === null) {
|
if (loadFunc === null) {
|
||||||
context.commit(LOADING, true, {root: true})
|
context.commit(LOADING, true, {root: true})
|
||||||
|
|
|
@ -25,7 +25,9 @@ import ListModel from '@/models/list'
|
||||||
import ListService from '../services/list'
|
import ListService from '../services/list'
|
||||||
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
|
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
|
||||||
|
|
||||||
export const store = createStore({
|
import type { RootStoreState } from './types'
|
||||||
|
|
||||||
|
export const store = createStore<RootStoreState>({
|
||||||
strict: import.meta.env.DEV,
|
strict: import.meta.env.DEV,
|
||||||
modules: {
|
modules: {
|
||||||
config,
|
config,
|
||||||
|
@ -37,7 +39,7 @@ export const store = createStore({
|
||||||
attachments,
|
attachments,
|
||||||
labels,
|
labels,
|
||||||
},
|
},
|
||||||
state: {
|
state: () => ({
|
||||||
loading: false,
|
loading: false,
|
||||||
loadingModule: null,
|
loadingModule: null,
|
||||||
// This is used to highlight the current list in menu for all list related views
|
// This is used to highlight the current list in menu for all list related views
|
||||||
|
@ -51,7 +53,7 @@ export const store = createStore({
|
||||||
menuActive: true,
|
menuActive: true,
|
||||||
keyboardShortcutsActive: false,
|
keyboardShortcutsActive: false,
|
||||||
quickActionsActive: false,
|
quickActionsActive: false,
|
||||||
},
|
}),
|
||||||
mutations: {
|
mutations: {
|
||||||
[LOADING](state, loading) {
|
[LOADING](state, loading) {
|
||||||
state.loading = loading
|
state.loading = loading
|
||||||
|
|
|
@ -1,21 +1,24 @@
|
||||||
import {findIndexById} from '@/helpers/utils'
|
import {findIndexById} from '@/helpers/utils'
|
||||||
|
|
||||||
|
import type { AttachmentState } from '@/store/types'
|
||||||
|
import type { IAttachment } from '@/models/attachment'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: () => ({
|
state: (): AttachmentState => ({
|
||||||
attachments: [],
|
attachments: [],
|
||||||
}),
|
}),
|
||||||
mutations: {
|
mutations: {
|
||||||
set(state, attachments) {
|
set(state: AttachmentState, attachments: IAttachment[]) {
|
||||||
console.debug('Set attachments', attachments)
|
console.debug('Set attachments', attachments)
|
||||||
state.attachments = attachments
|
state.attachments = attachments
|
||||||
},
|
},
|
||||||
add(state, attachment) {
|
add(state: AttachmentState, attachment: IAttachment) {
|
||||||
console.debug('Add attachement', attachment)
|
console.debug('Add attachement', attachment)
|
||||||
state.attachments.push(attachment)
|
state.attachments.push(attachment)
|
||||||
},
|
},
|
||||||
removeById(state, id) {
|
removeById(state: AttachmentState, id: IAttachment['id']) {
|
||||||
const attachmentIndex = findIndexById(state.attachments, id)
|
const attachmentIndex = findIndexById<IAttachment>(state.attachments, id)
|
||||||
state.attachments.splice(attachmentIndex, 1)
|
state.attachments.splice(attachmentIndex, 1)
|
||||||
console.debug('Remove attachement', id)
|
console.debug('Remove attachement', id)
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import type { ActionContext } from 'vuex'
|
||||||
|
|
||||||
import {HTTPFactory, AuthenticatedHTTPFactory} from '@/http-common'
|
import {HTTPFactory, AuthenticatedHTTPFactory} from '@/http-common'
|
||||||
import {i18n, getCurrentLanguage, saveLanguage} from '@/i18n'
|
import {i18n, getCurrentLanguage, saveLanguage} from '@/i18n'
|
||||||
import {objectToSnakeCase} from '@/helpers/case'
|
import {objectToSnakeCase} from '@/helpers/case'
|
||||||
|
@ -8,12 +10,10 @@ import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
|
||||||
import {setLoading} from '@/store/helper'
|
import {setLoading} from '@/store/helper'
|
||||||
import {success} from '@/message'
|
import {success} from '@/message'
|
||||||
import {redirectToProvider} from '@/helpers/redirectToProvider'
|
import {redirectToProvider} from '@/helpers/redirectToProvider'
|
||||||
|
import type { RootStoreState, AuthState, Info} from '@/store/types'
|
||||||
|
import {AUTH_TYPES} from '@/store/types'
|
||||||
|
import type { IUserSettings } from '@/models/userSettings'
|
||||||
|
|
||||||
const AUTH_TYPES = {
|
|
||||||
'UNKNOWN': 0,
|
|
||||||
'USER': 1,
|
|
||||||
'LINK_SHARE': 2,
|
|
||||||
}
|
|
||||||
|
|
||||||
const defaultSettings = settings => {
|
const defaultSettings = settings => {
|
||||||
if (typeof settings.weekStart === 'undefined' || settings.weekStart === '') {
|
if (typeof settings.weekStart === 'undefined' || settings.weekStart === '') {
|
||||||
|
@ -24,7 +24,7 @@ const defaultSettings = settings => {
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: () => ({
|
state: (): AuthState => ({
|
||||||
authenticated: false,
|
authenticated: false,
|
||||||
isLinkShareAuth: false,
|
isLinkShareAuth: false,
|
||||||
info: null,
|
info: null,
|
||||||
|
@ -34,13 +34,13 @@ export default {
|
||||||
settings: {},
|
settings: {},
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
authUser(state) {
|
authUser(state: AuthState) {
|
||||||
return state.authenticated && (
|
return state.authenticated && (
|
||||||
state.info &&
|
state.info &&
|
||||||
state.info.type === AUTH_TYPES.USER
|
state.info.type === AUTH_TYPES.USER
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
authLinkShare(state) {
|
authLinkShare(state: AuthState) {
|
||||||
return state.authenticated && (
|
return state.authenticated && (
|
||||||
state.info &&
|
state.info &&
|
||||||
state.info.type === AUTH_TYPES.LINK_SHARE
|
state.info.type === AUTH_TYPES.LINK_SHARE
|
||||||
|
@ -48,7 +48,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
info(state, info) {
|
info(state: AuthState, info: Info) {
|
||||||
state.info = info
|
state.info = info
|
||||||
if (info !== null) {
|
if (info !== null) {
|
||||||
state.avatarUrl = info.getAvatarUrl()
|
state.avatarUrl = info.getAvatarUrl()
|
||||||
|
@ -60,31 +60,32 @@ export default {
|
||||||
state.isLinkShareAuth = info.id < 0
|
state.isLinkShareAuth = info.id < 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
setUserSettings(state, settings) {
|
setUserSettings(state: AuthState, settings: IUserSettings) {
|
||||||
state.settings = defaultSettings(settings)
|
state.settings = defaultSettings(settings)
|
||||||
const info = state.info !== null ? state.info : {}
|
const info = state.info !== null ? state.info : {} as Info
|
||||||
info.name = settings.name
|
info.name = settings.name
|
||||||
state.info = info
|
state.info = info
|
||||||
},
|
},
|
||||||
authenticated(state, authenticated) {
|
authenticated(state: AuthState, authenticated: boolean) {
|
||||||
state.authenticated = authenticated
|
state.authenticated = authenticated
|
||||||
},
|
},
|
||||||
isLinkShareAuth(state, is) {
|
isLinkShareAuth(state: AuthState, isLinkShareAuth: boolean) {
|
||||||
state.isLinkShareAuth = is
|
state.isLinkShareAuth = isLinkShareAuth
|
||||||
},
|
},
|
||||||
needsTotpPasscode(state, needs) {
|
needsTotpPasscode(state: AuthState, needsTotpPasscode: boolean) {
|
||||||
state.needsTotpPasscode = needs
|
state.needsTotpPasscode = needsTotpPasscode
|
||||||
},
|
},
|
||||||
reloadAvatar(state) {
|
reloadAvatar(state: AuthState) {
|
||||||
|
if (!state.info) return
|
||||||
state.avatarUrl = `${state.info.getAvatarUrl()}&=${+new Date()}`
|
state.avatarUrl = `${state.info.getAvatarUrl()}&=${+new Date()}`
|
||||||
},
|
},
|
||||||
lastUserRefresh(state) {
|
lastUserRefresh(state: AuthState) {
|
||||||
state.lastUserInfoRefresh = new Date()
|
state.lastUserInfoRefresh = new Date()
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
// Logs a user in with a set of credentials.
|
// Logs a user in with a set of credentials.
|
||||||
async login(ctx, credentials) {
|
async login(ctx: ActionContext<AuthState, RootStoreState>, credentials) {
|
||||||
const HTTP = HTTPFactory()
|
const HTTP = HTTPFactory()
|
||||||
ctx.commit(LOADING, true, {root: true})
|
ctx.commit(LOADING, true, {root: true})
|
||||||
|
|
||||||
|
@ -115,7 +116,7 @@ export default {
|
||||||
|
|
||||||
// Registers a new user and logs them in.
|
// Registers a new user and logs them in.
|
||||||
// Not sure if this is the right place to put the logic in, maybe a seperate js component would be better suited.
|
// Not sure if this is the right place to put the logic in, maybe a seperate js component would be better suited.
|
||||||
async register(ctx, credentials) {
|
async register(ctx: ActionContext<AuthState, RootStoreState>, credentials) {
|
||||||
const HTTP = HTTPFactory()
|
const HTTP = HTTPFactory()
|
||||||
ctx.commit(LOADING, true, {root: true})
|
ctx.commit(LOADING, true, {root: true})
|
||||||
try {
|
try {
|
||||||
|
@ -132,7 +133,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async openIdAuth(ctx, {provider, code}) {
|
async openIdAuth(ctx: ActionContext<AuthState, RootStoreState>, {provider, code}) {
|
||||||
const HTTP = HTTPFactory()
|
const HTTP = HTTPFactory()
|
||||||
ctx.commit(LOADING, true, {root: true})
|
ctx.commit(LOADING, true, {root: true})
|
||||||
|
|
||||||
|
@ -154,7 +155,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async linkShareAuth(ctx, {hash, password}) {
|
async linkShareAuth(ctx: ActionContext<AuthState, RootStoreState>, {hash, password}) {
|
||||||
const HTTP = HTTPFactory()
|
const HTTP = HTTPFactory()
|
||||||
const response = await HTTP.post('/shares/' + hash + '/auth', {
|
const response = await HTTP.post('/shares/' + hash + '/auth', {
|
||||||
password: password,
|
password: password,
|
||||||
|
@ -165,7 +166,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Populates user information from jwt token saved in local storage in store
|
// Populates user information from jwt token saved in local storage in store
|
||||||
checkAuth(ctx) {
|
checkAuth(ctx: ActionContext<AuthState, RootStoreState>) {
|
||||||
|
|
||||||
// This function can be called from multiple places at the same time and shortly after one another.
|
// This function can be called from multiple places at the same time and shortly after one another.
|
||||||
// To prevent hitting the api too frequently or race conditions, we check at most once per minute.
|
// To prevent hitting the api too frequently or race conditions, we check at most once per minute.
|
||||||
|
@ -197,7 +198,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
redirectToProviderIfNothingElseIsEnabled({rootState}) {
|
redirectToProviderIfNothingElseIsEnabled({rootState}: ActionContext<AuthState, RootStoreState>) {
|
||||||
const {auth} = rootState.config
|
const {auth} = rootState.config
|
||||||
if (
|
if (
|
||||||
auth.local.enabled === false &&
|
auth.local.enabled === false &&
|
||||||
|
@ -209,7 +210,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async refreshUserInfo({state, commit, dispatch}) {
|
async refreshUserInfo({state, commit, dispatch}: ActionContext<AuthState, RootStoreState>) {
|
||||||
const jwt = getToken()
|
const jwt = getToken()
|
||||||
if (!jwt) {
|
if (!jwt) {
|
||||||
return
|
return
|
||||||
|
@ -243,7 +244,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async saveUserSettings(ctx, payload) {
|
async saveUserSettings(ctx: ActionContext<AuthState, RootStoreState>, payload) {
|
||||||
const {settings} = payload
|
const {settings} = payload
|
||||||
const showMessage = payload.showMessage ?? true
|
const showMessage = payload.showMessage ?? true
|
||||||
const userSettingsService = new UserSettingsService()
|
const userSettingsService = new UserSettingsService()
|
||||||
|
@ -264,7 +265,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Renews the api token and saves it to local storage
|
// Renews the api token and saves it to local storage
|
||||||
renewToken(ctx) {
|
renewToken(ctx: ActionContext<AuthState, RootStoreState>) {
|
||||||
// FIXME: Timeout to avoid race conditions when authenticated as a user (=auth token in localStorage) and as a
|
// FIXME: Timeout to avoid race conditions when authenticated as a user (=auth token in localStorage) and as a
|
||||||
// link share in another tab. Without the timeout both the token renew and link share auth are executed at
|
// link share in another tab. Without the timeout both the token renew and link share auth are executed at
|
||||||
// the same time and one might win over the other.
|
// the same time and one might win over the other.
|
||||||
|
@ -285,7 +286,7 @@ export default {
|
||||||
}
|
}
|
||||||
}, 5000)
|
}, 5000)
|
||||||
},
|
},
|
||||||
logout(ctx) {
|
logout(ctx: ActionContext<AuthState, RootStoreState>) {
|
||||||
removeToken()
|
removeToken()
|
||||||
window.localStorage.clear() // Clear all settings and history we might have saved in local storage.
|
window.localStorage.clear() // Clear all settings and history we might have saved in local storage.
|
||||||
ctx.dispatch('checkAuth')
|
ctx.dispatch('checkAuth')
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
|
import type { ActionContext } from 'vuex'
|
||||||
|
import {parseURL} from 'ufo'
|
||||||
|
|
||||||
import {CONFIG} from '../mutation-types'
|
import {CONFIG} from '../mutation-types'
|
||||||
import {HTTPFactory} from '@/http-common'
|
import {HTTPFactory} from '@/http-common'
|
||||||
import {objectToCamelCase} from '@/helpers/case'
|
import {objectToCamelCase} from '@/helpers/case'
|
||||||
import {parseURL} from 'ufo'
|
import type { RootStoreState, ConfigState } from '@/store/types'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: () => ({
|
state: (): ConfigState => ({
|
||||||
// These are the api defaults.
|
// These are the api defaults.
|
||||||
version: '',
|
version: '',
|
||||||
frontendUrl: '',
|
frontendUrl: '',
|
||||||
|
@ -36,19 +39,19 @@ export default {
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
migratorsEnabled: state => state.availableMigrators?.length > 0,
|
migratorsEnabled: (state: ConfigState) => state.availableMigrators?.length > 0,
|
||||||
apiBase() {
|
apiBase() {
|
||||||
const {host, protocol} = parseURL(window.API_URL)
|
const {host, protocol} = parseURL(window.API_URL)
|
||||||
return protocol + '//' + host
|
return protocol + '//' + host
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
mutations: {
|
mutations: {
|
||||||
[CONFIG](state, config) {
|
[CONFIG](state: ConfigState, config: ConfigState) {
|
||||||
Object.assign(state, config)
|
Object.assign(state, config)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async update(ctx) {
|
async update(ctx: ActionContext<ConfigState, RootStoreState>) {
|
||||||
const HTTP = HTTPFactory()
|
const HTTP = HTTPFactory()
|
||||||
const {data: config} = await HTTP.get('info')
|
const {data: config} = await HTTP.get('info')
|
||||||
ctx.commit(CONFIG, objectToCamelCase(config))
|
ctx.commit(CONFIG, objectToCamelCase(config))
|
||||||
|
|
|
@ -7,10 +7,15 @@ import {success} from '@/message'
|
||||||
import BucketService from '../../services/bucket'
|
import BucketService from '../../services/bucket'
|
||||||
import {setLoading} from '../helper'
|
import {setLoading} from '../helper'
|
||||||
import TaskCollectionService from '@/services/taskCollection'
|
import TaskCollectionService from '@/services/taskCollection'
|
||||||
|
import type { ActionContext } from 'vuex'
|
||||||
|
import type { RootStoreState, KanbanState } from '@/store/types'
|
||||||
|
import type { ITask } from '@/models/task'
|
||||||
|
import type { IList } from '@/models/list'
|
||||||
|
import type { IBucket } from '@/models/bucket'
|
||||||
|
|
||||||
const TASKS_PER_BUCKET = 25
|
const TASKS_PER_BUCKET = 25
|
||||||
|
|
||||||
function getTaskIndicesById(state, taskId) {
|
function getTaskIndicesById(state: KanbanState, taskId: ITask['id']) {
|
||||||
let taskIndex
|
let taskIndex
|
||||||
const bucketIndex = state.buckets.findIndex(({ tasks }) => {
|
const bucketIndex = state.buckets.findIndex(({ tasks }) => {
|
||||||
taskIndex = findIndexById(tasks, taskId)
|
taskIndex = findIndexById(tasks, taskId)
|
||||||
|
@ -23,7 +28,7 @@ function getTaskIndicesById(state, taskId) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const addTaskToBucketAndSort = (state, task) => {
|
const addTaskToBucketAndSort = (state: KanbanState, task: ITask) => {
|
||||||
const bucketIndex = findIndexById(state.buckets, task.bucketId)
|
const bucketIndex = findIndexById(state.buckets, task.bucketId)
|
||||||
state.buckets[bucketIndex].tasks.push(task)
|
state.buckets[bucketIndex].tasks.push(task)
|
||||||
state.buckets[bucketIndex].tasks.sort((a, b) => a.kanbanPosition > b.kanbanPosition ? 1 : -1)
|
state.buckets[bucketIndex].tasks.sort((a, b) => a.kanbanPosition > b.kanbanPosition ? 1 : -1)
|
||||||
|
@ -36,7 +41,7 @@ const addTaskToBucketAndSort = (state, task) => {
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
|
|
||||||
state: () => ({
|
state: (): KanbanState => ({
|
||||||
buckets: [],
|
buckets: [],
|
||||||
listId: 0,
|
listId: 0,
|
||||||
bucketLoading: {},
|
bucketLoading: {},
|
||||||
|
@ -45,11 +50,11 @@ export default {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
mutations: {
|
mutations: {
|
||||||
setListId(state, listId) {
|
setListId(state: KanbanState, listId: IList['id']) {
|
||||||
state.listId = parseInt(listId)
|
state.listId = parseInt(listId)
|
||||||
},
|
},
|
||||||
|
|
||||||
setBuckets(state, buckets) {
|
setBuckets(state: KanbanState, buckets: IBucket[]) {
|
||||||
state.buckets = buckets
|
state.buckets = buckets
|
||||||
buckets.forEach(b => {
|
buckets.forEach(b => {
|
||||||
state.taskPagesPerBucket[b.id] = 1
|
state.taskPagesPerBucket[b.id] = 1
|
||||||
|
@ -57,31 +62,51 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
addBucket(state, bucket) {
|
addBucket(state: KanbanState, bucket: IBucket) {
|
||||||
state.buckets.push(bucket)
|
state.buckets.push(bucket)
|
||||||
},
|
},
|
||||||
|
|
||||||
removeBucket(state, bucket) {
|
removeBucket(state: KanbanState, bucket: IBucket) {
|
||||||
const bucketIndex = findIndexById(state.buckets, bucket.id)
|
const bucketIndex = findIndexById(state.buckets, bucket.id)
|
||||||
state.buckets.splice(bucketIndex, 1)
|
state.buckets.splice(bucketIndex, 1)
|
||||||
},
|
},
|
||||||
|
|
||||||
setBucketById(state, bucket) {
|
setBucketById(state: KanbanState, bucket: IBucket) {
|
||||||
const bucketIndex = findIndexById(state.buckets, bucket.id)
|
const bucketIndex = findIndexById(state.buckets, bucket.id)
|
||||||
state.buckets[bucketIndex] = bucket
|
state.buckets[bucketIndex] = bucket
|
||||||
},
|
},
|
||||||
|
|
||||||
setBucketByIndex(state, {bucketIndex, bucket}) {
|
setBucketByIndex(state: KanbanState, {
|
||||||
|
bucketIndex,
|
||||||
|
bucket,
|
||||||
|
} : {
|
||||||
|
bucketIndex: number,
|
||||||
|
bucket: IBucket
|
||||||
|
}) {
|
||||||
state.buckets[bucketIndex] = bucket
|
state.buckets[bucketIndex] = bucket
|
||||||
},
|
},
|
||||||
|
|
||||||
setTaskInBucketByIndex(state, {bucketIndex, taskIndex, task}) {
|
setTaskInBucketByIndex(state: KanbanState, {
|
||||||
|
bucketIndex,
|
||||||
|
taskIndex,
|
||||||
|
task,
|
||||||
|
} : {
|
||||||
|
bucketIndex: number,
|
||||||
|
taskIndex: number,
|
||||||
|
task: ITask
|
||||||
|
}) {
|
||||||
const bucket = state.buckets[bucketIndex]
|
const bucket = state.buckets[bucketIndex]
|
||||||
bucket.tasks[taskIndex] = task
|
bucket.tasks[taskIndex] = task
|
||||||
state.buckets[bucketIndex] = bucket
|
state.buckets[bucketIndex] = bucket
|
||||||
},
|
},
|
||||||
|
|
||||||
setTasksInBucketByBucketId(state, {bucketId, tasks}) {
|
setTasksInBucketByBucketId(state: KanbanState, {
|
||||||
|
bucketId,
|
||||||
|
tasks,
|
||||||
|
} : {
|
||||||
|
bucketId: IBucket['id'],
|
||||||
|
tasks: ITask[],
|
||||||
|
}) {
|
||||||
const bucketIndex = findIndexById(state.buckets, bucketId)
|
const bucketIndex = findIndexById(state.buckets, bucketId)
|
||||||
state.buckets[bucketIndex] = {
|
state.buckets[bucketIndex] = {
|
||||||
...state.buckets[bucketIndex],
|
...state.buckets[bucketIndex],
|
||||||
|
@ -89,7 +114,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setTaskInBucket(state, task) {
|
setTaskInBucket(state: KanbanState, task: ITask) {
|
||||||
// If this gets invoked without any tasks actually loaded, we can save the hassle of finding the task
|
// If this gets invoked without any tasks actually loaded, we can save the hassle of finding the task
|
||||||
if (state.buckets.length === 0) {
|
if (state.buckets.length === 0) {
|
||||||
return
|
return
|
||||||
|
@ -133,7 +158,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
addTaskToBucket(state, task) {
|
addTaskToBucket(state: KanbanState, task: ITask) {
|
||||||
const bucketIndex = findIndexById(state.buckets, task.bucketId)
|
const bucketIndex = findIndexById(state.buckets, task.bucketId)
|
||||||
const oldBucket = state.buckets[bucketIndex]
|
const oldBucket = state.buckets[bucketIndex]
|
||||||
const newBucket = {
|
const newBucket = {
|
||||||
|
@ -146,7 +171,10 @@ export default {
|
||||||
state.buckets[bucketIndex] = newBucket
|
state.buckets[bucketIndex] = newBucket
|
||||||
},
|
},
|
||||||
|
|
||||||
addTasksToBucket(state, {tasks, bucketId}) {
|
addTasksToBucket(state: KanbanState, {tasks, bucketId}: {
|
||||||
dpschen marked this conversation as resolved
Outdated
konrad
commented
Shouldn't the Shouldn't the `state` parameter be typed as well here?
dpschen
commented
It should already be typed by the vuex Module type It should already be typed by the [vuex Module type](https://kolaente.dev/vikunja/frontend/src/commit/c1f5f92fa164637040198c1a1b8de73f9ed9e0c7/src/store/modules/kanban.ts#L41)
|
|||||||
|
tasks: ITask[];
|
||||||
|
bucketId: IBucket['id'];
|
||||||
|
}) {
|
||||||
const bucketIndex = findIndexById(state.buckets, bucketId)
|
const bucketIndex = findIndexById(state.buckets, bucketId)
|
||||||
const oldBucket = state.buckets[bucketIndex]
|
const oldBucket = state.buckets[bucketIndex]
|
||||||
const newBucket = {
|
const newBucket = {
|
||||||
|
@ -159,7 +187,7 @@ export default {
|
||||||
state.buckets[bucketIndex] = newBucket
|
state.buckets[bucketIndex] = newBucket
|
||||||
},
|
},
|
||||||
|
|
||||||
removeTaskInBucket(state, task) {
|
removeTaskInBucket(state: KanbanState, task: ITask) {
|
||||||
// If this gets invoked without any tasks actually loaded, we can save the hassle of finding the task
|
// If this gets invoked without any tasks actually loaded, we can save the hassle of finding the task
|
||||||
if (state.buckets.length === 0) {
|
if (state.buckets.length === 0) {
|
||||||
return
|
return
|
||||||
|
@ -168,8 +196,10 @@ export default {
|
||||||
const { bucketIndex, taskIndex } = getTaskIndicesById(state, task.id)
|
const { bucketIndex, taskIndex } = getTaskIndicesById(state, task.id)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
|
!bucketIndex ||
|
||||||
state.buckets[bucketIndex]?.id !== task.bucketId ||
|
state.buckets[bucketIndex]?.id !== task.bucketId ||
|
||||||
state.buckets[bucketIndex]?.tasks[taskIndex]?.id !== task.id
|
!taskIndex ||
|
||||||
|
(state.buckets[bucketIndex]?.tasks[taskIndex]?.id !== task.id)
|
||||||
) {
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -177,39 +207,40 @@ export default {
|
||||||
state.buckets[bucketIndex].tasks.splice(taskIndex, 1)
|
state.buckets[bucketIndex].tasks.splice(taskIndex, 1)
|
||||||
},
|
},
|
||||||
|
|
||||||
setBucketLoading(state, {bucketId, loading}) {
|
setBucketLoading(state: KanbanState, {bucketId, loading}) {
|
||||||
state.bucketLoading[bucketId] = loading
|
state.bucketLoading[bucketId] = loading
|
||||||
},
|
},
|
||||||
|
|
||||||
setTasksLoadedForBucketPage(state, {bucketId, page}) {
|
setTasksLoadedForBucketPage(state: KanbanState, {bucketId, page}) {
|
||||||
state.taskPagesPerBucket[bucketId] = page
|
state.taskPagesPerBucket[bucketId] = page
|
||||||
},
|
},
|
||||||
|
|
||||||
setAllTasksLoadedForBucket(state, bucketId) {
|
setAllTasksLoadedForBucket(state: KanbanState, bucketId) {
|
||||||
state.allTasksLoadedForBucket[bucketId] = true
|
state.allTasksLoadedForBucket[bucketId] = true
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
getters: {
|
getters: {
|
||||||
getBucketById(state) {
|
getBucketById(state: KanbanState) {
|
||||||
return (bucketId) => findById(state.buckets, bucketId)
|
return (bucketId) => findById(state.buckets, bucketId)
|
||||||
},
|
},
|
||||||
|
|
||||||
getTaskById(state) {
|
getTaskById(state: KanbanState) {
|
||||||
return (id) => {
|
return (id) => {
|
||||||
const { bucketIndex, taskIndex } = getTaskIndicesById(state, id)
|
const { bucketIndex, taskIndex } = getTaskIndicesById(state, id)
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bucketIndex,
|
bucketIndex,
|
||||||
taskIndex,
|
taskIndex,
|
||||||
task: state.buckets[bucketIndex]?.tasks?.[taskIndex] || null,
|
task: bucketIndex && taskIndex && state.buckets[bucketIndex]?.tasks?.[taskIndex] || null,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
async loadBucketsForList(ctx, {listId, params}) {
|
async loadBucketsForList(ctx: ActionContext<KanbanState, RootStoreState>, {listId, params}) {
|
||||||
const cancel = setLoading(ctx, 'kanban')
|
const cancel = setLoading(ctx, 'kanban')
|
||||||
|
|
||||||
// Clear everything to prevent having old buckets in the list if loading the buckets from this list takes a few moments
|
// Clear everything to prevent having old buckets in the list if loading the buckets from this list takes a few moments
|
||||||
|
@ -219,7 +250,7 @@ export default {
|
||||||
|
|
||||||
const bucketService = new BucketService()
|
const bucketService = new BucketService()
|
||||||
try {
|
try {
|
||||||
const response = await bucketService.getAll({listId: listId}, params)
|
const response = await bucketService.getAll({listId}, params)
|
||||||
ctx.commit('setBuckets', response)
|
ctx.commit('setBuckets', response)
|
||||||
ctx.commit('setListId', listId)
|
ctx.commit('setListId', listId)
|
||||||
return response
|
return response
|
||||||
|
@ -228,7 +259,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadNextTasksForBucket(ctx, {listId, ps = {}, bucketId}) {
|
async loadNextTasksForBucket(ctx: ActionContext<KanbanState, RootStoreState>, {listId, ps = {}, bucketId}) {
|
||||||
const isLoading = ctx.state.bucketLoading[bucketId] ?? false
|
const isLoading = ctx.state.bucketLoading[bucketId] ?? false
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
return
|
return
|
||||||
|
@ -270,7 +301,7 @@ export default {
|
||||||
|
|
||||||
const taskService = new TaskCollectionService()
|
const taskService = new TaskCollectionService()
|
||||||
try {
|
try {
|
||||||
const tasks = await taskService.getAll({listId: listId}, params, page)
|
const tasks = await taskService.getAll({listId}, params, page)
|
||||||
ctx.commit('addTasksToBucket', {tasks, bucketId: bucketId})
|
ctx.commit('addTasksToBucket', {tasks, bucketId: bucketId})
|
||||||
ctx.commit('setTasksLoadedForBucketPage', {bucketId, page})
|
ctx.commit('setTasksLoadedForBucketPage', {bucketId, page})
|
||||||
if (taskService.totalPages <= page) {
|
if (taskService.totalPages <= page) {
|
||||||
|
@ -283,7 +314,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async createBucket(ctx, bucket) {
|
async createBucket(ctx: ActionContext<KanbanState, RootStoreState>, bucket: IBucket) {
|
||||||
const cancel = setLoading(ctx, 'kanban')
|
const cancel = setLoading(ctx, 'kanban')
|
||||||
|
|
||||||
const bucketService = new BucketService()
|
const bucketService = new BucketService()
|
||||||
|
@ -296,7 +327,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteBucket(ctx, {bucket, params}) {
|
async deleteBucket(ctx: ActionContext<KanbanState, RootStoreState>, {bucket, params}) {
|
||||||
const cancel = setLoading(ctx, 'kanban')
|
const cancel = setLoading(ctx, 'kanban')
|
||||||
|
|
||||||
const bucketService = new BucketService()
|
const bucketService = new BucketService()
|
||||||
|
@ -304,14 +335,14 @@ export default {
|
||||||
const response = await bucketService.delete(bucket)
|
const response = await bucketService.delete(bucket)
|
||||||
ctx.commit('removeBucket', bucket)
|
ctx.commit('removeBucket', bucket)
|
||||||
// We reload all buckets because tasks are being moved from the deleted bucket
|
// We reload all buckets because tasks are being moved from the deleted bucket
|
||||||
ctx.dispatch('loadBucketsForList', {listId: bucket.listId, params: params})
|
ctx.dispatch('loadBucketsForList', {listId: bucket.listId, params})
|
||||||
return response
|
return response
|
||||||
} finally {
|
} finally {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateBucket(ctx, updatedBucketData) {
|
async updateBucket(ctx: ActionContext<KanbanState, RootStoreState>, updatedBucketData) {
|
||||||
const cancel = setLoading(ctx, 'kanban')
|
const cancel = setLoading(ctx, 'kanban')
|
||||||
|
|
||||||
const bucketIndex = findIndexById(ctx.state.buckets, updatedBucketData.id)
|
const bucketIndex = findIndexById(ctx.state.buckets, updatedBucketData.id)
|
||||||
|
@ -339,10 +370,10 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateBucketTitle(ctx, { id, title }) {
|
async updateBucketTitle(ctx: ActionContext<KanbanState, RootStoreState>, { id, title }) {
|
||||||
const bucket = findById(ctx.state.buckets, id)
|
const bucket = findById(ctx.state.buckets, id)
|
||||||
|
|
||||||
if (bucket.title === title) {
|
if (bucket?.title === title) {
|
||||||
// bucket title has not changed
|
// bucket title has not changed
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +1,19 @@
|
||||||
|
import type { ActionContext } from 'vuex'
|
||||||
|
|
||||||
|
import {i18n} from '@/i18n'
|
||||||
|
import {success} from '@/message'
|
||||||
import LabelService from '@/services/label'
|
import LabelService from '@/services/label'
|
||||||
import {setLoading} from '@/store/helper'
|
import {setLoading} from '@/store/helper'
|
||||||
import {success} from '@/message'
|
import type { LabelState, RootStoreState } from '@/store/types'
|
||||||
import {i18n} from '@/i18n'
|
|
||||||
import {getLabelsByIds, filterLabelsByQuery} from '@/helpers/labels'
|
import {getLabelsByIds, filterLabelsByQuery} from '@/helpers/labels'
|
||||||
import {createNewIndexer} from '@/indexes'
|
import {createNewIndexer} from '@/indexes'
|
||||||
|
import type { ILabel } from '@/models/label'
|
||||||
|
|
||||||
const {add, remove, update} = createNewIndexer('labels', ['title', 'description'])
|
const {add, remove, update} = createNewIndexer('labels', ['title', 'description'])
|
||||||
|
|
||||||
async function getAllLabels(page = 1) {
|
async function getAllLabels(page = 1): Promise<ILabel[]> {
|
||||||
const labelService = new LabelService()
|
const labelService = new LabelService()
|
||||||
const labels = await labelService.getAll({}, {}, page)
|
const labels = await labelService.getAll({}, {}, page) as ILabel[]
|
||||||
if (page < labelService.totalPages) {
|
if (page < labelService.totalPages) {
|
||||||
const nextLabels = await getAllLabels(page + 1)
|
const nextLabels = await getAllLabels(page + 1)
|
||||||
return labels.concat(nextLabels)
|
return labels.concat(nextLabels)
|
||||||
|
@ -20,45 +24,44 @@ async function getAllLabels(page = 1) {
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: () => ({
|
state: (): LabelState => ({
|
||||||
// The labels are stored as an object which has the label ids as keys.
|
|
||||||
labels: {},
|
labels: {},
|
||||||
loaded: false,
|
loaded: false,
|
||||||
}),
|
}),
|
||||||
mutations: {
|
mutations: {
|
||||||
setLabels(state, labels) {
|
setLabels(state: LabelState, labels: ILabel[]) {
|
||||||
labels.forEach(l => {
|
labels.forEach(l => {
|
||||||
state.labels[l.id] = l
|
state.labels[l.id] = l
|
||||||
add(l)
|
add(l)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setLabel(state, label) {
|
setLabel(state: LabelState, label: ILabel) {
|
||||||
state.labels[label.id] = label
|
state.labels[label.id] = label
|
||||||
update(label)
|
update(label)
|
||||||
},
|
},
|
||||||
removeLabelById(state, label) {
|
removeLabelById(state: LabelState, label: ILabel) {
|
||||||
remove(label)
|
remove(label)
|
||||||
delete state.labels[label.id]
|
delete state.labels[label.id]
|
||||||
},
|
},
|
||||||
setLoaded(state, loaded) {
|
setLoaded(state: LabelState, loaded: boolean) {
|
||||||
state.loaded = loaded
|
state.loaded = loaded
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getLabelsByIds(state) {
|
getLabelsByIds(state: LabelState) {
|
||||||
return (ids) => getLabelsByIds(state, ids)
|
return (ids: ILabel['id'][]) => getLabelsByIds(state, ids)
|
||||||
},
|
},
|
||||||
filterLabelsByQuery(state) {
|
filterLabelsByQuery(state: LabelState) {
|
||||||
return (labelsToHide, query) => filterLabelsByQuery(state, labelsToHide, query)
|
return (labelsToHide: ILabel[], query: string) => filterLabelsByQuery(state, labelsToHide, query)
|
||||||
},
|
},
|
||||||
getLabelsByExactTitles(state) {
|
getLabelsByExactTitles(state: LabelState) {
|
||||||
return labelTitles => Object
|
return labelTitles => Object
|
||||||
.values(state.labels)
|
.values(state.labels)
|
||||||
.filter(({title}) => labelTitles.some(l => l.toLowerCase() === title.toLowerCase()))
|
.filter(({title}) => labelTitles.some(l => l.toLowerCase() === title.toLowerCase()))
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async loadAllLabels(ctx, {forceLoad} = {}) {
|
async loadAllLabels(ctx: ActionContext<LabelState, RootStoreState>, {forceLoad} = {}) {
|
||||||
if (ctx.state.loaded && !forceLoad) {
|
if (ctx.state.loaded && !forceLoad) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -74,7 +77,7 @@ export default {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async deleteLabel(ctx, label) {
|
async deleteLabel(ctx: ActionContext<LabelState, RootStoreState>, label: ILabel) {
|
||||||
const cancel = setLoading(ctx, 'labels')
|
const cancel = setLoading(ctx, 'labels')
|
||||||
const labelService = new LabelService()
|
const labelService = new LabelService()
|
||||||
|
|
||||||
|
@ -87,7 +90,7 @@ export default {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async updateLabel(ctx, label) {
|
async updateLabel(ctx: ActionContext<LabelState, RootStoreState>, label: ILabel) {
|
||||||
const cancel = setLoading(ctx, 'labels')
|
const cancel = setLoading(ctx, 'labels')
|
||||||
const labelService = new LabelService()
|
const labelService = new LabelService()
|
||||||
|
|
||||||
|
@ -100,7 +103,7 @@ export default {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async createLabel(ctx, label) {
|
async createLabel(ctx: ActionContext<LabelState, RootStoreState>, label: ILabel) {
|
||||||
const cancel = setLoading(ctx, 'labels')
|
const cancel = setLoading(ctx, 'labels')
|
||||||
const labelService = new LabelService()
|
const labelService = new LabelService()
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,9 @@ import ListService from '@/services/list'
|
||||||
import {setLoading} from '@/store/helper'
|
import {setLoading} from '@/store/helper'
|
||||||
import {removeListFromHistory} from '@/modules/listHistory'
|
import {removeListFromHistory} from '@/modules/listHistory'
|
||||||
import {createNewIndexer} from '@/indexes'
|
import {createNewIndexer} from '@/indexes'
|
||||||
|
import type {ListState, RootStoreState} from '@/store/types'
|
||||||
|
import type {ActionContext} from 'vuex'
|
||||||
|
import type {IList} from '@/models/list'
|
||||||
|
|
||||||
const {add, remove, search, update} = createNewIndexer('lists', ['title', 'description'])
|
const {add, remove, search, update} = createNewIndexer('lists', ['title', 'description'])
|
||||||
|
|
||||||
|
@ -10,37 +13,37 @@ const FavoriteListsNamespace = -2
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
// The state is an object which has the list ids as keys.
|
// The state is an object which has the list ids as keys.
|
||||||
state: () => ({}),
|
state: (): ListState => ({}),
|
||||||
mutations: {
|
mutations: {
|
||||||
setList(state, list) {
|
setList(state: ListState, list: IList) {
|
||||||
state[list.id] = list
|
state[list.id] = list
|
||||||
update(list)
|
update(list)
|
||||||
},
|
},
|
||||||
setLists(state, lists) {
|
setLists(state: ListState, lists: IList[]) {
|
||||||
lists.forEach(l => {
|
lists.forEach(l => {
|
||||||
state[l.id] = l
|
state[l.id] = l
|
||||||
add(l)
|
add(l)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
removeListById(state, list) {
|
removeListById(state: ListState, list: IList) {
|
||||||
remove(list)
|
remove(list)
|
||||||
delete state[list.id]
|
delete state[list.id]
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getListById: state => id => {
|
getListById: (state: ListState) => (id: IList['id']) => {
|
||||||
if (typeof state[id] !== 'undefined') {
|
if (typeof state[id] !== 'undefined') {
|
||||||
return state[id]
|
return state[id]
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
findListByExactname: state => name => {
|
findListByExactname: (state: ListState) => (name: string) => {
|
||||||
const list = Object.values(state).find(l => {
|
const list = Object.values(state).find(l => {
|
||||||
return l.title.toLowerCase() === name.toLowerCase()
|
return l.title.toLowerCase() === name.toLowerCase()
|
||||||
})
|
})
|
||||||
return typeof list === 'undefined' ? null : list
|
return typeof list === 'undefined' ? null : list
|
||||||
},
|
},
|
||||||
searchList: state => (query, includeArchived = false) => {
|
searchList: (state: ListState) => (query: string, includeArchived = false) => {
|
||||||
return search(query)
|
return search(query)
|
||||||
?.filter(value => value > 0)
|
?.filter(value => value > 0)
|
||||||
.map(id => state[id])
|
.map(id => state[id])
|
||||||
|
@ -49,14 +52,14 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
toggleListFavorite(ctx, list) {
|
toggleListFavorite(ctx: ActionContext<ListState, RootStoreState>, list: IList) {
|
||||||
return ctx.dispatch('updateList', {
|
return ctx.dispatch('updateList', {
|
||||||
...list,
|
...list,
|
||||||
isFavorite: !list.isFavorite,
|
isFavorite: !list.isFavorite,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
async createList(ctx, list) {
|
async createList(ctx: ActionContext<ListState, RootStoreState>, list: IList) {
|
||||||
const cancel = setLoading(ctx, 'lists')
|
const cancel = setLoading(ctx, 'lists')
|
||||||
const listService = new ListService()
|
const listService = new ListService()
|
||||||
|
|
||||||
|
@ -71,7 +74,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async updateList(ctx, list) {
|
async updateList(ctx: ActionContext<ListState, RootStoreState>, list: IList) {
|
||||||
const cancel = setLoading(ctx, 'lists')
|
const cancel = setLoading(ctx, 'lists')
|
||||||
const listService = new ListService()
|
const listService = new ListService()
|
||||||
|
|
||||||
|
@ -106,7 +109,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteList(ctx, list) {
|
async deleteList(ctx: ActionContext<ListState, RootStoreState>, list: IList) {
|
||||||
const cancel = setLoading(ctx, 'lists')
|
const cancel = setLoading(ctx, 'lists')
|
||||||
const listService = new ListService()
|
const listService = new ListService()
|
||||||
|
|
||||||
|
|
|
@ -1,22 +1,27 @@
|
||||||
|
import type {ActionContext} from 'vuex'
|
||||||
|
|
||||||
import NamespaceService from '../../services/namespace'
|
import NamespaceService from '../../services/namespace'
|
||||||
import {setLoading} from '@/store/helper'
|
import {setLoading} from '@/store/helper'
|
||||||
import {createNewIndexer} from '@/indexes'
|
import {createNewIndexer} from '@/indexes'
|
||||||
|
import type {NamespaceState, RootStoreState} from '@/store/types'
|
||||||
|
import type {INamespace} from '@/models/namespace'
|
||||||
|
import type {IList} from '@/models/list'
|
||||||
|
|
||||||
const {add, remove, search, update} = createNewIndexer('namespaces', ['title', 'description'])
|
const {add, remove, search, update} = createNewIndexer('namespaces', ['title', 'description'])
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: () => ({
|
state: (): NamespaceState => ({
|
||||||
namespaces: [],
|
namespaces: [],
|
||||||
}),
|
}),
|
||||||
mutations: {
|
mutations: {
|
||||||
namespaces(state, namespaces) {
|
namespaces(state: NamespaceState, namespaces: INamespace[]) {
|
||||||
state.namespaces = namespaces
|
state.namespaces = namespaces
|
||||||
namespaces.forEach(n => {
|
namespaces.forEach(n => {
|
||||||
add(n)
|
add(n)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setNamespaceById(state, namespace) {
|
setNamespaceById(state: NamespaceState, namespace: INamespace) {
|
||||||
const namespaceIndex = state.namespaces.findIndex(n => n.id === namespace.id)
|
const namespaceIndex = state.namespaces.findIndex(n => n.id === namespace.id)
|
||||||
|
|
||||||
if (namespaceIndex === -1) {
|
if (namespaceIndex === -1) {
|
||||||
|
@ -30,7 +35,7 @@ export default {
|
||||||
state.namespaces[namespaceIndex] = namespace
|
state.namespaces[namespaceIndex] = namespace
|
||||||
update(namespace)
|
update(namespace)
|
||||||
},
|
},
|
||||||
setListInNamespaceById(state, list) {
|
setListInNamespaceById(state: NamespaceState, list: IList) {
|
||||||
for (const n in state.namespaces) {
|
for (const n in state.namespaces) {
|
||||||
// We don't have the namespace id on the list which means we need to loop over all lists until we find it.
|
// We don't have the namespace id on the list which means we need to loop over all lists until we find it.
|
||||||
// FIXME: Not ideal at all - we should fix that at the api level.
|
// FIXME: Not ideal at all - we should fix that at the api level.
|
||||||
|
@ -46,11 +51,11 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addNamespace(state, namespace) {
|
addNamespace(state: NamespaceState, namespace: INamespace) {
|
||||||
state.namespaces.push(namespace)
|
state.namespaces.push(namespace)
|
||||||
add(namespace)
|
add(namespace)
|
||||||
},
|
},
|
||||||
removeNamespaceById(state, namespaceId) {
|
removeNamespaceById(state: NamespaceState, namespaceId: INamespace['id']) {
|
||||||
for (const n in state.namespaces) {
|
for (const n in state.namespaces) {
|
||||||
if (state.namespaces[n].id === namespaceId) {
|
if (state.namespaces[n].id === namespaceId) {
|
||||||
remove(state.namespaces[n])
|
remove(state.namespaces[n])
|
||||||
|
@ -59,7 +64,7 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
addListToNamespace(state, list) {
|
addListToNamespace(state: NamespaceState, list: IList) {
|
||||||
for (const n in state.namespaces) {
|
for (const n in state.namespaces) {
|
||||||
if (state.namespaces[n].id === list.namespaceId) {
|
if (state.namespaces[n].id === list.namespaceId) {
|
||||||
state.namespaces[n].lists.push(list)
|
state.namespaces[n].lists.push(list)
|
||||||
|
@ -67,7 +72,7 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
removeListFromNamespaceById(state, list) {
|
removeListFromNamespaceById(state: NamespaceState, list: IList) {
|
||||||
for (const n in state.namespaces) {
|
for (const n in state.namespaces) {
|
||||||
// We don't have the namespace id on the list which means we need to loop over all lists until we find it.
|
// We don't have the namespace id on the list which means we need to loop over all lists until we find it.
|
||||||
// FIXME: Not ideal at all - we should fix that at the api level.
|
// FIXME: Not ideal at all - we should fix that at the api level.
|
||||||
|
@ -83,7 +88,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getListAndNamespaceById: state => (listId, ignorePseudoNamespaces = false) => {
|
getListAndNamespaceById: (state: NamespaceState) => (listId: IList['id'], ignorePseudoNamespaces = false) => {
|
||||||
for (const n in state.namespaces) {
|
for (const n in state.namespaces) {
|
||||||
|
|
||||||
if (ignorePseudoNamespaces && state.namespaces[n].id < 0) {
|
if (ignorePseudoNamespaces && state.namespaces[n].id < 0) {
|
||||||
|
@ -101,10 +106,10 @@ export default {
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
},
|
},
|
||||||
getNamespaceById: state => namespaceId => {
|
getNamespaceById: (state: NamespaceState) => (namespaceId: INamespace['id']) => {
|
||||||
return state.namespaces.find(({id}) => id == namespaceId) || null
|
return state.namespaces.find(({id}) => id == namespaceId) || null
|
||||||
},
|
},
|
||||||
searchNamespace: (state, getters) => query => {
|
searchNamespace: (state: NamespaceState, getters) => (query: string) => {
|
||||||
return search(query)
|
return search(query)
|
||||||
?.filter(value => value > 0)
|
?.filter(value => value > 0)
|
||||||
.map(getters.getNamespaceById)
|
.map(getters.getNamespaceById)
|
||||||
|
@ -113,7 +118,7 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async loadNamespaces(ctx) {
|
async loadNamespaces(ctx: ActionContext<NamespaceState, RootStoreState>) {
|
||||||
const cancel = setLoading(ctx, 'namespaces')
|
const cancel = setLoading(ctx, 'namespaces')
|
||||||
|
|
||||||
const namespaceService = new NamespaceService()
|
const namespaceService = new NamespaceService()
|
||||||
|
@ -133,20 +138,20 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
loadNamespacesIfFavoritesDontExist(ctx) {
|
loadNamespacesIfFavoritesDontExist(ctx: ActionContext<NamespaceState, RootStoreState>) {
|
||||||
// The first or second namespace should be the one holding all favorites
|
// The first or second namespace should be the one holding all favorites
|
||||||
if (ctx.state.namespaces[0].id !== -2 && ctx.state.namespaces[1]?.id !== -2) {
|
if (ctx.state.namespaces[0].id !== -2 && ctx.state.namespaces[1]?.id !== -2) {
|
||||||
return ctx.dispatch('loadNamespaces')
|
return ctx.dispatch('loadNamespaces')
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
removeFavoritesNamespaceIfEmpty(ctx) {
|
removeFavoritesNamespaceIfEmpty(ctx: ActionContext<NamespaceState, RootStoreState>) {
|
||||||
if (ctx.state.namespaces[0].id === -2 && ctx.state.namespaces[0].lists.length === 0) {
|
if (ctx.state.namespaces[0].id === -2 && ctx.state.namespaces[0].lists.length === 0) {
|
||||||
ctx.state.namespaces.splice(0, 1)
|
ctx.state.namespaces.splice(0, 1)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async deleteNamespace(ctx, namespace) {
|
async deleteNamespace(ctx: ActionContext<NamespaceState, RootStoreState>, namespace: INamespace) {
|
||||||
const cancel = setLoading(ctx, 'namespaces')
|
const cancel = setLoading(ctx, 'namespaces')
|
||||||
const namespaceService = new NamespaceService()
|
const namespaceService = new NamespaceService()
|
||||||
|
|
||||||
|
@ -159,7 +164,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async createNamespace(ctx, namespace) {
|
async createNamespace(ctx: ActionContext<NamespaceState, RootStoreState>, namespace: INamespace) {
|
||||||
const cancel = setLoading(ctx, 'namespaces')
|
const cancel = setLoading(ctx, 'namespaces')
|
||||||
const namespaceService = new NamespaceService()
|
const namespaceService = new NamespaceService()
|
||||||
|
|
||||||
|
|
|
@ -1,20 +1,25 @@
|
||||||
import router from '@/router'
|
import router from '@/router'
|
||||||
|
import type { ActionContext } from 'vuex'
|
||||||
|
import {formatISO} from 'date-fns'
|
||||||
|
|
||||||
import TaskService from '@/services/task'
|
import TaskService from '@/services/task'
|
||||||
import TaskAssigneeService from '@/services/taskAssignee'
|
import TaskAssigneeService from '@/services/taskAssignee'
|
||||||
import TaskAssigneeModel from '../../models/taskAssignee'
|
import TaskAssigneeModel from '@/models/taskAssignee'
|
||||||
import LabelTaskModel from '../../models/labelTask'
|
import LabelTaskModel from '@/models/labelTask'
|
||||||
import LabelTaskService from '@/services/labelTask'
|
import LabelTaskService from '@/services/labelTask'
|
||||||
import {HAS_TASKS} from '../mutation-types'
|
import {HAS_TASKS} from '../mutation-types'
|
||||||
import {setLoading} from '../helper'
|
import {setLoading} from '../helper'
|
||||||
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
|
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
|
||||||
|
|
||||||
import {parseTaskText} from '@/modules/parseTaskText'
|
import {parseTaskText} from '@/modules/parseTaskText'
|
||||||
import TaskModel from '@/models/task'
|
import TaskModel, { type ITask } from '@/models/task'
|
||||||
import {formatISO} from 'date-fns'
|
|
||||||
import LabelTask from '@/models/labelTask'
|
import LabelTask from '@/models/labelTask'
|
||||||
import LabelModel from '@/models/label'
|
import LabelModel, { type ILabel } from '@/models/label'
|
||||||
import UserService from '@/services/user'
|
import UserService from '@/services/user'
|
||||||
|
import type { RootStoreState, TaskState } from '@/store/types'
|
||||||
|
import type { IUser } from '@/models/user'
|
||||||
|
import type { IAttachment } from '@/models/attachment'
|
||||||
|
import type { IList } from '@/models/list'
|
||||||
|
|
||||||
// IDEA: maybe use a small fuzzy search here to prevent errors
|
// IDEA: maybe use a small fuzzy search here to prevent errors
|
||||||
function findPropertyByValue(object, key, value) {
|
function findPropertyByValue(object, key, value) {
|
||||||
|
@ -24,16 +29,16 @@ function findPropertyByValue(object, key, value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user exists
|
// Check if the user exists
|
||||||
function validateUsername(users, username) {
|
function validateUsername(users: IUser[], username: IUser['username']) {
|
||||||
return findPropertyByValue(users, 'username', username)
|
return findPropertyByValue(users, 'username', username)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the label exists
|
// Check if the label exists
|
||||||
function validateLabel(labels, label) {
|
function validateLabel(labels: ILabel[], label: ILabel) {
|
||||||
return findPropertyByValue(labels, 'title', label)
|
return findPropertyByValue(labels, 'title', label)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addLabelToTask(task, label) {
|
async function addLabelToTask(task: ITask, label: ILabel) {
|
||||||
const labelTask = new LabelTask({
|
const labelTask = new LabelTask({
|
||||||
taskId: task.id,
|
taskId: task.id,
|
||||||
labelId: label.id,
|
labelId: label.id,
|
||||||
|
@ -62,9 +67,9 @@ async function findAssignees(parsedTaskAssignees) {
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
state: () => ({}),
|
state: (): TaskState => ({}),
|
||||||
actions: {
|
actions: {
|
||||||
async loadTasks(ctx, params) {
|
async loadTasks(ctx: ActionContext<TaskState, RootStoreState>, params) {
|
||||||
const taskService = new TaskService()
|
const taskService = new TaskService()
|
||||||
|
|
||||||
const cancel = setLoading(ctx, 'tasks')
|
const cancel = setLoading(ctx, 'tasks')
|
||||||
|
@ -77,7 +82,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async update(ctx, task) {
|
async update(ctx: ActionContext<TaskState, RootStoreState>, task: ITask) {
|
||||||
const cancel = setLoading(ctx, 'tasks')
|
const cancel = setLoading(ctx, 'tasks')
|
||||||
|
|
||||||
const taskService = new TaskService()
|
const taskService = new TaskService()
|
||||||
|
@ -90,7 +95,7 @@ export default {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
async delete(ctx, task) {
|
async delete(ctx: ActionContext<TaskState, RootStoreState>, task: ITask) {
|
||||||
const taskService = new TaskService()
|
const taskService = new TaskService()
|
||||||
const response = await taskService.delete(task)
|
const response = await taskService.delete(task)
|
||||||
ctx.commit('kanban/removeTaskInBucket', task, {root: true})
|
ctx.commit('kanban/removeTaskInBucket', task, {root: true})
|
||||||
|
@ -99,7 +104,13 @@ export default {
|
||||||
|
|
||||||
// Adds a task attachment in store.
|
// Adds a task attachment in store.
|
||||||
// This is an action to be able to commit other mutations
|
// This is an action to be able to commit other mutations
|
||||||
addTaskAttachment(ctx, {taskId, attachment}) {
|
addTaskAttachment(ctx: ActionContext<TaskState, RootStoreState>, {
|
||||||
|
taskId,
|
||||||
|
attachment,
|
||||||
|
}: {
|
||||||
|
taskId: ITask['id']
|
||||||
|
attachment: IAttachment
|
||||||
|
}) {
|
||||||
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
|
const t = ctx.rootGetters['kanban/getTaskById'](taskId)
|
||||||
if (t.task !== null) {
|
if (t.task !== null) {
|
||||||
const attachments = [
|
const attachments = [
|
||||||
|
@ -119,7 +130,13 @@ export default {
|
||||||
ctx.commit('attachments/add', attachment, {root: true})
|
ctx.commit('attachments/add', attachment, {root: true})
|
||||||
},
|
},
|
||||||
|
|
||||||
async addAssignee(ctx, {user, taskId}) {
|
async addAssignee(ctx: ActionContext<TaskState, RootStoreState>, {
|
||||||
|
user,
|
||||||
|
taskId,
|
||||||
|
}: {
|
||||||
|
user: IUser,
|
||||||
|
taskId: ITask['id']
|
||||||
|
}) {
|
||||||
const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: taskId})
|
const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: taskId})
|
||||||
|
|
||||||
const taskAssigneeService = new TaskAssigneeService()
|
const taskAssigneeService = new TaskAssigneeService()
|
||||||
|
@ -148,7 +165,13 @@ export default {
|
||||||
return r
|
return r
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeAssignee(ctx, {user, taskId}) {
|
async removeAssignee(ctx: ActionContext<TaskState, RootStoreState>, {
|
||||||
|
user,
|
||||||
|
taskId,
|
||||||
|
}: {
|
||||||
|
user: IUser,
|
||||||
|
taskId: ITask['id']
|
||||||
|
}) {
|
||||||
const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: taskId})
|
const taskAssignee = new TaskAssigneeModel({userId: user.id, taskId: taskId})
|
||||||
|
|
||||||
const taskAssigneeService = new TaskAssigneeService()
|
const taskAssigneeService = new TaskAssigneeService()
|
||||||
|
@ -175,8 +198,14 @@ export default {
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
async addLabel(ctx, {label, taskId}) {
|
async addLabel(ctx: ActionContext<TaskState, RootStoreState>, {
|
||||||
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
|
label,
|
||||||
|
taskId,
|
||||||
|
} : {
|
||||||
|
label: ILabel,
|
||||||
|
taskId: ITask['id']
|
||||||
|
}) {
|
||||||
|
const labelTask = new LabelTaskModel({taskId, labelId: label.id})
|
||||||
|
|
||||||
const labelTaskService = new LabelTaskService()
|
const labelTaskService = new LabelTaskService()
|
||||||
const r = await labelTaskService.create(labelTask)
|
const r = await labelTaskService.create(labelTask)
|
||||||
|
@ -205,8 +234,8 @@ export default {
|
||||||
return r
|
return r
|
||||||
},
|
},
|
||||||
|
|
||||||
async removeLabel(ctx, {label, taskId}) {
|
async removeLabel(ctx: ActionContext<TaskState, RootStoreState>, {label, taskId}) {
|
||||||
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
|
const labelTask = new LabelTaskModel({taskId, labelId: label.id})
|
||||||
|
|
||||||
const labelTaskService = new LabelTaskService()
|
const labelTaskService = new LabelTaskService()
|
||||||
const response = await labelTaskService.delete(labelTask)
|
const response = await labelTaskService.delete(labelTask)
|
||||||
|
@ -234,7 +263,10 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
// Do everything that is involved in finding, creating and adding the label to the task
|
// Do everything that is involved in finding, creating and adding the label to the task
|
||||||
async addLabelsToTask({rootState, dispatch}, { task, parsedLabels }) {
|
async addLabelsToTask({rootState, dispatch}: ActionContext<TaskState, RootStoreState>, {
|
||||||
|
task,
|
||||||
|
parsedLabels,
|
||||||
|
}) {
|
||||||
if (parsedLabels.length <= 0) {
|
if (parsedLabels.length <= 0) {
|
||||||
return task
|
return task
|
||||||
}
|
}
|
||||||
|
@ -257,7 +289,10 @@ export default {
|
||||||
return task
|
return task
|
||||||
},
|
},
|
||||||
|
|
||||||
findListId({ rootGetters }, { list: listName, listId }) {
|
findListId({ rootGetters }: ActionContext<TaskState, RootStoreState>, { list: listName, listId }: {
|
||||||
|
list: string,
|
||||||
|
listId: IList['id']
|
||||||
|
}) {
|
||||||
let foundListId = null
|
let foundListId = null
|
||||||
|
|
||||||
// Uses the following ways to get the list id of the new task:
|
// Uses the following ways to get the list id of the new task:
|
||||||
|
@ -285,12 +320,14 @@ export default {
|
||||||
return foundListId
|
return foundListId
|
||||||
},
|
},
|
||||||
|
|
||||||
async createNewTask({dispatch, commit}, {
|
async createNewTask({dispatch, commit}: ActionContext<TaskState, RootStoreState>, {
|
||||||
title,
|
title,
|
||||||
bucketId,
|
bucketId,
|
||||||
listId,
|
listId,
|
||||||
position,
|
position,
|
||||||
}) {
|
} :
|
||||||
|
Partial<ITask>,
|
||||||
|
) {
|
||||||
const cancel = setLoading({commit}, 'tasks')
|
const cancel = setLoading({commit}, 'tasks')
|
||||||
const parsedTask = parseTaskText(title, getQuickAddMagicMode())
|
const parsedTask = parseTaskText(title, getQuickAddMagicMode())
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,116 @@
|
||||||
|
import type { IBucket } from '@/models/bucket'
|
||||||
|
import type { IUserSettings } from '@/models/userSettings'
|
||||||
|
import type { IList } from '@/models/list'
|
||||||
|
import type { IAttachment } from '@/models/attachment'
|
||||||
|
import type { ILabel } from '@/models/label'
|
||||||
|
import type { INamespace } from '@/models/namespace'
|
||||||
|
|
||||||
|
export interface RootStoreState {
|
||||||
|
loading: boolean,
|
||||||
|
loadingModule: null,
|
||||||
|
currentList: IList,
|
||||||
|
background: string,
|
||||||
|
blurHash: string,
|
||||||
|
hasTasks: boolean,
|
||||||
|
menuActive: boolean,
|
||||||
|
keyboardShortcutsActive: boolean,
|
||||||
|
quickActionsActive: boolean,
|
||||||
|
|
||||||
|
// modules
|
||||||
|
attachments: AttachmentState,
|
||||||
|
auth: AuthState,
|
||||||
|
config: ConfigState,
|
||||||
|
kanban: KanbanState,
|
||||||
|
labels: LabelState,
|
||||||
|
lists: ListState,
|
||||||
|
namespaces: NamespaceState,
|
||||||
|
tasks: TaskState,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AttachmentState {
|
||||||
|
attachments: IAttachment[],
|
||||||
konrad
commented
I think that's the user id of the currently authenticated user. I *think* that's the user id of the currently authenticated user.
dpschen
commented
Meaning Meaning `IUser['id']`?
konrad
commented
Yes. Yes.
|
|||||||
|
}
|
||||||
|
|
||||||
|
export const AUTH_TYPES = {
|
||||||
|
'UNKNOWN': 0,
|
||||||
|
'USER': 1,
|
||||||
|
'LINK_SHARE': 2,
|
||||||
|
} as const
|
||||||
|
|
||||||
|
export interface Info {
|
||||||
|
id: number // what kind of id is this?
|
||||||
|
type: typeof AUTH_TYPES[keyof typeof AUTH_TYPES],
|
||||||
|
getAvatarUrl: () => string
|
||||||
|
settings: IUserSettings
|
||||||
|
name: string
|
||||||
|
email: string
|
||||||
|
exp: any
|
||||||
|
}
|
||||||
|
export interface AuthState {
|
||||||
|
authenticated: boolean,
|
||||||
|
isLinkShareAuth: boolean,
|
||||||
|
info: Info | null,
|
||||||
|
needsTotpPasscode: boolean,
|
||||||
|
avatarUrl: string,
|
||||||
|
lastUserInfoRefresh: Date | null,
|
||||||
|
settings: IUserSettings,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ConfigState {
|
||||||
|
version: string,
|
||||||
|
frontendUrl: string,
|
||||||
|
motd: string,
|
||||||
|
linkSharingEnabled: boolean,
|
||||||
|
maxFileSize: '20MB',
|
||||||
|
registrationEnabled: boolean,
|
||||||
|
availableMigrators: [],
|
||||||
|
taskAttachmentsEnabled: boolean,
|
||||||
|
totpEnabled: boolean,
|
||||||
|
enabledBackgroundProviders: [],
|
||||||
|
legal: {
|
||||||
|
imprintUrl: string,
|
||||||
|
privacyPolicyUrl: string,
|
||||||
|
},
|
||||||
|
caldavEnabled: boolean,
|
||||||
|
userDeletionEnabled: boolean,
|
||||||
|
taskCommentsEnabled: boolean,
|
||||||
|
auth: {
|
||||||
|
local: {
|
||||||
|
enabled: boolean,
|
||||||
|
},
|
||||||
|
openidConnect: {
|
||||||
|
enabled: boolean,
|
||||||
|
redirectUrl: string,
|
||||||
|
providers: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface KanbanState {
|
||||||
|
buckets: IBucket[],
|
||||||
|
listId: IList['id'],
|
||||||
|
bucketLoading: {},
|
||||||
|
taskPagesPerBucket: {
|
||||||
|
[id: IBucket['id']]: number
|
||||||
|
},
|
||||||
|
allTasksLoadedForBucket: {
|
||||||
|
[id: IBucket['id']]: boolean
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LabelState {
|
||||||
|
labels: {
|
||||||
|
[id: ILabel['id']]: ILabel
|
||||||
|
},
|
||||||
|
loaded: boolean,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ListState {
|
||||||
|
[id: IList['id']]: IList
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NamespaceState {
|
||||||
|
namespaces: INamespace[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TaskState {}
|
|
@ -68,11 +68,11 @@ import SavedFilterService from '@/services/savedFilter'
|
||||||
|
|
||||||
import {objectToSnakeCase} from '@/helpers/case'
|
import {objectToSnakeCase} from '@/helpers/case'
|
||||||
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
|
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
|
||||||
import type ListModel from '@/models/list'
|
import type { IList } from '@/models/list'
|
||||||
|
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
|
|
||||||
function useSavedFilter(listId: MaybeRef<ListModel['id']>) {
|
function useSavedFilter(listId: MaybeRef<IList['id']>) {
|
||||||
const filterService = shallowRef(new SavedFilterService())
|
const filterService = shallowRef(new SavedFilterService())
|
||||||
|
|
||||||
const filter = ref(new SavedFilterModel())
|
const filter = ref(new SavedFilterModel())
|
||||||
|
|
|
@ -113,7 +113,7 @@
|
||||||
import {defineComponent} from 'vue'
|
import {defineComponent} from 'vue'
|
||||||
import {mapState} from 'vuex'
|
import {mapState} from 'vuex'
|
||||||
|
|
||||||
import LabelModel from '../../models/label'
|
import LabelModel, { type ILabel } from '../../models/label'
|
||||||
import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
|
import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
|
||||||
|
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
|
@ -150,7 +150,7 @@ export default defineComponent({
|
||||||
loading: state => state[LOADING] && state[LOADING_MODULE] === 'labels',
|
loading: state => state[LOADING] && state[LOADING_MODULE] === 'labels',
|
||||||
}),
|
}),
|
||||||
methods: {
|
methods: {
|
||||||
deleteLabel(label: LabelModel) {
|
deleteLabel(label: ILabel) {
|
||||||
this.showDeleteModal = false
|
this.showDeleteModal = false
|
||||||
this.isLabelEdit = false
|
this.isLabelEdit = false
|
||||||
return this.$store.dispatch('labels/deleteLabel', label)
|
return this.$store.dispatch('labels/deleteLabel', label)
|
||||||
|
@ -158,7 +158,7 @@ export default defineComponent({
|
||||||
editLabelSubmit() {
|
editLabelSubmit() {
|
||||||
return this.$store.dispatch('labels/updateLabel', this.labelEditLabel)
|
return this.$store.dispatch('labels/updateLabel', this.labelEditLabel)
|
||||||
},
|
},
|
||||||
editLabel(label: LabelModel) {
|
editLabel(label: ILabel) {
|
||||||
if (label.createdBy.id !== this.userInfo.id) {
|
if (label.createdBy.id !== this.userInfo.id) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -180,7 +180,7 @@ export default defineComponent({
|
||||||
this.editorActive = false
|
this.editorActive = false
|
||||||
this.$nextTick(() => this.editorActive = true)
|
this.$nextTick(() => this.editorActive = true)
|
||||||
},
|
},
|
||||||
showDeleteDialoge(label: LabelModel) {
|
showDeleteDialoge(label: ILabel) {
|
||||||
this.labelToDelete = label
|
this.labelToDelete = label
|
||||||
this.showDeleteModal = true
|
this.showDeleteModal = true
|
||||||
},
|
},
|
||||||
|
|
|
@ -153,9 +153,9 @@ import {ALPHABETICAL_SORT} from '@/components/list/partials/filters.vue'
|
||||||
|
|
||||||
import draggable from 'zhyswan-vuedraggable'
|
import draggable from 'zhyswan-vuedraggable'
|
||||||
import {calculateItemPosition} from '../../helpers/calculateItemPosition'
|
import {calculateItemPosition} from '../../helpers/calculateItemPosition'
|
||||||
import type TaskModel from '@/models/task'
|
import type { ITask } from '@/models/task'
|
||||||
|
|
||||||
function sortTasks(tasks: TaskModel[]) {
|
function sortTasks(tasks: ITask[]) {
|
||||||
if (tasks === null || tasks === []) {
|
if (tasks === null || tasks === []) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -274,7 +274,7 @@ export default defineComponent({
|
||||||
focusNewTaskInput() {
|
focusNewTaskInput() {
|
||||||
this.$refs.addTask.focusTaskInput()
|
this.$refs.addTask.focusTaskInput()
|
||||||
},
|
},
|
||||||
updateTaskList(task: TaskModel) {
|
updateTaskList(task: ITask) {
|
||||||
if ( this.isAlphabeticalSorting ) {
|
if ( this.isAlphabeticalSorting ) {
|
||||||
// reload tasks with current filter and sorting
|
// reload tasks with current filter and sorting
|
||||||
this.loadTasks(1, undefined, undefined, true)
|
this.loadTasks(1, undefined, undefined, true)
|
||||||
|
@ -288,11 +288,11 @@ export default defineComponent({
|
||||||
|
|
||||||
this.$store.commit(HAS_TASKS, true)
|
this.$store.commit(HAS_TASKS, true)
|
||||||
},
|
},
|
||||||
editTask(id: TaskModel['id']) {
|
editTask(id: ITask['id']) {
|
||||||
this.taskEditTask = {...this.tasks.find(t => t.id === parseInt(id))}
|
this.taskEditTask = {...this.tasks.find(t => t.id === parseInt(id))}
|
||||||
this.isTaskEdit = true
|
this.isTaskEdit = true
|
||||||
},
|
},
|
||||||
updateTasks(updatedTask: TaskModel) {
|
updateTasks(updatedTask: ITask) {
|
||||||
for (const t in this.tasks) {
|
for (const t in this.tasks) {
|
||||||
if (this.tasks[t].id === updatedTask.id) {
|
if (this.tasks[t].id === updatedTask.id) {
|
||||||
this.tasks[t] = updatedTask
|
this.tasks[t] = updatedTask
|
||||||
|
|
|
@ -197,7 +197,7 @@ import Pagination from '@/components/misc/pagination.vue'
|
||||||
import Popup from '@/components/misc/popup.vue'
|
import Popup from '@/components/misc/popup.vue'
|
||||||
|
|
||||||
import {useTaskList} from '@/composables/taskList'
|
import {useTaskList} from '@/composables/taskList'
|
||||||
import type TaskModel from '@/models/task'
|
import type { ITask } from '@/models/task'
|
||||||
|
|
||||||
const ACTIVE_COLUMNS_DEFAULT = {
|
const ACTIVE_COLUMNS_DEFAULT = {
|
||||||
id: true,
|
id: true,
|
||||||
|
@ -253,7 +253,7 @@ const {
|
||||||
currentPage,
|
currentPage,
|
||||||
sortByParam,
|
sortByParam,
|
||||||
} = taskList
|
} = taskList
|
||||||
const tasks: Ref<TaskModel[]> = taskList.tasks
|
const tasks: Ref<ITask[]> = taskList.tasks
|
||||||
|
|
||||||
Object.assign(params.value, {
|
Object.assign(params.value, {
|
||||||
filter_by: [],
|
filter_by: [],
|
||||||
|
|
|
@ -30,7 +30,7 @@ import CreateEdit from '@/components/misc/create-edit.vue'
|
||||||
import Multiselect from '@/components/input/multiselect.vue'
|
import Multiselect from '@/components/input/multiselect.vue'
|
||||||
|
|
||||||
import ListDuplicateModel from '@/models/listDuplicateModel'
|
import ListDuplicateModel from '@/models/listDuplicateModel'
|
||||||
import NamespaceModel from '@/models/namespace'
|
import type {INamespace} from '@/models/namespace'
|
||||||
|
|
||||||
import {success} from '@/message'
|
import {success} from '@/message'
|
||||||
import {useTitle} from '@/composables/useTitle'
|
import {useTitle} from '@/composables/useTitle'
|
||||||
|
@ -44,9 +44,9 @@ const {
|
||||||
findNamespaces,
|
findNamespaces,
|
||||||
} = useNameSpaceSearch()
|
} = useNameSpaceSearch()
|
||||||
|
|
||||||
const selectedNamespace = ref<NamespaceModel>()
|
const selectedNamespace = ref<INamespace>()
|
||||||
|
|
||||||
function selectNamespace(namespace: NamespaceModel) {
|
function selectNamespace(namespace: INamespace) {
|
||||||
selectedNamespace.value = namespace
|
selectedNamespace.value = namespace
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,14 +80,14 @@ import ColorPicker from '@/components/input/colorPicker.vue'
|
||||||
import CreateEdit from '@/components/misc/create-edit.vue'
|
import CreateEdit from '@/components/misc/create-edit.vue'
|
||||||
|
|
||||||
import {CURRENT_LIST} from '@/store/mutation-types'
|
import {CURRENT_LIST} from '@/store/mutation-types'
|
||||||
import type ListModel from '@/models/list'
|
import type { IList } from '@/models/list'
|
||||||
|
|
||||||
import { useList } from '@/composables/useList'
|
import { useList } from '@/composables/useList'
|
||||||
import { useTitle } from '@/composables/useTitle'
|
import { useTitle } from '@/composables/useTitle'
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
listId: {
|
listId: {
|
||||||
type: Number as PropType<ListModel['id']>,
|
type: Number as PropType<IList['id']>,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -49,7 +49,6 @@ import {useStore} from 'vuex'
|
||||||
import {useRoute, useRouter} from 'vue-router'
|
import {useRoute, useRouter} from 'vue-router'
|
||||||
import {useI18n} from 'vue-i18n'
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
import type TaskModel from '@/models/task'
|
|
||||||
import {formatDate} from '@/helpers/time/formatDate'
|
import {formatDate} from '@/helpers/time/formatDate'
|
||||||
import {setTitle} from '@/helpers/setTitle'
|
import {setTitle} from '@/helpers/setTitle'
|
||||||
|
|
||||||
|
@ -59,13 +58,14 @@ import DatepickerWithRange from '@/components/date/datepickerWithRange.vue'
|
||||||
import {DATE_RANGES} from '@/components/date/dateRanges'
|
import {DATE_RANGES} from '@/components/date/dateRanges'
|
||||||
import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
|
import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
|
||||||
import LlamaCool from '@/assets/llama-cool.svg?component'
|
import LlamaCool from '@/assets/llama-cool.svg?component'
|
||||||
|
import type { ITask } from '@/models/task'
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const {t} = useI18n({useScope: 'global'})
|
const {t} = useI18n({useScope: 'global'})
|
||||||
|
|
||||||
const tasks = ref<TaskModel[]>([])
|
const tasks = ref<ITask[]>([])
|
||||||
const showNothingToDo = ref<boolean>(false)
|
const showNothingToDo = ref<boolean>(false)
|
||||||
|
|
||||||
setTimeout(() => showNothingToDo.value = true, 100)
|
setTimeout(() => showNothingToDo.value = true, 100)
|
||||||
|
@ -182,7 +182,7 @@ async function loadPendingTasks(from: string, to: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: this modification should happen in the store
|
// FIXME: this modification should happen in the store
|
||||||
function updateTasks(updatedTask: TaskModel) {
|
function updateTasks(updatedTask: ITask) {
|
||||||
for (const t in tasks.value) {
|
for (const t in tasks.value) {
|
||||||
if (tasks.value[t].id === updatedTask.id) {
|
if (tasks.value[t].id === updatedTask.id) {
|
||||||
tasks.value[t] = updatedTask
|
tasks.value[t] = updatedTask
|
||||||
|
|
|
@ -425,7 +425,7 @@
|
||||||
import {defineComponent} from 'vue'
|
import {defineComponent} from 'vue'
|
||||||
|
|
||||||
import TaskService from '../../services/task'
|
import TaskService from '../../services/task'
|
||||||
import TaskModel from '../../models/task'
|
import TaskModel, { type ITask } from '@/models/task'
|
||||||
|
|
||||||
import { PRIORITIES as priorites } from '@/models/constants/priorities'
|
import { PRIORITIES as priorites } from '@/models/constants/priorities'
|
||||||
import {RIGHTS as rights} from '@/models/constants/rights'
|
import {RIGHTS as rights} from '@/models/constants/rights'
|
||||||
|
@ -452,10 +452,10 @@ import {CURRENT_LIST} from '@/store/mutation-types'
|
||||||
import {uploadFile} from '@/helpers/attachments'
|
import {uploadFile} from '@/helpers/attachments'
|
||||||
import ChecklistSummary from '../../components/tasks/partials/checklist-summary.vue'
|
import ChecklistSummary from '../../components/tasks/partials/checklist-summary.vue'
|
||||||
import CreatedUpdated from '@/components/tasks/partials/createdUpdated.vue'
|
import CreatedUpdated from '@/components/tasks/partials/createdUpdated.vue'
|
||||||
import type ListModel from '@/models/list'
|
|
||||||
import { setTitle } from '@/helpers/setTitle'
|
import { setTitle } from '@/helpers/setTitle'
|
||||||
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
|
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
|
||||||
import {getListTitle} from '@/helpers/getListTitle'
|
import {getListTitle} from '@/helpers/getListTitle'
|
||||||
|
import type { IList } from '@/models/list'
|
||||||
|
|
||||||
function scrollIntoView(el) {
|
function scrollIntoView(el) {
|
||||||
if (!el) {
|
if (!el) {
|
||||||
|
@ -597,7 +597,7 @@ export default defineComponent({
|
||||||
return uploadFile(this.taskId, ...args)
|
return uploadFile(this.taskId, ...args)
|
||||||
},
|
},
|
||||||
|
|
||||||
async loadTask(taskId: TaskModel['id']) {
|
async loadTask(taskId: ITask['id']) {
|
||||||
if (taskId === undefined) {
|
if (taskId === undefined) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -703,7 +703,7 @@ export default defineComponent({
|
||||||
this.saveTask(true, this.toggleTaskDone)
|
this.saveTask(true, this.toggleTaskDone)
|
||||||
},
|
},
|
||||||
|
|
||||||
async changeList(list: ListModel) {
|
async changeList(list: IList) {
|
||||||
this.$store.commit('kanban/removeTaskInBucket', this.task)
|
this.$store.commit('kanban/removeTaskInBucket', this.task)
|
||||||
this.task.listId = list.id
|
this.task.listId = list.id
|
||||||
await this.saveTask()
|
await this.saveTask()
|
||||||
|
|
|
@ -162,23 +162,23 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import {computed, ref} from 'vue'
|
import {computed, ref} from 'vue'
|
||||||
|
import {useI18n} from 'vue-i18n'
|
||||||
|
|
||||||
import Editor from '@/components/input/AsyncEditor'
|
import Editor from '@/components/input/AsyncEditor'
|
||||||
import {useStore} from 'vuex'
|
import {useStore} from 'vuex'
|
||||||
|
|
||||||
import TeamService from '../../services/team'
|
import TeamService from '@/services/team'
|
||||||
import type TeamModel from '../../models/team'
|
import TeamMemberService from '@/services/teamMember'
|
||||||
import TeamMemberService from '../../services/teamMember'
|
import UserService from '@/services/user'
|
||||||
import type TeamMemberModel from '../../models/teamMember'
|
|
||||||
import type UserModel from '../../models/user'
|
|
||||||
import UserService from '../../services/user'
|
|
||||||
import {RIGHTS as Rights} from '@/models/constants/rights'
|
import {RIGHTS as Rights} from '@/models/constants/rights'
|
||||||
|
|
||||||
import Multiselect from '@/components/input/multiselect.vue'
|
import Multiselect from '@/components/input/multiselect.vue'
|
||||||
import {useRoute, useRouter} from 'vue-router'
|
import {useRoute, useRouter} from 'vue-router'
|
||||||
import {useTitle} from '@/composables/useTitle'
|
import {useTitle} from '@/composables/useTitle'
|
||||||
import {useI18n} from 'vue-i18n'
|
|
||||||
import {success} from '@/message'
|
import {success} from '@/message'
|
||||||
|
import type { ITeam } from '@/models/team'
|
||||||
|
import type { IUser } from '@/models/user'
|
||||||
|
import type { ITeamMember } from '@/models/teamMember'
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
@ -198,11 +198,11 @@ const teamService = ref<TeamService>(new TeamService())
|
||||||
const teamMemberService = ref<TeamMemberService>(new TeamMemberService())
|
const teamMemberService = ref<TeamMemberService>(new TeamMemberService())
|
||||||
const userService = ref<UserService>(new UserService())
|
const userService = ref<UserService>(new UserService())
|
||||||
|
|
||||||
const team = ref<TeamModel>()
|
const team = ref<ITeam>()
|
||||||
const teamId = computed(() => route.params.id)
|
const teamId = computed(() => route.params.id)
|
||||||
const memberToDelete = ref<TeamMemberModel>()
|
const memberToDelete = ref<ITeamMember>()
|
||||||
const newMember = ref<UserModel>()
|
const newMember = ref<IUser>()
|
||||||
const foundUsers = ref<UserModel[]>()
|
const foundUsers = ref<IUser[]>()
|
||||||
|
|
||||||
const showDeleteModal = ref(false)
|
const showDeleteModal = ref(false)
|
||||||
const showUserDeleteModal = ref(false)
|
const showUserDeleteModal = ref(false)
|
||||||
|
@ -257,7 +257,7 @@ async function addUser() {
|
||||||
success({message: t('team.edit.userAddedSuccess')})
|
success({message: t('team.edit.userAddedSuccess')})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function toggleUserType(member: TeamMemberModel) {
|
async function toggleUserType(member: ITeamMember) {
|
||||||
// FIXME: direct manipulation
|
// FIXME: direct manipulation
|
||||||
member.admin = !member.admin
|
member.admin = !member.admin
|
||||||
member.teamId = teamId.value
|
member.teamId = teamId.value
|
||||||
|
@ -282,7 +282,7 @@ async function findUser(query: string) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const users = await userService.value.getAll({}, {s: query})
|
const users = await userService.value.getAll({}, {s: query})
|
||||||
foundUsers.value = users.filter((u: UserModel) => u.id !== userInfo.value.id)
|
foundUsers.value = users.filter((u: IUser) => u.id !== userInfo.value.id)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -77,8 +77,8 @@ import {success} from '@/message'
|
||||||
import BaseButton from '@/components/base/BaseButton.vue'
|
import BaseButton from '@/components/base/BaseButton.vue'
|
||||||
import Message from '@/components/misc/message.vue'
|
import Message from '@/components/misc/message.vue'
|
||||||
import CaldavTokenService from '@/services/caldavToken'
|
import CaldavTokenService from '@/services/caldavToken'
|
||||||
import type CaldavTokenModel from '@/models/caldavToken'
|
|
||||||
import { formatDateShort } from '@/helpers/time/formatDate'
|
import { formatDateShort } from '@/helpers/time/formatDate'
|
||||||
|
import type { ICaldavToken } from '@/models/caldavToken'
|
||||||
|
|
||||||
const copy = useCopyToClipboard()
|
const copy = useCopyToClipboard()
|
||||||
|
|
||||||
|
@ -86,19 +86,19 @@ const {t} = useI18n({useScope: 'global'})
|
||||||
useTitle(() => `${t('user.settings.caldav.title')} - ${t('user.settings.title')}`)
|
useTitle(() => `${t('user.settings.caldav.title')} - ${t('user.settings.title')}`)
|
||||||
|
|
||||||
const service = shallowReactive(new CaldavTokenService())
|
const service = shallowReactive(new CaldavTokenService())
|
||||||
const tokens = ref<CaldavTokenModel[]>([])
|
const tokens = ref<ICaldavToken[]>([])
|
||||||
|
|
||||||
service.getAll().then((result: CaldavTokenModel[]) => {
|
service.getAll().then((result: ICaldavToken[]) => {
|
||||||
tokens.value = result
|
tokens.value = result
|
||||||
})
|
})
|
||||||
|
|
||||||
const newToken = ref<CaldavTokenModel>()
|
const newToken = ref<ICaldavToken>()
|
||||||
async function createToken() {
|
async function createToken() {
|
||||||
newToken.value = await service.create({}) as CaldavTokenModel
|
newToken.value = await service.create({}) as ICaldavToken
|
||||||
tokens.value.push(newToken.value)
|
tokens.value.push(newToken.value)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteToken(token: CaldavTokenModel) {
|
async function deleteToken(token: ICaldavToken) {
|
||||||
const r = await service.delete(token)
|
const r = await service.delete(token)
|
||||||
tokens.value = tokens.value.filter(({id}) => id !== token.id)
|
tokens.value = tokens.value.filter(({id}) => id !== token.id)
|
||||||
success(r)
|
success(r)
|
||||||
|
|
Reference in New Issue
Why use
declare
for some properties instead of only defining the property?I had to use declare everywhere where a property 'isn't defined'. All of them are, but typescript doesn't understand that this happens mostly in the constructor of the abstract service.
Not sure if this here is the right way to do things. Might be one reason why the build fails.
Makes sense!