diff --git a/app/bun.lockb b/app/bun.lockb index 385d7782..d66236d7 100755 Binary files a/app/bun.lockb and b/app/bun.lockb differ diff --git a/app/eslint.config.ts b/app/eslint.config.ts new file mode 100644 index 00000000..4c4d1803 --- /dev/null +++ b/app/eslint.config.ts @@ -0,0 +1,72 @@ +import type { Linter } from 'eslint'; +import react from 'eslint-plugin-react'; +import globals from 'globals'; +import eslintJS from '@eslint/js'; +import eslintTS from 'typescript-eslint'; + +const config: Linter.Config[] = [ + ...eslintTS.configs.recommended as Linter.Config[], + { + ...eslintJS.configs.recommended, + files: ['**/*.{ts,mts,tsx}'], + ignores: ['*.js'], + plugins: { + react + }, + settings: { + react: { + ...react.configs.recommended.settings, + version: 'detect' + } + }, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true + } + }, + globals: { + ...globals.browser, + } + }, + rules: { + ...react.configs.recommended.rules, + 'no-restricted-globals': ['error'], + 'no-console': ['error', { + allow: ['warn', 'error'] + }], + '@typescript-eslint/no-explicit-any': 'warn', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-unused-expressions': 'off', + '@typescript-eslint/no-empty-object-type': 'off', + 'react/prop-types': 'off', + 'react/no-unescaped-entities': 'off', + 'no-script-url': 'error', + 'no-eval': 'error', + 'quotes': [ + 'error', + 'single', + { allowTemplateLiterals: true } + ], + 'jsx-quotes': [ + 'error', + 'prefer-double', + ], + '@typescript-eslint/prefer-ts-expect-error': 'error', + 'object-curly-spacing': [ + 'error', + 'always' + ], + 'array-bracket-spacing': [ + 'error', + 'never' + ], + 'eqeqeq': [ + 'error', + 'smart', + ], + 'no-multi-spaces': 'error' + } + } +]; +export default config; diff --git a/app/package.json b/app/package.json index 21c1a6e5..5218786d 100644 --- a/app/package.json +++ b/app/package.json @@ -1,6 +1,6 @@ { "name": "micropad", - "version": "4.5.1", + "version": "4.5.2", "private": true, "scripts": { "preinstall": "python3 ../libs/build-libs.py && ./get_precache_files.py > src/extraPrecacheFiles.ts", @@ -11,24 +11,27 @@ "test": "TZ=NZ jest", "typecheck": "tsc --noEmit --p ./tsconfig.json", "typecheck:watch": "tsc --noEmit --watch -p ./tsconfig.json", - "lint": "eslint src/" + "lint": "eslint --flag unstable_ts_config src/", + "lint:fix": "eslint --flag unstable_ts_config --fix src/" }, "engines": { "node": ">=17.1.0" }, "dependencies": { + "@eslint/js": "^9.9.0", "@fontsource/abeezee": "^4.5.10", - "@monaco-editor/react": "^4.5.1", + "@monaco-editor/react": "^4.6.0", "@nick_webster/photon": "^0.3.1", - "@redux-devtools/extension": "^3.2.5", - "@reduxjs/toolkit": "^1.9.5", - "@sentry/integrations": "^7.63.0", - "@sentry/react": "^7.63.0", - "@sentry/tracing": "^7.63.0", - "browserslist-useragent-regexp": "^4.1.0", + "@redux-devtools/extension": "^3.3.0", + "@reduxjs/toolkit": "^1.9.7", + "@sentry/integrations": "^7.114.0", + "@sentry/react": "^7.118.0", + "@sentry/tracing": "^7.114.0", + "@types/eslint": "^9.6.0", + "browserslist-useragent-regexp": "^4.1.3", "date-fns": "^2.30.0", "deep-freeze": "~0.0.1", - "fend-wasm-web": "^1.2.0", + "fend-wasm-web": "^1.5.1", "json-stringify-safe": "^5.0.1", "jszip": "^3.10.1", "localforage": "^1.10.0", @@ -37,31 +40,31 @@ "monaco-editor": "~0.37.1", "mousetrap": "^1.6.5", "opus-recorder": "^4.1.4", - "pdfobject": "^2.2.12", - "re-resizable": "^6.9.11", + "pdfobject": "^2.3.0", + "re-resizable": "^6.9.17", "react": "^17.0.2", "react-dom": "^17.0.2", - "react-draggable": "^4.4.5", + "react-draggable": "^4.4.6", "react-materialize": "^3.10.0", - "react-redux": "^8.1.2", - "react-select": "^5.7.4", + "react-redux": "^8.1.3", + "react-select": "^5.8.0", "react-treeview": "~0.4.7", "redux": "^4.2.1", "redux-observable": "^2.0.0", "rxjs": "^7.8.1", "save-as": "^0.1.8", - "semver": "^7.5.4", + "semver": "^7.6.3", "showdown": "^1.9.1", "typescript": "~5.0.4", "typescript-fsa": "^3.0.0", "upad-parse": "^7.5.2", "vex-dialog": "^1.1.0", "vex-js": "^4.1.0", - "workbox-core": "^6.6.0", - "workbox-expiration": "^6.6.0", - "workbox-precaching": "^6.6.0", - "workbox-routing": "^6.6.0", - "workbox-strategies": "^6.6.0" + "workbox-core": "^6.6.1", + "workbox-expiration": "^6.6.1", + "workbox-precaching": "^6.6.1", + "workbox-routing": "^6.6.1", + "workbox-strategies": "^6.6.1" }, "eslintConfig": { "extends": [ @@ -138,38 +141,30 @@ ] }, "devDependencies": { - "@babel/core": "^7.22.10", - "@babel/plugin-syntax-flow": "^7.22.5", - "@babel/plugin-transform-react-jsx": "^7.22.5", - "@sentry/esbuild-plugin": "^2.6.2", - "@types/deep-freeze": "^0.1.2", - "@types/html-minifier": "^4.0.2", + "@babel/core": "^7.25.2", + "@babel/plugin-syntax-flow": "^7.24.7", + "@babel/plugin-transform-react-jsx": "^7.25.2", + "@eslint/eslintrc": "^3.1.0", + "@sentry/esbuild-plugin": "^2.22.0", + "@types/deep-freeze": "^0.1.5", + "@types/eslint__js": "^8.42.3", + "@types/html-minifier": "^4.0.5", "@types/jest": "^27.5.2", "@types/jest-image-snapshot": "^4.3.2", - "@types/json-stringify-safe": "^5.0.0", - "@types/mousetrap": "^1.6.11", - "@types/node": "^18.17.5", - "@types/react": "^17.0.62", - "@types/react-dom": "^17.0.20", - "@types/semver": "^7.5.0", + "@types/json-stringify-safe": "^5.0.3", + "@types/mousetrap": "^1.6.15", + "@types/node": "^18.19.44", + "@types/react": "^17.0.80", + "@types/react-dom": "^17.0.25", + "@types/semver": "^7.5.8", "@types/showdown": "^1.9.4", - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.62.0", "babel-eslint": "^10.1.0", - "browserslist": "^4.21.10", + "browserslist": "^4.23.3", "esbuild": "~0.16.17", "esbuild-plugin-browserslist": "~0.6.0", "esbuild-plugin-manifest": "~0.5.0", - "eslint": "^8.47.0", - "@eslint/eslintrc": "^2.1.2", - "eslint-config-react-app": "^7.0.1", - "eslint-plugin-flowtype": "^8.0.3", - "eslint-plugin-import": "^2.28.0", - "eslint-plugin-jest": "^26.9.0", - "eslint-plugin-jest-playwright": "~0.9.0", - "eslint-plugin-jsx-a11y": "^6.7.1", - "eslint-plugin-react": "^7.33.1", - "eslint-plugin-react-hooks": "^4.6.0", + "eslint": "^9.9.0", + "eslint-plugin-react": "^7.35.0", "expect-playwright": "^0.8.0", "html-minifier": "^4.0.0", "jest": "^27.5.1", @@ -179,16 +174,22 @@ "jest-junit": "^13.2.0", "jest-playwright-preset": "^1.7.2", "jest-runner": "^27.5.1", - "playwright": "^1.37.0", + "jiti": "^1.21.6", + "playwright": "^1.46.0", "servor": "^4.0.2", "ts-jest": "^27.1.5", - "workbox-build": "^6.6.0" + "typescript-eslint": "^8.1.0", + "workbox-build": "^6.6.1" }, "resolutions": { "@types/react": "^17", "react": "^17", "redux": "^4", - "rxjs": "^7.5.7" + "rxjs": "^7.5.7", + "internal-slot": "^1.0.5" }, - "packageManager": "yarn@3.5.0" + "packageManager": "yarn@1.22.22", + "trustedDependencies": [ + "@sentry/cli" + ] } diff --git a/app/src/app/actions.ts b/app/src/app/actions.ts index c9975c7e..a70dbe86 100644 --- a/app/src/app/actions.ts +++ b/app/src/app/actions.ts @@ -52,24 +52,24 @@ type ActionTypes = { const actionCreator = actionCreatorFactory(); export const actions = { - parseNpx: actionCreator.async('PARSE_NPX'), - saveNotepad: actionCreator.async('SAVE_NOTEPAD'), - getNotepadList: actionCreator.async('GET_NOTEPAD_LIST'), - downloadNotepad: actionCreator.async('DOWNLOAD_NOTEPAD'), - openNotepadFromStorage: actionCreator.async('OPEN_NOTEPAD_FROM_STORAGE'), - renameNotepad: actionCreator.async('RENAME_NOTEPAD'), - checkNoteAssets: actionCreator.async<[string, NoteElement[]], FlatNotepad, any>('CHECK_NOTE_ASSETS'), - loadNote: actionCreator.async('LOAD_NOTE'), - expandAllExplorer: actionCreator.async('EXPAND_ALL_EXPLORER'), + parseNpx: actionCreator.async('PARSE_NPX'), + saveNotepad: actionCreator.async('SAVE_NOTEPAD'), + getNotepadList: actionCreator.async('GET_NOTEPAD_LIST'), + downloadNotepad: actionCreator.async('DOWNLOAD_NOTEPAD'), + openNotepadFromStorage: actionCreator.async('OPEN_NOTEPAD_FROM_STORAGE'), + renameNotepad: actionCreator.async('RENAME_NOTEPAD'), + checkNoteAssets: actionCreator.async<[string, NoteElement[]], FlatNotepad, void>('CHECK_NOTE_ASSETS'), + loadNote: actionCreator.async('LOAD_NOTE'), + expandAllExplorer: actionCreator.async('EXPAND_ALL_EXPLORER'), print: actionCreator.async('PRINT'), - syncLogin: actionCreator.async('SYNC_LOGIN'), - getSyncedNotepadList: actionCreator.async('SYNC_GET_NOTEPAD_LIST'), - syncDownload: actionCreator.async('SYNC_DOWNLOAD'), + syncLogin: actionCreator.async('SYNC_LOGIN'), + getSyncedNotepadList: actionCreator.async('SYNC_GET_NOTEPAD_LIST'), + syncDownload: actionCreator.async('SYNC_DOWNLOAD'), syncUpload: actionCreator.async('SYNC_UPLOAD'), - deleteFromSync: actionCreator.async('SYNC_DELETE'), - addToSync: actionCreator.async('SYNC_CREATE'), + deleteFromSync: actionCreator.async('SYNC_DELETE'), + addToSync: actionCreator.async('SYNC_CREATE'), quickNote: actionCreator.async('QUICK_NOTE'), - indexNotepads: actionCreator.async('INDEX_NOTEPADS'), + indexNotepads: actionCreator.async('INDEX_NOTEPADS'), exportAll: actionCreator.async('EXPORT_ALL_NOTEPADS'), exportToMarkdown: actionCreator.async('EXPORT_ALL_NOTEPADS_TO_MD'), clearOldData: actionCreator.async<{ silent: boolean }, void, Error>('CLEAR_OLD_DATA'), diff --git a/app/src/app/components/NavItem.tsx b/app/src/app/components/NavItem.tsx index 9bb30c6c..8d09cf4b 100644 --- a/app/src/app/components/NavItem.tsx +++ b/app/src/app/components/NavItem.tsx @@ -14,5 +14,6 @@ const NavItem2 = React.memo((props: NavItemProps) => { // @ts-expect-error Adding an extra property that's not on the type (onTouchEnd). The real JS uses it. return ; }); +NavItem2.displayName = 'NavItem2'; export default NavItem2; diff --git a/app/src/app/components/explorer/NotepadExplorerComponent.tsx b/app/src/app/components/explorer/NotepadExplorerComponent.tsx index c10061d0..19292987 100644 --- a/app/src/app/components/explorer/NotepadExplorerComponent.tsx +++ b/app/src/app/components/explorer/NotepadExplorerComponent.tsx @@ -14,9 +14,9 @@ import AppSettingsComponent from './app-settings/AppSettingsContainer'; // @ts-expect-error TS2307 import NewSectionVideo from '../../assets/instructions/new-section.mp4'; -// @ts-expect-error +// @ts-expect-error TS2307 import OpenNoteVideo from '../../assets/instructions/open-note.mp4'; -// @ts-expect-error +// @ts-expect-error TS2307 import OpenNotepadVideo from '../../assets/instructions/open-notepad.mp4'; import { notepadExplorerConnector } from './NotepadExplorerContainer'; import { ConnectedProps } from 'react-redux'; diff --git a/app/src/app/components/explorer/app-settings/AppSettingsComponent.tsx b/app/src/app/components/explorer/app-settings/AppSettingsComponent.tsx index 8404acad..5ddc5d83 100644 --- a/app/src/app/components/explorer/app-settings/AppSettingsComponent.tsx +++ b/app/src/app/components/explorer/app-settings/AppSettingsComponent.tsx @@ -11,7 +11,7 @@ const AppSettingsComponent = (props: ConnectedProps
  • -
  • props.feelingLucky()}>I'm feeling lucky
  • +
  • props.feelingLucky()}>I'm feeling lucky
  • {props.cryptoStatus.hasEncryptedNotebooks &&
  • props.clearOldData()}>Clear old/unused data
  • } {props.cryptoStatus.hasSavedPasswords &&
  • props.forgetSavedPasswords()}>Forget saved passwords
  • }
diff --git a/app/src/app/components/header/notepad-dropdown/NotepadDropdownComponent.tsx b/app/src/app/components/header/notepad-dropdown/NotepadDropdownComponent.tsx index 115d185f..470c4224 100644 --- a/app/src/app/components/header/notepad-dropdown/NotepadDropdownComponent.tsx +++ b/app/src/app/components/header/notepad-dropdown/NotepadDropdownComponent.tsx @@ -25,7 +25,7 @@ const NotepadDropdownComponent = React.memo((props: Props) => { const { notepadTitles, syncState, downloadNotepad } = props; const openNotepad = (event) => { - let title = event.currentTarget.textContent; + const title = event.currentTarget.textContent; props.openNotepadFromStorage(title); }; @@ -34,8 +34,8 @@ const NotepadDropdownComponent = React.memo((props: Props) => { if (!title) return; let notepad = new FlatNotepad(title); - let section = FlatNotepad.makeFlatSection('Unorganised Notes'); - let note = new Note('Untitled Note').clone({ parent: section.internalRef }); + const section = FlatNotepad.makeFlatSection('Unorganised Notes'); + const note = new Note('Untitled Note').clone({ parent: section.internalRef }); notepad = notepad.addSection(section).addNote(note); if (title) props.newNotepad!(notepad); @@ -78,5 +78,6 @@ const NotepadDropdownComponent = React.memo((props: Props) => { ); }); +NotepadDropdownComponent.displayName = 'NotepadDropdownComponent'; export default NotepadDropdownComponent; diff --git a/app/src/app/components/header/upload-notepads/UploadNotepadsComponent.tsx b/app/src/app/components/header/upload-notepads/UploadNotepadsComponent.tsx index 8e347994..f9bc6e6c 100644 --- a/app/src/app/components/header/upload-notepads/UploadNotepadsComponent.tsx +++ b/app/src/app/components/header/upload-notepads/UploadNotepadsComponent.tsx @@ -45,7 +45,7 @@ export default class UploadNotepadsComponent extends React.Component { - for (let name in zip.files) { + for (const name in zip.files) { if (name.split('.').pop()!.toLowerCase() !== 'npx') continue; zip.file(name)!.async('text') diff --git a/app/src/app/components/note-element-modal/NoteElementModalComponent.tsx b/app/src/app/components/note-element-modal/NoteElementModalComponent.tsx index 45172546..ca5b86c1 100644 --- a/app/src/app/components/note-element-modal/NoteElementModalComponent.tsx +++ b/app/src/app/components/note-element-modal/NoteElementModalComponent.tsx @@ -61,7 +61,7 @@ async function renderNote({ npx, findNote }: Props): Promise { regex: /(===[^]+?===|''[^]+?''|;;[^]+?;;|\$\$[^]+?\$\$|\$[^]+?\$)/gi, replace: function(s: string, match: string) { matches.push('<Maths won\'t display in this view. See the help notepad.>
' + match); - let n = matches.length - 1; + const n = matches.length - 1; return '%ph' + n + 'ph%'; } }, @@ -69,7 +69,7 @@ async function renderNote({ npx, findNote }: Props): Promise { type: 'output', filter: function(text: string) { for (let i = 0; i < matches.length; ++i) { - let pat = '%ph' + i + 'ph%'; + const pat = '%ph' + i + 'ph%'; text = text.replace(new RegExp(pat, 'gi'), matches[i]); } // reset array diff --git a/app/src/app/components/note-viewer/NoteViewerComponent.tsx b/app/src/app/components/note-viewer/NoteViewerComponent.tsx index fabc0214..59b87eb6 100644 --- a/app/src/app/components/note-viewer/NoteViewerComponent.tsx +++ b/app/src/app/components/note-viewer/NoteViewerComponent.tsx @@ -91,7 +91,7 @@ export default class NoteViewerComponent extends React.Component { const { element, edit } = this.props; - let path = event.path || (event.composedPath && event.composedPath()) || [event.target]; + const path = event.path || (event.composedPath && event.composedPath()) || [event.target]; if (path[0].tagName.toLowerCase() === 'button') return; edit(element.args.id); diff --git a/app/src/app/components/note-viewer/elements/ImageElementComponent.tsx b/app/src/app/components/note-viewer/elements/ImageElementComponent.tsx index 82c4f9ae..c595bfde 100644 --- a/app/src/app/components/note-viewer/elements/ImageElementComponent.tsx +++ b/app/src/app/components/note-viewer/elements/ImageElementComponent.tsx @@ -85,7 +85,7 @@ export default class ImageElementComponent extends React.Component { const { element, edit } = this.props; - let path = event.path || (event.composedPath && event.composedPath()) || [event.target]; + const path = event.path || (event.composedPath && event.composedPath()) || [event.target]; if (path[0].tagName.toLowerCase() === 'button') return; edit(element.args.id); diff --git a/app/src/app/components/note-viewer/elements/NoteElementComponent.tsx b/app/src/app/components/note-viewer/elements/NoteElementComponent.tsx index c098d774..08b52f3b 100644 --- a/app/src/app/components/note-viewer/elements/NoteElementComponent.tsx +++ b/app/src/app/components/note-viewer/elements/NoteElementComponent.tsx @@ -46,7 +46,7 @@ export default class NoteElementComponent extends React.Component { const { element, edit } = this.props; - let path = event.path || (event.composedPath && event.composedPath()) || [event.target]; + const path = event.path || (event.composedPath && event.composedPath()) || [event.target]; if (path[0].tagName.toLowerCase() === 'button') return; edit(element.args.id); diff --git a/app/src/app/components/note-viewer/elements/drawing/DrawingElementComponent.tsx b/app/src/app/components/note-viewer/elements/drawing/DrawingElementComponent.tsx index 9298964c..58536d90 100644 --- a/app/src/app/components/note-viewer/elements/drawing/DrawingElementComponent.tsx +++ b/app/src/app/components/note-viewer/elements/drawing/DrawingElementComponent.tsx @@ -1,5 +1,5 @@ import './DrawingElementComponent.css'; -import React from 'react'; +import React, { Ref, RefCallback } from 'react'; import { INoteElementComponentProps } from '../NoteElementComponent'; import { trim } from './trim-canvas'; import { Resizable } from 're-resizable'; @@ -82,8 +82,8 @@ export default class DrawingElementComponent extends React.Component { }} > this.canvasElement = e?.inner ?? null} + // @ts-expect-error TypeScript gets confused with all the different ways to do a Ref in react. + ref={((e: DrawingCanvas | undefined) => this.canvasElement = e?.inner ?? null)} key={`drawing-canvas-${this.props.element.args.id}`} className="drawing-element__view" width="500" @@ -161,7 +161,7 @@ export default class DrawingElementComponent extends React.Component { const isNewEditor = this.props.elementEditing !== prevProps?.elementEditing; if (isNewEditor) { // Restore saved image to canvas - let img: HTMLImageElement = new Image(); + const img: HTMLImageElement = new Image(); img.onload = () => { const canvasElement = this.canvasElement; if (!canvasElement) return; diff --git a/app/src/app/components/note-viewer/elements/drawing/trim-canvas.ts b/app/src/app/components/note-viewer/elements/drawing/trim-canvas.ts index 905df2cb..315cb6e5 100644 --- a/app/src/app/components/note-viewer/elements/drawing/trim-canvas.ts +++ b/app/src/app/components/note-viewer/elements/drawing/trim-canvas.ts @@ -1,18 +1,26 @@ // MIT http://rem.mit-license.org +type Bound = { + top: number | null, + left: number | null, + right: number | null, + bottom: number | null, +} + export function trim(c) { - let ctx = c.getContext('2d'), - copy = document.createElement('canvas').getContext('2d')!, - pixels = ctx.getImageData(0, 0, c.width, c.height), - l = pixels.data.length, - i, - bound = { - top: null, - left: null, - right: null, - bottom: null - }, - x, y; + const ctx = c.getContext('2d'); + const copy = document.createElement('canvas').getContext('2d')!; + const pixels = ctx.getImageData(0, 0, c.width, c.height); + const l = pixels.data.length; + const bound: Bound = { + top: null, + left: null, + right: null, + bottom: null + }; + let i: number; + let x: number; + let y: number; for (i = 0; i < l; i += 4) { if (pixels.data[i + 3] !== 0) { diff --git a/app/src/app/components/note-viewer/elements/markdown/MarkdownElementComponent.tsx b/app/src/app/components/note-viewer/elements/markdown/MarkdownElementComponent.tsx index 9aeb81f3..ba356c88 100644 --- a/app/src/app/components/note-viewer/elements/markdown/MarkdownElementComponent.tsx +++ b/app/src/app/components/note-viewer/elements/markdown/MarkdownElementComponent.tsx @@ -1,5 +1,4 @@ -// @ts-expect-error -// eslint-disable-next-line import/no-webpack-loader-syntax +// @ts-expect-error TS2307 import helpNpx from '../../../../assets/Help.npx'; import './MarkdownElementComponent.css'; @@ -43,7 +42,7 @@ type Props = ConnectedProps & IMarkdownElementC const converter = configureShowdown(); -// eslint-disable-next-line no-restricted-globals + self.MonacoEnvironment = { getWorkerUrl: (moduleId, label) => build.defs.MONACO_WORKER_PATH } diff --git a/app/src/app/components/note-viewer/elements/markdown/MarkdownTransformers.ts b/app/src/app/components/note-viewer/elements/markdown/MarkdownTransformers.ts index d5a40334..351388c3 100644 --- a/app/src/app/components/note-viewer/elements/markdown/MarkdownTransformers.ts +++ b/app/src/app/components/note-viewer/elements/markdown/MarkdownTransformers.ts @@ -77,7 +77,7 @@ export const mathsTransformer: () => MarkdownTransformer[] = () => { type: 'output', filter: text => { for (let i = 0; i < matches.length; ++i) { - let pat = '%MATHPLACEHOLDER' + i + 'ENDMATHPLACEHOLDER%'; + const pat = '%MATHPLACEHOLDER' + i + 'ENDMATHPLACEHOLDER%'; text = text.replace(new RegExp(pat, 'gi'), matches[i]); } diff --git a/app/src/app/components/note-viewer/elements/markdown/enable-tabs.ts b/app/src/app/components/note-viewer/elements/markdown/enable-tabs.ts index be477e43..275fb415 100644 --- a/app/src/app/components/note-viewer/elements/markdown/enable-tabs.ts +++ b/app/src/app/components/note-viewer/elements/markdown/enable-tabs.ts @@ -32,14 +32,14 @@ export function enableTabs(el: HTMLTextAreaElement, event): boolean | void { // Thanks to https://github.com/facebook/react/issues/10135#issuecomment-314441175 function setNativeValue(element, value) { - const valueSetter = Object.getOwnPropertyDescriptor(element, 'value')?.set!; + const valueSetter = Object.getOwnPropertyDescriptor(element, 'value')?.set; const prototype = Object.getPrototypeOf(element); - const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set!; + const prototypeValueSetter = Object.getOwnPropertyDescriptor(prototype, 'value')?.set; - if (valueSetter && valueSetter !== prototypeValueSetter) { + if (valueSetter !== prototypeValueSetter && prototypeValueSetter) { prototypeValueSetter.call(element, value); } else { - valueSetter.call(element, value); + valueSetter!.call(element, value); } } } diff --git a/app/src/app/components/singleton-modal/SingletonModalComponent.tsx b/app/src/app/components/singleton-modal/SingletonModalComponent.tsx index f452ab96..aae396ae 100644 --- a/app/src/app/components/singleton-modal/SingletonModalComponent.tsx +++ b/app/src/app/components/singleton-modal/SingletonModalComponent.tsx @@ -32,7 +32,7 @@ const SingletonModalComponent = (props: Props) => { fixedFooter={props.fixedFooter} header={props.header} trigger={props.trigger} - actions={props.actions ?? [Close]} + actions={props.actions ?? [Close]} options={props.options ?? DEFAULT_MODAL_OPTIONS}> {props.children} diff --git a/app/src/app/components/sync/LoginComponent.tsx b/app/src/app/components/sync/LoginComponent.tsx index f7ef2a75..aec385bb 100644 --- a/app/src/app/components/sync/LoginComponent.tsx +++ b/app/src/app/components/sync/LoginComponent.tsx @@ -30,7 +30,7 @@ export default class LoginComponent extends React.Component + this.login()}> Login diff --git a/app/src/app/components/sync/SyncProErrorComponent.tsx b/app/src/app/components/sync/SyncProErrorComponent.tsx index 82eb5287..439836ae 100644 --- a/app/src/app/components/sync/SyncProErrorComponent.tsx +++ b/app/src/app/components/sync/SyncProErrorComponent.tsx @@ -5,7 +5,6 @@ import SingletonModalComponent from '../singleton-modal/SingletonModalContainer' import Button2 from '../Button'; export const SyncProErrorComponent = () => ( - /* eslint-disable jsx-a11y/anchor-has-content */ Manage {SYNC_NAME}} actions={[ - Logout, - Close + Logout, + Close ]} options={DEFAULT_MODAL_OPTIONS}>
Go to Manage {SYNC_NAME}
diff --git a/app/src/app/epics/ExplorerEpics.ts b/app/src/app/epics/ExplorerEpics.ts index 4bd03f91..9cfe3654 100644 --- a/app/src/app/epics/ExplorerEpics.ts +++ b/app/src/app/epics/ExplorerEpics.ts @@ -29,7 +29,7 @@ export const autoLoadNewSection$ = (action$: Observable, state$: withLatestFrom(state$), map(([action, state]): [NewNotepadObjectAction, FlatNotepad] => [ (action as MicroPadActions['newSection']).payload, - state.notepads.notepad?.item! + state.notepads.notepad!.item! ]), filter(([insertAction, notepad]: [NewNotepadObjectAction, FlatNotepad]) => !!insertAction && !!notepad), map(([insertAction, notepad]: [NewNotepadObjectAction, FlatNotepad]) => { diff --git a/app/src/app/epics/NoteEpics.ts b/app/src/app/epics/NoteEpics.ts index 0adf6eb6..05e2b2c5 100644 --- a/app/src/app/epics/NoteEpics.ts +++ b/app/src/app/epics/NoteEpics.ts @@ -17,7 +17,7 @@ const loadNote$ = (action$: Observable, state$: EpicStore, { not ofType(actions.loadNote.started.type), tap(() => notificationService.dismissToasts()), withLatestFrom(state$), - map(([action, state]): [string, FlatNotepad] => [(action as MicroPadActions['loadNote']['started']).payload, state.notepads.notepad?.item!]), + map(([action, state]): [string, FlatNotepad] => [(action as MicroPadActions['loadNote']['started']).payload, state.notepads.notepad!.item!]), filter(([ref, notepad]: [string, FlatNotepad]) => !!ref && !!notepad), map(([ref, notepad]: [string, FlatNotepad]) => ({ ref: ref, note: notepad.notes[ref] })), mergeMap(({ ref, note }: { ref: string, note: Note | undefined }) => { @@ -44,10 +44,10 @@ const checkNoteAssets$ = (action$: Observable, state$: EpicStore from(getNoteAssets(elements)) .pipe(map((res): [string, NoteElement[], object] => [ref, res.elements, res.blobUrls])) ), - map(([ref, elements, blobUrls]: [string, NoteElement[], object]): [string, NoteElement[], object, FlatNotepad] => [ref, elements, blobUrls, state$.value.notepads.notepad?.item!]), + map(([ref, elements, blobUrls]: [string, NoteElement[], object]): [string, NoteElement[], object, FlatNotepad] => [ref, elements, blobUrls, state$.value.notepads.notepad!.item!]), filter(([_ref, _elements, _blobUrls, notepad]: [string, NoteElement[], object, FlatNotepad]) => !!notepad), mergeMap(([ref, elements, blobUrls, notepad]: [string, NoteElement[], object, FlatNotepad]) => { - let newNotepad = notepad.clone({ + const newNotepad = notepad.clone({ notes: { ...notepad.notes, [ref]: notepad.notes[ref].clone({ elements }) @@ -119,7 +119,7 @@ const reloadNote$ = (action$: Observable, state$: EpicStore) => const autoLoadNewNote$ = (action$: Observable, state$: EpicStore) => action$.pipe( ofType(actions.newNote.type), - map((action): [NewNotepadObjectAction, FlatNotepad] => [(action as MicroPadActions['newNote']).payload, state$.value.notepads.notepad?.item!]), + map((action): [NewNotepadObjectAction, FlatNotepad] => [(action as MicroPadActions['newNote']).payload, state$.value.notepads.notepad!.item!]), filter(([insertAction, notepad]: [NewNotepadObjectAction, FlatNotepad]) => !!insertAction && !!insertAction.parent && !!notepad), map(([insertAction, notepad]: [NewNotepadObjectAction, FlatNotepad]) => // Get a note with the new title that is in the expected parent @@ -286,14 +286,14 @@ function getNoteAssets(elements: NoteElement[]): Promise<{ elements: NoteElement function dataURItoBlob(dataURI: string) { // convert base64 to raw binary data held in a string // doesn't handle URLEncoded DataURIs - see SO answer #6850276 for code that does this - let byteString = atob(dataURI.split(',')[1]); + const byteString = atob(dataURI.split(',')[1]); // separate out the mime component - let mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; + const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]; // write the bytes of the string to an ArrayBuffer - let ab = new ArrayBuffer(byteString.length); - let ia = new Uint8Array(ab); + const ab = new ArrayBuffer(byteString.length); + const ia = new Uint8Array(ab); for (let i = 0; i < byteString.length; i++) { ia[i] = byteString.charCodeAt(i); } diff --git a/app/src/app/epics/NotepadEpics.ts b/app/src/app/epics/NotepadEpics.ts index b1dbd0e9..69d1b8f3 100644 --- a/app/src/app/epics/NotepadEpics.ts +++ b/app/src/app/epics/NotepadEpics.ts @@ -331,7 +331,7 @@ const renameNotepad$ = (action$: Observable, state$: EpicStore) ofType(actions.renameNotepad.started.type), filter(() => !!state$.value.notepads.notepad?.item?.title), switchMap(action => { - const oldTitle = state$.value.notepads.notepad?.item?.title!; + const oldTitle = state$.value.notepads.notepad!.item!.title!; return from(NOTEPAD_STORAGE.removeItem(oldTitle)) .pipe( @@ -447,9 +447,9 @@ const quickNotepad$ = (action$: Observable) => action$.pipe( ofType(actions.quickNotepad.type), map(() => { - let notepad = new FlatNotepad(`Untitled Notepad (${format(new Date(), 'EEEE, d LLLL yyyy pp')})`); - let section = FlatNotepad.makeFlatSection('Unorganised Notes'); - let note = new Note('Untitled Note').clone({ parent: section.internalRef }); + const notepad = new FlatNotepad(`Untitled Notepad (${format(new Date(), 'EEEE, d LLLL yyyy pp')})`); + const section = FlatNotepad.makeFlatSection('Unorganised Notes'); + const note = new Note('Untitled Note').clone({ parent: section.internalRef }); return notepad.addSection(section).addNote(note); }), diff --git a/app/src/app/epics/PrintEpics.ts b/app/src/app/epics/PrintEpics.ts index ecd4112d..a14b4bdb 100644 --- a/app/src/app/epics/PrintEpics.ts +++ b/app/src/app/epics/PrintEpics.ts @@ -14,7 +14,7 @@ export const generateMarkdownForPrint$ = (action$: Observable, s action$.pipe( ofType(actions.print.started.type), withLatestFrom(state$), - map(([,state]): [FlatNotepad, string] => [state.notepads.notepad?.item!, state.currentNote.ref]), + map(([,state]): [FlatNotepad, string] => [state.notepads.notepad!.item!, state.currentNote.ref]), filter(([notepad, noteRef]: [FlatNotepad, string]) => !!notepad && !!noteRef), map(([notepad, noteRef]: [FlatNotepad, string]) => notepad.notes[noteRef]), filterTruthy(), diff --git a/app/src/app/epics/StorageEpics.ts b/app/src/app/epics/StorageEpics.ts index 16a20c20..5357a7f5 100644 --- a/app/src/app/epics/StorageEpics.ts +++ b/app/src/app/epics/StorageEpics.ts @@ -201,7 +201,7 @@ const persistLastOpenedNote$ = (action$: Observable, state$: Epi ofType(actions.loadNote.done.type), filter(() => !!state$.value.notepads.notepad?.item), map((action): LastOpenedNotepad => ({ - notepadTitle: state$.value.notepads.notepad?.item?.title!, + notepadTitle: state$.value.notepads.notepad!.item!.title!, noteRef: (action as MicroPadActions['loadNote']['done']).payload.params })), tap(lastOpened => diff --git a/app/src/app/epics/SyncEpics.ts b/app/src/app/epics/SyncEpics.ts index 9a275a02..b2278520 100644 --- a/app/src/app/epics/SyncEpics.ts +++ b/app/src/app/epics/SyncEpics.ts @@ -204,7 +204,7 @@ export const upload$ = (action$: Observable, state$: EpicStore, const blobs: Array = await optimiseAssets( getStorage().assetStorage, orderedAssetList.map(([uuid]) => uuid), - state$.value.notepads.notepad?.item! + state$.value.notepads.notepad!.item! ); orderedAssetList .map(([, url]) => url) diff --git a/app/src/app/epics/index.ts b/app/src/app/epics/index.ts index 72bc7ed8..739668b0 100644 --- a/app/src/app/epics/index.ts +++ b/app/src/app/epics/index.ts @@ -1,5 +1,4 @@ -// @ts-expect-error -// eslint-disable-next-line import/no-webpack-loader-syntax +// @ts-expect-error TS2307 import helpNpx from '../assets/Help.npx'; import { createEpicMiddleware, StateObservable } from 'redux-observable'; diff --git a/app/src/app/reducers/AbstractReducer.ts b/app/src/app/reducers/AbstractReducer.ts index a2759cc9..d9e8a4dd 100644 --- a/app/src/app/reducers/AbstractReducer.ts +++ b/app/src/app/reducers/AbstractReducer.ts @@ -23,7 +23,12 @@ export abstract class AbstractReducer { return !!this.handlers[action.type] ? this.handlers[action.type](state, action as MicroPadAction) : state; } - protected handle(handler: ReducerHandler, ...actions: ActionCreator[]): void { + protected handle(handler: ReducerHandler, ...actions: ActionCreator[]): void { + actions.forEach(action => this.handlers[action.type] = handler); + } + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protected handleMany(handler: ReducerHandler, ...actions: ActionCreator[]): void { actions.forEach(action => this.handlers[action.type] = handler); } diff --git a/app/src/app/reducers/IsExportingReducer.ts b/app/src/app/reducers/IsExportingReducer.ts index ef364344..10285bfa 100644 --- a/app/src/app/reducers/IsExportingReducer.ts +++ b/app/src/app/reducers/IsExportingReducer.ts @@ -27,7 +27,7 @@ export class IsExportingReducer extends AbstractReducer { ); this.handle( - (state, action) => ({ isLoading: false, error: action.payload.error }), + (_state, action) => ({ isLoading: false, error: action.payload.error }), actions.exportAll.failed, actions.exportToMarkdown.failed ); diff --git a/app/src/app/reducers/NotepadsReducer.ts b/app/src/app/reducers/NotepadsReducer.ts index fdf0526a..bf86fafe 100644 --- a/app/src/app/reducers/NotepadsReducer.ts +++ b/app/src/app/reducers/NotepadsReducer.ts @@ -435,7 +435,7 @@ export class NotepadsReducer extends AbstractReducer { }; } else if (isType(action, actions.encryptNotepad)) { if (!state.notepad || !state.notepad.item) return state; - let notepad = state.notepad.item; + const notepad = state.notepad.item; return { ...state, diff --git a/app/src/app/reducers/SearchReducer.ts b/app/src/app/reducers/SearchReducer.ts index 2ddd911c..2f7a71fd 100644 --- a/app/src/app/reducers/SearchReducer.ts +++ b/app/src/app/reducers/SearchReducer.ts @@ -1,6 +1,6 @@ import { AbstractReducer } from './AbstractReducer'; import { actions } from '../actions'; -import { SearchIndices } from '../types/ActionTypes'; +import { RestoreJsonNotepadAndLoadNoteAction, SearchIndices } from '../types/ActionTypes'; export interface ISearchState { query: string; @@ -30,7 +30,7 @@ export class SearchReducer extends AbstractReducer { super(); // Reset state on notepad close/update - this.handle(() => this.initialState, actions.parseNpx.done, actions.parseNpx.failed, actions.deleteNotepad); + this.handleMany(() => this.initialState, actions.parseNpx.done, actions.parseNpx.failed, actions.deleteNotepad); // Search query/results this.handle( @@ -53,7 +53,7 @@ export class SearchReducer extends AbstractReducer { indices: action.payload.result }), actions.indexNotepads.done); - this.handle(state => ({ + this.handleMany(state => ({ ...state, query: this.initialState.query, results: this.initialState.results diff --git a/app/src/app/root.tsx b/app/src/app/root.tsx index 46d86a31..82ed29f8 100644 --- a/app/src/app/root.tsx +++ b/app/src/app/root.tsx @@ -1,6 +1,5 @@ /* Special Imports */ -// @ts-expect-error -// eslint-disable-next-line import/no-webpack-loader-syntax +// @ts-expect-error TS2307 import helpNpx from './assets/Help.npx'; /* CSS Imports */ import '@fontsource/abeezee'; @@ -33,7 +32,7 @@ import NoteViewerComponent from './containers/NoteViewerContainer'; import { enableKeyboardShortcuts } from './services/shortcuts'; import PrintViewOrAppContainerComponent from './containers/PrintViewContainer'; import NoteElementModalComponent from './components/note-element-modal/NoteElementModalComponent'; -import { SyncUser } from './types/SyncTypes'; +import { SyncLoginRequest, SyncUser } from './types/SyncTypes'; import InsertElementComponent from './containers/InsertElementContainer'; import { ThemeName } from './types/Themes'; import AppBodyComponent from './containers/AppBodyContainer'; @@ -230,7 +229,7 @@ async function hydrateStoreFromLocalforage() { if (shouldWordWrap !== null) store.dispatch(actions.toggleWordWrap(shouldWordWrap)); const syncUser: SyncUser | null = await SYNC_STORAGE.getItem('sync user'); - if (!!syncUser && !!syncUser.token && !!syncUser.username) store.dispatch(actions.syncLogin.done({ params: {} as any, result: syncUser })); + if (!!syncUser && !!syncUser.token && !!syncUser.username) store.dispatch(actions.syncLogin.done({ params: {} as SyncLoginRequest, result: syncUser })); const theme = await localforage.getItem('theme'); if (!!theme) store.dispatch(actions.selectTheme(theme)); @@ -272,7 +271,7 @@ async function displayWhatsNew() { } function notepadDownloadHandler() { - // eslint-disable-next-line no-restricted-globals + const downloadNotepadUrl = new URLSearchParams(location.search).get('download'); if (!!downloadNotepadUrl) store.dispatch(actions.downloadNotepad.started(downloadNotepadUrl)); } diff --git a/app/src/app/services/DifferenceEngine.ts b/app/src/app/services/DifferenceEngine.ts index 5deba44d..1eb83493 100644 --- a/app/src/app/services/DifferenceEngine.ts +++ b/app/src/app/services/DifferenceEngine.ts @@ -171,6 +171,5 @@ function callApi(parent: string, endpoint: string, resource: string, payload? } function shouldUseDevApi(): boolean { - // eslint-disable-next-line no-restricted-globals return !!new URLSearchParams(location.search).get('local'); } diff --git a/app/src/app/services/FendService.ts b/app/src/app/services/FendService.ts index cdf4eaf0..8af0ce28 100644 --- a/app/src/app/services/FendService.ts +++ b/app/src/app/services/FendService.ts @@ -3,7 +3,7 @@ import { ajax } from 'rxjs/ajax'; import { catchError, map } from 'rxjs/operators'; import { initialise, initialiseWithHandlers } from 'fend-wasm-web'; -export const START_FEND$: Observable = ajax({ +export const START_FEND$: Observable = ajax({ url: 'https://getmicropad.com/cors-proxy/moolah.xml', method: 'GET', timeout: 3000, diff --git a/app/src/app/services/dialogs.ts b/app/src/app/services/dialogs.ts index 19994c07..31886837 100644 --- a/app/src/app/services/dialogs.ts +++ b/app/src/app/services/dialogs.ts @@ -1,8 +1,8 @@ import 'vex-js/dist/css/vex.css'; import 'vex-js/dist/css/vex-theme-top.css'; -const Vex = require('vex-js'); -const VexDialog = require('vex-dialog'); +import Vex from 'vex-js'; +import VexDialog from 'vex-dialog'; Vex.registerPlugin(VexDialog); Vex.defaultOptions!.className = 'vex-theme-top'; @@ -75,10 +75,10 @@ export class Dialog { private static ID_COUNT: number = 0; - private static loadVex(vexFn: (opts: any) => void, opts: VexOpts): Promise { + private static loadVex(vexFn: (opts: unknown) => void, opts: VexOpts): Promise { return new Promise(resolve => { setTimeout(() => { - let modal = document.querySelector('.modal.open'); + const modal = document.querySelector('.modal.open'); if (modal) modal.style.display = 'none'; vexFn.bind(Vex.dialog)({ diff --git a/app/src/app/types/DeepReadOnly.ts b/app/src/app/types/DeepReadOnly.ts index 92ac74eb..aba639c8 100644 --- a/app/src/app/types/DeepReadOnly.ts +++ b/app/src/app/types/DeepReadOnly.ts @@ -1,5 +1,6 @@ export type DeepReadonly = T extends (infer R)[] ? DeepReadonlyArray : + // eslint-disable-next-line @typescript-eslint/no-unsafe-function-type T extends Function ? T : T extends object ? DeepReadonlyObject : T; diff --git a/app/src/app/types/materializecss.d.ts b/app/src/app/types/materializecss.d.ts index 14551077..e58b9806 100644 --- a/app/src/app/types/materializecss.d.ts +++ b/app/src/app/types/materializecss.d.ts @@ -59,7 +59,7 @@ export type AutocompleteInstance = { /** Index of the current selected option. */ activeIndex: number, /** Instance of the dropdown plugin for this autocomplete. */ - dropdown: any, + dropdown: unknown, /** Open autocomplete dropdown. */ open(): void, diff --git a/app/src/app/util.ts b/app/src/app/util.ts index 488278dd..f478c19c 100644 --- a/app/src/app/util.ts +++ b/app/src/app/util.ts @@ -17,13 +17,11 @@ export const filterTruthy = () => filter((a: T | undefined | null | false): a export const noEmit = () => filter((_a): _a is never => false); export function isDev(includeNextDev: boolean = true): boolean { - /* eslint-disable no-restricted-globals */ const params = new URLSearchParams(location.search); return ( !params.get('prod') && (location.hostname === 'localhost' || location.hostname === '127.0.0.1' || (includeNextDev && location.hostname === 'next.getmicropad.com')) ); - /* eslint-enable no-restricted-globals */ } export function isMobile(): boolean { @@ -57,7 +55,7 @@ export function getAsBase64(blob: Blob): Promise { return new Promise(resolve => { try { const reader = new FileReader(); - reader.onload = event => resolve((event.target as any).result); + reader.onload = event => resolve((event.target as { result: string }).result); reader.readAsDataURL(blob); } catch (e) { console.warn(e); @@ -70,7 +68,7 @@ export function getBytes(blob: Blob): Promise { return new Promise(resolve => { try { const reader = new FileReader(); - reader.onload = event => resolve((event.target as any).result); + reader.onload = event => resolve((event.target as { result: ArrayBuffer }).result); reader.readAsArrayBuffer(blob); } catch (e) { console.warn(e); diff --git a/app/src/index.tsx b/app/src/index.tsx index 93803fab..d38e2cb5 100644 --- a/app/src/index.tsx +++ b/app/src/index.tsx @@ -6,7 +6,6 @@ window.MicroPadGlobals = {}; // `window.isSupported` is set by the Unsupported Browser logic. if (window.isSupported) { - // eslint-disable-next-line no-restricted-globals const params = new URLSearchParams(location.search); const isInTest = params.has('integration'); diff --git a/app/src/react-app-env.d.ts b/app/src/react-app-env.d.ts index b71f1fb7..c3d10e20 100644 --- a/app/src/react-app-env.d.ts +++ b/app/src/react-app-env.d.ts @@ -1,7 +1,3 @@ -/// -/// -/// - declare namespace NodeJS { interface ProcessEnv { readonly NODE_ENV: 'development' | 'production' | 'test'; diff --git a/app/src/sentry.ts b/app/src/sentry.ts index fc324892..e04a6dce 100644 --- a/app/src/sentry.ts +++ b/app/src/sentry.ts @@ -44,7 +44,5 @@ export function createSentryReduxEnhancer() { function getDevEnv(): SentryEnv { if (!isDev()) return SentryEnv.PROD; - /* eslint-disable no-restricted-globals */ return location.hostname === 'next.getmicropad.com' ? SentryEnv.NEXT_DEV : SentryEnv.LOCAL; - /* eslint-enable no-restricted-globals */ } diff --git a/app/src/service-worker.ts b/app/src/service-worker.ts index 8e67b43a..46461764 100644 --- a/app/src/service-worker.ts +++ b/app/src/service-worker.ts @@ -1,5 +1,4 @@ /// -/* eslint-disable no-restricted-globals */ // This service worker can be customized! // See https://developers.google.com/web/tools/workbox/modules diff --git a/app/tsconfig.json b/app/tsconfig.json index 09cdde0f..312a8abb 100644 --- a/app/tsconfig.json +++ b/app/tsconfig.json @@ -27,6 +27,7 @@ "incremental": true }, "include": [ - "src" + "src", + "eslint.config.ts" ] }