diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 00000000..b6a7d89c --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +16 diff --git a/package.json b/package.json index dbf7786e..ca23ff30 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "etesync-web", - "version": "0.6.0", + "version": "0.7.0", "private": true, "dependencies": { "@date-io/moment": "^1.x", @@ -16,9 +16,9 @@ "localforage": "^1.9.0", "memoizee": "^0.4.14", "moment": "^2.27.0", - "react": "^16.13.1", + "react": "^17.0.2", "react-big-calendar": "^0.26.0", - "react-dom": "^16.13.1", + "react-dom": "^17.0.2", "react-dropzone": "^10.0.4", "react-redux": "^7.2.1", "react-router": "^5.2.0", @@ -48,10 +48,10 @@ "@types/color": "^3.0.1", "@types/jest": "^24.0.4", "@types/memoizee": "^0.4.4", - "@types/node": "^11.9.3", - "@types/react": "^16.9.0", + "@types/node": "^16.0.0", + "@types/react": "^17.0.0", "@types/react-big-calendar": "^0.22.3", - "@types/react-dom": "^16.9.0", + "@types/react-dom": "^17.0.0", "@types/react-redux": "^7.1.9", "@types/react-router": "^5.1.8", "@types/react-router-dom": "^5.1.5", @@ -60,12 +60,16 @@ "@types/redux-logger": "^3.0.8", "@types/urijs": "^1.15.38", "@types/uuid": "^3.4.3", - "typescript": "~3.9.7" + "typescript": "~4.9.5" }, "browserslist": [ ">0.2%", "not dead", "not ie <= 11", "not op_mini all" - ] + ], + "resolutions": { + "@types/react": "^17.0.0", + "@types/react-dom": "^17.0.0" + } } diff --git a/src/Collections/Collection.tsx b/src/Collections/Collection.tsx index 1782e253..480a8364 100644 --- a/src/Collections/Collection.tsx +++ b/src/Collections/Collection.tsx @@ -52,14 +52,14 @@ class Collection extends React.Component { {isAdmin && <> diff --git a/src/Collections/CollectionEdit.tsx b/src/Collections/CollectionEdit.tsx index 8983b1e0..abe15d5a 100644 --- a/src/Collections/CollectionEdit.tsx +++ b/src/Collections/CollectionEdit.tsx @@ -126,7 +126,7 @@ export default function CollectionEdit(props: PropsType) { }, }; - const colTypes = { + const colTypes: Record = { "etebase.vcard": "Address Book", "etebase.vevent": "Calendar", "etebase.vtodo": "Task List", diff --git a/src/Collections/CollectionImport.tsx b/src/Collections/CollectionImport.tsx index 683db69f..1f405291 100644 --- a/src/Collections/CollectionImport.tsx +++ b/src/Collections/CollectionImport.tsx @@ -23,7 +23,7 @@ interface PropsType { export default function CollectionImport(props: PropsType) { const [selectedCollection, setSelectedCollection] = React.useState(); - const collectionMap = { + const collectionMap: Record = { "etebase.vcard": [], "etebase.vevent": [], "etebase.vtodo": [], diff --git a/src/Collections/CollectionList.tsx b/src/Collections/CollectionList.tsx index 28c54271..cf7829b7 100644 --- a/src/Collections/CollectionList.tsx +++ b/src/Collections/CollectionList.tsx @@ -27,7 +27,7 @@ interface PropsType { export default function CollectionList(props: PropsType) { const history = useHistory(); - const collectionMap = { + const collectionMap: Record = { "etebase.vcard": [], "etebase.vevent": [], "etebase.vtodo": [], @@ -58,7 +58,7 @@ export default function CollectionList(props: PropsType) { diff --git a/src/Contacts/ContactEdit.tsx b/src/Contacts/ContactEdit.tsx index 3983bc85..50c89acb 100644 --- a/src/Contacts/ContactEdit.tsx +++ b/src/Contacts/ContactEdit.tsx @@ -147,9 +147,9 @@ class ContactEdit extends React.PureComponent { collectionUid: string; showDeleteDialog: boolean; - collectionGroups: {}; + collectionGroups: Record; newGroups: string[]; - originalGroups: {}; + originalGroups: Record; }; constructor(props: PropsType) { @@ -264,7 +264,7 @@ class ContactEdit extends React.PureComponent { public addValueType(name: string, _type?: string) { const type = _type ? _type : "home"; this.setState((prevState) => { - const newArray = prevState[name].slice(0); + const newArray = (prevState as any)[name].slice(0); newArray.push(new ValueType(type)); return { ...prevState, @@ -275,7 +275,7 @@ class ContactEdit extends React.PureComponent { public removeValueType(name: string, idx: number) { this.setState((prevState) => { - const newArray = prevState[name].slice(0); + const newArray = (prevState as any)[name].slice(0); newArray.splice(idx, 1); return { ...prevState, @@ -286,7 +286,7 @@ class ContactEdit extends React.PureComponent { public handleValueTypeChange(name: string, idx: number, value: ValueType) { this.setState((prevState) => { - const newArray = prevState[name].slice(0); + const newArray = (prevState as any)[name].slice(0); newArray[idx] = value; return { ...prevState, @@ -302,7 +302,7 @@ class ContactEdit extends React.PureComponent { } public getCollectionGroups(collectionUid: string) { - const groups = {}; + const groups: Record = {}; this.props.allGroups.forEach((group) => { if (collectionUid === group.collectionUid) { groups[group.fn] = group; diff --git a/src/Contacts/GroupEdit.tsx b/src/Contacts/GroupEdit.tsx index fec34e9b..954474cd 100644 --- a/src/Contacts/GroupEdit.tsx +++ b/src/Contacts/GroupEdit.tsx @@ -97,7 +97,7 @@ class GroupEdit extends React.PureComponent { public addValueType(name: string, _type?: string) { const type = _type ? _type : "home"; this.setState((prevState) => { - const newArray = prevState[name].slice(0); + const newArray = (prevState as any)[name].slice(0); newArray.push(new ValueType(type)); return { ...prevState, @@ -108,7 +108,7 @@ class GroupEdit extends React.PureComponent { public removeValueType(name: string, idx: number) { this.setState((prevState) => { - const newArray = prevState[name].slice(0); + const newArray = (prevState as any)[name].slice(0); newArray.splice(idx, 1); return { ...prevState, @@ -119,7 +119,7 @@ class GroupEdit extends React.PureComponent { public handleValueTypeChange(name: string, idx: number, value: ValueType) { this.setState((prevState) => { - const newArray = prevState[name].slice(0); + const newArray = (prevState as any)[name].slice(0); newArray[idx] = value; return { ...prevState, @@ -135,7 +135,7 @@ class GroupEdit extends React.PureComponent { } public getCollectionGroups(collectionUid: string) { - const groups = {}; + const groups: Record = {}; this.props.allGroups.forEach((group) => { if (collectionUid === group.collectionUid) { groups[group.fn] = null; diff --git a/src/Contacts/SearchableAddressBook.tsx b/src/Contacts/SearchableAddressBook.tsx index 9fb894bb..e4ca0abf 100644 --- a/src/Contacts/SearchableAddressBook.tsx +++ b/src/Contacts/SearchableAddressBook.tsx @@ -15,7 +15,7 @@ import AddressBook from "./AddressBook"; const useStyles = makeStyles((theme) => ({ topBar: { - backgroundColor: theme.palette.primary[500], + backgroundColor: theme.palette.primary.main, }, })); diff --git a/src/Contacts/Toolbar.tsx b/src/Contacts/Toolbar.tsx index dab0db45..0b131a5b 100644 --- a/src/Contacts/Toolbar.tsx +++ b/src/Contacts/Toolbar.tsx @@ -8,7 +8,7 @@ import InputAdornment from "@material-ui/core/InputAdornment"; const transitionTimeout = 300; -const transitionStyles = { +const transitionStyles: Record = { entering: { visibility: "visible", width: "100%", overflow: "hidden" }, entered: { visibility: "visible", width: "100%" }, exiting: { visibility: "visible", width: "0%", overflow: "hidden" }, diff --git a/src/Debug.tsx b/src/Debug.tsx index 6a3c0e82..6ae1b212 100644 --- a/src/Debug.tsx +++ b/src/Debug.tsx @@ -63,7 +63,7 @@ export default function Debug() { const col = colMgr.cacheLoad(cachedCollection); const itemMgr = colMgr.getItemManager(col); - const wantedEntries = {}; + const wantedEntries: Record = {}; const wantAll = (itemsUids.trim() === "all"); itemsUids.split("\n").forEach((ent) => wantedEntries[ent.trim()] = true); diff --git a/src/Tasks/TaskList.tsx b/src/Tasks/TaskList.tsx index b2fa1e3b..7af3e73a 100644 --- a/src/Tasks/TaskList.tsx +++ b/src/Tasks/TaskList.tsx @@ -94,7 +94,7 @@ function getSortFunction(sortOrder: string) { const useStyles = makeStyles((theme) => ({ topBar: { - backgroundColor: theme.palette.primary[500], + backgroundColor: theme.palette.primary.main, }, })); diff --git a/src/Tasks/Toolbar.tsx b/src/Tasks/Toolbar.tsx index 2fd877b1..3d8bc228 100644 --- a/src/Tasks/Toolbar.tsx +++ b/src/Tasks/Toolbar.tsx @@ -24,7 +24,7 @@ import { CachedCollection } from "../Pim/helpers"; const transitionTimeout = 300; -const transitionStyles = { +const transitionStyles: Record = { entering: { visibility: "visible", width: "100%", overflow: "hidden" }, entered: { visibility: "visible", width: "100%" }, exiting: { visibility: "visible", width: "0%", overflow: "hidden" }, diff --git a/src/helpers.tsx b/src/helpers.tsx index d77d7894..49ac6b65 100644 --- a/src/helpers.tsx +++ b/src/helpers.tsx @@ -29,7 +29,7 @@ export function handleInputChange(self: React.Component, part?: string) { } else { self.setState({ [part]: { - ...self.state[part], + ...(self.state as Record)[part], ...newState, }, }); @@ -37,7 +37,7 @@ export function handleInputChange(self: React.Component, part?: string) { }; } -export function insertSorted(array: T[] = [], newItem: T, key: string) { +export function insertSorted>(array: T[] = [], newItem: T, key: string) { if (array.length === 0) { return [newItem]; } diff --git a/src/persist-state-history.tsx b/src/persist-state-history.tsx index bc7ddbcd..3088fbd6 100644 --- a/src/persist-state-history.tsx +++ b/src/persist-state-history.tsx @@ -5,7 +5,7 @@ import * as React from "react"; import { withRouter } from "react-router"; // FIXME: Should probably tie this to the history object, or at least based on the depth of the history -const stateCache = {}; +const stateCache: Record = {}; type Constructor = new(...args: any[]) => T; diff --git a/src/pim-types.ts b/src/pim-types.ts index fbc16876..cee3e7bf 100644 --- a/src/pim-types.ts +++ b/src/pim-types.ts @@ -22,9 +22,9 @@ export function timezoneLoadFromName(timezone: string | null) { return null; } - let zone = zones.zones[timezone]; - if (!zone && zones.aliases[timezone]) { - zone = zones.zones[zones.aliases[timezone]]; + let zone = (zones.zones as Record)[timezone]; + if (!zone && (zones.aliases as Record)[timezone]) { + zone = (zones.zones as Record)[(zones.aliases as Record)[timezone].aliasTo]; } if (!zone) { @@ -185,6 +185,13 @@ export class TaskType extends EventType { constructor(comp?: ICAL.Component | null) { super(comp ? comp : new ICAL.Component("vtodo")); + // Override endDate — not applicable for tasks + Object.defineProperty(this, "endDate", { + get() { + return undefined as any; + }, + configurable: true, + }); } get finished() { @@ -264,11 +271,6 @@ export class TaskType extends EventType { return this.component.getFirstPropertyValue("related-to"); } - get endDate() { - // XXX: A hack to override this as it shouldn't be used - return undefined as any; - } - get allDay() { return !!((this.startDate?.isDate) || (this.dueDate?.isDate)); } diff --git a/src/routes.ts b/src/routes.ts index eab05973..4440d7ce 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -6,9 +6,9 @@ export interface RouteKeysType { } export class RouteResolver { - public routes: {}; + public routes: Record; - constructor(routes: {}) { + constructor(routes: Record) { this.routes = routes; } diff --git a/src/store/reducers.ts b/src/store/reducers.ts index 11b31741..3f5444f0 100644 --- a/src/store/reducers.ts +++ b/src/store/reducers.ts @@ -179,7 +179,7 @@ for (const func in actions) { func.startsWith("update") || func.startsWith("delete")) { - fetchActions.push(actions[func]); + fetchActions.push((actions as Record)[func]); } } diff --git a/src/widgets/ExternalLink.tsx b/src/widgets/ExternalLink.tsx index 4f1e8182..56089424 100644 --- a/src/widgets/ExternalLink.tsx +++ b/src/widgets/ExternalLink.tsx @@ -3,10 +3,10 @@ import * as React from "react"; -export const ExternalLink = React.memo(({ children, ...props }: any) => ( - +export const ExternalLink = React.memo(React.forwardRef(({ children, ...props }, ref) => ( + {children} -)); +))); export default ExternalLink; diff --git a/src/widgets/RRule.tsx b/src/widgets/RRule.tsx index e7b8ddda..46e2d4bb 100644 --- a/src/widgets/RRule.tsx +++ b/src/widgets/RRule.tsx @@ -124,9 +124,9 @@ export default function RRule(props: PropsType) { } for (const key of Object.keys(updatedOptions)) { - const value = updatedOptions[key]; + const value = (updatedOptions as Record)[key]; if ((value === undefined) || (value?.length === 0)) { - delete updatedOptions[key]; + delete (updatedOptions as Record)[key]; continue; } } @@ -206,7 +206,7 @@ export default function RRule(props: PropsType) { } {options.bysetpos && -