From 599e28e5e5d56e4ced338ec1c79fea7d4576b85a Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Mon, 17 Oct 2022 13:14:07 +0200 Subject: [PATCH 01/17] feat: type improvements --- package.json | 1 + pnpm-lock.yaml | 6 + src/components/home/update.vue | 2 +- src/components/input/datepicker.vue | 2 +- src/components/input/editor.vue | 1 + src/components/input/multiselect.vue | 5 +- src/components/misc/colorBubble.vue | 4 +- .../notifications/notifications.vue | 2 +- src/components/tasks/add-task.vue | 2 +- src/components/tasks/partials/attachments.vue | 7 +- src/components/tasks/partials/heading.vue | 3 +- src/components/tasks/partials/listSearch.vue | 9 +- src/directives/shortcut.ts | 2 +- src/helpers/case.ts | 16 +-- src/helpers/closeWhenClickedOutside.ts | 4 +- src/helpers/parseDateOrNull.ts | 4 +- src/helpers/saveListView.ts | 23 +-- src/helpers/time/calculateDayInterval.test.ts | 10 +- src/helpers/time/calculateDayInterval.ts | 4 +- src/helpers/time/createDateFromString.ts | 2 +- src/helpers/time/formatDate.ts | 2 +- src/modelTypes/IUser.ts | 3 + src/modelTypes/IUserSettings.ts | 1 + src/models/emailUpdate.ts | 2 +- src/models/passwordUpdate.ts | 2 +- src/models/task.ts | 1 + src/models/user.ts | 3 + src/models/userSettings.ts | 1 + src/modules/listHistory.ts | 2 +- src/modules/parseTaskText.test.ts | 133 +++++++++--------- src/services/attachment.ts | 2 +- src/services/savedFilter.ts | 4 +- src/stores/base.ts | 2 +- src/stores/lists.ts | 2 +- src/views/Home.vue | 5 +- src/views/labels/NewLabel.vue | 2 +- src/views/namespaces/settings/edit.vue | 4 +- src/views/tasks/TaskDetailView.vue | 2 +- src/views/user/OpenIdAuth.vue | 4 +- src/views/user/Register.vue | 4 +- src/views/user/settings/Caldav.vue | 2 +- src/views/user/settings/General.vue | 2 +- src/views/user/settings/TOTP.vue | 3 +- 43 files changed, 162 insertions(+), 135 deletions(-) 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('') From a2c1702eef64dd779c86940898bd49fc2c96233f Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Mon, 17 Oct 2022 13:22:10 +0200 Subject: [PATCH 02/17] feat: type global components and especially icon prop --- src/components/misc/Icon.ts | 5 +++- src/components/misc/card.vue | 5 +++- src/components/misc/create-edit.vue | 5 +++- src/components/misc/dropdown.vue | 5 ++-- src/components/misc/subscription.vue | 3 ++- src/types/global-components.d.ts | 21 +++++++++++++++ src/types/vue-fontawesome.ts | 40 ++++++++++++++++++++++++++++ tsconfig.app.json | 2 +- 8 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 src/types/global-components.d.ts create mode 100644 src/types/vue-fontawesome.ts diff --git a/src/components/misc/Icon.ts b/src/components/misc/Icon.ts index be5ef27ebf..b1ce58ee4c 100644 --- a/src/components/misc/Icon.ts +++ b/src/components/misc/Icon.ts @@ -70,6 +70,8 @@ import { } from '@fortawesome/free-regular-svg-icons' import {FontAwesomeIcon} from '@fortawesome/vue-fontawesome' +import type { FontAwesomeIcon as FontAwesomeIconFixedTypes } from '@/types/vue-fontawesome' + library.add(faAlignLeft) library.add(faAngleRight) library.add(faArchive) @@ -136,4 +138,5 @@ library.add(faTrashAlt) library.add(faUser) library.add(faUsers) -export default FontAwesomeIcon \ No newline at end of file +// overwriting the wrong types +export default FontAwesomeIcon as unknown as FontAwesomeIconFixedTypes \ No newline at end of file diff --git a/src/components/misc/card.vue b/src/components/misc/card.vue index 7ca3a3df74..0d96916d36 100644 --- a/src/components/misc/card.vue +++ b/src/components/misc/card.vue @@ -35,6 +35,9 @@ diff --git a/src/components/misc/subscription.vue b/src/components/misc/subscription.vue index 50f1f07193..41beab9517 100644 --- a/src/components/misc/subscription.vue +++ b/src/components/misc/subscription.vue @@ -13,7 +13,7 @@ v-else-if="type === 'dropdown'" v-tooltip="tooltipText" @click="changeSubscription" - :class="{'is-disabled': disabled}" + :disabled="disabled" :icon="iconName" > {{ buttonText }} From 0b58973d872d8d54c9a829a06c8535a7a7115613 Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Mon, 17 Oct 2022 13:45:03 +0200 Subject: [PATCH 06/17] feat: rework popup --- src/components/misc/popup.vue | 48 +++++++++++++++++------------------ 1 file changed, 23 insertions(+), 25 deletions(-) diff --git a/src/components/misc/popup.vue b/src/components/misc/popup.vue index b6da6a25d1..7d8139d3ce 100644 --- a/src/components/misc/popup.vue +++ b/src/components/misc/popup.vue @@ -1,20 +1,20 @@ From 35f4bb138554d300757420261d70d1a6bf6b9cc0 Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Mon, 17 Oct 2022 13:54:38 +0200 Subject: [PATCH 07/17] fix: setModuleLoading LoadingState type --- src/stores/helper.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/stores/helper.ts b/src/stores/helper.ts index d074648589..ea9dff6f29 100644 --- a/src/stores/helper.ts +++ b/src/stores/helper.ts @@ -1,12 +1,10 @@ -import type { StoreDefinition } from 'pinia' - export interface LoadingState { isLoading: boolean } const LOADING_TIMEOUT = 100 -export const setModuleLoading = >(store: LoadingStore, loadFunc : ((isLoading: boolean) => void) | null = null) => { +export const setModuleLoading = (store: Store, loadFunc : ((isLoading: boolean) => void) | null = null) => { const timeout = setTimeout(() => { if (loadFunc === null) { store.isLoading = true From 964aba4824418e431955881be284e35f412e873b Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Mon, 17 Oct 2022 15:32:43 +0200 Subject: [PATCH 08/17] fix: better kanban updateBucket types --- src/stores/kanban.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stores/kanban.ts b/src/stores/kanban.ts index 25583a0e4d..76d29dfe2b 100644 --- a/src/stores/kanban.ts +++ b/src/stores/kanban.ts @@ -364,7 +364,7 @@ export const useKanbanStore = defineStore('kanban', { } }, - async updateBucket(updatedBucketData: IBucket) { + async updateBucket(updatedBucketData: Partial) { const cancel = setModuleLoading(this) const bucketIndex = findIndexById(this.buckets, updatedBucketData.id) From d6cb965ea7330f80f1e3c213442a049f63cba57e Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Mon, 17 Oct 2022 15:36:36 +0200 Subject: [PATCH 09/17] fix: disable props destructure error --- .eslintrc.cjs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.eslintrc.cjs b/.eslintrc.cjs index e0d091949d..2e107b9892 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -37,6 +37,10 @@ module.exports = { '@typescript-eslint/no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }], 'vue/multi-word-component-names': 0, + // disabled until we have support for reactivityTransform + // See https://github.com/vuejs/eslint-plugin-vue/issues/1948 + // see also setting in `vite.config` + 'vue/no-setup-props-destructure': 0, }, 'parser': 'vue-eslint-parser', 'parserOptions': { From 53c9a9bc9c466d3182b8374a5c1b2562c68a65a6 Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Mon, 17 Oct 2022 15:49:02 +0200 Subject: [PATCH 10/17] jsx templates --- tsconfig.app.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tsconfig.app.json b/tsconfig.app.json index 67aa6abf77..cf027777d7 100644 --- a/tsconfig.app.json +++ b/tsconfig.app.json @@ -18,6 +18,7 @@ } }, "vueCompilerOptions": { - "strictTemplates": true + // "strictTemplates": true + "jsxTemplates": true } } \ No newline at end of file From 5d601ca4b34cd7368ff6061659617fff2836cdbc Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Thu, 27 Oct 2022 16:00:39 +0200 Subject: [PATCH 11/17] fix: missing href --- src/components/base/BaseButton.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/base/BaseButton.vue b/src/components/base/BaseButton.vue index 3b09e1dd86..fe12853290 100644 --- a/src/components/base/BaseButton.vue +++ b/src/components/base/BaseButton.vue @@ -22,6 +22,7 @@ Date: Thu, 27 Oct 2022 16:00:53 +0200 Subject: [PATCH 12/17] chore: remove comment --- src/models/user.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/user.ts b/src/models/user.ts index 8a3ae505b0..02929a98ed 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -28,7 +28,7 @@ export default class UserModel extends AbstractModel implements IUser { updated: Date settings: IUserSettings - isLocalUser: boolean // FIXME: what should this be + isLocalUser: boolean deletionScheduledAt: null constructor(data: Partial = {}) { From caa29c152d35b28658773b838de0a8909d0e509f Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Fri, 28 Oct 2022 18:49:09 +0200 Subject: [PATCH 13/17] chore: improve multiselect hover types when hovering over props you can only see comments if written with JSDoc --- src/components/input/multiselect.vue | 66 +++++++++++++++++++++------- 1 file changed, 49 insertions(+), 17 deletions(-) diff --git a/src/components/input/multiselect.vue b/src/components/input/multiselect.vue index 9b92c41bdd..fce2a61e82 100644 --- a/src/components/input/multiselect.vue +++ b/src/components/input/multiselect.vue @@ -100,38 +100,52 @@ function elementInResults(elem: string | any, label: string, query: string): boo } const props = defineProps({ - // When true, shows a loading spinner + /** + * When true, shows a loading spinner + */ loading: { type: Boolean, default: false, }, - // The placeholder of the search input + /** + * The placeholder of the search input + */ placeholder: { type: String, default: '', }, - // The search results where the @search listener needs to put the results into + /** + * The search results where the @search listener needs to put the results into + */ searchResults: { type: Array as PropType<{[id: string]: any}>, default: () => [], }, - // The name of the property of the searched object to show the user. - // If empty the component will show all raw data of an entry. + /** + * The name of the property of the searched object to show the user. + * If empty the component will show all raw data of an entry. + */ label: { type: String, default: '', }, - // The object with the value, updated every time an entry is selected. + /** + * 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. + /** + * If true, will provide an "add this as a new value" entry which fires an @create event when clicking on it. + */ creatable: { type: Boolean, default: false, }, - // The text shown next to the new value option. + /** + * The text shown next to the new value option. + */ createPlaceholder: { type: String, default() { @@ -139,7 +153,9 @@ const props = defineProps({ return t('input.multiselect.createPlaceholder') }, }, - // The text shown next to an option. + /** + * The text shown next to an option. + */ selectPlaceholder: { type: String, default() { @@ -147,22 +163,30 @@ const props = defineProps({ return t('input.multiselect.selectPlaceholder') }, }, - // If true, allows for selecting multiple items. v-model will be an array with all selected values in that case. + /** + * If true, allows for selecting multiple items. v-model will be an array with all selected values in that case. + */ multiple: { type: Boolean, default: false, }, - // If true, displays the search results inline instead of using a dropdown. + /** + * If true, displays the search results inline instead of using a dropdown. + */ inline: { type: Boolean, default: false, }, - // If true, shows search results when no query is specified. + /** + * If true, shows search results when no query is specified. + */ showEmpty: { type: Boolean, default: true, }, - // The delay in ms after which the search event will be fired. Used to avoid hitting the network on every keystroke. + /** + * The delay in ms after which the search event will be fired. Used to avoid hitting the network on every keystroke. + */ searchDelay: { type: Number, default: 200, @@ -175,13 +199,21 @@ const props = defineProps({ const emit = defineEmits<{ (e: 'update:modelValue', value: null): void - // @search: Triggered every time the search query input changes + /** + * 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. + /** + * Triggered every time an option from the search results is selected. Also triggers a change in v-model. + */ (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. + /** + * 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. + /** + * 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 }>() From 480aa8813ec28e1228e02ba78dd3ee3037f4928a Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Fri, 28 Oct 2022 18:49:31 +0200 Subject: [PATCH 14/17] fix: Multiselect modelValue prop type --- src/components/input/multiselect.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/input/multiselect.vue b/src/components/input/multiselect.vue index fce2a61e82..c8636d64a8 100644 --- a/src/components/input/multiselect.vue +++ b/src/components/input/multiselect.vue @@ -133,7 +133,7 @@ const props = defineProps({ * The object with the value, updated every time an entry is selected. */ modelValue: { - type: [] as PropType<{[key: string]: any}>, + type: [Object] as PropType<{[key: string]: any}>, default: null, }, /** From 0182695cda1252a65df3f48fdc316e82cd7fadbd Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Tue, 27 Sep 2022 22:54:37 +0200 Subject: [PATCH 15/17] feat: add type info --- src/helpers/markdownRenderer.ts | 2 +- src/helpers/saveListView.ts | 13 +++++++------ src/sentry.ts | 4 +++- src/services/migrator/abstractMigrationFile.ts | 2 +- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/helpers/markdownRenderer.ts b/src/helpers/markdownRenderer.ts index e3725fd331..2c39935345 100644 --- a/src/helpers/markdownRenderer.ts +++ b/src/helpers/markdownRenderer.ts @@ -35,7 +35,7 @@ export function setupMarkdownRenderer(checkboxId: string) { return isLocal ? html : html.replace(/^ 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: IList['id'], routeName: string) => { +/** + * Save the current list view to local storage + */ +export function saveListView(listId: IList['id'], routeName: string) { if (routeName.includes('settings.')) { return } - + if (!listId) { return } - + + // We use local storage and not the store here to make it persistent across reloads. const savedListView = localStorage.getItem('listView') let savedListViewJson: ListView | false = false if (savedListView !== null) { diff --git a/src/sentry.ts b/src/sentry.ts index 678a1c7380..fad06913fc 100644 --- a/src/sentry.ts +++ b/src/sentry.ts @@ -1,6 +1,8 @@ +import type { App } from 'vue' +import type { Router } from 'vue-router' import {VERSION} from './version.json' -export default async function setupSentry(app, router) { +export default async function setupSentry(app: App, router: Router) { const Sentry = await import('@sentry/vue') const {Integrations} = await import('@sentry/tracing') diff --git a/src/services/migrator/abstractMigrationFile.ts b/src/services/migrator/abstractMigrationFile.ts index 1e6a19347a..2b1cf39e15 100644 --- a/src/services/migrator/abstractMigrationFile.ts +++ b/src/services/migrator/abstractMigrationFile.ts @@ -6,7 +6,7 @@ import AbstractService from '../abstractService' export default class AbstractMigrationFileService extends AbstractService { serviceUrlKey = '' - constructor(serviceUrlKey: '') { + constructor(serviceUrlKey: string) { super({ create: '/migration/' + serviceUrlKey + '/migrate', }) From 3c5bfcc6f3cece0f3bd6e4f862a187c17a2c4d6c Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Tue, 27 Sep 2022 22:55:05 +0200 Subject: [PATCH 16/17] fix: potential issue with refs in Avatar --- src/views/user/settings/Avatar.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/user/settings/Avatar.vue b/src/views/user/settings/Avatar.vue index e71a66bf22..0a27faa84e 100644 --- a/src/views/user/settings/Avatar.vue +++ b/src/views/user/settings/Avatar.vue @@ -23,7 +23,7 @@ {{ $t('user.settings.avatar.uploadAvatar') }} From e01df4d36996aa281ef73ee74f3ac5316a0b8a98 Mon Sep 17 00:00:00 2001 From: Dominik Pschenitschni Date: Tue, 1 Nov 2022 14:27:35 +0100 Subject: [PATCH 17/17] fix: coverImageAttachmentId --- src/modelTypes/ITask.ts | 2 +- src/models/task.ts | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/modelTypes/ITask.ts b/src/modelTypes/ITask.ts index bcbcf39253..8651e89eee 100644 --- a/src/modelTypes/ITask.ts +++ b/src/modelTypes/ITask.ts @@ -36,7 +36,7 @@ export interface ITask extends IAbstract { percentDone: number relatedTasks: Partial> attachments: IAttachment[] - coverImageAttachmentId: IAttachment['id'] + coverImageAttachmentId: IAttachment['id'] | null identifier: string index: number isFavorite: boolean diff --git a/src/models/task.ts b/src/models/task.ts index 03f419ddda..2499f97787 100644 --- a/src/models/task.ts +++ b/src/models/task.ts @@ -84,6 +84,7 @@ export default class TaskModel extends AbstractModel implements ITask { index = 0 isFavorite = false subscription: ISubscription = null + coverImageAttachmentId: IAttachment['id'] = null position = 0 kanbanPosition = 0