diff --git a/package.json b/package.json index 59d52046c2..3034d396f6 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "autoprefixer": "10.4.13", "browserslist": "4.21.4", "caniuse-lite": "1.0.30001427", + "csstype": "3.1.1", "cypress": "10.11.0", "esbuild": "0.15.12", "eslint": "8.26.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b1c2f6fb84..68e0b149be 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -41,6 +41,7 @@ specifiers: camel-case: 4.1.2 caniuse-lite: 1.0.30001427 codemirror: 5.65.9 + csstype: 3.1.1 cypress: 10.11.0 date-fns: 2.29.3 dayjs: 1.11.6 @@ -157,6 +158,7 @@ devDependencies: autoprefixer: 10.4.13_postcss@8.4.18 browserslist: 4.21.4 caniuse-lite: 1.0.30001427 + csstype: 3.1.1 cypress: 10.11.0 esbuild: 0.15.12 eslint: 8.26.0 @@ -5219,6 +5221,10 @@ packages: /csstype/2.6.19: resolution: {integrity: sha512-ZVxXaNy28/k3kJg0Fou5MiYpp88j7H9hLZp8PDC3jV0WFjfH5E9xHb56L0W59cPbKbcHXeP4qyT8PrHp8t6LcQ==} + /csstype/3.1.1: + resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + dev: true + /cyclist/1.0.1: resolution: {integrity: sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==} dev: true diff --git a/src/components/home/update.vue b/src/components/home/update.vue index 491418d25f..8479b8bc42 100644 --- a/src/components/home/update.vue +++ b/src/components/home/update.vue @@ -26,7 +26,7 @@ if (navigator && navigator.serviceWorker) { ) } -function showRefreshUI(e) { +function showRefreshUI(e: Event) { console.log('recieved refresh event', e) registration.value = e.detail updateAvailable.value = true diff --git a/src/components/input/datepicker.vue b/src/components/input/datepicker.vue index 6b62e132c2..f8b55eabb1 100644 --- a/src/components/input/datepicker.vue +++ b/src/components/input/datepicker.vue @@ -193,7 +193,7 @@ function toggleDatePopup() { } const datepickerPopup = ref(null) -function hideDatePopup(e) { +function hideDatePopup(e: MouseEvent) { if (show.value) { closeWhenClickedOutside(e, datepickerPopup.value, close) } diff --git a/src/components/input/editor.vue b/src/components/input/editor.vue index c2041dd8d0..07022305a0 100644 --- a/src/components/input/editor.vue +++ b/src/components/input/editor.vue @@ -115,6 +115,7 @@ const props = defineProps({ default: true, }, bottomActions: { + type: Array, default: () => [], }, emptyText: { diff --git a/src/components/input/multiselect.vue b/src/components/input/multiselect.vue index f965c635cb..9b92c41bdd 100644 --- a/src/components/input/multiselect.vue +++ b/src/components/input/multiselect.vue @@ -123,6 +123,7 @@ const props = defineProps({ }, // The object with the value, updated every time an entry is selected. modelValue: { + type: [] as PropType<{[key: string]: any}>, default: null, }, // If true, will provide an "add this as a new value" entry which fires an @create event when clicking on it. @@ -177,14 +178,14 @@ const emit = defineEmits<{ // @search: Triggered every time the search query input changes (e: 'search', query: string): void // @select: Triggered every time an option from the search results is selected. Also triggers a change in v-model. - (e: 'select', value: null): void + (e: 'select', value: {[key: string]: any}): void // @create: If nothing or no exact match was found and `creatable` is true, this event is triggered with the current value of the search query. (e: 'create', query: string): void // @remove: If `multiple` is enabled, this will be fired every time an item is removed from the array of selected items. (e: 'remove', value: null): void }>() -const query = ref('') +const query = ref('') const searchTimeout = ref | null>(null) const localLoading = ref(false) const showSearchResults = ref(false) diff --git a/src/components/misc/colorBubble.vue b/src/components/misc/colorBubble.vue index 1b1f037d01..3b7a8ba31a 100644 --- a/src/components/misc/colorBubble.vue +++ b/src/components/misc/colorBubble.vue @@ -6,10 +6,10 @@ diff --git a/src/components/notifications/notifications.vue b/src/components/notifications/notifications.vue index c93d12b56a..9e19b34c1c 100644 --- a/src/components/notifications/notifications.vue +++ b/src/components/notifications/notifications.vue @@ -76,7 +76,7 @@ const notifications = computed(() => { }) const userInfo = computed(() => authStore.info) -let interval: number +let interval: ReturnType onMounted(() => { loadNotifications() diff --git a/src/components/tasks/add-task.vue b/src/components/tasks/add-task.vue index 84d1fcc17f..cb8f515e4a 100644 --- a/src/components/tasks/add-task.vue +++ b/src/components/tasks/add-task.vue @@ -214,7 +214,7 @@ async function addTask() { return rel }) await Promise.all(relations) - } catch (e: { message?: string }) { + } catch (e: any) { newTaskTitle.value = taskTitleBackup if (e?.message === 'NO_LIST') { errorMessage.value = t('list.create.addListRequired') diff --git a/src/components/tasks/partials/attachments.vue b/src/components/tasks/partials/attachments.vue index 1880e85142..93107d9da0 100644 --- a/src/components/tasks/partials/attachments.vue +++ b/src/components/tasks/partials/attachments.vue @@ -165,7 +165,6 @@ import BaseButton from '@/components/base/BaseButton.vue' import AttachmentService from '@/services/attachment' import {SUPPORTED_IMAGE_SUFFIX} from '@/models/attachment' -import type AttachmentModel from '@/models/attachment' import type {IAttachment} from '@/modelTypes/IAttachment' import type {ITask} from '@/modelTypes/ITask' @@ -227,9 +226,9 @@ function uploadFilesToTask(files: File[] | FileList) { uploadFiles(attachmentService, props.task.id, files) } -const attachmentToDelete = ref(null) +const attachmentToDelete = ref(null) -function setAttachmentToDelete(attachment: AttachmentModel | null) { +function setAttachmentToDelete(attachment: IAttachment | null) { attachmentToDelete.value = attachment } @@ -250,7 +249,7 @@ async function deleteAttachment() { const attachmentImageBlobUrl = ref(null) -async function viewOrDownload(attachment: AttachmentModel) { +async function viewOrDownload(attachment: IAttachment) { if (SUPPORTED_IMAGE_SUFFIX.some((suffix) => attachment.file.name.endsWith(suffix))) { attachmentImageBlobUrl.value = await attachmentService.getBlobUrl(attachment) } else { diff --git a/src/components/tasks/partials/heading.vue b/src/components/tasks/partials/heading.vue index 930395af6d..e2215ddabd 100644 --- a/src/components/tasks/partials/heading.vue +++ b/src/components/tasks/partials/heading.vue @@ -4,7 +4,7 @@

- @@ -25,6 +25,7 @@ import type {IList} from '@/modelTypes/IList' import Multiselect from '@/components/input/multiselect.vue' import {useListStore} from '@/stores/lists' import {useNamespaceStore} from '@/stores/namespaces' +import type { INamespace } from '@/modelTypes/INamespace' const props = defineProps({ modelValue: { @@ -65,7 +66,7 @@ function select(l: IList | null) { emit('update:modelValue', list) } -function namespace(namespaceId: number) { +function namespace(namespaceId: INamespace['id']) { const namespace = namespaceStore.getNamespaceById(namespaceId) return namespace !== null ? namespace.title diff --git a/src/directives/shortcut.ts b/src/directives/shortcut.ts index 001ed9602e..bb14a71095 100644 --- a/src/directives/shortcut.ts +++ b/src/directives/shortcut.ts @@ -2,7 +2,7 @@ import type {Directive} from 'vue' import {install, uninstall} from '@github/hotkey' import {isAppleDevice} from '@/helpers/isAppleDevice' -const directive: Directive = { +const directive = >{ mounted(el, {value}) { if(value === '') { return diff --git a/src/helpers/case.ts b/src/helpers/case.ts index 33547c621f..81933d3610 100644 --- a/src/helpers/case.ts +++ b/src/helpers/case.ts @@ -3,17 +3,15 @@ import {snakeCase} from 'snake-case' /** * Transforms field names to camel case. - * @param object - * @returns {*} */ -export function objectToCamelCase(object) { +export function objectToCamelCase(object: Record) { // When calling recursively, this can be called without being and object or array in which case we just return the value if (typeof object !== 'object') { return object } - const parsedObject = {} + const parsedObject: Record = {} for (const m in object) { parsedObject[camelCase(m)] = object[m] @@ -25,7 +23,7 @@ export function objectToCamelCase(object) { // Call it again for arrays if (Array.isArray(object[m])) { - parsedObject[camelCase(m)] = object[m].map(o => objectToCamelCase(o)) + parsedObject[camelCase(m)] = object[m].map((o: Record) => objectToCamelCase(o)) // Because typeof [] === 'object' is true for arrays, we leave the loop here to prevent converting arrays to objects. continue } @@ -40,17 +38,15 @@ export function objectToCamelCase(object) { /** * Transforms field names to snake case - used before making an api request. - * @param object - * @returns {*} */ -export function objectToSnakeCase(object) { +export function objectToSnakeCase(object: Record) { // When calling recursively, this can be called without being and object or array in which case we just return the value if (typeof object !== 'object') { return object } - const parsedObject = {} + const parsedObject: Record = {} for (const m in object) { parsedObject[snakeCase(m)] = object[m] @@ -65,7 +61,7 @@ export function objectToSnakeCase(object) { // Call it again for arrays if (Array.isArray(object[m])) { - parsedObject[snakeCase(m)] = object[m].map(o => objectToSnakeCase(o)) + parsedObject[snakeCase(m)] = object[m].map((o: Record) => objectToSnakeCase(o)) // Because typeof [] === 'object' is true for arrays, we leave the loop here to prevent converting arrays to objects. continue } diff --git a/src/helpers/closeWhenClickedOutside.ts b/src/helpers/closeWhenClickedOutside.ts index 1352f8ae4e..e9201db20a 100644 --- a/src/helpers/closeWhenClickedOutside.ts +++ b/src/helpers/closeWhenClickedOutside.ts @@ -5,11 +5,11 @@ * @param rootElement * @param closeCallback A closure function to call when the click event happened outside of the rootElement. */ -export const closeWhenClickedOutside = (event, rootElement, closeCallback) => { +export const closeWhenClickedOutside = (event: MouseEvent, rootElement: HTMLElement, closeCallback: () => void) => { // We walk up the tree to see if any parent of the clicked element is the root element. // If it is not, we call the close callback. We're doing all this hassle to only call the // closing callback when a click happens outside of the rootElement. - let parent = event.target.parentElement + let parent = (event.target as HTMLElement)?.parentElement while (parent !== rootElement) { if (parent === null || parent.parentElement === null) { parent = null diff --git a/src/helpers/parseDateOrNull.ts b/src/helpers/parseDateOrNull.ts index 836b3040d7..680a5bb255 100644 --- a/src/helpers/parseDateOrNull.ts +++ b/src/helpers/parseDateOrNull.ts @@ -1,12 +1,12 @@ /** * Make date objects from timestamps */ -export function parseDateOrNull(date) { +export function parseDateOrNull(date: string | Date) { if (date instanceof Date) { return date } - if ((typeof date === 'string' || date instanceof String) && !date.startsWith('0001')) { + if ((typeof date === 'string') && !date.startsWith('0001')) { return new Date(date) } diff --git a/src/helpers/saveListView.ts b/src/helpers/saveListView.ts index ad350e8380..fccac3030d 100644 --- a/src/helpers/saveListView.ts +++ b/src/helpers/saveListView.ts @@ -1,6 +1,13 @@ // Save the current list view to local storage + +import type { IList } from '@/modelTypes/IList' + +type ListView = Record + +const DEFAULT_LIST_VIEW = 'list.list' as const + // We use local storage and not a store here to make it persistent across reloads. -export const saveListView = (listId, routeName) => { +export const saveListView = (listId: IList['id'], routeName: string) => { if (routeName.includes('settings.')) { return } @@ -10,12 +17,12 @@ export const saveListView = (listId, routeName) => { } const savedListView = localStorage.getItem('listView') - let savedListViewJson = false + let savedListViewJson: ListView | false = false if (savedListView !== null) { - savedListViewJson = JSON.parse(savedListView) + savedListViewJson = JSON.parse(savedListView) as ListView } - let listView = {} + let listView: ListView = {} if (savedListViewJson) { listView = savedListViewJson } @@ -24,7 +31,7 @@ export const saveListView = (listId, routeName) => { localStorage.setItem('listView', JSON.stringify(listView)) } -export const getListView = listId => { +export const getListView = (listId: IList['id']) => { // Remove old stored settings const savedListView = localStorage.getItem('listView') if (savedListView !== null && savedListView.startsWith('list.')) { @@ -32,13 +39,13 @@ export const getListView = listId => { } if (!savedListView) { - return 'list.list' + return DEFAULT_LIST_VIEW } - const savedListViewJson = JSON.parse(savedListView) + const savedListViewJson: ListView = JSON.parse(savedListView) if (!savedListViewJson[listId]) { - return 'list.list' + return DEFAULT_LIST_VIEW } return savedListViewJson[listId] diff --git a/src/helpers/time/calculateDayInterval.test.ts b/src/helpers/time/calculateDayInterval.test.ts index 136b99322b..9992359e63 100644 --- a/src/helpers/time/calculateDayInterval.test.ts +++ b/src/helpers/time/calculateDayInterval.test.ts @@ -10,7 +10,7 @@ const days = { friday: 5, saturday: 6, sunday: 0, -} +} as Record for (const n in days) { test(`today on a ${n}`, () => { @@ -32,7 +32,7 @@ const nextMonday = { friday: 3, saturday: 2, sunday: 1, -} +} as Record for (const n in nextMonday) { test(`next monday on a ${n}`, () => { @@ -48,7 +48,7 @@ const thisWeekend = { friday: 1, saturday: 0, sunday: 0, -} +} as Record for (const n in thisWeekend) { test(`this weekend on a ${n}`, () => { @@ -64,7 +64,7 @@ const laterThisWeek = { friday: 0, saturday: 0, sunday: 0, -} +} as Record for (const n in laterThisWeek) { test(`later this week on a ${n}`, () => { @@ -80,7 +80,7 @@ const laterNextWeek = { friday: 7 + 0, saturday: 7 + 0, sunday: 7 + 0, -} +} as Record for (const n in laterNextWeek) { test(`later next week on a ${n} (this week)`, () => { diff --git a/src/helpers/time/calculateDayInterval.ts b/src/helpers/time/calculateDayInterval.ts index 19e5fd5a8b..7f04d2ec61 100644 --- a/src/helpers/time/calculateDayInterval.ts +++ b/src/helpers/time/calculateDayInterval.ts @@ -1,4 +1,6 @@ -export function calculateDayInterval(dateString: string, currentDay = (new Date().getDay())) { +type Day = T + +export function calculateDayInterval(dateString: string, currentDay = (new Date().getDay())): Day { switch (dateString) { case 'today': return 0 diff --git a/src/helpers/time/createDateFromString.ts b/src/helpers/time/createDateFromString.ts index 5e9271aa78..b18471b27a 100644 --- a/src/helpers/time/createDateFromString.ts +++ b/src/helpers/time/createDateFromString.ts @@ -6,7 +6,7 @@ * @param dateString * @returns {Date} */ -export const createDateFromString = dateString => { +export function createDateFromString(dateString: string | Date) { if (dateString instanceof Date) { return dateString } diff --git a/src/helpers/time/formatDate.ts b/src/helpers/time/formatDate.ts index 5eb7ccc378..90ae195a28 100644 --- a/src/helpers/time/formatDate.ts +++ b/src/helpers/time/formatDate.ts @@ -8,7 +8,7 @@ import {i18n} from '@/i18n' const locales = {en: enGB, de, ch: de, fr, ru} -export function dateIsValid(date) { +export function dateIsValid(date: Date | null) { if (date === null) { return false } diff --git a/src/modelTypes/IUser.ts b/src/modelTypes/IUser.ts index 45d46298ce..54b66def45 100644 --- a/src/modelTypes/IUser.ts +++ b/src/modelTypes/IUser.ts @@ -20,4 +20,7 @@ export interface IUser extends IAbstract { created: Date updated: Date settings: IUserSettings + + isLocalUser: boolean + deletionScheduledAt: string | Date | null } \ No newline at end of file diff --git a/src/modelTypes/IUserSettings.ts b/src/modelTypes/IUserSettings.ts index 31e921e0ef..5c2c713f9d 100644 --- a/src/modelTypes/IUserSettings.ts +++ b/src/modelTypes/IUserSettings.ts @@ -8,6 +8,7 @@ export interface IUserSettings extends IAbstract { discoverableByName: boolean discoverableByEmail: boolean overdueTasksRemindersEnabled: boolean + overdueTasksRemindersTime: any defaultListId: undefined | IList['id'] weekStart: 0 | 1 | 2 | 3 | 4 | 5 | 6 timezone: string diff --git a/src/models/emailUpdate.ts b/src/models/emailUpdate.ts index 2f50da4014..5ab3012b94 100644 --- a/src/models/emailUpdate.ts +++ b/src/models/emailUpdate.ts @@ -6,7 +6,7 @@ export default class EmailUpdateModel extends AbstractModel implem newEmail = '' password = '' - constructor(data : Partial) { + constructor(data : Partial = {}) { super() this.assignData(data) } diff --git a/src/models/passwordUpdate.ts b/src/models/passwordUpdate.ts index a52c9e10ad..177e3e3f62 100644 --- a/src/models/passwordUpdate.ts +++ b/src/models/passwordUpdate.ts @@ -6,7 +6,7 @@ export default class PasswordUpdateModel extends AbstractModel newPassword = '' oldPassword = '' - constructor(data: Partial) { + constructor(data: Partial = {}) { super() this.assignData(data) } diff --git a/src/models/task.ts b/src/models/task.ts index a133c5e65a..03f419ddda 100644 --- a/src/models/task.ts +++ b/src/models/task.ts @@ -79,6 +79,7 @@ export default class TaskModel extends AbstractModel implements ITask { percentDone = 0 relatedTasks: Partial> = {} attachments: IAttachment[] = [] + coverImageAttachmentId: IAttachment['id'] = null identifier = '' index = 0 isFavorite = false diff --git a/src/models/user.ts b/src/models/user.ts index bbceef61c9..8a3ae505b0 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -28,6 +28,9 @@ export default class UserModel extends AbstractModel implements IUser { updated: Date settings: IUserSettings + isLocalUser: boolean // FIXME: what should this be + deletionScheduledAt: null + constructor(data: Partial = {}) { super() this.assignData(data) diff --git a/src/models/userSettings.ts b/src/models/userSettings.ts index ddb6f0296e..86e8a1126e 100644 --- a/src/models/userSettings.ts +++ b/src/models/userSettings.ts @@ -9,6 +9,7 @@ export default class UserSettingsModel extends AbstractModel impl discoverableByName = false discoverableByEmail = false overdueTasksRemindersEnabled = true + overdueTasksRemindersTime = undefined defaultListId = undefined weekStart = 0 as IUserSettings['weekStart'] timezone = '' diff --git a/src/modules/listHistory.ts b/src/modules/listHistory.ts index 379910da26..aaf79d9d47 100644 --- a/src/modules/listHistory.ts +++ b/src/modules/listHistory.ts @@ -1,4 +1,4 @@ -interface ListHistory { +export interface ListHistory { id: number; } diff --git a/src/modules/parseTaskText.test.ts b/src/modules/parseTaskText.test.ts index 728d1c8e39..5828bd057a 100644 --- a/src/modules/parseTaskText.test.ts +++ b/src/modules/parseTaskText.test.ts @@ -4,7 +4,8 @@ import {parseTaskText, PrefixMode} from './parseTaskText' import {getDateFromText, parseDate} from '../helpers/time/parseDate' import {calculateDayInterval} from '../helpers/time/calculateDayInterval' import {PRIORITIES} from '@/constants/priorities' -import { MILLISECONDS_A_DAY } from '@/constants/date' +import {MILLISECONDS_A_DAY} from '@/constants/date' +import type {IRepeatAfter} from '@/types/IRepeatAfter' describe('Parse Task Text', () => { beforeEach(() => { @@ -31,9 +32,9 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const now = new Date() - expect(result.date.getFullYear()).toBe(now.getFullYear()) - expect(result.date.getMonth()).toBe(now.getMonth()) - expect(result.date.getDate()).toBe(now.getDate()) + expect(result?.date?.getFullYear()).toBe(now.getFullYear()) + expect(result?.date?.getMonth()).toBe(now.getMonth()) + expect(result?.date?.getDate()).toBe(now.getDate()) expect(result.labels).toHaveLength(1) expect(result.labels[0]).toBe('label') expect(result.list).toBe('list') @@ -61,18 +62,18 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const now = new Date() - expect(result.date.getFullYear()).toBe(now.getFullYear()) - expect(result.date.getMonth()).toBe(now.getMonth()) - expect(result.date.getDate()).toBe(now.getDate()) + expect(result?.date?.getFullYear()).toBe(now.getFullYear()) + expect(result?.date?.getMonth()).toBe(now.getMonth()) + expect(result?.date?.getDate()).toBe(now.getDate()) }) it('should recognize today', () => { const result = parseTaskText('Lorem Ipsum today') expect(result.text).toBe('Lorem Ipsum') const now = new Date() - expect(result.date.getFullYear()).toBe(now.getFullYear()) - expect(result.date.getMonth()).toBe(now.getMonth()) - expect(result.date.getDate()).toBe(now.getDate()) + expect(result?.date?.getFullYear()).toBe(now.getFullYear()) + expect(result?.date?.getMonth()).toBe(now.getMonth()) + expect(result?.date?.getDate()).toBe(now.getDate()) }) describe('should recognize today with a time', () => { const cases = { @@ -93,11 +94,11 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const now = new Date() - expect(result.date.getFullYear()).toBe(now.getFullYear()) - expect(result.date.getMonth()).toBe(now.getMonth()) - expect(result.date.getDate()).toBe(now.getDate()) - expect(`${result.date.getHours()}:${result.date.getMinutes()}`).toBe(cases[c as keyof typeof cases]) - expect(result.date.getSeconds()).toBe(0) + expect(result?.date?.getFullYear()).toBe(now.getFullYear()) + expect(result?.date?.getMonth()).toBe(now.getMonth()) + expect(result?.date?.getDate()).toBe(now.getDate()) + expect(`${result?.date?.getHours()}:${result?.date?.getMinutes()}`).toBe(cases[c as keyof typeof cases]) + expect(result?.date?.getSeconds()).toBe(0) }) } }) @@ -107,9 +108,9 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const tomorrow = new Date() tomorrow.setDate(tomorrow.getDate() + 1) - expect(result.date.getFullYear()).toBe(tomorrow.getFullYear()) - expect(result.date.getMonth()).toBe(tomorrow.getMonth()) - expect(result.date.getDate()).toBe(tomorrow.getDate()) + expect(result?.date?.getFullYear()).toBe(tomorrow.getFullYear()) + expect(result?.date?.getMonth()).toBe(tomorrow.getMonth()) + expect(result?.date?.getDate()).toBe(tomorrow.getDate()) }) it('should recognize next monday', () => { const result = parseTaskText('Lorem Ipsum next monday') @@ -119,9 +120,9 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const nextMonday = new Date() nextMonday.setDate(nextMonday.getDate() + untilNextMonday) - expect(result.date.getFullYear()).toBe(nextMonday.getFullYear()) - expect(result.date.getMonth()).toBe(nextMonday.getMonth()) - expect(result.date.getDate()).toBe(nextMonday.getDate()) + expect(result?.date?.getFullYear()).toBe(nextMonday.getFullYear()) + expect(result?.date?.getMonth()).toBe(nextMonday.getMonth()) + expect(result?.date?.getDate()).toBe(nextMonday.getDate()) }) it('should recognize next monday and ignore casing', () => { const result = parseTaskText('Lorem Ipsum nExt Monday') @@ -131,9 +132,9 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const nextMonday = new Date() nextMonday.setDate(nextMonday.getDate() + untilNextMonday) - expect(result.date.getFullYear()).toBe(nextMonday.getFullYear()) - expect(result.date.getMonth()).toBe(nextMonday.getMonth()) - expect(result.date.getDate()).toBe(nextMonday.getDate()) + expect(result?.date?.getFullYear()).toBe(nextMonday.getFullYear()) + expect(result?.date?.getMonth()).toBe(nextMonday.getMonth()) + expect(result?.date?.getDate()).toBe(nextMonday.getDate()) }) it('should recognize this weekend', () => { const result = parseTaskText('Lorem Ipsum this weekend') @@ -143,9 +144,9 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const thisWeekend = new Date() thisWeekend.setDate(thisWeekend.getDate() + untilThisWeekend) - expect(result.date.getFullYear()).toBe(thisWeekend.getFullYear()) - expect(result.date.getMonth()).toBe(thisWeekend.getMonth()) - expect(result.date.getDate()).toBe(thisWeekend.getDate()) + expect(result?.date?.getFullYear()).toBe(thisWeekend.getFullYear()) + expect(result?.date?.getMonth()).toBe(thisWeekend.getMonth()) + expect(result?.date?.getDate()).toBe(thisWeekend.getDate()) }) it('should recognize later this week', () => { const result = parseTaskText('Lorem Ipsum later this week') @@ -155,9 +156,9 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const laterThisWeek = new Date() laterThisWeek.setDate(laterThisWeek.getDate() + untilLaterThisWeek) - expect(result.date.getFullYear()).toBe(laterThisWeek.getFullYear()) - expect(result.date.getMonth()).toBe(laterThisWeek.getMonth()) - expect(result.date.getDate()).toBe(laterThisWeek.getDate()) + expect(result?.date?.getFullYear()).toBe(laterThisWeek.getFullYear()) + expect(result?.date?.getMonth()).toBe(laterThisWeek.getMonth()) + expect(result?.date?.getDate()).toBe(laterThisWeek.getDate()) }) it('should recognize later next week', () => { const result = parseTaskText('Lorem Ipsum later next week') @@ -167,9 +168,9 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const laterNextWeek = new Date() laterNextWeek.setDate(laterNextWeek.getDate() + untilLaterNextWeek) - expect(result.date.getFullYear()).toBe(laterNextWeek.getFullYear()) - expect(result.date.getMonth()).toBe(laterNextWeek.getMonth()) - expect(result.date.getDate()).toBe(laterNextWeek.getDate()) + expect(result?.date?.getFullYear()).toBe(laterNextWeek.getFullYear()) + expect(result?.date?.getMonth()).toBe(laterNextWeek.getMonth()) + expect(result?.date?.getDate()).toBe(laterNextWeek.getDate()) }) it('should recognize next week', () => { const result = parseTaskText('Lorem Ipsum next week') @@ -179,9 +180,9 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const nextWeek = new Date() nextWeek.setDate(nextWeek.getDate() + untilNextWeek) - expect(result.date.getFullYear()).toBe(nextWeek.getFullYear()) - expect(result.date.getMonth()).toBe(nextWeek.getMonth()) - expect(result.date.getDate()).toBe(nextWeek.getDate()) + expect(result?.date?.getFullYear()).toBe(nextWeek.getFullYear()) + expect(result?.date?.getMonth()).toBe(nextWeek.getMonth()) + expect(result?.date?.getDate()).toBe(nextWeek.getDate()) }) it('should recognize next month', () => { const result = parseTaskText('Lorem Ipsum next month') @@ -190,9 +191,9 @@ describe('Parse Task Text', () => { const nextMonth = new Date() nextMonth.setDate(1) nextMonth.setMonth(nextMonth.getMonth() + 1) - expect(result.date.getFullYear()).toBe(nextMonth.getFullYear()) - expect(result.date.getMonth()).toBe(nextMonth.getMonth()) - expect(result.date.getDate()).toBe(nextMonth.getDate()) + expect(result?.date?.getFullYear()).toBe(nextMonth.getFullYear()) + expect(result?.date?.getMonth()).toBe(nextMonth.getMonth()) + expect(result?.date?.getDate()).toBe(nextMonth.getDate()) }) it('should recognize a date', () => { const result = parseTaskText('Lorem Ipsum 06/26/2021') @@ -200,9 +201,9 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const date = new Date() date.setFullYear(2021, 5, 26) - expect(result.date.getFullYear()).toBe(date.getFullYear()) - expect(result.date.getMonth()).toBe(date.getMonth()) - expect(result.date.getDate()).toBe(date.getDate()) + expect(result?.date?.getFullYear()).toBe(date.getFullYear()) + expect(result?.date?.getMonth()).toBe(date.getMonth()) + expect(result?.date?.getDate()).toBe(date.getDate()) }) it('should recognize end of month', () => { const result = parseTaskText('Lorem Ipsum end of month') @@ -210,9 +211,9 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const curDate = new Date() const date = new Date(curDate.getFullYear(), curDate.getMonth() + 1, 0) - expect(result.date.getFullYear()).toBe(date.getFullYear()) - expect(result.date.getMonth()).toBe(date.getMonth()) - expect(result.date.getDate()).toBe(date.getDate()) + expect(result?.date?.getFullYear()).toBe(date.getFullYear()) + expect(result?.date?.getMonth()).toBe(date.getMonth()) + expect(result?.date?.getDate()).toBe(date.getDate()) }) const cases = { @@ -244,7 +245,7 @@ describe('Parse Task Text', () => { 'Sunday': 7, 'sun': 7, 'Sun': 7, - } + } as Record for (const c in cases) { it(`should recognize ${c} as weekday`, () => { const result = parseTaskText(`Lorem Ipsum ${c}`) @@ -252,7 +253,7 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const nextDate = new Date() nextDate.setDate(nextDate.getDate() + ((cases[c] + 7 - nextDate.getDay()) % 7)) - expect(`${result.date.getFullYear()}-${result.date.getMonth()}-${result.date.getDate()}`).toBe(`${nextDate.getFullYear()}-${nextDate.getMonth()}-${nextDate.getDate()}`) + expect(`${result?.date?.getFullYear()}-${result?.date?.getMonth()}-${result?.date?.getDate()}`).toBe(`${nextDate.getFullYear()}-${nextDate.getMonth()}-${nextDate.getDate()}`) }) } it('should recognize weekdays with time', () => { @@ -261,8 +262,8 @@ describe('Parse Task Text', () => { expect(result.text).toBe('Lorem Ipsum') const nextThursday = new Date() nextThursday.setDate(nextThursday.getDate() + ((4 + 7 - nextThursday.getDay()) % 7)) - expect(`${result.date.getFullYear()}-${result.date.getMonth()}-${result.date.getDate()}`).toBe(`${nextThursday.getFullYear()}-${nextThursday.getMonth()}-${nextThursday.getDate()}`) - expect(`${result.date.getHours()}:${result.date.getMinutes()}`).toBe('14:0') + expect(`${result?.date?.getFullYear()}-${result?.date?.getMonth()}-${result?.date?.getDate()}`).toBe(`${nextThursday.getFullYear()}-${nextThursday.getMonth()}-${nextThursday.getDate()}`) + expect(`${result?.date?.getHours()}:${result?.date?.getMinutes()}`).toBe('14:0') }) it('should recognize dates of the month in the past but next month', () => { const time = new Date(2022, 0, 15) @@ -271,8 +272,8 @@ describe('Parse Task Text', () => { const result = parseTaskText(`Lorem Ipsum ${time.getDate() - 1}th`) expect(result.text).toBe('Lorem Ipsum') - expect(result.date.getDate()).toBe(time.getDate() - 1) - expect(result.date.getMonth()).toBe(time.getMonth() + 1) + expect(result?.date?.getDate()).toBe(time.getDate() - 1) + expect(result?.date?.getMonth()).toBe(time.getMonth() + 1) }) it('should recognize dates of the month in the past but next month when february is the next month', () => { const jan = new Date(2022, 0, 30) @@ -282,8 +283,8 @@ describe('Parse Task Text', () => { const expectedDate = new Date(2022, 2, jan.getDate() - 1) expect(result.text).toBe('Lorem Ipsum') - expect(result.date.getDate()).toBe(expectedDate.getDate()) - expect(result.date.getMonth()).toBe(expectedDate.getMonth()) + expect(result?.date?.getDate()).toBe(expectedDate.getDate()) + expect(result?.date?.getMonth()).toBe(expectedDate.getMonth()) }) it('should recognize dates of the month in the past but next month when the next month has less days than this one', () => { const mar = new Date(2022, 2, 32) @@ -293,15 +294,15 @@ describe('Parse Task Text', () => { const expectedDate = new Date(2022, 4, 31) expect(result.text).toBe('Lorem Ipsum') - expect(result.date.getDate()).toBe(expectedDate.getDate()) - expect(result.date.getMonth()).toBe(expectedDate.getMonth()) + expect(result?.date?.getDate()).toBe(expectedDate.getDate()) + expect(result?.date?.getMonth()).toBe(expectedDate.getMonth()) }) it('should recognize dates of the month in the future', () => { const nextDay = new Date(+new Date() + MILLISECONDS_A_DAY) const result = parseTaskText(`Lorem Ipsum ${nextDay.getDate()}nd`) expect(result.text).toBe('Lorem Ipsum') - expect(result.date.getDate()).toBe(nextDay.getDate()) + expect(result?.date?.getDate()).toBe(nextDay.getDate()) }) it('should only recognize weekdays with a space before or after them 1', () => { const result = parseTaskText('Lorem Ipsum renewed') @@ -382,7 +383,7 @@ describe('Parse Task Text', () => { 'saturday': 6, 'sun': 7, 'sunday': 7, - } + } as Record const prefix = [ 'next ', @@ -399,9 +400,9 @@ describe('Parse Task Text', () => { next.setDate(next.getDate() + distance) expect(result.text).toBe('Lorem Ipsum') - expect(result.date.getFullYear()).toBe(next.getFullYear()) - expect(result.date.getMonth()).toBe(next.getMonth()) - expect(result.date.getDate()).toBe(next.getDate()) + expect(result?.date?.getFullYear()).toBe(next.getFullYear()) + expect(result?.date?.getMonth()).toBe(next.getMonth()) + expect(result?.date?.getDate()).toBe(next.getDate()) }) } }) @@ -462,7 +463,7 @@ describe('Parse Task Text', () => { 'dolor sit amet oct 21': '2021-10-21', 'dolor sit amet nov 21': '2021-11-21', 'dolor sit amet dec 21': '2021-12-21', - } + } as Record for (const c in cases) { it(`should parse '${c}' as '${cases[c]}'`, () => { @@ -472,7 +473,7 @@ describe('Parse Task Text', () => { return } - expect(`${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`).toBe(cases[c]) + expect(`${date?.getFullYear()}-${date.getMonth() + 1}-${date?.getDate()}`).toBe(cases[c]) }) } }) @@ -510,7 +511,7 @@ describe('Parse Task Text', () => { 'Something at 10:00 in 5 days': '2021-6-29 10:0', 'Something at 10:00 17th': '2021-7-17 10:0', 'Something at 10:00 sep 17th': '2021-9-17 10:0', - } + } as Record for (const c in cases) { it(`should parse '${c}' as '${cases[c]}'`, () => { @@ -695,15 +696,15 @@ describe('Parse Task Text', () => { 'every eight hours': {type: 'hours', amount: 8}, 'every nine hours': {type: 'hours', amount: 9}, 'every ten hours': {type: 'hours', amount: 10}, - } + } as Record for (const c in cases) { it(`should parse ${c} as recurring date every ${cases[c].amount} ${cases[c].type}`, () => { const result = parseTaskText(`Lorem Ipsum ${c}`) expect(result.text).toBe('Lorem Ipsum') - expect(result.repeats.type).toBe(cases[c].type) - expect(result.repeats.amount).toBe(cases[c].amount) + expect(result?.repeats?.type).toBe(cases[c].type) + expect(result?.repeats?.amount).toBe(cases[c].amount) }) } }) diff --git a/src/services/attachment.ts b/src/services/attachment.ts index 98cc718ec6..ecd730e418 100644 --- a/src/services/attachment.ts +++ b/src/services/attachment.ts @@ -7,7 +7,7 @@ import type { IAttachment } from '@/modelTypes/IAttachment' import {downloadBlob} from '@/helpers/downloadBlob' -export default class AttachmentService extends AbstractService { +export default class AttachmentService extends AbstractService { constructor() { super({ create: '/tasks/{taskId}/attachments', diff --git a/src/services/savedFilter.ts b/src/services/savedFilter.ts index 92e790344c..6c12f8182c 100644 --- a/src/services/savedFilter.ts +++ b/src/services/savedFilter.ts @@ -84,7 +84,7 @@ export function useSavedFilter(listId?: MaybeRef) { const filterService = shallowReactive(new SavedFilterService()) - const filter = ref(new SavedFilterModel()) + const filter = ref(new SavedFilterModel()) const filters = computed({ get: () => filter.value.filters, set(value) { @@ -92,7 +92,7 @@ export function useSavedFilter(listId?: MaybeRef) { }, }) - // loadSavedFilter + // load SavedFilter watch(() => unref(listId), async (watchedListId) => { if (watchedListId === undefined) { return diff --git a/src/stores/base.ts b/src/stores/base.ts index 5f8c6cacdc..258ff27454 100644 --- a/src/stores/base.ts +++ b/src/stores/base.ts @@ -86,7 +86,7 @@ export const useBaseStore = defineStore('base', () => { } async function handleSetCurrentList( - {list, forceUpdate = false}: {list: IList | null, forceUpdate: boolean}, + {list, forceUpdate = false}: {list: IList | null, forceUpdate?: boolean}, ) { if (list === null) { setCurrentList({}) diff --git a/src/stores/lists.ts b/src/stores/lists.ts index 91b4158cd3..e03eac8269 100644 --- a/src/stores/lists.ts +++ b/src/stores/lists.ts @@ -180,7 +180,7 @@ export const useListStore = defineStore('list', () => { export function useList(listId: MaybeRef) { const listService = shallowReactive(new ListService()) const {loading: isLoading} = toRefs(listService) - const list: ListModel = reactive(new ListModel()) + const list: IList = reactive(new ListModel()) const {t} = useI18n({useScope: 'global'}) watch( diff --git a/src/views/Home.vue b/src/views/Home.vue index 73b53ca9b3..b55952c2a8 100644 --- a/src/views/Home.vue +++ b/src/views/Home.vue @@ -14,7 +14,6 @@ @@ -76,6 +75,7 @@ import {useConfigStore} from '@/stores/config' import {useNamespaceStore} from '@/stores/namespaces' import {useAuthStore} from '@/stores/auth' import {useTaskStore} from '@/stores/tasks' +import type {IList} from '@/modelTypes/IList' const salutation = useDaytimeSalutation() @@ -94,12 +94,11 @@ const listHistory = computed(() => { return getHistory() .map(l => listStore.getListById(l.id)) - .filter(l => l !== null) + .filter((l): l is IList => l !== null) }) const migratorsEnabled = computed(() => configStore.availableMigrators?.length > 0) const hasTasks = computed(() => baseStore.hasTasks) -const defaultListId = computed(() => authStore.settings.defaultListId) const defaultNamespaceId = computed(() => namespaceStore.namespaces?.[0]?.id || 0) const hasLists = computed(() => namespaceStore.namespaces?.[0]?.lists.length > 0) const loading = computed(() => taskStore.isLoading) diff --git a/src/views/labels/NewLabel.vue b/src/views/labels/NewLabel.vue index b6701eafa2..d012710e2e 100644 --- a/src/views/labels/NewLabel.vue +++ b/src/views/labels/NewLabel.vue @@ -66,7 +66,7 @@ async function newLabel() { showError.value = false const labelStore = useLabelStore() - const newLabel = labelStore.createLabel(label.value) + const newLabel = await labelStore.createLabel(label.value) router.push({ name: 'labels.index', params: {id: newLabel.id}, diff --git a/src/views/namespaces/settings/edit.vue b/src/views/namespaces/settings/edit.vue index 2365f33103..7c25b49634 100644 --- a/src/views/namespaces/settings/edit.vue +++ b/src/views/namespaces/settings/edit.vue @@ -71,11 +71,13 @@ import {useI18n} from 'vue-i18n' import {useTitle} from '@/composables/useTitle' import {useNamespaceStore} from '@/stores/namespaces' +import type {INamespace} from '@/modelTypes/INamespace' + const {t} = useI18n({useScope: 'global'}) const namespaceStore = useNamespaceStore() const namespaceService = ref(new NamespaceService()) -const namespace = ref(new NamespaceModel()) +const namespace = ref(new NamespaceModel()) const editorActive = ref(false) const title = ref('') useTitle(() => title.value) diff --git a/src/views/tasks/TaskDetailView.vue b/src/views/tasks/TaskDetailView.vue index d308b35904..82257ccb7a 100644 --- a/src/views/tasks/TaskDetailView.vue +++ b/src/views/tasks/TaskDetailView.vue @@ -558,7 +558,7 @@ const canWrite = computed(() => ( const color = computed(() => { const color = task.getHexColor ? task.getHexColor() - : false + : undefined return color === TASK_DEFAULT_COLOR ? '' diff --git a/src/views/user/OpenIdAuth.vue b/src/views/user/OpenIdAuth.vue index 0d4b6cb8d0..85ba173e54 100644 --- a/src/views/user/OpenIdAuth.vue +++ b/src/views/user/OpenIdAuth.vue @@ -50,14 +50,14 @@ async function authenticateWithCode() { if (localStorage.getItem('authenticating')) { return } - localStorage.setItem('authenticating', true) + localStorage.setItem('authenticating', 'true') errorMessage.value = '' if (typeof route.query.error !== 'undefined') { localStorage.removeItem('authenticating') errorMessage.value = typeof route.query.message !== 'undefined' - ? route.query.message + ? route.query.message as string : t('user.auth.openIdGeneralError') return } diff --git a/src/views/user/Register.vue b/src/views/user/Register.vue index bdbf0f8d30..26d89f28ed 100644 --- a/src/views/user/Register.vue +++ b/src/views/user/Register.vue @@ -130,8 +130,8 @@ async function submit() { try { await authStore.register(toRaw(credentials)) - } catch (e) { - errorMessage.value = e.message + } catch (e: any) { + errorMessage.value = e?.message } } diff --git a/src/views/user/settings/Caldav.vue b/src/views/user/settings/Caldav.vue index 190618bb6c..cb22185853 100644 --- a/src/views/user/settings/Caldav.vue +++ b/src/views/user/settings/Caldav.vue @@ -41,7 +41,7 @@ {{ tk.id }} {{ formatDateShort(tk.created) }} - + {{ $t('misc.delete') }} diff --git a/src/views/user/settings/General.vue b/src/views/user/settings/General.vue index e4fdddf23f..8dbf3b8c0c 100644 --- a/src/views/user/settings/General.vue +++ b/src/views/user/settings/General.vue @@ -246,7 +246,7 @@ watch( const listStore = useListStore() const defaultList = computed({ - get: () => listStore.getListById(settings.value.defaultListId), + get: () => listStore.getListById(settings.value.defaultListId) || undefined, set(l) { settings.value.defaultListId = l ? l.id : DEFAULT_LIST_ID }, diff --git a/src/views/user/settings/TOTP.vue b/src/views/user/settings/TOTP.vue index 11ca323b73..c589c54e20 100644 --- a/src/views/user/settings/TOTP.vue +++ b/src/views/user/settings/TOTP.vue @@ -79,13 +79,14 @@ import {success} from '@/message' import {useTitle} from '@/composables/useTitle' import {useConfigStore} from '@/stores/config' +import type {ITotp} from '@/modelTypes/ITotp' const {t} = useI18n({useScope: 'global'}) useTitle(() => `${t('user.settings.totp.title')} - ${t('user.settings.title')}`) const totpService = shallowReactive(new TotpService()) -const totp = ref(new TotpModel()) +const totp = ref(new TotpModel()) const totpQR = ref('') const totpEnrolled = ref(false) const totpConfirmPasscode = ref('')