feature/convert-abstract-service-to-ts #1798
|
@ -156,8 +156,8 @@ import {calculateItemPosition} from '@/helpers/calculateItemPosition'
|
|||
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
|
||||
import {getListTitle} from '@/helpers/getListTitle'
|
||||
import {useEventListener} from '@vueuse/core'
|
||||
import type NamespaceModel from '@/models/namespace'
|
||||
import type ListModel from '@/models/list'
|
||||
import type { IList } from '@/models/list'
|
||||
import type { INamespace } from '@/models/namespace'
|
||||
|
||||
const drag = ref(false)
|
||||
const dragOptions = {
|
||||
|
@ -172,7 +172,7 @@ const loading = computed(() => store.state.loading && store.state.loadingModule
|
|||
|
||||
|
||||
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(() => {
|
||||
return namespaces.value.map(({lists}) => {
|
||||
|
@ -195,7 +195,7 @@ useEventListener('resize', resize)
|
|||
onMounted(() => resize())
|
||||
|
||||
|
||||
function toggleFavoriteList(list: ListModel) {
|
||||
function toggleFavoriteList(list: IList) {
|
||||
// The favorites pseudo list is always favorite
|
||||
// Archived lists cannot be marked favorite
|
||||
if (list.id === -1 || list.isArchived) {
|
||||
|
@ -209,14 +209,14 @@ function resize() {
|
|||
store.commit(MENU_ACTIVE, window.innerWidth >= 770)
|
||||
}
|
||||
|
||||
function toggleLists(namespaceId: NamespaceModel['id']) {
|
||||
function toggleLists(namespaceId: INamespace['id']) {
|
||||
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
|
||||
onBeforeMount(async () => {
|
||||
const namespaces = await store.dispatch('namespaces/loadNamespaces') as NamespaceModel[]
|
||||
const namespaces = await store.dispatch('namespaces/loadNamespaces') as INamespace[]
|
||||
namespaces.forEach(n => {
|
||||
if (typeof listsVisible.value[n.id] === 'undefined') {
|
||||
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
|
||||
// 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
|
||||
|
@ -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) {
|
||||
if (!e.newIndex && e.newIndex !== 0) return
|
||||
|
|
|
@ -76,24 +76,24 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed, watchEffect} from 'vue'
|
||||
import {ref, computed, watchEffect, type PropType} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
|
||||
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
|
||||
import Dropdown from '@/components/misc/dropdown.vue'
|
||||
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||
import TaskSubscription from '@/components/misc/subscription.vue'
|
||||
import ListModel from '@/models/list'
|
||||
import type SubscriptionModel from '@/models/subscription'
|
||||
import type {IList} from '@/models/list'
|
||||
import type { ISubscription } from '@/models/subscription'
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: ListModel,
|
||||
type: Object as PropType<IList>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
||||
const subscription = ref<SubscriptionModel | null>(null)
|
||||
const subscription = ref<ISubscription | null>(null)
|
||||
watchEffect(() => {
|
||||
subscription.value = props.list.subscription ?? null
|
||||
})
|
||||
|
|
|
@ -43,9 +43,9 @@ import ListService from '@/services/list'
|
|||
import {getBlobFromBlurHash} from '@/helpers/getBlobFromBlurHash'
|
||||
|
||||
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||
import type ListModel from '@/models/list'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import type { IList } from '@/models/list'
|
||||
|
||||
const background = ref<string | null>(null)
|
||||
const backgroundLoading = ref(false)
|
||||
|
@ -53,7 +53,7 @@ const blurHashUrl = ref('')
|
|||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Object as PropType<ListModel>,
|
||||
type: Object as PropType<IList>,
|
||||
required: true,
|
||||
},
|
||||
showArchived: {
|
||||
|
@ -86,7 +86,7 @@ async function loadBackground() {
|
|||
|
||||
const store = useStore()
|
||||
|
||||
function toggleFavoriteList(list: ListModel) {
|
||||
function toggleFavoriteList(list: IList) {
|
||||
// The favorites pseudo list is always favorite
|
||||
// Archived lists cannot be marked favorite
|
||||
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 SubscriptionService from '@/services/subscription'
|
||||
import SubscriptionModel from '@/models/subscription'
|
||||
import SubscriptionModel, { type ISubscription } from '@/models/subscription'
|
||||
|
||||
import {success} from '@/message'
|
||||
|
||||
|
@ -51,7 +51,7 @@ const props = defineProps({
|
|||
default: true,
|
||||
},
|
||||
subscription: {
|
||||
type: Object as PropType<SubscriptionModel>,
|
||||
type: Object as PropType<ISubscription>,
|
||||
default: null,
|
||||
},
|
||||
type: {
|
||||
|
|
|
@ -54,15 +54,16 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, onMounted} from 'vue'
|
||||
import {ref, onMounted, type PropType} from 'vue'
|
||||
|
||||
import Dropdown from '@/components/misc/dropdown.vue'
|
||||
import DropdownItem from '@/components/misc/dropdown-item.vue'
|
||||
import TaskSubscription from '@/components/misc/subscription.vue'
|
||||
import type { INamespace } from '@/models/namespace'
|
||||
|
||||
const props = defineProps({
|
||||
namespace: {
|
||||
type: Object, // NamespaceModel
|
||||
type: Object as PropType<INamespace>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -52,7 +52,7 @@ import {computed, onMounted, onUnmounted, ref} from 'vue'
|
|||
import NotificationService from '@/services/notification'
|
||||
import BaseButton from '@/components/base/BaseButton.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 {useStore} from 'vuex'
|
||||
import {useRouter} from 'vue-router'
|
||||
|
@ -63,7 +63,7 @@ const LOAD_NOTIFICATIONS_INTERVAL = 10000
|
|||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
const allNotifications = ref<NotificationModel[]>([])
|
||||
const allNotifications = ref<INotification[]>([])
|
||||
const showNotifications = ref(false)
|
||||
const popup = ref(null)
|
||||
|
||||
|
|
|
@ -181,13 +181,13 @@ import {useStore} from 'vuex'
|
|||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import {RIGHTS} from '@/models/constants/rights'
|
||||
import LinkShareModel from '@/models/linkShare'
|
||||
import type ListModel from '@/models/list'
|
||||
import LinkShareModel, { type ILinkShare } from '@/models/linkShare'
|
||||
|
||||
import LinkShareService from '@/services/linkShare'
|
||||
|
||||
import {useCopyToClipboard} from '@/composables/useCopyToClipboard'
|
||||
import {success} from '@/message'
|
||||
import type { IList } from '@/models/list'
|
||||
|
||||
const props = defineProps({
|
||||
listId: {
|
||||
|
@ -198,7 +198,7 @@ const props = defineProps({
|
|||
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
const linkShares = ref<LinkShareModel[]>([])
|
||||
const linkShares = ref<ILinkShare[]>([])
|
||||
const linkShareService = shallowReactive(new LinkShareService())
|
||||
const selectedRight = ref(RIGHTS.READ)
|
||||
const name = ref('')
|
||||
|
@ -217,7 +217,7 @@ watch(
|
|||
const store = useStore()
|
||||
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) {
|
||||
return
|
||||
|
@ -226,7 +226,7 @@ async function load(listId: ListModel['id']) {
|
|||
linkShares.value = await linkShareService.getAll({listId})
|
||||
}
|
||||
|
||||
async function add(listId: ListModel['id']) {
|
||||
async function add(listId: IList['id']) {
|
||||
const newLinkShare = new LinkShareModel({
|
||||
right: selectedRight.value,
|
||||
listId,
|
||||
|
@ -242,7 +242,7 @@ async function add(listId: ListModel['id']) {
|
|||
await load(listId)
|
||||
}
|
||||
|
||||
async function remove(listId: ListModel['id']) {
|
||||
async function remove(listId: IList['id']) {
|
||||
try {
|
||||
await linkShareService.delete(new LinkShareModel({
|
||||
id: linkIdToDelete.value,
|
||||
|
|
|
@ -143,18 +143,22 @@ import {useStore} from 'vuex'
|
|||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import UserNamespaceService from '@/services/userNamespace'
|
||||
import UserNamespaceModel from '@/models/userNamespace'
|
||||
import UserListModel from '@/models/userList'
|
||||
import UserNamespaceModel, { type IUserNamespace } from '@/models/userNamespace'
|
||||
|
||||
import UserListService from '@/services/userList'
|
||||
import UserListModel, { type IUserList } from '@/models/userList'
|
||||
|
||||
import UserService from '@/services/user'
|
||||
import UserModel from '@/models/user'
|
||||
import UserModel, { type IUser } from '@/models/user'
|
||||
|
||||
import TeamNamespaceService from '@/services/teamNamespace'
|
||||
import TeamNamespaceModel from '@/models/teamNamespace'
|
||||
import TeamListModel from '@/models/teamList'
|
||||
import TeamNamespaceModel, { type ITeamNamespace } from '@/models/teamNamespace'
|
||||
|
||||
import TeamListService from '@/services/teamList'
|
||||
import TeamListModel, { type ITeamList } from '@/models/teamList'
|
||||
|
||||
import TeamService from '@/services/team'
|
||||
import TeamModel from '@/models/team'
|
||||
import TeamModel, { type ITeam } from '@/models/team'
|
||||
|
||||
import {RIGHTS} from '@/models/constants/rights'
|
||||
import Multiselect from '@/components/input/multiselect.vue'
|
||||
|
@ -183,10 +187,10 @@ const props = defineProps({
|
|||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
// 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 stuffModel: UserNamespaceModel | UserListModel | TeamListModel | TeamNamespaceModel
|
||||
let searchService: ShallowReactive<UserService | TeamService>
|
||||
let sharable: Ref<UserModel | TeamModel>
|
||||
let stuffService: UserNamespaceService | UserListService | TeamListService | TeamNamespaceService
|
||||
let stuffModel: IUserNamespace | IUserList | ITeamList | ITeamNamespace
|
||||
let searchService: UserService | TeamService
|
||||
let sharable: Ref<IUser | ITeam>
|
||||
|
||||
const searchLabel = ref('')
|
||||
const selectedRight = ref({})
|
||||
|
|
|
@ -76,14 +76,14 @@
|
|||
</template>
|
||||
|
||||
<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 {useI18n} from 'vue-i18n'
|
||||
|
||||
import Editor from '@/components/input/AsyncEditor'
|
||||
|
||||
import TaskService from '@/services/task'
|
||||
import TaskModel from '@/models/task'
|
||||
import TaskModel, { type ITask } from '@/models/task'
|
||||
import EditLabels from './partials/editLabels.vue'
|
||||
import Reminders from './partials/reminders.vue'
|
||||
import ColorPicker from '../input/colorPicker.vue'
|
||||
|
@ -93,14 +93,16 @@ import {success} from '@/message'
|
|||
const {t} = useI18n({useScope: 'global'})
|
||||
const router = useRouter()
|
||||
|
||||
const props = defineProps<{
|
||||
task?: TaskModel | null,
|
||||
}>()
|
||||
const props = defineProps({
|
||||
task: {
|
||||
type: Object as PropType<ITask | null>,
|
||||
},
|
||||
})
|
||||
|
||||
const taskService = shallowReactive(new TaskService())
|
||||
|
||||
const editorActive = ref(false)
|
||||
let taskEditTask: TaskModel | undefined
|
||||
let taskEditTask: ITask | undefined
|
||||
|
||||
|
||||
// FIXME: this initialization should not be necessary here
|
||||
|
|
|
@ -147,8 +147,7 @@
|
|||
import {defineComponent} from 'vue'
|
||||
|
||||
import AttachmentService from '../../../services/attachment'
|
||||
import AttachmentModel from '../../../models/attachment'
|
||||
import type FileModel from '@/models/file'
|
||||
import AttachmentModel, { type IAttachment } from '@/models/attachment'
|
||||
import User from '@/components/misc/user.vue'
|
||||
import {mapState} from 'vuex'
|
||||
|
||||
|
@ -157,6 +156,7 @@ import { uploadFiles, generateAttachmentUrl } from '@/helpers/attachments'
|
|||
import {formatDate, formatDateSince, formatDateLong} from '@/helpers/time/formatDate'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton'
|
||||
import type { IFile } from '@/models/file'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'attachments',
|
||||
|
@ -192,7 +192,7 @@ export default defineComponent({
|
|||
setup(props) {
|
||||
const copy = useCopyToClipboard()
|
||||
|
||||
function copyUrl(attachment: AttachmentModel) {
|
||||
function copyUrl(attachment: IAttachment) {
|
||||
copy(generateAttachmentUrl(props.taskId, attachment.id))
|
||||
}
|
||||
|
||||
|
@ -235,7 +235,7 @@ export default defineComponent({
|
|||
formatDateSince,
|
||||
formatDateLong,
|
||||
|
||||
downloadAttachment(attachment: AttachmentModel) {
|
||||
downloadAttachment(attachment: IAttachment) {
|
||||
this.attachmentService.download(attachment)
|
||||
},
|
||||
uploadNewAttachment() {
|
||||
|
@ -245,7 +245,7 @@ export default defineComponent({
|
|||
|
||||
this.uploadFiles(this.$refs.files.files)
|
||||
},
|
||||
uploadFiles(files: FileModel[]) {
|
||||
uploadFiles(files: IFile[]) {
|
||||
uploadFiles(this.attachmentService, this.taskId, files)
|
||||
},
|
||||
async deleteAttachment() {
|
||||
|
|
|
@ -10,15 +10,15 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {computed} from 'vue'
|
||||
import {computed, type PropType} from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
|
||||
import {getChecklistStatistics} from '@/helpers/checklistFromText'
|
||||
import TaskModel from '@/models/task'
|
||||
import type {ITask} from '@/models/task'
|
||||
|
||||
const props = defineProps({
|
||||
task: {
|
||||
type: TaskModel,
|
||||
type: Object as PropType<ITask>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -159,12 +159,12 @@ import {useI18n} from 'vue-i18n'
|
|||
import Editor from '@/components/input/AsyncEditor'
|
||||
|
||||
import TaskCommentService from '@/services/taskComment'
|
||||
import TaskCommentModel from '@/models/taskComment'
|
||||
import TaskCommentModel, { type ITaskComment } from '@/models/taskComment'
|
||||
import {uploadFile} from '@/helpers/attachments'
|
||||
import {success} from '@/message'
|
||||
import {formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
||||
|
||||
import type TaskModel from '@/models/task'
|
||||
import type { ITask } from '@/models/task'
|
||||
const props = defineProps({
|
||||
taskId: {
|
||||
type: Number,
|
||||
|
@ -178,7 +178,7 @@ const props = defineProps({
|
|||
const {t} = useI18n({useScope: 'global'})
|
||||
const store = useStore()
|
||||
|
||||
const comments = ref<TaskCommentModel[]>([])
|
||||
const comments = ref<ITaskComment[]>([])
|
||||
|
||||
const showDeleteModal = ref(false)
|
||||
const commentToDelete = reactive(new TaskCommentModel())
|
||||
|
@ -188,8 +188,8 @@ const commentEdit = reactive(new TaskCommentModel())
|
|||
|
||||
const newComment = reactive(new TaskCommentModel())
|
||||
|
||||
const saved = ref<TaskModel['id'] | null>(null)
|
||||
const saving = ref<TaskModel['id'] | null>(null)
|
||||
const saved = ref<ITask['id'] | null>(null)
|
||||
const saving = ref<ITask['id'] | null>(null)
|
||||
|
||||
const userAvatar = computed(() => store.state.auth.info.getAvatarUrl(48))
|
||||
const currentUserId = computed(() => store.state.auth.info.id)
|
||||
|
@ -215,7 +215,7 @@ function attachmentUpload(...args) {
|
|||
|
||||
const taskCommentService = shallowReactive(new TaskCommentService())
|
||||
|
||||
async function loadComments(taskId: TaskModel['id']) {
|
||||
async function loadComments(taskId: ITask['id']) {
|
||||
if (!enabled.value) {
|
||||
return
|
||||
}
|
||||
|
@ -259,12 +259,12 @@ async function addComment() {
|
|||
}
|
||||
}
|
||||
|
||||
function toggleEdit(comment: TaskCommentModel) {
|
||||
function toggleEdit(comment: ITaskComment) {
|
||||
isCommentEdit.value = !isCommentEdit.value
|
||||
Object.assign(commentEdit, comment)
|
||||
}
|
||||
|
||||
function toggleDelete(commentId: TaskCommentModel['id']) {
|
||||
function toggleDelete(commentId: ITaskComment['id']) {
|
||||
showDeleteModal.value = !showDeleteModal.value
|
||||
commentToDelete.id = commentId
|
||||
}
|
||||
|
@ -294,7 +294,7 @@ async function editComment() {
|
|||
}
|
||||
}
|
||||
|
||||
async function deleteComment(commentToDelete: TaskCommentModel) {
|
||||
async function deleteComment(commentToDelete: ITaskComment) {
|
||||
try {
|
||||
await taskCommentService.delete(commentToDelete)
|
||||
const index = comments.value.findIndex(({id}) => id === commentToDelete.id)
|
||||
|
|
|
@ -27,13 +27,13 @@
|
|||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, toRefs} from 'vue'
|
||||
import TaskModel from '@/models/task'
|
||||
import {computed, toRefs, type PropType} from 'vue'
|
||||
import type { ITask } from '@/models/task'
|
||||
import {formatISO, formatDateLong, formatDateSince} from '@/helpers/time/formatDate'
|
||||
|
||||
const props = defineProps({
|
||||
task: {
|
||||
type: TaskModel,
|
||||
type: Object as PropType<ITask>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -38,17 +38,17 @@
|
|||
</template>
|
||||
|
||||
<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 {useI18n} from 'vue-i18n'
|
||||
import flatPickr from 'vue-flatpickr-component'
|
||||
|
||||
import TaskService from '@/services/task'
|
||||
import TaskModel from '@/models/task'
|
||||
import { type ITask } from '@/models/task'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: TaskModel,
|
||||
type: Object as PropType<ITask>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
@ -58,7 +58,7 @@ const {t} = useI18n({useScope: 'global'})
|
|||
const store = useStore()
|
||||
|
||||
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.
|
||||
const dueDate = ref<Date>()
|
||||
|
|
|
@ -30,17 +30,17 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref,computed, watch} from 'vue'
|
||||
import {ref,computed, watch, type PropType} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
|
||||
import Editor from '@/components/input/AsyncEditor'
|
||||
|
||||
import TaskModel from '@/models/task'
|
||||
import type { ITask } from '@/models/task'
|
||||
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: TaskModel,
|
||||
type: Object as PropType<ITask>,
|
||||
required: true,
|
||||
},
|
||||
attachmentUpload: {
|
||||
|
@ -54,7 +54,7 @@ const props = defineProps({
|
|||
|
||||
const emit = defineEmits(['update:modelValue'])
|
||||
|
||||
const task = ref<TaskModel>({description: ''})
|
||||
const task = ref<ITask>({description: ''})
|
||||
const saved = ref(false)
|
||||
|
||||
// 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 {includesById} from '@/helpers/utils'
|
||||
import type UserModel from '@/models/user'
|
||||
import ListUserService from '@/services/listUsers'
|
||||
import {success} from '@/message'
|
||||
import type { IUser } from '@/models/user'
|
||||
|
||||
const props = defineProps({
|
||||
taskId: {
|
||||
|
@ -54,7 +54,7 @@ const props = defineProps({
|
|||
default: false,
|
||||
},
|
||||
modelValue: {
|
||||
type: Array as PropType<UserModel[]>,
|
||||
type: Array as PropType<IUser[]>,
|
||||
default: () => [],
|
||||
},
|
||||
})
|
||||
|
@ -65,7 +65,7 @@ const {t} = useI18n({useScope: 'global'})
|
|||
|
||||
const listUserService = shallowReactive(new ListUserService())
|
||||
const foundUsers = ref([])
|
||||
const assignees = ref<UserModel[]>([])
|
||||
const assignees = ref<IUser[]>([])
|
||||
|
||||
watch(
|
||||
() => 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})
|
||||
emit('update:modelValue', assignees.value)
|
||||
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})
|
||||
|
||||
// Remove the assignee from the list
|
||||
|
|
|
@ -43,7 +43,7 @@ import {type PropType, ref, computed, shallowReactive, watch} from 'vue'
|
|||
import {useStore} from 'vuex'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import LabelModel from '@/models/label'
|
||||
import LabelModel, { type ILabel } from '@/models/label'
|
||||
import LabelTaskService from '@/services/labelTask'
|
||||
import {success} from '@/message'
|
||||
|
||||
|
@ -52,7 +52,7 @@ import Multiselect from '@/components/input/multiselect.vue'
|
|||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Array as PropType<LabelModel[]>,
|
||||
type: Array as PropType<ILabel[]>,
|
||||
default: () => [],
|
||||
},
|
||||
taskId: {
|
||||
|
@ -71,7 +71,7 @@ const store = useStore()
|
|||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
const labelTaskService = shallowReactive(new LabelTaskService())
|
||||
const labels = ref<LabelModel[]>([])
|
||||
const labels = ref<ILabel[]>([])
|
||||
const query = ref('')
|
||||
|
||||
watch(
|
||||
|
@ -92,7 +92,7 @@ function findLabel(newQuery: string) {
|
|||
query.value = newQuery
|
||||
}
|
||||
|
||||
async function addLabel(label: LabelModel, showNotification = true) {
|
||||
async function addLabel(label: ILabel, showNotification = true) {
|
||||
const bubble = () => {
|
||||
emit('update:modelValue', 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) {
|
||||
await store.dispatch('tasks/removeLabel', {label, taskId: props.taskId})
|
||||
}
|
||||
|
|
|
@ -32,18 +32,18 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {ref, computed} from 'vue'
|
||||
import {ref, computed, type PropType} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
|
||||
import BaseButton from '@/components/base/BaseButton.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 { useCopyToClipboard } from '@/composables/useCopyToClipboard'
|
||||
|
||||
const props = defineProps({
|
||||
task: {
|
||||
type: TaskModel,
|
||||
type: Object as PropType<ITask>,
|
||||
required: true,
|
||||
},
|
||||
canWrite: {
|
||||
|
|
|
@ -74,7 +74,7 @@ import User from '../../../components/misc/user.vue'
|
|||
import Done from '@/components/misc/Done.vue'
|
||||
import Labels from '../../../components/tasks/partials/labels.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 {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||
|
@ -96,7 +96,7 @@ export default defineComponent({
|
|||
},
|
||||
props: {
|
||||
task: {
|
||||
type: Object as PropType<TaskModel>,
|
||||
type: Object as PropType<ITask>,
|
||||
required: true,
|
||||
},
|
||||
loading: {
|
||||
|
@ -117,7 +117,7 @@ export default defineComponent({
|
|||
formatISO,
|
||||
formatDateSince,
|
||||
colorIsDark,
|
||||
async toggleTaskDone(task: TaskModel) {
|
||||
async toggleTaskDone(task: ITask) {
|
||||
this.loadingInternal = true
|
||||
try {
|
||||
const done = !task.done
|
||||
|
|
|
@ -11,12 +11,12 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type LabelModel from '@/models/label'
|
||||
import type { PropType } from 'vue'
|
||||
import type { ILabel } from '@/models/label'
|
||||
|
||||
defineProps({
|
||||
labels: {
|
||||
type: Array as PropType<LabelModel[]>,
|
||||
type: Array as PropType<ILabel[]>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -21,15 +21,12 @@ import {reactive, ref, watch} from 'vue'
|
|||
import type {PropType} from 'vue'
|
||||
import {useStore} from 'vuex'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
import ListModel from '@/models/list'
|
||||
import ListModel, { type IList } from '@/models/list'
|
||||
import Multiselect from '@/components/input/multiselect.vue'
|
||||
|
||||
const props = defineProps({
|
||||
modelValue: {
|
||||
type: Object as PropType<ListModel>,
|
||||
validator(value) {
|
||||
return value instanceof ListModel
|
||||
},
|
||||
type: Object as PropType<IList>,
|
||||
required: false,
|
||||
},
|
||||
})
|
||||
|
@ -38,7 +35,7 @@ const emit = defineEmits(['update:modelValue'])
|
|||
const store = useStore()
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
const list: ListModel= reactive(new ListModel())
|
||||
const list: IList = reactive(new ListModel())
|
||||
|
||||
watch(
|
||||
() => props.modelValue,
|
||||
|
@ -57,7 +54,7 @@ function findLists(query: string) {
|
|||
foundLists.value = store.getters['lists/searchList'](query)
|
||||
}
|
||||
|
||||
function select(l: ListModel | null) {
|
||||
function select(l: IList | null) {
|
||||
Object.assign(list, l)
|
||||
emit('update:modelValue', list)
|
||||
}
|
||||
|
|
|
@ -63,14 +63,14 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
import {ref, reactive, watch, type PropType} from 'vue'
|
||||
import {error} from '@/message'
|
||||
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({
|
||||
modelValue: {
|
||||
type: Object as PropType<TaskModel>,
|
||||
type: Object as PropType<ITask>,
|
||||
default: () => ({}),
|
||||
required: true,
|
||||
},
|
||||
|
@ -84,7 +84,7 @@ const {t} = useI18n({useScope: 'global'})
|
|||
|
||||
const emit = defineEmits(['update:modelValue', 'change'])
|
||||
|
||||
const task = ref<TaskModel>()
|
||||
const task = ref<ITask>()
|
||||
const repeatAfter = reactive({
|
||||
amount: 0,
|
||||
type: '',
|
||||
|
|
|
@ -98,7 +98,7 @@
|
|||
<script lang="ts">
|
||||
import {defineComponent} from 'vue'
|
||||
|
||||
import TaskModel from '../../../models/task'
|
||||
import TaskModel, { type ITask } from '../../../models/task'
|
||||
import PriorityLabel from './priorityLabel.vue'
|
||||
import TaskService from '../../../services/task'
|
||||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
@ -129,7 +129,7 @@ export default defineComponent({
|
|||
},
|
||||
props: {
|
||||
theTask: {
|
||||
type: TaskModel,
|
||||
type: Object as PropType<ITask>,
|
||||
required: true,
|
||||
},
|
||||
isArchived: {
|
||||
|
|
|
@ -1,22 +1,27 @@
|
|||
import AttachmentModel from '@/models/attachment'
|
||||
import FileModel from '@/models/file'
|
||||
import AttachmentModel, { type IAttachment } from '@/models/attachment'
|
||||
import type {IFile} from '@/models/file'
|
||||
|
||||
import AttachmentService from '@/services/attachment'
|
||||
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 files = [file]
|
||||
|
||||
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 response = await attachmentService.create(attachmentModel, files)
|
||||
console.debug(`Uploaded attachments for task ${taskId}, response was`, response)
|
||||
|
||||
response.success?.map((attachment: AttachmentModel) => {
|
||||
response.success?.map((attachment: IAttachment) => {
|
||||
store.dispatch('tasks/addTaskAttachment', {
|
||||
taskId,
|
||||
attachment,
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import {i18n} from '@/i18n'
|
||||
import type { IList } from '@/models/list'
|
||||
|
||||
import type ListModal from '@/models/list'
|
||||
|
||||
export function getListTitle(l: ListModal) {
|
||||
export function getListTitle(l: IList) {
|
||||
if (l.id === -1) {
|
||||
return i18n.global.t('list.pseudo.favorites.title')
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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) {
|
||||
return i18n.global.t('namespace.pseudo.sharedLists.title')
|
||||
}
|
||||
|
|
|
@ -1,18 +1,10 @@
|
|||
import {createNewIndexer} from '../indexes'
|
||||
|
||||
import type {LabelState} from '@/store/types'
|
||||
import type {ILabel} from '@/models/label'
|
||||
|
||||
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
|
||||
* @param {Object} state
|
||||
|
@ -20,7 +12,7 @@ interface labelState {
|
|||
* @param {String} query
|
||||
* @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)
|
||||
|
||||
return search(query)
|
||||
|
@ -36,6 +28,6 @@ export function filterLabelsByQuery(state: labelState, labelsToHide: label[], qu
|
|||
* @param {Array} ids
|
||||
* @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))
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import ListModel from '@/models/list'
|
||||
import type {IList} from '@/models/list'
|
||||
|
||||
const key = 'collapsedBuckets'
|
||||
|
||||
|
@ -11,7 +11,10 @@ const getAllState = () => {
|
|||
return JSON.parse(saved)
|
||||
}
|
||||
|
||||
export const saveCollapsedBucketState = (listId: ListModel['id'], collapsedBuckets) => {
|
||||
export const saveCollapsedBucketState = (
|
||||
listId: IList['id'],
|
||||
collapsedBuckets,
|
||||
) => {
|
||||
const state = getAllState()
|
||||
state[listId] = collapsedBuckets
|
||||
for (const bucketId in state[listId]) {
|
||||
|
@ -22,7 +25,7 @@ export const saveCollapsedBucketState = (listId: ListModel['id'], collapsedBucke
|
|||
localStorage.setItem(key, JSON.stringify(state))
|
||||
}
|
||||
|
||||
export const getCollapsedBucketState = (listId : ListModel['id']) => {
|
||||
export const getCollapsedBucketState = (listId : IList['id']) => {
|
||||
const state = getAllState()
|
||||
if (typeof state[listId] !== 'undefined') {
|
||||
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
|
||||
// FilterIds from listIds are always positive
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,7 +2,11 @@ import {objectToCamelCase} from '@/helpers/case'
|
|||
import {omitBy, isNil} from '@/helpers/utils'
|
||||
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.
|
||||
|
|
|
@ -1,12 +1,21 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import UserModel from './user'
|
||||
import FileModel from './file'
|
||||
import UserModel, {type IUser} from './user'
|
||||
import FileModel, {type IFile} from './file'
|
||||
import type {IAbstract} from './abstractModel'
|
||||
|
||||
export default class AttachmentModel extends AbstractModel {
|
||||
export interface IAttachment extends IAbstract {
|
||||
id: number
|
||||
taskId: number
|
||||
createdBy: UserModel
|
||||
file: FileModel
|
||||
createdBy: IUser
|
||||
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
|
||||
|
||||
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 {
|
||||
avatarProvider: AVATAR_PROVIDERS
|
||||
export interface IAvatar extends IAbstract {
|
||||
avatarProvider: AvatarProvider
|
||||
}
|
||||
|
||||
export default class AvatarModel extends AbstractModel implements IAvatar {
|
||||
declare avatarProvider: AvatarProvider
|
||||
|
||||
defaults() {
|
||||
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
|
||||
url: string
|
||||
thumb: string
|
||||
|
@ -9,6 +9,17 @@ export default class BackgroundImageModel extends AbstractModel {
|
|||
authorName: 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() {
|
||||
return {
|
||||
|
|
|
@ -1,17 +1,31 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import UserModel from './user'
|
||||
import TaskModel from './task'
|
||||
import AbstractModel, { type IAbstract } from './abstractModel'
|
||||
import UserModel, { type IUser } from './user'
|
||||
import TaskModel, { type ITask } from './task'
|
||||
|
||||
export default class BucketModel extends AbstractModel {
|
||||
export interface IBucket extends IAbstract {
|
||||
id: number
|
||||
title: string
|
||||
listId: number
|
||||
limit: number
|
||||
tasks: TaskModel[]
|
||||
tasks: ITask[]
|
||||
isDoneBucket: boolean
|
||||
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
|
||||
updated: Date
|
||||
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import AbstractModel, { type IAbstract } from './abstractModel'
|
||||
|
||||
export default class CaldavTokenModel extends AbstractModel {
|
||||
id: number
|
||||
created: Date
|
||||
export interface ICaldavToken extends IAbstract {
|
||||
id: number;
|
||||
created: Date;
|
||||
}
|
||||
|
||||
export default class CaldavTokenModel extends AbstractModel implements ICaldavToken {
|
||||
declare id: number
|
||||
declare created: Date
|
||||
|
||||
constructor(data? : Object) {
|
||||
super(data)
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
|
||||
export default class EmailUpdateModel extends AbstractModel {
|
||||
interface IEmailUpdate {
|
||||
newEmail: string
|
||||
password: string
|
||||
}
|
||||
|
||||
export default class EmailUpdateModel extends AbstractModel implements IEmailUpdate {
|
||||
declare newEmail: string
|
||||
declare password: string
|
||||
|
||||
defaults() {
|
||||
return {
|
||||
|
|
|
@ -1,11 +1,19 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
|
||||
export default class FileModel extends AbstractModel {
|
||||
export interface IFile {
|
||||
id: number
|
||||
mime: string
|
||||
name: string
|
||||
size: number
|
||||
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) {
|
||||
super(data)
|
||||
|
|
|
@ -1,19 +1,33 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import UserModel from './user'
|
||||
import UserModel, { type IUser } from './user'
|
||||
import {colorIsDark} from '@/helpers/color/colorIsDark'
|
||||
|
||||
const DEFAULT_LABEL_BACKGROUND_COLOR = 'e8e8e8'
|
||||
export default class LabelModel extends AbstractModel {
|
||||
|
||||
export interface ILabel {
|
||||
id: number
|
||||
title: string
|
||||
hexColor: string
|
||||
description: string
|
||||
createdBy: UserModel
|
||||
createdBy: IUser
|
||||
listId: number
|
||||
textColor: string
|
||||
|
||||
created: 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) {
|
||||
super(data)
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
|
||||
export default class LabelTask extends AbstractModel {
|
||||
interface ILabel {
|
||||
id: number
|
||||
taskId: number
|
||||
labelId: number
|
||||
}
|
||||
|
||||
export default class LabelTask extends AbstractModel implements ILabel {
|
||||
declare id: number
|
||||
declare taskId: number
|
||||
declare labelId: number
|
||||
|
||||
defaults() {
|
||||
return {
|
||||
|
|
|
@ -1,18 +1,31 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import UserModel from './user'
|
||||
import AbstractModel, { type IAbstract } from './abstractModel'
|
||||
import UserModel, { type IUser } from './user'
|
||||
import {RIGHTS, type Right} from '@/models/constants/rights'
|
||||
|
||||
export default class LinkShareModel extends AbstractModel {
|
||||
export interface ILinkShare extends IAbstract {
|
||||
id: number
|
||||
hash: string
|
||||
right: Right
|
||||
sharedBy: UserModel
|
||||
sharedBy: IUser
|
||||
sharingType: number // FIXME: use correct numbers
|
||||
listId: number
|
||||
name: string
|
||||
password: string
|
||||
created: 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) {
|
||||
// The constructor of AbstractModel handles all the default parsing.
|
||||
|
|
|
@ -1,28 +1,49 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import TaskModel from './task'
|
||||
import UserModel from './user'
|
||||
import type NamespaceModel from './namespace'
|
||||
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
|
||||
import SubscriptionModel from '@/models/subscription'
|
||||
import AbstractModel, { type IAbstract } from '@/models/abstractModel'
|
||||
import TaskModel, { type ITask } from '@/models/task'
|
||||
import UserModel, { type IUser } from '@/models/user'
|
||||
import SubscriptionModel, { type ISubscription } from '@/models/subscription'
|
||||
import type { INamespace } from '@/models/namespace'
|
||||
|
||||
export default class ListModel extends AbstractModel {
|
||||
import {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
|
||||
|
||||
export interface IList extends IAbstract {
|
||||
id: number
|
||||
title: string
|
||||
description: string
|
||||
owner: UserModel
|
||||
tasks: TaskModel[]
|
||||
namespaceId: NamespaceModel['id']
|
||||
owner: IUser
|
||||
tasks: ITask[]
|
||||
namespaceId: INamespace['id']
|
||||
isArchived: boolean
|
||||
hexColor: string
|
||||
identifier: string
|
||||
backgroundInformation: any
|
||||
isFavorite: boolean
|
||||
subscription: SubscriptionModel
|
||||
subscription: ISubscription
|
||||
position: number
|
||||
backgroundBlurHash: string
|
||||
|
||||
created: 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) {
|
||||
super(data)
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import ListModel from './list'
|
||||
import NamespaceModel from './namespace'
|
||||
import ListModel, { type IList } from './list'
|
||||
import type { INamespace } from './namespace'
|
||||
|
||||
export default class ListDuplicateModel extends AbstractModel {
|
||||
export interface ListDuplicate {
|
||||
listId: number
|
||||
namespaceId: NamespaceModel['id']
|
||||
list: ListModel
|
||||
namespaceId: INamespace['id']
|
||||
list: IList
|
||||
}
|
||||
|
||||
export default class ListDuplicateModel extends AbstractModel implements ListDuplicate {
|
||||
declare listId: number
|
||||
declare namespaceId: INamespace['id']
|
||||
list: IList
|
||||
|
||||
constructor(data) {
|
||||
super(data)
|
||||
|
|
|
@ -1,17 +1,31 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import ListModel from './list'
|
||||
import UserModel from './user'
|
||||
import SubscriptionModel from '@/models/subscription'
|
||||
import AbstractModel, { type IAbstract } from './abstractModel'
|
||||
import ListModel, { type IList } from './list'
|
||||
import UserModel, { type IUser } from './user'
|
||||
import SubscriptionModel, { type ISubscription } from '@/models/subscription'
|
||||
|
||||
export default class NamespaceModel extends AbstractModel {
|
||||
export interface INamespace extends IAbstract {
|
||||
id: number
|
||||
title: string
|
||||
description: string
|
||||
owner: UserModel
|
||||
lists: ListModel[]
|
||||
owner: IUser
|
||||
lists: IList[]
|
||||
isArchived: boolean
|
||||
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
|
||||
updated: Date
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import AbstractModel from '@/models/abstractModel'
|
||||
import {parseDateOrNull} from '@/helpers/parseDateOrNull'
|
||||
import UserModel from '@/models/user'
|
||||
import TaskModel from '@/models/task'
|
||||
import TaskCommentModel from '@/models/taskComment'
|
||||
import UserModel, { type IUser } from '@/models/user'
|
||||
import TaskModel, { type ITask } from '@/models/task'
|
||||
import TaskCommentModel, { type ITaskComment } from '@/models/taskComment'
|
||||
import ListModel from '@/models/list'
|
||||
import TeamModel from '@/models/team'
|
||||
import TeamModel, { type ITeam } from '@/models/team'
|
||||
|
||||
export const NOTIFICATION_NAMES = {
|
||||
'TASK_COMMENT': 'task.comment',
|
||||
|
@ -15,32 +15,32 @@ export const NOTIFICATION_NAMES = {
|
|||
} as const
|
||||
|
||||
interface Notification {
|
||||
doer: UserModel
|
||||
doer: IUser
|
||||
}
|
||||
interface NotificationTask extends Notification {
|
||||
task: TaskModel
|
||||
comment: TaskCommentModel
|
||||
task: ITask
|
||||
comment: ITaskComment
|
||||
}
|
||||
|
||||
interface NotificationAssigned extends Notification {
|
||||
task: TaskModel
|
||||
assignee: UserModel
|
||||
task: ITask
|
||||
assignee: IUser
|
||||
}
|
||||
|
||||
interface NotificationDeleted extends Notification {
|
||||
task: TaskModel
|
||||
task: ITask
|
||||
}
|
||||
|
||||
interface NotificationCreated extends Notification {
|
||||
task: TaskModel
|
||||
task: ITask
|
||||
}
|
||||
|
||||
interface NotificationMemberAdded extends Notification {
|
||||
member: UserModel
|
||||
team: TeamModel
|
||||
member: IUser
|
||||
team: ITeam
|
||||
}
|
||||
|
||||
export default class NotificationModel extends AbstractModel {
|
||||
export interface INotification {
|
||||
id: number
|
||||
name: string
|
||||
notification: NotificationTask | NotificationAssigned | NotificationDeleted | NotificationCreated | NotificationMemberAdded
|
||||
|
@ -48,6 +48,16 @@ export default class NotificationModel extends AbstractModel {
|
|||
readAt: Date | null
|
||||
|
||||
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) {
|
||||
super(data)
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
|
||||
export default class PasswordResetModel extends AbstractModel {
|
||||
export interface IPasswordReset {
|
||||
token: string
|
||||
newPassword: string
|
||||
email: string
|
||||
}
|
||||
|
||||
export default class PasswordResetModel extends AbstractModel implements IPasswordReset {
|
||||
token: string
|
||||
declare newPassword: string
|
||||
declare email: string
|
||||
|
||||
constructor(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
|
||||
oldPassword: string
|
||||
}
|
||||
|
||||
export default class PasswordUpdateModel extends AbstractModel implements IPasswordUpdate {
|
||||
declare newPassword: string
|
||||
declare oldPassword: string
|
||||
|
||||
defaults() {
|
||||
return {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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
|
||||
title: string
|
||||
description: string
|
||||
|
@ -15,7 +15,26 @@ export default class SavedFilterModel extends AbstractModel {
|
|||
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
|
||||
updated: Date
|
||||
|
||||
|
|
|
@ -1,11 +1,20 @@
|
|||
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
|
||||
entity: string // 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
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import UserModel from './user'
|
||||
import LabelModel from './label'
|
||||
import AttachmentModel from './attachment'
|
||||
import type { Priority } from '@/models/constants/priorities'
|
||||
|
||||
import AbstractModel from '@/models/abstractModel'
|
||||
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 type ListModel from './list'
|
||||
import type { Priority } from './constants/priorities'
|
||||
import type { IBucket } from './bucket'
|
||||
|
||||
const SUPPORTS_TRIGGERED_NOTIFICATION = 'Notification' in window && 'showTrigger' in Notification.prototype
|
||||
export const TASK_DEFAULT_COLOR = '#1973ff'
|
||||
|
@ -24,15 +26,15 @@ export interface RepeatAfter {
|
|||
amount: number
|
||||
}
|
||||
|
||||
export default class TaskModel extends AbstractModel {
|
||||
export interface ITask {
|
||||
id: number
|
||||
title: string
|
||||
description: string
|
||||
done: boolean
|
||||
doneAt: Date | null
|
||||
priority: Priority
|
||||
labels: LabelModel[]
|
||||
assignees: UserModel[]
|
||||
labels: ILabel[]
|
||||
assignees: IUser[]
|
||||
|
||||
dueDate: Date | null
|
||||
startDate: Date | null
|
||||
|
@ -41,26 +43,64 @@ export default class TaskModel extends AbstractModel {
|
|||
repeatFromCurrentDate: boolean
|
||||
repeatMode: TaskRepeatMode
|
||||
reminderDates: Date[]
|
||||
parentTaskId: TaskModel['id']
|
||||
parentTaskId: ITask['id']
|
||||
hexColor: string
|
||||
percentDone: number
|
||||
relatedTasks: { [relationKind: string]: TaskModel } // FIXME: use relationKinds
|
||||
attachments: AttachmentModel[]
|
||||
relatedTasks: { [relationKind: string]: ITask } // FIXME: use relationKinds
|
||||
attachments: IAttachment[]
|
||||
identifier: string
|
||||
index: number
|
||||
isFavorite: boolean
|
||||
subscription: SubscriptionModel
|
||||
subscription: ISubscription
|
||||
|
||||
position: 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
|
||||
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)
|
||||
|
||||
this.id = Number(this.id)
|
||||
|
@ -120,6 +160,7 @@ export default class TaskModel extends AbstractModel {
|
|||
|
||||
this.listId = Number(this.listId)
|
||||
}
|
||||
bucketId: number
|
||||
|
||||
defaults() {
|
||||
return {
|
||||
|
|
|
@ -1,11 +1,17 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import type UserModel from './user'
|
||||
import type TaskModel from './task'
|
||||
import type { ITask } from './task'
|
||||
import type { IUser } from './user'
|
||||
|
||||
export default class TaskAssigneeModel extends AbstractModel {
|
||||
export interface ITaskAssignee {
|
||||
created: Date
|
||||
userId: UserModel['id']
|
||||
taskId: TaskModel['id']
|
||||
userId: IUser['id']
|
||||
taskId: ITask['id']
|
||||
}
|
||||
|
||||
export default class TaskAssigneeModel extends AbstractModel implements ITaskAssignee {
|
||||
created: Date
|
||||
declare userId: IUser['id']
|
||||
declare taskId: ITask['id']
|
||||
|
||||
constructor(data) {
|
||||
super(data)
|
||||
|
|
|
@ -1,12 +1,22 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import UserModel from './user'
|
||||
import type TaskModel from './task'
|
||||
import UserModel, { type IUser } from './user'
|
||||
import type { ITask } from './task'
|
||||
|
||||
export default class TaskCommentModel extends AbstractModel {
|
||||
export interface ITaskComment {
|
||||
id: number
|
||||
taskId: TaskModel['id']
|
||||
taskId: ITask['id']
|
||||
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
|
||||
updated: Date
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import UserModel from './user'
|
||||
import type TaskModel from './task'
|
||||
import UserModel, { type IUser } from './user'
|
||||
import type { ITask } from './task'
|
||||
|
||||
export const RELATION_KIND = {
|
||||
'SUBTASK': 'subtask',
|
||||
|
@ -19,13 +19,23 @@ export const RELATION_KINDS = [...Object.values(RELATION_KIND)] as const
|
|||
|
||||
export type RelationKind = typeof RELATION_KINDS[number]
|
||||
|
||||
export default class TaskRelationModel extends AbstractModel {
|
||||
export interface ITaskRelationModel {
|
||||
id: number
|
||||
otherTaskId: TaskModel['id']
|
||||
taskId: TaskModel['id']
|
||||
otherTaskId: ITask['id']
|
||||
taskId: ITask['id']
|
||||
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
|
||||
|
||||
constructor(data) {
|
||||
|
|
|
@ -1,16 +1,28 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import UserModel from './user'
|
||||
import TeamMemberModel from './teamMember'
|
||||
import UserModel, { type IUser } from './user'
|
||||
import TeamMemberModel, { type ITeamMember } from './teamMember'
|
||||
import {RIGHTS, type Right} from '@/models/constants/rights'
|
||||
|
||||
export default class TeamModel extends AbstractModel {
|
||||
id: 0
|
||||
export interface ITeam {
|
||||
id: number
|
||||
name: string
|
||||
description: string
|
||||
members: TeamMemberModel[]
|
||||
members: ITeamMember[]
|
||||
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
|
||||
updated: Date
|
||||
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import TeamShareBaseModel from './teamShareBase'
|
||||
import type ListModel from './list'
|
||||
import type { IList } from './list'
|
||||
|
||||
export default class TeamListModel extends TeamShareBaseModel {
|
||||
listId: ListModel['id']
|
||||
export interface ITeamList {
|
||||
listId: IList['id']
|
||||
}
|
||||
|
||||
export default class TeamListModel extends TeamShareBaseModel implements ITeamList {
|
||||
declare listId: IList['id']
|
||||
|
||||
defaults() {
|
||||
return {
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
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
|
||||
teamId: ListModel['id']
|
||||
teamId: IList['id']
|
||||
}
|
||||
|
||||
export default class TeamMemberModel extends UserModel implements ITeamMember {
|
||||
declare admin: boolean
|
||||
declare teamId: IList['id']
|
||||
|
||||
defaults() {
|
||||
return {
|
||||
|
|
|
@ -1,8 +1,12 @@
|
|||
import TeamShareBaseModel from './teamShareBase'
|
||||
import type NamespaceModel from './namespace'
|
||||
import type { INamespace } from './namespace'
|
||||
|
||||
export default class TeamNamespaceModel extends TeamShareBaseModel {
|
||||
namespaceId: NamespaceModel['id']
|
||||
export interface ITeamNamespace {
|
||||
namespaceId: INamespace['id']
|
||||
}
|
||||
|
||||
export default class TeamNamespaceModel extends TeamShareBaseModel implements ITeamNamespace {
|
||||
declare namespaceId: INamespace['id']
|
||||
|
||||
defaults() {
|
||||
return {
|
||||
|
|
|
@ -1,14 +1,22 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import type TeamModel from './team'
|
||||
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.
|
||||
* It is extended in a way so it can be used for namespaces as well for lists.
|
||||
*/
|
||||
export default class TeamShareBaseModel extends AbstractModel {
|
||||
teamId: TeamModel['id']
|
||||
right: Right
|
||||
export default class TeamShareBaseModel extends AbstractModel implements ITeamShareBase {
|
||||
declare teamId: ITeam['id']
|
||||
declare right: Right
|
||||
|
||||
created: Date
|
||||
updated: Date
|
||||
|
|
|
@ -1,9 +1,15 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
|
||||
export default class TotpModel extends AbstractModel {
|
||||
export interface ITotp {
|
||||
secret: string
|
||||
enabled: boolean
|
||||
url: string
|
||||
}
|
||||
|
||||
export default class TotpModel extends AbstractModel implements ITotp{
|
||||
declare secret: string
|
||||
declare enabled: boolean
|
||||
declare url: string
|
||||
|
||||
defaults() {
|
||||
return {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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
|
||||
email: string
|
||||
username: string
|
||||
|
@ -9,7 +9,18 @@ export default class UserModel extends AbstractModel {
|
|||
|
||||
created: 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) {
|
||||
super(data)
|
||||
|
@ -28,6 +39,7 @@ export default class UserModel extends AbstractModel {
|
|||
email: '',
|
||||
username: '',
|
||||
name: '',
|
||||
|
||||
created: null,
|
||||
updated: null,
|
||||
settings: null,
|
||||
|
|
|
@ -1,8 +1,13 @@
|
|||
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
|
||||
export default class UserListModel extends UserShareBaseModel {
|
||||
listId: ListModel['id']
|
||||
export default class UserListModel extends UserShareBaseModel implements IUserList {
|
||||
declare listId: IList['id']
|
||||
|
||||
defaults() {
|
||||
return {
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
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
|
||||
export default class UserNamespaceModel extends UserShareBaseModel {
|
||||
namespaceId: NamespaceModel['id']
|
||||
export default class UserNamespaceModel extends UserShareBaseModel implements IUserNamespace {
|
||||
declare namespaceId: INamespace['id']
|
||||
|
||||
defaults() {
|
||||
return {
|
||||
|
|
|
@ -1,16 +1,27 @@
|
|||
|
||||
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
|
||||
emailRemindersEnabled: boolean
|
||||
discoverableByName: boolean
|
||||
discoverableByEmail: boolean
|
||||
overdueTasksRemindersEnabled: boolean
|
||||
defaultListId: undefined | ListModel['id']
|
||||
defaultListId: undefined | IList['id']
|
||||
weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6
|
||||
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() {
|
||||
return {
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
import AbstractModel from './abstractModel'
|
||||
import type UserModel from './user'
|
||||
import {RIGHTS, type Right} from '@/models/constants/rights'
|
||||
import type { IUser } from './user'
|
||||
|
||||
export default class UserShareBaseModel extends AbstractModel {
|
||||
userId: UserModel['id']
|
||||
export interface IUserShareBase {
|
||||
userId: IUser['id']
|
||||
right: Right
|
||||
|
||||
created: Date
|
||||
updated: Date
|
||||
}
|
||||
|
||||
export default class UserShareBaseModel extends AbstractModel implements IUserShareBase {
|
||||
declare userId: IUser['id']
|
||||
declare right: Right
|
||||
|
||||
created: Date
|
||||
updated: Date
|
||||
|
||||
constructor(data) {
|
||||
super(data)
|
||||
|
|
|
@ -3,7 +3,7 @@ import {beforeEach, afterEach, describe, it, expect, vi} from 'vitest'
|
|||
import {parseTaskText} from './parseTaskText'
|
||||
import {getDateFromText, getDateFromTextIn} from '../helpers/time/parseDate'
|
||||
import {calculateDayInterval} from '../helpers/time/calculateDayInterval'
|
||||
import {PRIORITIES} from '@/models/constants/priorities.ts'
|
||||
import {PRIORITIES} from '@/models/constants/priorities'
|
||||
|
||||
describe('Parse Task Text', () => {
|
||||
beforeEach(() => {
|
||||
|
|
|
@ -2,9 +2,9 @@ import {AuthenticatedHTTPFactory} from '@/http-common'
|
|||
import type {Method} from 'axios'
|
||||
|
||||
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 FileModel from '@/models/file'
|
||||
import type { IFile } from '@/models/file'
|
||||
|
||||
interface Paths {
|
||||
create : string
|
||||
|
@ -12,6 +12,7 @@ interface Paths {
|
|||
getAll : string
|
||||
update : string
|
||||
delete : string
|
||||
reset?: string
|
||||
}
|
||||
|
||||
function convertObject(o: Record<string, unknown>) {
|
||||
|
@ -39,7 +40,7 @@ function prepareParams(params: Record<string, unknown | unknown[]>) {
|
|||
return objectToSnakeCase(params)
|
||||
}
|
||||
|
||||
export default class AbstractService<Model extends AbstractModel = AbstractModel> {
|
||||
export default class AbstractService<Model extends IAbstract = IAbstract> {
|
||||
|
||||
/////////////////////////////
|
||||
// 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.
|
||||
* 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()
|
||||
|
||||
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({
|
||||
url,
|
||||
method,
|
||||
|
@ -302,7 +303,7 @@ export default class AbstractService<Model extends AbstractModel = AbstractModel
|
|||
* @param params Optional query parameters
|
||||
* @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 === '') {
|
||||
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.
|
||||
* @param url
|
||||
* @param file {FileModel}
|
||||
* @param file {IFile}
|
||||
* @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)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import AbstractService from './abstractService'
|
||||
import AttachmentModel from '../models/attachment'
|
||||
import AttachmentModel, { type IAttachment } from '../models/attachment'
|
||||
import {formatISO} from 'date-fns'
|
||||
import {downloadBlob} from '@/helpers/downloadBlob'
|
||||
import type FileModel from '@/models/file'
|
||||
import type { IFile } from '@/models/file'
|
||||
|
||||
export default class AttachmentService extends AbstractService<AttachmentModel> {
|
||||
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))
|
||||
return model
|
||||
}
|
||||
|
@ -34,11 +34,11 @@ export default class AttachmentService extends AbstractService<AttachmentModel>
|
|||
return data
|
||||
}
|
||||
|
||||
getBlobUrl(model: AttachmentModel) {
|
||||
getBlobUrl(model: IAttachment) {
|
||||
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)
|
||||
return downloadBlob(url, model.file.name)
|
||||
}
|
||||
|
@ -48,7 +48,7 @@ export default class AttachmentService extends AbstractService<AttachmentModel>
|
|||
* @param files
|
||||
* @returns {Promise<any|never>}
|
||||
*/
|
||||
create(model: AttachmentModel, files: FileModel[]) {
|
||||
create(model: IAttachment, files: IFile[]) {
|
||||
const data = new FormData()
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
// TODO: Validation of file size
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
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() {
|
||||
super({
|
||||
get: '/user/settings/avatar',
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import AbstractService from './abstractService'
|
||||
import BackgroundImageModel from '../models/backgroundImage'
|
||||
import ListModel from '../models/list'
|
||||
import BackgroundImageModel, { type IBackgroundImage } from '../models/backgroundImage'
|
||||
import ListModel from '@/models/list'
|
||||
|
||||
export default class BackgroundUnsplashService extends AbstractService {
|
||||
export default class BackgroundUnsplashService extends AbstractService<IBackgroundImage> {
|
||||
constructor() {
|
||||
super({
|
||||
getAll: '/backgrounds/unsplash/search',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import AbstractService from './abstractService'
|
||||
import ListModel from '../models/list'
|
||||
import type FileModel from '@/models/file'
|
||||
import ListModel, { type IList } from '../models/list'
|
||||
import type { IFile } from '@/models/file'
|
||||
|
||||
export default class BackgroundUploadService extends AbstractService {
|
||||
constructor() {
|
||||
|
@ -22,7 +22,7 @@ export default class BackgroundUploadService extends AbstractService {
|
|||
* @param file
|
||||
* @returns {Promise<any|never>}
|
||||
*/
|
||||
create(listId: ListModel['id'], file: FileModel) {
|
||||
create(listId: IList['id'], file: IFile) {
|
||||
return this.uploadFile(
|
||||
this.getReplacedRoute(this.paths.create, {listId}),
|
||||
file,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import AbstractService from './abstractService'
|
||||
import BucketModel from '../models/bucket'
|
||||
import BucketModel, { type IBucket } from '../models/bucket'
|
||||
import TaskService from '@/services/task'
|
||||
|
||||
export default class BucketService extends AbstractService {
|
||||
export default class BucketService extends AbstractService<IBucket> {
|
||||
constructor() {
|
||||
super({
|
||||
getAll: '/lists/{listId}/buckets',
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import {formatISO} from 'date-fns'
|
||||
import CaldavTokenModel from '../models/caldavToken'
|
||||
import CaldavTokenModel, {type ICaldavToken} from '../models/caldavToken'
|
||||
import AbstractService from './abstractService'
|
||||
|
||||
export default class CaldavTokenService extends AbstractService {
|
||||
export default class CaldavTokenService extends AbstractService<ICaldavToken> {
|
||||
constructor() {
|
||||
super({
|
||||
getAll: '/user/settings/token/caldav',
|
||||
|
@ -11,7 +11,7 @@ export default class CaldavTokenService extends AbstractService {
|
|||
})
|
||||
}
|
||||
|
||||
processModel(model: Partial<CaldavTokenModel>) {
|
||||
processModel(model: Partial<ICaldavToken>) {
|
||||
return {
|
||||
...model,
|
||||
created: formatISO(new Date(model.created)),
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import AbstractService from './abstractService'
|
||||
import LabelModel from '../models/label'
|
||||
import LabelModel, { type ILabel } from '@/models/label'
|
||||
import {formatISO} from 'date-fns'
|
||||
import {colorFromHex} from '@/helpers/color/colorFromHex'
|
||||
|
||||
export default class LabelService extends AbstractService {
|
||||
export default class LabelService extends AbstractService<ILabel> {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/labels',
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import AbstractService from './abstractService'
|
||||
import ListModel from '../models/list'
|
||||
import ListModel, { type IList } from '@/models/list'
|
||||
import TaskService from './task'
|
||||
import {formatISO} from 'date-fns'
|
||||
import {colorFromHex} from '@/helpers/color/colorFromHex'
|
||||
|
||||
export default class ListService extends AbstractService {
|
||||
export default class ListService extends AbstractService<IList> {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/namespaces/{namespaceId}/lists',
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import AbstractService from './abstractService'
|
||||
import NamespaceModel from '../models/namespace'
|
||||
import NamespaceModel, { type INamespace } from '../models/namespace'
|
||||
import {formatISO} from 'date-fns'
|
||||
import {colorFromHex} from '@/helpers/color/colorFromHex'
|
||||
|
||||
export default class NamespaceService extends AbstractService {
|
||||
export default class NamespaceService extends AbstractService<INamespace> {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/namespaces',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import AbstractService from './abstractService'
|
||||
import TaskModel from '../models/task'
|
||||
import TaskModel, { type ITask } from '../models/task'
|
||||
import AttachmentService from './attachment'
|
||||
import LabelService from './label'
|
||||
|
||||
|
@ -113,7 +113,7 @@ export default class TaskService extends AbstractService {
|
|||
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 TaskCommentModel from '../models/taskComment'
|
||||
import TaskCommentModel, { type ITaskComment } from '../models/taskComment'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
export default class TaskCommentService extends AbstractService {
|
||||
export default class TaskCommentService extends AbstractService<ITaskComment> {
|
||||
constructor() {
|
||||
super({
|
||||
create: '/tasks/{taskId}/comments',
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
import type { ActionContext } from 'vuex'
|
||||
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.
|
||||
|
@ -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|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(() => {
|
||||
if (loadFunc === null) {
|
||||
context.commit(LOADING, true, {root: true})
|
||||
|
|
|
@ -25,7 +25,9 @@ import ListModel from '@/models/list'
|
|||
import ListService from '../services/list'
|
||||
import {checkAndSetApiUrl} from '@/helpers/checkAndSetApiUrl'
|
||||
|
||||
export const store = createStore({
|
||||
import type { RootStoreState } from './types'
|
||||
|
||||
export const store = createStore<RootStoreState>({
|
||||
strict: import.meta.env.DEV,
|
||||
modules: {
|
||||
config,
|
||||
|
@ -37,7 +39,7 @@ export const store = createStore({
|
|||
attachments,
|
||||
labels,
|
||||
},
|
||||
state: {
|
||||
state: () => ({
|
||||
loading: false,
|
||||
loadingModule: null,
|
||||
// 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,
|
||||
keyboardShortcutsActive: false,
|
||||
quickActionsActive: false,
|
||||
},
|
||||
}),
|
||||
mutations: {
|
||||
[LOADING](state, loading) {
|
||||
state.loading = loading
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
import {findIndexById} from '@/helpers/utils'
|
||||
|
||||
import type { AttachmentState } from '@/store/types'
|
||||
import type { IAttachment } from '@/models/attachment'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: () => ({
|
||||
state: (): AttachmentState => ({
|
||||
attachments: [],
|
||||
}),
|
||||
mutations: {
|
||||
set(state, attachments) {
|
||||
set(state: AttachmentState, attachments: IAttachment[]) {
|
||||
console.debug('Set attachments', attachments)
|
||||
state.attachments = attachments
|
||||
},
|
||||
add(state, attachment) {
|
||||
add(state: AttachmentState, attachment: IAttachment) {
|
||||
console.debug('Add attachement', attachment)
|
||||
state.attachments.push(attachment)
|
||||
},
|
||||
removeById(state, id) {
|
||||
const attachmentIndex = findIndexById(state.attachments, id)
|
||||
removeById(state: AttachmentState, id: IAttachment['id']) {
|
||||
const attachmentIndex = findIndexById<IAttachment>(state.attachments, id)
|
||||
state.attachments.splice(attachmentIndex, 1)
|
||||
console.debug('Remove attachement', id)
|
||||
},
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import type { ActionContext } from 'vuex'
|
||||
|
||||
import {HTTPFactory, AuthenticatedHTTPFactory} from '@/http-common'
|
||||
import {i18n, getCurrentLanguage, saveLanguage} from '@/i18n'
|
||||
import {objectToSnakeCase} from '@/helpers/case'
|
||||
|
@ -8,12 +10,10 @@ import {getToken, refreshToken, removeToken, saveToken} from '@/helpers/auth'
|
|||
import {setLoading} from '@/store/helper'
|
||||
import {success} from '@/message'
|
||||
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 => {
|
||||
if (typeof settings.weekStart === 'undefined' || settings.weekStart === '') {
|
||||
|
@ -24,7 +24,7 @@ const defaultSettings = settings => {
|
|||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: () => ({
|
||||
state: (): AuthState => ({
|
||||
authenticated: false,
|
||||
isLinkShareAuth: false,
|
||||
info: null,
|
||||
|
@ -34,13 +34,13 @@ export default {
|
|||
settings: {},
|
||||
}),
|
||||
getters: {
|
||||
authUser(state) {
|
||||
authUser(state: AuthState) {
|
||||
return state.authenticated && (
|
||||
state.info &&
|
||||
state.info.type === AUTH_TYPES.USER
|
||||
)
|
||||
},
|
||||
authLinkShare(state) {
|
||||
authLinkShare(state: AuthState) {
|
||||
return state.authenticated && (
|
||||
state.info &&
|
||||
state.info.type === AUTH_TYPES.LINK_SHARE
|
||||
|
@ -48,7 +48,7 @@ export default {
|
|||
},
|
||||
},
|
||||
mutations: {
|
||||
info(state, info) {
|
||||
info(state: AuthState, info: Info) {
|
||||
state.info = info
|
||||
if (info !== null) {
|
||||
state.avatarUrl = info.getAvatarUrl()
|
||||
|
@ -60,31 +60,32 @@ export default {
|
|||
state.isLinkShareAuth = info.id < 0
|
||||
}
|
||||
},
|
||||
setUserSettings(state, settings) {
|
||||
setUserSettings(state: AuthState, settings: IUserSettings) {
|
||||
state.settings = defaultSettings(settings)
|
||||
const info = state.info !== null ? state.info : {}
|
||||
const info = state.info !== null ? state.info : {} as Info
|
||||
info.name = settings.name
|
||||
state.info = info
|
||||
},
|
||||
authenticated(state, authenticated) {
|
||||
authenticated(state: AuthState, authenticated: boolean) {
|
||||
state.authenticated = authenticated
|
||||
},
|
||||
isLinkShareAuth(state, is) {
|
||||
state.isLinkShareAuth = is
|
||||
isLinkShareAuth(state: AuthState, isLinkShareAuth: boolean) {
|
||||
state.isLinkShareAuth = isLinkShareAuth
|
||||
},
|
||||
needsTotpPasscode(state, needs) {
|
||||
state.needsTotpPasscode = needs
|
||||
needsTotpPasscode(state: AuthState, needsTotpPasscode: boolean) {
|
||||
state.needsTotpPasscode = needsTotpPasscode
|
||||
},
|
||||
reloadAvatar(state) {
|
||||
reloadAvatar(state: AuthState) {
|
||||
if (!state.info) return
|
||||
state.avatarUrl = `${state.info.getAvatarUrl()}&=${+new Date()}`
|
||||
},
|
||||
lastUserRefresh(state) {
|
||||
lastUserRefresh(state: AuthState) {
|
||||
state.lastUserInfoRefresh = new Date()
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
// Logs a user in with a set of credentials.
|
||||
async login(ctx, credentials) {
|
||||
async login(ctx: ActionContext<AuthState, RootStoreState>, credentials) {
|
||||
const HTTP = HTTPFactory()
|
||||
ctx.commit(LOADING, true, {root: true})
|
||||
|
||||
|
@ -115,7 +116,7 @@ export default {
|
|||
|
||||
// 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.
|
||||
async register(ctx, credentials) {
|
||||
async register(ctx: ActionContext<AuthState, RootStoreState>, credentials) {
|
||||
const HTTP = HTTPFactory()
|
||||
ctx.commit(LOADING, true, {root: true})
|
||||
try {
|
||||
|
@ -132,7 +133,7 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
async openIdAuth(ctx, {provider, code}) {
|
||||
async openIdAuth(ctx: ActionContext<AuthState, RootStoreState>, {provider, code}) {
|
||||
const HTTP = HTTPFactory()
|
||||
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 response = await HTTP.post('/shares/' + hash + '/auth', {
|
||||
password: password,
|
||||
|
@ -165,7 +166,7 @@ export default {
|
|||
},
|
||||
|
||||
// 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.
|
||||
// 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
|
||||
if (
|
||||
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()
|
||||
if (!jwt) {
|
||||
return
|
||||
|
@ -243,7 +244,7 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
async saveUserSettings(ctx, payload) {
|
||||
async saveUserSettings(ctx: ActionContext<AuthState, RootStoreState>, payload) {
|
||||
const {settings} = payload
|
||||
const showMessage = payload.showMessage ?? true
|
||||
const userSettingsService = new UserSettingsService()
|
||||
|
@ -264,7 +265,7 @@ export default {
|
|||
},
|
||||
|
||||
// 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
|
||||
// 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.
|
||||
|
@ -285,7 +286,7 @@ export default {
|
|||
}
|
||||
}, 5000)
|
||||
},
|
||||
logout(ctx) {
|
||||
logout(ctx: ActionContext<AuthState, RootStoreState>) {
|
||||
removeToken()
|
||||
window.localStorage.clear() // Clear all settings and history we might have saved in local storage.
|
||||
ctx.dispatch('checkAuth')
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import type { ActionContext } from 'vuex'
|
||||
import {parseURL} from 'ufo'
|
||||
|
||||
import {CONFIG} from '../mutation-types'
|
||||
import {HTTPFactory} from '@/http-common'
|
||||
import {objectToCamelCase} from '@/helpers/case'
|
||||
import {parseURL} from 'ufo'
|
||||
import type { RootStoreState, ConfigState } from '@/store/types'
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: () => ({
|
||||
state: (): ConfigState => ({
|
||||
// These are the api defaults.
|
||||
version: '',
|
||||
frontendUrl: '',
|
||||
|
@ -36,19 +39,19 @@ export default {
|
|||
},
|
||||
}),
|
||||
getters: {
|
||||
migratorsEnabled: state => state.availableMigrators?.length > 0,
|
||||
migratorsEnabled: (state: ConfigState) => state.availableMigrators?.length > 0,
|
||||
apiBase() {
|
||||
const {host, protocol} = parseURL(window.API_URL)
|
||||
return protocol + '//' + host
|
||||
},
|
||||
},
|
||||
mutations: {
|
||||
[CONFIG](state, config) {
|
||||
[CONFIG](state: ConfigState, config: ConfigState) {
|
||||
Object.assign(state, config)
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async update(ctx) {
|
||||
async update(ctx: ActionContext<ConfigState, RootStoreState>) {
|
||||
const HTTP = HTTPFactory()
|
||||
const {data: config} = await HTTP.get('info')
|
||||
ctx.commit(CONFIG, objectToCamelCase(config))
|
||||
|
|
|
@ -7,10 +7,15 @@ import {success} from '@/message'
|
|||
import BucketService from '../../services/bucket'
|
||||
import {setLoading} from '../helper'
|
||||
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
|
||||
|
||||
function getTaskIndicesById(state, taskId) {
|
||||
function getTaskIndicesById(state: KanbanState, taskId: ITask['id']) {
|
||||
let taskIndex
|
||||
const bucketIndex = state.buckets.findIndex(({ tasks }) => {
|
||||
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)
|
||||
state.buckets[bucketIndex].tasks.push(task)
|
||||
state.buckets[bucketIndex].tasks.sort((a, b) => a.kanbanPosition > b.kanbanPosition ? 1 : -1)
|
||||
|
@ -36,7 +41,7 @@ const addTaskToBucketAndSort = (state, task) => {
|
|||
export default {
|
||||
namespaced: true,
|
||||
|
||||
state: () => ({
|
||||
state: (): KanbanState => ({
|
||||
buckets: [],
|
||||
listId: 0,
|
||||
bucketLoading: {},
|
||||
|
@ -45,11 +50,11 @@ export default {
|
|||
}),
|
||||
|
||||
mutations: {
|
||||
setListId(state, listId) {
|
||||
setListId(state: KanbanState, listId: IList['id']) {
|
||||
state.listId = parseInt(listId)
|
||||
},
|
||||
|
||||
setBuckets(state, buckets) {
|
||||
setBuckets(state: KanbanState, buckets: IBucket[]) {
|
||||
state.buckets = buckets
|
||||
buckets.forEach(b => {
|
||||
state.taskPagesPerBucket[b.id] = 1
|
||||
|
@ -57,31 +62,51 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
addBucket(state, bucket) {
|
||||
addBucket(state: KanbanState, bucket: IBucket) {
|
||||
state.buckets.push(bucket)
|
||||
},
|
||||
|
||||
removeBucket(state, bucket) {
|
||||
removeBucket(state: KanbanState, bucket: IBucket) {
|
||||
const bucketIndex = findIndexById(state.buckets, bucket.id)
|
||||
state.buckets.splice(bucketIndex, 1)
|
||||
},
|
||||
|
||||
setBucketById(state, bucket) {
|
||||
setBucketById(state: KanbanState, bucket: IBucket) {
|
||||
const bucketIndex = findIndexById(state.buckets, bucket.id)
|
||||
state.buckets[bucketIndex] = bucket
|
||||
},
|
||||
|
||||
setBucketByIndex(state, {bucketIndex, bucket}) {
|
||||
setBucketByIndex(state: KanbanState, {
|
||||
bucketIndex,
|
||||
bucket,
|
||||
} : {
|
||||
bucketIndex: number,
|
||||
bucket: IBucket
|
||||
}) {
|
||||
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]
|
||||
bucket.tasks[taskIndex] = task
|
||||
state.buckets[bucketIndex] = bucket
|
||||
},
|
||||
|
||||
setTasksInBucketByBucketId(state, {bucketId, tasks}) {
|
||||
setTasksInBucketByBucketId(state: KanbanState, {
|
||||
bucketId,
|
||||
tasks,
|
||||
} : {
|
||||
bucketId: IBucket['id'],
|
||||
tasks: ITask[],
|
||||
}) {
|
||||
const bucketIndex = findIndexById(state.buckets, bucketId)
|
||||
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 (state.buckets.length === 0) {
|
||||
return
|
||||
|
@ -133,7 +158,7 @@ export default {
|
|||
}
|
||||
},
|
||||
|
||||
addTaskToBucket(state, task) {
|
||||
addTaskToBucket(state: KanbanState, task: ITask) {
|
||||
const bucketIndex = findIndexById(state.buckets, task.bucketId)
|
||||
const oldBucket = state.buckets[bucketIndex]
|
||||
const newBucket = {
|
||||
|
@ -146,7 +171,10 @@ export default {
|
|||
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 oldBucket = state.buckets[bucketIndex]
|
||||
const newBucket = {
|
||||
|
@ -159,7 +187,7 @@ export default {
|
|||
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 (state.buckets.length === 0) {
|
||||
return
|
||||
|
@ -168,8 +196,10 @@ export default {
|
|||
const { bucketIndex, taskIndex } = getTaskIndicesById(state, task.id)
|
||||
|
||||
if (
|
||||
!bucketIndex ||
|
||||
state.buckets[bucketIndex]?.id !== task.bucketId ||
|
||||
state.buckets[bucketIndex]?.tasks[taskIndex]?.id !== task.id
|
||||
!taskIndex ||
|
||||
(state.buckets[bucketIndex]?.tasks[taskIndex]?.id !== task.id)
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
@ -177,39 +207,40 @@ export default {
|
|||
state.buckets[bucketIndex].tasks.splice(taskIndex, 1)
|
||||
},
|
||||
|
||||
setBucketLoading(state, {bucketId, loading}) {
|
||||
setBucketLoading(state: KanbanState, {bucketId, loading}) {
|
||||
state.bucketLoading[bucketId] = loading
|
||||
},
|
||||
|
||||
setTasksLoadedForBucketPage(state, {bucketId, page}) {
|
||||
setTasksLoadedForBucketPage(state: KanbanState, {bucketId, page}) {
|
||||
state.taskPagesPerBucket[bucketId] = page
|
||||
},
|
||||
|
||||
setAllTasksLoadedForBucket(state, bucketId) {
|
||||
setAllTasksLoadedForBucket(state: KanbanState, bucketId) {
|
||||
state.allTasksLoadedForBucket[bucketId] = true
|
||||
},
|
||||
},
|
||||
|
||||
getters: {
|
||||
getBucketById(state) {
|
||||
getBucketById(state: KanbanState) {
|
||||
return (bucketId) => findById(state.buckets, bucketId)
|
||||
},
|
||||
|
||||
getTaskById(state) {
|
||||
getTaskById(state: KanbanState) {
|
||||
return (id) => {
|
||||
const { bucketIndex, taskIndex } = getTaskIndicesById(state, id)
|
||||
|
||||
|
||||
return {
|
||||
bucketIndex,
|
||||
taskIndex,
|
||||
task: state.buckets[bucketIndex]?.tasks?.[taskIndex] || null,
|
||||
task: bucketIndex && taskIndex && state.buckets[bucketIndex]?.tasks?.[taskIndex] || null,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
actions: {
|
||||
async loadBucketsForList(ctx, {listId, params}) {
|
||||
async loadBucketsForList(ctx: ActionContext<KanbanState, RootStoreState>, {listId, params}) {
|
||||
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
|
||||
|
@ -219,7 +250,7 @@ export default {
|
|||
|
||||
const bucketService = new BucketService()
|
||||
try {
|
||||
const response = await bucketService.getAll({listId: listId}, params)
|
||||
const response = await bucketService.getAll({listId}, params)
|
||||
ctx.commit('setBuckets', response)
|
||||
ctx.commit('setListId', listId)
|
||||
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
|
||||
if (isLoading) {
|
||||
return
|
||||
|
@ -270,7 +301,7 @@ export default {
|
|||
|
||||
const taskService = new TaskCollectionService()
|
||||
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('setTasksLoadedForBucketPage', {bucketId, 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 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 bucketService = new BucketService()
|
||||
|
@ -304,14 +335,14 @@ export default {
|
|||
const response = await bucketService.delete(bucket)
|
||||
ctx.commit('removeBucket', 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
|
||||
} finally {
|
||||
cancel()
|
||||
}
|
||||
},
|
||||
|
||||
async updateBucket(ctx, updatedBucketData) {
|
||||
async updateBucket(ctx: ActionContext<KanbanState, RootStoreState>, updatedBucketData) {
|
||||
const cancel = setLoading(ctx, 'kanban')
|
||||
|
||||
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)
|
||||
|
||||
if (bucket.title === title) {
|
||||
if (bucket?.title === title) {
|
||||
// bucket title has not changed
|
||||
return
|
||||
}
|
||||
|
|
|
@ -1,15 +1,19 @@
|
|||
import type { ActionContext } from 'vuex'
|
||||
|
||||
import {i18n} from '@/i18n'
|
||||
import {success} from '@/message'
|
||||
import LabelService from '@/services/label'
|
||||
import {setLoading} from '@/store/helper'
|
||||
import {success} from '@/message'
|
||||
import {i18n} from '@/i18n'
|
||||
import type { LabelState, RootStoreState } from '@/store/types'
|
||||
import {getLabelsByIds, filterLabelsByQuery} from '@/helpers/labels'
|
||||
import {createNewIndexer} from '@/indexes'
|
||||
import type { ILabel } from '@/models/label'
|
||||
|
||||
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 labels = await labelService.getAll({}, {}, page)
|
||||
const labels = await labelService.getAll({}, {}, page) as ILabel[]
|
||||
if (page < labelService.totalPages) {
|
||||
const nextLabels = await getAllLabels(page + 1)
|
||||
return labels.concat(nextLabels)
|
||||
|
@ -20,45 +24,44 @@ async function getAllLabels(page = 1) {
|
|||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: () => ({
|
||||
// The labels are stored as an object which has the label ids as keys.
|
||||
state: (): LabelState => ({
|
||||
labels: {},
|
||||
loaded: false,
|
||||
}),
|
||||
mutations: {
|
||||
setLabels(state, labels) {
|
||||
setLabels(state: LabelState, labels: ILabel[]) {
|
||||
labels.forEach(l => {
|
||||
state.labels[l.id] = l
|
||||
add(l)
|
||||
})
|
||||
},
|
||||
setLabel(state, label) {
|
||||
setLabel(state: LabelState, label: ILabel) {
|
||||
state.labels[label.id] = label
|
||||
update(label)
|
||||
},
|
||||
removeLabelById(state, label) {
|
||||
removeLabelById(state: LabelState, label: ILabel) {
|
||||
remove(label)
|
||||
delete state.labels[label.id]
|
||||
},
|
||||
setLoaded(state, loaded) {
|
||||
setLoaded(state: LabelState, loaded: boolean) {
|
||||
state.loaded = loaded
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
getLabelsByIds(state) {
|
||||
return (ids) => getLabelsByIds(state, ids)
|
||||
getLabelsByIds(state: LabelState) {
|
||||
return (ids: ILabel['id'][]) => getLabelsByIds(state, ids)
|
||||
},
|
||||
filterLabelsByQuery(state) {
|
||||
return (labelsToHide, query) => filterLabelsByQuery(state, labelsToHide, query)
|
||||
filterLabelsByQuery(state: LabelState) {
|
||||
return (labelsToHide: ILabel[], query: string) => filterLabelsByQuery(state, labelsToHide, query)
|
||||
},
|
||||
getLabelsByExactTitles(state) {
|
||||
getLabelsByExactTitles(state: LabelState) {
|
||||
return labelTitles => Object
|
||||
.values(state.labels)
|
||||
.filter(({title}) => labelTitles.some(l => l.toLowerCase() === title.toLowerCase()))
|
||||
},
|
||||
},
|
||||
actions: {
|
||||
async loadAllLabels(ctx, {forceLoad} = {}) {
|
||||
async loadAllLabels(ctx: ActionContext<LabelState, RootStoreState>, {forceLoad} = {}) {
|
||||
if (ctx.state.loaded && !forceLoad) {
|
||||
return
|
||||
}
|
||||
|
@ -74,7 +77,7 @@ export default {
|
|||
cancel()
|
||||
}
|
||||
},
|
||||
async deleteLabel(ctx, label) {
|
||||
async deleteLabel(ctx: ActionContext<LabelState, RootStoreState>, label: ILabel) {
|
||||
const cancel = setLoading(ctx, 'labels')
|
||||
const labelService = new LabelService()
|
||||
|
||||
|
@ -87,7 +90,7 @@ export default {
|
|||
cancel()
|
||||
}
|
||||
},
|
||||
async updateLabel(ctx, label) {
|
||||
async updateLabel(ctx: ActionContext<LabelState, RootStoreState>, label: ILabel) {
|
||||
const cancel = setLoading(ctx, 'labels')
|
||||
const labelService = new LabelService()
|
||||
|
||||
|
@ -100,7 +103,7 @@ export default {
|
|||
cancel()
|
||||
}
|
||||
},
|
||||
async createLabel(ctx, label) {
|
||||
async createLabel(ctx: ActionContext<LabelState, RootStoreState>, label: ILabel) {
|
||||
const cancel = setLoading(ctx, 'labels')
|
||||
const labelService = new LabelService()
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@ import ListService from '@/services/list'
|
|||
import {setLoading} from '@/store/helper'
|
||||
import {removeListFromHistory} from '@/modules/listHistory'
|
||||
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'])
|
||||
|
||||
|
@ -10,37 +13,37 @@ const FavoriteListsNamespace = -2
|
|||
export default {
|
||||
namespaced: true,
|
||||
// The state is an object which has the list ids as keys.
|
||||
state: () => ({}),
|
||||
state: (): ListState => ({}),
|
||||
mutations: {
|
||||
setList(state, list) {
|
||||
setList(state: ListState, list: IList) {
|
||||
state[list.id] = list
|
||||
update(list)
|
||||
},
|
||||
setLists(state, lists) {
|
||||
setLists(state: ListState, lists: IList[]) {
|
||||
lists.forEach(l => {
|
||||
state[l.id] = l
|
||||
add(l)
|
||||
})
|
||||
},
|
||||
removeListById(state, list) {
|
||||
removeListById(state: ListState, list: IList) {
|
||||
remove(list)
|
||||
delete state[list.id]
|
||||
},
|
||||
},
|
||||
getters: {
|
||||
getListById: state => id => {
|
||||
getListById: (state: ListState) => (id: IList['id']) => {
|
||||
if (typeof state[id] !== 'undefined') {
|
||||
return state[id]
|
||||
}
|
||||
return null
|
||||
},
|
||||
findListByExactname: state => name => {
|
||||
findListByExactname: (state: ListState) => (name: string) => {
|
||||
const list = Object.values(state).find(l => {
|
||||
return l.title.toLowerCase() === name.toLowerCase()
|
||||
})
|
||||
return typeof list === 'undefined' ? null : list
|
||||
},
|
||||
searchList: state => (query, includeArchived = false) => {
|
||||
searchList: (state: ListState) => (query: string, includeArchived = false) => {
|
||||
return search(query)
|
||||
?.filter(value => value > 0)
|
||||
.map(id => state[id])
|
||||
|
@ -49,14 +52,14 @@ export default {
|
|||
},
|
||||
},
|
||||
actions: {
|
||||
toggleListFavorite(ctx, list) {
|
||||
toggleListFavorite(ctx: ActionContext<ListState, RootStoreState>, list: IList) {
|
||||
return ctx.dispatch('updateList', {
|
||||
...list,
|
||||
isFavorite: !list.isFavorite,
|
||||
})
|
||||
},
|
||||
|
||||
async createList(ctx, list) {
|
||||
async createList(ctx: ActionContext<ListState, RootStoreState>, list: IList) {
|
||||
const cancel = setLoading(ctx, 'lists')
|
||||
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 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 listService = new ListService()
|
||||
|
||||
|
|
|
@ -1,22 +1,27 @@
|
|||
import type {ActionContext} from 'vuex'
|
||||
|
||||
import NamespaceService from '../../services/namespace'
|
||||
import {setLoading} from '@/store/helper'
|
||||
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'])
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: () => ({
|
||||
state: (): NamespaceState => ({
|
||||
namespaces: [],
|
||||
}),
|
||||
mutations: {
|
||||
namespaces(state, namespaces) {
|
||||
namespaces(state: NamespaceState, namespaces: INamespace[]) {
|
||||
state.namespaces = namespaces
|
||||
namespaces.forEach(n => {
|
||||
add(n)
|
||||
})
|
||||
},
|
||||
setNamespaceById(state, namespace) {
|
||||
setNamespaceById(state: NamespaceState, namespace: INamespace) {
|
||||
const namespaceIndex = state.namespaces.findIndex(n => n.id === namespace.id)
|
||||
|
||||
if (namespaceIndex === -1) {
|
||||
|
@ -30,7 +35,7 @@ export default {
|
|||
state.namespaces[namespaceIndex] = namespace
|
||||
update(namespace)
|
||||
},
|
||||
setListInNamespaceById(state, list) {
|
||||
setListInNamespaceById(state: NamespaceState, list: IList) {
|
||||
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.
|
||||
// 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)
|
||||
add(namespace)
|
||||
},
|
||||
removeNamespaceById(state, namespaceId) {
|
||||
removeNamespaceById(state: NamespaceState, namespaceId: INamespace['id']) {
|
||||
for (const n in state.namespaces) {
|
||||
if (state.namespaces[n].id === namespaceId) {
|
||||
remove(state.namespaces[n])
|
||||
|
@ -59,7 +64,7 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
addListToNamespace(state, list) {
|
||||
addListToNamespace(state: NamespaceState, list: IList) {
|
||||
for (const n in state.namespaces) {
|
||||
if (state.namespaces[n].id === list.namespaceId) {
|
||||
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) {
|
||||
// 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.
|
||||
|
@ -83,7 +88,7 @@ export default {
|
|||
},
|
||||
},
|
||||
getters: {
|
||||
getListAndNamespaceById: state => (listId, ignorePseudoNamespaces = false) => {
|
||||
getListAndNamespaceById: (state: NamespaceState) => (listId: IList['id'], ignorePseudoNamespaces = false) => {
|
||||
for (const n in state.namespaces) {
|
||||
|
||||
if (ignorePseudoNamespaces && state.namespaces[n].id < 0) {
|
||||
|
@ -101,10 +106,10 @@ export default {
|
|||
}
|
||||
return null
|
||||
},
|
||||
getNamespaceById: state => namespaceId => {
|
||||
getNamespaceById: (state: NamespaceState) => (namespaceId: INamespace['id']) => {
|
||||
return state.namespaces.find(({id}) => id == namespaceId) || null
|
||||
},
|
||||
searchNamespace: (state, getters) => query => {
|
||||
searchNamespace: (state: NamespaceState, getters) => (query: string) => {
|
||||
return search(query)
|
||||
?.filter(value => value > 0)
|
||||
.map(getters.getNamespaceById)
|
||||
|
@ -113,7 +118,7 @@ export default {
|
|||
},
|
||||
},
|
||||
actions: {
|
||||
async loadNamespaces(ctx) {
|
||||
async loadNamespaces(ctx: ActionContext<NamespaceState, RootStoreState>) {
|
||||
const cancel = setLoading(ctx, 'namespaces')
|
||||
|
||||
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
|
||||
if (ctx.state.namespaces[0].id !== -2 && ctx.state.namespaces[1]?.id !== -2) {
|
||||
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) {
|
||||
ctx.state.namespaces.splice(0, 1)
|
||||
}
|
||||
},
|
||||
|
||||
async deleteNamespace(ctx, namespace) {
|
||||
async deleteNamespace(ctx: ActionContext<NamespaceState, RootStoreState>, namespace: INamespace) {
|
||||
const cancel = setLoading(ctx, 'namespaces')
|
||||
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 namespaceService = new NamespaceService()
|
||||
|
||||
|
|
|
@ -1,20 +1,25 @@
|
|||
import router from '@/router'
|
||||
import type { ActionContext } from 'vuex'
|
||||
import {formatISO} from 'date-fns'
|
||||
|
||||
import TaskService from '@/services/task'
|
||||
import TaskAssigneeService from '@/services/taskAssignee'
|
||||
import TaskAssigneeModel from '../../models/taskAssignee'
|
||||
import LabelTaskModel from '../../models/labelTask'
|
||||
import TaskAssigneeModel from '@/models/taskAssignee'
|
||||
import LabelTaskModel from '@/models/labelTask'
|
||||
import LabelTaskService from '@/services/labelTask'
|
||||
import {HAS_TASKS} from '../mutation-types'
|
||||
import {setLoading} from '../helper'
|
||||
import {getQuickAddMagicMode} from '@/helpers/quickAddMagicMode'
|
||||
|
||||
import {parseTaskText} from '@/modules/parseTaskText'
|
||||
import TaskModel from '@/models/task'
|
||||
import {formatISO} from 'date-fns'
|
||||
import TaskModel, { type ITask } from '@/models/task'
|
||||
import LabelTask from '@/models/labelTask'
|
||||
import LabelModel from '@/models/label'
|
||||
import LabelModel, { type ILabel } from '@/models/label'
|
||||
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
|
||||
function findPropertyByValue(object, key, value) {
|
||||
|
@ -24,16 +29,16 @@ function findPropertyByValue(object, key, value) {
|
|||
}
|
||||
|
||||
// Check if the user exists
|
||||
function validateUsername(users, username) {
|
||||
function validateUsername(users: IUser[], username: IUser['username']) {
|
||||
return findPropertyByValue(users, 'username', username)
|
||||
}
|
||||
|
||||
// Check if the label exists
|
||||
function validateLabel(labels, label) {
|
||||
function validateLabel(labels: ILabel[], label: ILabel) {
|
||||
return findPropertyByValue(labels, 'title', label)
|
||||
}
|
||||
|
||||
async function addLabelToTask(task, label) {
|
||||
async function addLabelToTask(task: ITask, label: ILabel) {
|
||||
const labelTask = new LabelTask({
|
||||
taskId: task.id,
|
||||
labelId: label.id,
|
||||
|
@ -62,9 +67,9 @@ async function findAssignees(parsedTaskAssignees) {
|
|||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state: () => ({}),
|
||||
state: (): TaskState => ({}),
|
||||
actions: {
|
||||
async loadTasks(ctx, params) {
|
||||
async loadTasks(ctx: ActionContext<TaskState, RootStoreState>, params) {
|
||||
const taskService = new TaskService()
|
||||
|
||||
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 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 response = await taskService.delete(task)
|
||||
ctx.commit('kanban/removeTaskInBucket', task, {root: true})
|
||||
|
@ -99,7 +104,13 @@ export default {
|
|||
|
||||
// Adds a task attachment in store.
|
||||
// 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)
|
||||
if (t.task !== null) {
|
||||
const attachments = [
|
||||
|
@ -119,7 +130,13 @@ export default {
|
|||
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 taskAssigneeService = new TaskAssigneeService()
|
||||
|
@ -148,7 +165,13 @@ export default {
|
|||
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 taskAssigneeService = new TaskAssigneeService()
|
||||
|
@ -175,8 +198,14 @@ export default {
|
|||
|
||||
},
|
||||
|
||||
async addLabel(ctx, {label, taskId}) {
|
||||
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
|
||||
async addLabel(ctx: ActionContext<TaskState, RootStoreState>, {
|
||||
label,
|
||||
taskId,
|
||||
} : {
|
||||
label: ILabel,
|
||||
taskId: ITask['id']
|
||||
}) {
|
||||
const labelTask = new LabelTaskModel({taskId, labelId: label.id})
|
||||
|
||||
const labelTaskService = new LabelTaskService()
|
||||
const r = await labelTaskService.create(labelTask)
|
||||
|
@ -205,8 +234,8 @@ export default {
|
|||
return r
|
||||
},
|
||||
|
||||
async removeLabel(ctx, {label, taskId}) {
|
||||
const labelTask = new LabelTaskModel({taskId: taskId, labelId: label.id})
|
||||
async removeLabel(ctx: ActionContext<TaskState, RootStoreState>, {label, taskId}) {
|
||||
const labelTask = new LabelTaskModel({taskId, labelId: label.id})
|
||||
|
||||
const labelTaskService = new LabelTaskService()
|
||||
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
|
||||
async addLabelsToTask({rootState, dispatch}, { task, parsedLabels }) {
|
||||
async addLabelsToTask({rootState, dispatch}: ActionContext<TaskState, RootStoreState>, {
|
||||
task,
|
||||
parsedLabels,
|
||||
}) {
|
||||
if (parsedLabels.length <= 0) {
|
||||
return task
|
||||
}
|
||||
|
@ -257,7 +289,10 @@ export default {
|
|||
return task
|
||||
},
|
||||
|
||||
findListId({ rootGetters }, { list: listName, listId }) {
|
||||
findListId({ rootGetters }: ActionContext<TaskState, RootStoreState>, { list: listName, listId }: {
|
||||
list: string,
|
||||
listId: IList['id']
|
||||
}) {
|
||||
let foundListId = null
|
||||
|
||||
// Uses the following ways to get the list id of the new task:
|
||||
|
@ -285,12 +320,14 @@ export default {
|
|||
return foundListId
|
||||
},
|
||||
|
||||
async createNewTask({dispatch, commit}, {
|
||||
async createNewTask({dispatch, commit}: ActionContext<TaskState, RootStoreState>, {
|
||||
title,
|
||||
bucketId,
|
||||
listId,
|
||||
position,
|
||||
}) {
|
||||
} :
|
||||
Partial<ITask>,
|
||||
) {
|
||||
const cancel = setLoading({commit}, 'tasks')
|
||||
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 {getSavedFilterIdFromListId} from '@/helpers/savedFilter'
|
||||
import type ListModel from '@/models/list'
|
||||
import type { IList } from '@/models/list'
|
||||
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
function useSavedFilter(listId: MaybeRef<ListModel['id']>) {
|
||||
function useSavedFilter(listId: MaybeRef<IList['id']>) {
|
||||
const filterService = shallowRef(new SavedFilterService())
|
||||
|
||||
const filter = ref(new SavedFilterModel())
|
||||
|
|
|
@ -113,7 +113,7 @@
|
|||
import {defineComponent} from 'vue'
|
||||
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 BaseButton from '@/components/base/BaseButton.vue'
|
||||
|
@ -150,7 +150,7 @@ export default defineComponent({
|
|||
loading: state => state[LOADING] && state[LOADING_MODULE] === 'labels',
|
||||
}),
|
||||
methods: {
|
||||
deleteLabel(label: LabelModel) {
|
||||
deleteLabel(label: ILabel) {
|
||||
this.showDeleteModal = false
|
||||
this.isLabelEdit = false
|
||||
return this.$store.dispatch('labels/deleteLabel', label)
|
||||
|
@ -158,7 +158,7 @@ export default defineComponent({
|
|||
editLabelSubmit() {
|
||||
return this.$store.dispatch('labels/updateLabel', this.labelEditLabel)
|
||||
},
|
||||
editLabel(label: LabelModel) {
|
||||
editLabel(label: ILabel) {
|
||||
if (label.createdBy.id !== this.userInfo.id) {
|
||||
return
|
||||
}
|
||||
|
@ -180,7 +180,7 @@ export default defineComponent({
|
|||
this.editorActive = false
|
||||
this.$nextTick(() => this.editorActive = true)
|
||||
},
|
||||
showDeleteDialoge(label: LabelModel) {
|
||||
showDeleteDialoge(label: ILabel) {
|
||||
this.labelToDelete = label
|
||||
this.showDeleteModal = true
|
||||
},
|
||||
|
|
|
@ -153,9 +153,9 @@ import {ALPHABETICAL_SORT} from '@/components/list/partials/filters.vue'
|
|||
|
||||
import draggable from 'zhyswan-vuedraggable'
|
||||
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 === []) {
|
||||
return
|
||||
}
|
||||
|
@ -274,7 +274,7 @@ export default defineComponent({
|
|||
focusNewTaskInput() {
|
||||
this.$refs.addTask.focusTaskInput()
|
||||
},
|
||||
updateTaskList(task: TaskModel) {
|
||||
updateTaskList(task: ITask) {
|
||||
if ( this.isAlphabeticalSorting ) {
|
||||
// reload tasks with current filter and sorting
|
||||
this.loadTasks(1, undefined, undefined, true)
|
||||
|
@ -288,11 +288,11 @@ export default defineComponent({
|
|||
|
||||
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.isTaskEdit = true
|
||||
},
|
||||
updateTasks(updatedTask: TaskModel) {
|
||||
updateTasks(updatedTask: ITask) {
|
||||
for (const t in this.tasks) {
|
||||
if (this.tasks[t].id === updatedTask.id) {
|
||||
this.tasks[t] = updatedTask
|
||||
|
|
|
@ -197,7 +197,7 @@ import Pagination from '@/components/misc/pagination.vue'
|
|||
import Popup from '@/components/misc/popup.vue'
|
||||
|
||||
import {useTaskList} from '@/composables/taskList'
|
||||
import type TaskModel from '@/models/task'
|
||||
import type { ITask } from '@/models/task'
|
||||
|
||||
const ACTIVE_COLUMNS_DEFAULT = {
|
||||
id: true,
|
||||
|
@ -253,7 +253,7 @@ const {
|
|||
currentPage,
|
||||
sortByParam,
|
||||
} = taskList
|
||||
const tasks: Ref<TaskModel[]> = taskList.tasks
|
||||
const tasks: Ref<ITask[]> = taskList.tasks
|
||||
|
||||
Object.assign(params.value, {
|
||||
filter_by: [],
|
||||
|
|
|
@ -30,7 +30,7 @@ import CreateEdit from '@/components/misc/create-edit.vue'
|
|||
import Multiselect from '@/components/input/multiselect.vue'
|
||||
|
||||
import ListDuplicateModel from '@/models/listDuplicateModel'
|
||||
import NamespaceModel from '@/models/namespace'
|
||||
import type {INamespace} from '@/models/namespace'
|
||||
|
||||
import {success} from '@/message'
|
||||
import {useTitle} from '@/composables/useTitle'
|
||||
|
@ -44,9 +44,9 @@ const {
|
|||
findNamespaces,
|
||||
} = useNameSpaceSearch()
|
||||
|
||||
const selectedNamespace = ref<NamespaceModel>()
|
||||
const selectedNamespace = ref<INamespace>()
|
||||
|
||||
function selectNamespace(namespace: NamespaceModel) {
|
||||
function selectNamespace(namespace: INamespace) {
|
||||
selectedNamespace.value = namespace
|
||||
}
|
||||
|
||||
|
|
|
@ -80,14 +80,14 @@ import ColorPicker from '@/components/input/colorPicker.vue'
|
|||
import CreateEdit from '@/components/misc/create-edit.vue'
|
||||
|
||||
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 { useTitle } from '@/composables/useTitle'
|
||||
|
||||
const props = defineProps({
|
||||
listId: {
|
||||
type: Number as PropType<ListModel['id']>,
|
||||
type: Number as PropType<IList['id']>,
|
||||
required: true,
|
||||
},
|
||||
})
|
||||
|
|
|
@ -49,7 +49,6 @@ import {useStore} from 'vuex'
|
|||
import {useRoute, useRouter} from 'vue-router'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import type TaskModel from '@/models/task'
|
||||
import {formatDate} from '@/helpers/time/formatDate'
|
||||
import {setTitle} from '@/helpers/setTitle'
|
||||
|
||||
|
@ -59,13 +58,14 @@ import DatepickerWithRange from '@/components/date/datepickerWithRange.vue'
|
|||
import {DATE_RANGES} from '@/components/date/dateRanges'
|
||||
import {LOADING, LOADING_MODULE} from '@/store/mutation-types'
|
||||
import LlamaCool from '@/assets/llama-cool.svg?component'
|
||||
import type { ITask } from '@/models/task'
|
||||
|
||||
const store = useStore()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const {t} = useI18n({useScope: 'global'})
|
||||
|
||||
const tasks = ref<TaskModel[]>([])
|
||||
const tasks = ref<ITask[]>([])
|
||||
const showNothingToDo = ref<boolean>(false)
|
||||
|
||||
setTimeout(() => showNothingToDo.value = true, 100)
|
||||
|
@ -182,7 +182,7 @@ async function loadPendingTasks(from: string, to: string) {
|
|||
}
|
||||
|
||||
// FIXME: this modification should happen in the store
|
||||
function updateTasks(updatedTask: TaskModel) {
|
||||
function updateTasks(updatedTask: ITask) {
|
||||
for (const t in tasks.value) {
|
||||
if (tasks.value[t].id === updatedTask.id) {
|
||||
tasks.value[t] = updatedTask
|
||||
|
|
|
@ -425,7 +425,7 @@
|
|||
import {defineComponent} from 'vue'
|
||||
|
||||
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 {RIGHTS as rights} from '@/models/constants/rights'
|
||||
|
@ -452,10 +452,10 @@ import {CURRENT_LIST} from '@/store/mutation-types'
|
|||
import {uploadFile} from '@/helpers/attachments'
|
||||
import ChecklistSummary from '../../components/tasks/partials/checklist-summary.vue'
|
||||
import CreatedUpdated from '@/components/tasks/partials/createdUpdated.vue'
|
||||
import type ListModel from '@/models/list'
|
||||
import { setTitle } from '@/helpers/setTitle'
|
||||
import {getNamespaceTitle} from '@/helpers/getNamespaceTitle'
|
||||
import {getListTitle} from '@/helpers/getListTitle'
|
||||
import type { IList } from '@/models/list'
|
||||
|
||||
function scrollIntoView(el) {
|
||||
if (!el) {
|
||||
|
@ -597,7 +597,7 @@ export default defineComponent({
|
|||
return uploadFile(this.taskId, ...args)
|
||||
},
|
||||
|
||||
async loadTask(taskId: TaskModel['id']) {
|
||||
async loadTask(taskId: ITask['id']) {
|
||||
if (taskId === undefined) {
|
||||
return
|
||||
}
|
||||
|
@ -703,7 +703,7 @@ export default defineComponent({
|
|||
this.saveTask(true, this.toggleTaskDone)
|
||||
},
|
||||
|
||||
async changeList(list: ListModel) {
|
||||
async changeList(list: IList) {
|
||||
this.$store.commit('kanban/removeTaskInBucket', this.task)
|
||||
this.task.listId = list.id
|
||||
await this.saveTask()
|
||||
|
|
|
@ -162,23 +162,23 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import {computed, ref} from 'vue'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
|
||||
import Editor from '@/components/input/AsyncEditor'
|
||||
import {useStore} from 'vuex'
|
||||
|
||||
import TeamService from '../../services/team'
|
||||
import type TeamModel from '../../models/team'
|
||||
import TeamMemberService from '../../services/teamMember'
|
||||
import type TeamMemberModel from '../../models/teamMember'
|
||||
import type UserModel from '../../models/user'
|
||||
import UserService from '../../services/user'
|
||||
import TeamService from '@/services/team'
|
||||
import TeamMemberService from '@/services/teamMember'
|
||||
import UserService from '@/services/user'
|
||||
import {RIGHTS as Rights} from '@/models/constants/rights'
|
||||
|
||||
import Multiselect from '@/components/input/multiselect.vue'
|
||||
import {useRoute, useRouter} from 'vue-router'
|
||||
import {useTitle} from '@/composables/useTitle'
|
||||
import {useI18n} from 'vue-i18n'
|
||||
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 route = useRoute()
|
||||
|
@ -198,11 +198,11 @@ const teamService = ref<TeamService>(new TeamService())
|
|||
const teamMemberService = ref<TeamMemberService>(new TeamMemberService())
|
||||
const userService = ref<UserService>(new UserService())
|
||||
|
||||
const team = ref<TeamModel>()
|
||||
const team = ref<ITeam>()
|
||||
const teamId = computed(() => route.params.id)
|
||||
const memberToDelete = ref<TeamMemberModel>()
|
||||
const newMember = ref<UserModel>()
|
||||
const foundUsers = ref<UserModel[]>()
|
||||
const memberToDelete = ref<ITeamMember>()
|
||||
const newMember = ref<IUser>()
|
||||
const foundUsers = ref<IUser[]>()
|
||||
|
||||
const showDeleteModal = ref(false)
|
||||
const showUserDeleteModal = ref(false)
|
||||
|
@ -257,7 +257,7 @@ async function addUser() {
|
|||
success({message: t('team.edit.userAddedSuccess')})
|
||||
}
|
||||
|
||||
async function toggleUserType(member: TeamMemberModel) {
|
||||
async function toggleUserType(member: ITeamMember) {
|
||||
// FIXME: direct manipulation
|
||||
member.admin = !member.admin
|
||||
member.teamId = teamId.value
|
||||
|
@ -282,7 +282,7 @@ async function findUser(query: string) {
|
|||
}
|
||||
|
||||
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>
|
||||
|
||||
|
|
|
@ -77,8 +77,8 @@ import {success} from '@/message'
|
|||
import BaseButton from '@/components/base/BaseButton.vue'
|
||||
import Message from '@/components/misc/message.vue'
|
||||
import CaldavTokenService from '@/services/caldavToken'
|
||||
import type CaldavTokenModel from '@/models/caldavToken'
|
||||
import { formatDateShort } from '@/helpers/time/formatDate'
|
||||
import type { ICaldavToken } from '@/models/caldavToken'
|
||||
|
||||
const copy = useCopyToClipboard()
|
||||
|
||||
|
@ -86,19 +86,19 @@ const {t} = useI18n({useScope: 'global'})
|
|||
useTitle(() => `${t('user.settings.caldav.title')} - ${t('user.settings.title')}`)
|
||||
|
||||
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
|
||||
})
|
||||
|
||||
const newToken = ref<CaldavTokenModel>()
|
||||
const newToken = ref<ICaldavToken>()
|
||||
async function createToken() {
|
||||
newToken.value = await service.create({}) as CaldavTokenModel
|
||||
newToken.value = await service.create({}) as ICaldavToken
|
||||
tokens.value.push(newToken.value)
|
||||
}
|
||||
|
||||
async function deleteToken(token: CaldavTokenModel) {
|
||||
async function deleteToken(token: ICaldavToken) {
|
||||
const r = await service.delete(token)
|
||||
tokens.value = tokens.value.filter(({id}) => id !== token.id)
|
||||
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!