Browse Source

Stricter linter (#3095)

* enforce some eslint rules

* enforce accessibility linter rules

* harden lint rules with only 1 or 2 warnings

* fix remaining rules with less than 3 warnings

* fix e2e tests

* fix remaining rules with less than 4 warnings

* fix remaining rules with less than 6 warnings

* fix `shuffleTaglines`

* fix companion build
Antoine du Hamel 3 years ago
parent
commit
6b7ad5e7c7
100 changed files with 469 additions and 366 deletions
  1. 69 56
      .eslintrc.js
  2. 1 0
      bin/upload-to-cdn.js
  3. 1 1
      examples/angular-example/karma.conf.js
  4. 1 0
      examples/bundled/sw.js
  5. 27 27
      examples/custom-provider/server/customprovider.js
  6. 1 0
      examples/dev/sw.js
  7. 1 1
      examples/react-native-expo/babel.config.js
  8. 3 2
      examples/redux/main.js
  9. 3 1
      packages/@uppy/aws-s3-multipart/src/MultipartUploader.js
  10. 5 3
      packages/@uppy/aws-s3-multipart/src/index.js
  11. 6 8
      packages/@uppy/companion-client/src/Provider.js
  12. 2 2
      packages/@uppy/companion-client/src/SearchProvider.js
  13. 1 1
      packages/@uppy/companion/src/companion.js
  14. 2 0
      packages/@uppy/companion/src/server/helpers/request.js
  15. 1 1
      packages/@uppy/companion/src/server/provider/credentials.js
  16. 7 7
      packages/@uppy/companion/src/server/provider/index.js
  17. 2 1
      packages/@uppy/companion/src/server/provider/zoom/adapter.js
  18. 5 2
      packages/@uppy/companion/src/server/provider/zoom/index.js
  19. 9 5
      packages/@uppy/companion/src/standalone/index.js
  20. 1 0
      packages/@uppy/companion/src/standalone/start-server.js
  21. 1 0
      packages/@uppy/core/src/loggers.js
  22. 29 28
      packages/@uppy/dashboard/src/components/AddFiles.js
  23. 1 0
      packages/@uppy/dashboard/src/components/Dashboard.js
  24. 1 1
      packages/@uppy/dashboard/src/components/EditorPanel.js
  25. 19 19
      packages/@uppy/dashboard/src/components/FileCard/index.js
  26. 5 4
      packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.js
  27. 3 1
      packages/@uppy/dashboard/src/components/FileItem/FilePreviewAndLink/index.js
  28. 0 1
      packages/@uppy/dashboard/src/components/FileItem/FileProgress/index.js
  29. 4 4
      packages/@uppy/dashboard/src/components/FileItem/index.js
  30. 1 0
      packages/@uppy/dashboard/src/components/FileItem/index.scss
  31. 2 1
      packages/@uppy/dashboard/src/components/FileList.js
  32. 65 39
      packages/@uppy/dashboard/src/index.js
  33. 1 0
      packages/@uppy/dashboard/src/utils/copyToClipboard.js
  34. 12 5
      packages/@uppy/dashboard/src/utils/createSuperFocus.js
  35. 11 5
      packages/@uppy/dashboard/src/utils/trapFocus.js
  36. 0 5
      packages/@uppy/golden-retriever/src/IndexedDBStore.js
  37. 1 4
      packages/@uppy/golden-retriever/src/ServiceWorker.js
  38. 1 1
      packages/@uppy/informer/src/TransitionGroup.js
  39. 1 1
      packages/@uppy/locales/src/ar_SA.js
  40. 1 1
      packages/@uppy/locales/src/bg_BG.js
  41. 1 1
      packages/@uppy/locales/src/cs_CZ.js
  42. 1 1
      packages/@uppy/locales/src/da_DK.js
  43. 1 1
      packages/@uppy/locales/src/de_DE.js
  44. 1 1
      packages/@uppy/locales/src/el_GR.js
  45. 1 1
      packages/@uppy/locales/src/en_US.js
  46. 1 1
      packages/@uppy/locales/src/es_ES.js
  47. 1 1
      packages/@uppy/locales/src/fa_IR.js
  48. 1 1
      packages/@uppy/locales/src/fi_FI.js
  49. 1 1
      packages/@uppy/locales/src/fr_FR.js
  50. 1 1
      packages/@uppy/locales/src/gl_ES.js
  51. 1 1
      packages/@uppy/locales/src/he_IL.js
  52. 1 1
      packages/@uppy/locales/src/hr_HR.js
  53. 1 1
      packages/@uppy/locales/src/hu_HU.js
  54. 1 1
      packages/@uppy/locales/src/id_ID.js
  55. 1 1
      packages/@uppy/locales/src/is_IS.js
  56. 1 1
      packages/@uppy/locales/src/it_IT.js
  57. 1 1
      packages/@uppy/locales/src/ja_JP.js
  58. 1 1
      packages/@uppy/locales/src/ko_KR.js
  59. 1 1
      packages/@uppy/locales/src/nb_NO.js
  60. 1 1
      packages/@uppy/locales/src/nl_NL.js
  61. 1 1
      packages/@uppy/locales/src/pl_PL.js
  62. 1 1
      packages/@uppy/locales/src/pt_BR.js
  63. 1 1
      packages/@uppy/locales/src/pt_PT.js
  64. 1 1
      packages/@uppy/locales/src/ro_RO.js
  65. 1 1
      packages/@uppy/locales/src/ru_RU.js
  66. 1 1
      packages/@uppy/locales/src/sk_SK.js
  67. 1 1
      packages/@uppy/locales/src/sr_RS_Cyrillic.js
  68. 1 1
      packages/@uppy/locales/src/sr_RS_Latin.js
  69. 1 1
      packages/@uppy/locales/src/sv_SE.js
  70. 1 1
      packages/@uppy/locales/src/th_TH.js
  71. 1 1
      packages/@uppy/locales/src/tr_TR.js
  72. 1 1
      packages/@uppy/locales/src/uk_UA.js
  73. 1 1
      packages/@uppy/locales/src/vi_VN.js
  74. 1 1
      packages/@uppy/locales/src/zh_CN.js
  75. 1 1
      packages/@uppy/locales/src/zh_TW.js
  76. 1 1
      packages/@uppy/locales/template.js
  77. 2 2
      packages/@uppy/provider-views/src/FooterActions.js
  78. 1 1
      packages/@uppy/provider-views/src/Item/components/ItemIcon.js
  79. 23 25
      packages/@uppy/provider-views/src/ProviderView/AuthView.js
  80. 19 14
      packages/@uppy/provider-views/src/ProviderView/ProviderView.js
  81. 20 14
      packages/@uppy/provider-views/src/SearchProviderView/SearchProviderView.js
  82. 5 5
      packages/@uppy/react-native/file-picker/index.js
  83. 0 3
      packages/@uppy/react-native/file-picker/instagram.js
  84. 1 0
      packages/@uppy/react-native/file-picker/url.js
  85. 1 1
      packages/@uppy/react/src/propTypes.js
  86. 3 0
      packages/@uppy/redux-dev-tools/src/index.js
  87. 3 4
      packages/@uppy/robodog/src/TransloaditResultsPlugin.js
  88. 2 0
      packages/@uppy/robodog/types/index.test-d.ts
  89. 2 1
      packages/@uppy/screen-capture/src/CaptureScreen.js
  90. 1 0
      packages/@uppy/screen-capture/src/StopWatch.js
  91. 5 2
      packages/@uppy/screen-capture/src/index.js
  92. 8 4
      packages/@uppy/status-bar/src/StatusBar.js
  93. 3 1
      packages/@uppy/status-bar/src/index.js
  94. 1 0
      packages/@uppy/status-bar/src/style.scss
  95. 2 2
      packages/@uppy/store-default/src/index.js
  96. 17 7
      packages/@uppy/store-redux/src/index.js
  97. 1 1
      packages/@uppy/store-redux/src/index.test.js
  98. 3 3
      packages/@uppy/thumbnail-generator/src/index.js
  99. 2 1
      packages/@uppy/transloadit/src/Assembly.js
  100. 1 1
      packages/@uppy/tus/src/getFingerprint.js

+ 69 - 56
.eslintrc.js

@@ -5,7 +5,7 @@
 const path = require('path')
 const path = require('path')
 
 
 const svgPresentationAttributes = [
 const svgPresentationAttributes = [
-  'alignment-baseline', 'baseline-shift', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolatio', 'color-interpolatio-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'display', 'dominant-baseline', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'overflow', 'pointer-events', 'shape-rendering', 'stop-color', 'stop-opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'transform', 'transform-origin', 'unicode-bidi', 'vector-effect', 'visibility', 'word-spacing', 'writing-mod',
+  'alignment-baseline', 'baseline-shift', 'class', 'clip', 'clip-path', 'clip-rule', 'color', 'color-interpolatio', 'color-interpolatio-filters', 'color-profile', 'color-rendering', 'cursor', 'direction', 'display', 'dominant-baseline', 'enable-background', 'fill', 'fill-opacity', 'fill-rule', 'filter', 'flood-color', 'flood-opacity', 'font-family', 'font-size', 'font-size-adjust', 'font-stretch', 'font-style', 'font-variant', 'font-weight', 'glyph-orientation-horizontal', 'glyph-orientation-vertical', 'image-rendering', 'kerning', 'letter-spacing', 'lighting-color', 'marker-end', 'marker-mid', 'marker-start', 'mask', 'opacity', 'overflow', 'pointer-events', 'shape-rendering', 'stop-color', 'stop-opacity', 'stroke', 'stroke-dasharray', 'stroke-dashoffset', 'stroke-linecap', 'stroke-linejoin', 'stroke-miterlimit', 'stroke-opacity', 'stroke-width', 'text-anchor', 'text-decoration', 'text-rendering', 'transform', 'transform-origin', 'unicode-bidi', 'vector-effect', 'visibility', 'word-spacing', 'writing-mod',
 ]
 ]
 
 
 module.exports = {
 module.exports = {
@@ -44,88 +44,89 @@ module.exports = {
   },
   },
   rules: {
   rules: {
     // transloadit rules we are actually ok with in the uppy repo
     // transloadit rules we are actually ok with in the uppy repo
-    'guard-for-in': ['off'],
-    'import/extensions': ['off'],
-    'strict': ['off'],
-    'key-spacing': ['off'],
+    'import/extensions': 'off',
+    'no-await-in-loop': 'off',
+    'object-shorthand': ['error', 'always'],
+    'strict': 'off',
+    'key-spacing': 'off',
+
+    // rules we want to enforce
+    'array-callback-return': 'error',
+    'implicit-arrow-linebreak': 'error',
+    'import/no-dynamic-require': 'error',
+    'import/no-extraneous-dependencies': 'error',
+    'max-len': 'error',
+    'no-empty': 'error',
+    'no-bitwise': 'error',
+    'no-continue': 'error',
+    'no-lonely-if': 'error',
+    'no-nested-ternary': 'error',
+    'no-restricted-properties': 'error',
+    'no-return-assign': 'error',
+    'no-underscore-dangle': 'error',
+    'no-useless-concat': 'error',
+    'no-var': 'error',
+    'node/handle-callback-err': 'error',
+    'prefer-destructuring': 'error',
+    'prefer-spread': 'error',
 
 
     // transloadit rules we would like to enforce in the future
     // transloadit rules we would like to enforce in the future
     // but will require separate PRs to gradually get there
     // but will require separate PRs to gradually get there
     // and so the meantime: just warn
     // and so the meantime: just warn
-    'array-callback-return': ['warn'],
-    'block-scoped-var': ['warn'],
     'class-methods-use-this': ['warn'],
     'class-methods-use-this': ['warn'],
     'consistent-return': ['warn'],
     'consistent-return': ['warn'],
     'default-case': ['warn'],
     'default-case': ['warn'],
     'global-require': ['warn'],
     'global-require': ['warn'],
-    'implicit-arrow-linebreak': ['warn'],
-    'import/no-dynamic-require': ['warn'],
     'import/no-unresolved': ['warn'],
     'import/no-unresolved': ['warn'],
     'import/order': ['warn'],
     'import/order': ['warn'],
-    'jsx-a11y/alt-text': ['warn'],
-    'jsx-a11y/anchor-has-content': ['warn'],
-    'jsx-a11y/click-events-have-key-events': ['warn'],
-    'jsx-a11y/control-has-associated-label': ['warn'],
-    'jsx-a11y/label-has-associated-control': ['warn'],
-    'jsx-a11y/media-has-caption': ['warn'],
-    'jsx-a11y/mouse-events-have-key-events': ['warn'],
-    'jsx-a11y/no-interactive-element-to-noninteractive-role': ['warn'],
-    'jsx-a11y/no-noninteractive-element-interactions': ['warn'],
-    'jsx-a11y/no-static-element-interactions': ['warn'],
-    'no-await-in-loop': ['warn'],
-    'no-bitwise': ['warn'],
-    'no-continue': ['warn'],
-    'no-empty': ['warn'],
-    'no-lonely-if': ['warn'],
     'no-mixed-operators': ['warn'],
     'no-mixed-operators': ['warn'],
-    'no-nested-ternary': ['warn'],
     'no-param-reassign': ['warn'],
     'no-param-reassign': ['warn'],
     'no-redeclare': ['warn'],
     'no-redeclare': ['warn'],
-    'no-restricted-globals': ['warn'],
-    'no-restricted-properties': ['warn'],
-    'no-restricted-syntax': ['warn'],
-    'no-return-assign': ['warn'],
     'no-shadow': ['warn'],
     'no-shadow': ['warn'],
-    'no-underscore-dangle': ['warn'],
     'no-unused-expressions': ['warn'],
     'no-unused-expressions': ['warn'],
     'no-unused-vars': ['warn'],
     'no-unused-vars': ['warn'],
     'no-use-before-define': ['warn'],
     'no-use-before-define': ['warn'],
-    'no-useless-concat': ['warn'],
-    'no-var': ['warn'],
-    'node/handle-callback-err': ['warn'],
-    'prefer-destructuring': ['warn'],
-    'prefer-spread': ['warn'],
     'radix': ['warn'],
     'radix': ['warn'],
-    'react/button-has-type': ['warn'],
+    'react/button-has-type': 'error',
     'react/destructuring-assignment': ['warn'],
     'react/destructuring-assignment': ['warn'],
-    'react/forbid-prop-types': ['warn'],
+    'react/forbid-prop-types': 'error',
     'react/jsx-props-no-spreading': ['warn'],
     'react/jsx-props-no-spreading': ['warn'],
-    'react/no-access-state-in-setstate': ['warn'],
-    'react/no-array-index-key': ['warn'],
-    'react/no-deprecated': ['warn'],
-    'react/no-this-in-sfc': ['warn'],
-    'react/no-will-update-set-state': ['warn'],
-    'react/prefer-stateless-function': ['warn'],
-    'react/sort-comp': ['warn'],
-    'react/style-prop-object': ['warn'],
-    'react/no-unknown-property': ['warn', {
+    'react/no-access-state-in-setstate': 'error',
+    'react/no-array-index-key': 'error',
+    'react/no-deprecated': 'error',
+    'react/no-this-in-sfc': 'error',
+    'react/no-will-update-set-state': 'error',
+    'react/prefer-stateless-function': 'error',
+    'react/sort-comp': 'error',
+    'react/style-prop-object': 'error',
+    'react/no-unknown-property': ['error', {
       ignore: svgPresentationAttributes,
       ignore: svgPresentationAttributes,
     }],
     }],
-    'vars-on-top': ['warn'],
-    'import/no-extraneous-dependencies': ['error'],
+
+    // accessibility
+    'jsx-a11y/alt-text': 'error',
+    'jsx-a11y/anchor-has-content': 'error',
+    'jsx-a11y/click-events-have-key-events': 'error',
+    'jsx-a11y/control-has-associated-label': 'error',
+    'jsx-a11y/label-has-associated-control': 'error',
+    'jsx-a11y/media-has-caption': 'error',
+    'jsx-a11y/mouse-events-have-key-events': 'error',
+    'jsx-a11y/no-interactive-element-to-noninteractive-role': 'error',
+    'jsx-a11y/no-noninteractive-element-interactions': 'error',
+    'jsx-a11y/no-static-element-interactions': 'error',
 
 
     // compat
     // compat
     'compat/compat': ['error'],
     'compat/compat': ['error'],
 
 
     // jsdoc
     // jsdoc
-    'jsdoc/check-alignment': ['warn'],
-    'jsdoc/check-examples': ['warn'],
+    'jsdoc/check-alignment': 'error',
+    'jsdoc/check-examples': 'error',
     'jsdoc/check-param-names': ['warn'],
     'jsdoc/check-param-names': ['warn'],
     'jsdoc/check-syntax': ['warn'],
     'jsdoc/check-syntax': ['warn'],
-    'jsdoc/check-tag-names': ['warn'],
-    'jsdoc/check-types': ['warn'],
-    'jsdoc/newline-after-description': ['warn'],
-    'jsdoc/valid-types': ['warn'],
+    'jsdoc/check-tag-names': 'error',
+    'jsdoc/check-types': 'error',
+    'jsdoc/newline-after-description': 'error',
+    'jsdoc/valid-types': 'error',
     'jsdoc/check-indentation': ['off'],
     'jsdoc/check-indentation': ['off'],
   },
   },
 
 
@@ -151,6 +152,13 @@ module.exports = {
   },
   },
 
 
   overrides: [
   overrides: [
+    {
+      files: ['./packages/@uppy/companion/**/*.js'],
+      rules: {
+        'no-restricted-syntax': 'warn',
+        'no-underscore-dangle': 'off',
+      },
+    },
     {
     {
       files: [
       files: [
         '*.test.js',
         '*.test.js',
@@ -162,17 +170,22 @@ module.exports = {
         'compat/compat': ['off'],
         'compat/compat': ['off'],
       },
       },
     },
     },
-
     {
     {
       files: [
       files: [
         'bin/**.js',
         'bin/**.js',
+        'examples/**/*.js',
+        'packages/@uppy/companion/test/**/*.js',
+        'test/**/*.js',
+        'test/**/*.ts',
+        '*.test.js',
+        '*.test-d.ts',
         'postcss.config.js',
         'postcss.config.js',
         '.eslintrc.js',
         '.eslintrc.js',
         'website/*.js',
         'website/*.js',
         'website/**/*.js',
         'website/**/*.js',
       ],
       ],
       rules: {
       rules: {
-        'no-console': ['off'],
+        'no-console': 'off',
         'import/no-extraneous-dependencies': ['error', {
         'import/no-extraneous-dependencies': ['error', {
           devDependencies: true,
           devDependencies: true,
         }],
         }],

+ 1 - 0
bin/upload-to-cdn.js

@@ -118,6 +118,7 @@ async function main (packageName, version) {
 
 
   const remote = !!version
   const remote = !!version
   if (!remote) {
   if (!remote) {
+    // eslint-disable-next-line import/no-dynamic-require
     version = require(`../packages/${packageName}/package.json`).version
     version = require(`../packages/${packageName}/package.json`).version
   }
   }
 
 

+ 1 - 1
examples/angular-example/karma.conf.js

@@ -1,7 +1,7 @@
 // Karma configuration file, see link for more information
 // Karma configuration file, see link for more information
 // https://karma-runner.github.io/1.0/config/configuration-file.html
 // https://karma-runner.github.io/1.0/config/configuration-file.html
 
 
-module.exports = function (config) {
+module.exports = function karma (config) {
   config.set({
   config.set({
     basePath: '',
     basePath: '',
     frameworks: ['jasmine', '@angular-devkit/build-angular'],
     frameworks: ['jasmine', '@angular-devkit/build-angular'],

+ 1 - 0
examples/bundled/sw.js

@@ -3,6 +3,7 @@
 // https://uppy.io/docs/golden-retriever/
 // https://uppy.io/docs/golden-retriever/
 
 
 /* globals clients */
 /* globals clients */
+/* eslint-disable no-restricted-globals */
 
 
 const fileCache = Object.create(null)
 const fileCache = Object.create(null)
 
 

+ 27 - 27
examples/custom-provider/server/customprovider.js

@@ -2,6 +2,32 @@ const request = require('request')
 
 
 const BASE_URL = 'https://api.unsplash.com'
 const BASE_URL = 'https://api.unsplash.com'
 
 
+function adaptData (res) {
+  const data = {
+    username: null,
+    items: [],
+    nextPagePath: null,
+  }
+
+  const items = res
+  items.forEach((item) => {
+    const isFolder = !!item.published_at
+    data.items.push({
+      isFolder,
+      icon: isFolder ? item.cover_photo.urls.thumb : item.urls.thumb,
+      name: item.title || item.description,
+      mimeType: isFolder ? null : 'image/jpeg',
+      id: item.id,
+      thumbnail: isFolder ? item.cover_photo.urls.thumb : item.urls.thumb,
+      requestPath: item.id,
+      modifiedDate: item.updated_at,
+      size: null,
+    })
+  })
+
+  return data
+}
+
 /**
 /**
  * an example of a custom provider module. It implements @uppy/companion's Provider interface
  * an example of a custom provider module. It implements @uppy/companion's Provider interface
  */
  */
@@ -28,7 +54,7 @@ class MyCustomProvider {
         return
         return
       }
       }
 
 
-      done(null, this._adaptData(body))
+      done(null, adaptData(body))
     })
     })
   }
   }
 
 
@@ -76,32 +102,6 @@ class MyCustomProvider {
       done(null, body.width * body.height)
       done(null, body.width * body.height)
     })
     })
   }
   }
-
-  _adaptData (res) {
-    const data = {
-      username: null,
-      items: [],
-      nextPagePath: null,
-    }
-
-    const items = res
-    items.forEach((item) => {
-      const isFolder = !!item.published_at
-      data.items.push({
-        isFolder,
-        icon: isFolder ? item.cover_photo.urls.thumb : item.urls.thumb,
-        name: item.title || item.description,
-        mimeType: isFolder ? null : 'image/jpeg',
-        id: item.id,
-        thumbnail: isFolder ? item.cover_photo.urls.thumb : item.urls.thumb,
-        requestPath: item.id,
-        modifiedDate: item.updated_at,
-        size: null,
-      })
-    })
-
-    return data
-  }
 }
 }
 
 
 module.exports = MyCustomProvider
 module.exports = MyCustomProvider

+ 1 - 0
examples/dev/sw.js

@@ -1,4 +1,5 @@
 /* globals clients */
 /* globals clients */
+/* eslint-disable no-restricted-globals */
 
 
 const fileCache = Object.create(null)
 const fileCache = Object.create(null)
 
 

+ 1 - 1
examples/react-native-expo/babel.config.js

@@ -1,4 +1,4 @@
-module.exports = function (api) {
+module.exports = function babel (api) {
   api.cache(true)
   api.cache(true)
   return {
   return {
     presets: ['babel-preset-expo'],
     presets: ['babel-preset-expo'],

+ 3 - 2
examples/redux/main.js

@@ -27,8 +27,9 @@ let enhancer = applyMiddleware(
   uppyReduxStore.middleware(),
   uppyReduxStore.middleware(),
   logger
   logger
 )
 )
-if (window.__REDUX_DEVTOOLS_EXTENSION__) {
-  enhancer = compose(enhancer, window.__REDUX_DEVTOOLS_EXTENSION__())
+if (typeof __REDUX_DEVTOOLS_EXTENSION__ !== 'undefined') {
+  // eslint-disable-next-line no-undef
+  enhancer = compose(enhancer, __REDUX_DEVTOOLS_EXTENSION__())
 }
 }
 
 
 const store = createStore(reducer, enhancer)
 const store = createStore(reducer, enhancer)

+ 3 - 1
packages/@uppy/aws-s3-multipart/src/MultipartUploader.js

@@ -181,8 +181,10 @@ class MultipartUploader {
 
 
     const candidates = []
     const candidates = []
     for (let i = 0; i < this.chunkState.length; i++) {
     for (let i = 0; i < this.chunkState.length; i++) {
+      // eslint-disable-next-line no-continue
       if (this.lockedCandidatesForBatch.includes(i)) continue
       if (this.lockedCandidatesForBatch.includes(i)) continue
       const state = this.chunkState[i]
       const state = this.chunkState[i]
+      // eslint-disable-next-line no-continue
       if (state.done || state.busy) continue
       if (state.done || state.busy) continue
 
 
       candidates.push(i)
       candidates.push(i)
@@ -319,7 +321,7 @@ class MultipartUploader {
     const xhr = new XMLHttpRequest()
     const xhr = new XMLHttpRequest()
     xhr.open('PUT', url, true)
     xhr.open('PUT', url, true)
     if (headers) {
     if (headers) {
-      Object.keys(headers).map((key) => {
+      Object.keys(headers).forEach((key) => {
         xhr.setRequestHeader(key, headers[key])
         xhr.setRequestHeader(key, headers[key])
       })
       })
     }
     }

+ 5 - 3
packages/@uppy/aws-s3-multipart/src/index.js

@@ -80,7 +80,7 @@ module.exports = class AwsS3Multipart extends BasePlugin {
 
 
     const metadata = {}
     const metadata = {}
 
 
-    Object.keys(file.meta).map(key => {
+    Object.keys(file.meta).forEach(key => {
       if (file.meta[key] != null) {
       if (file.meta[key] != null) {
         metadata[key] = file.meta[key].toString()
         metadata[key] = file.meta[key].toString()
       }
       }
@@ -239,7 +239,8 @@ module.exports = class AwsS3Multipart extends BasePlugin {
           queuedRequest.abort()
           queuedRequest.abort()
           upload.pause()
           upload.pause()
         } else {
         } else {
-          // Resuming an upload should be queued, else you could pause and then resume a queued upload to make it skip the queue.
+          // Resuming an upload should be queued, else you could pause and then
+          // resume a queued upload to make it skip the queue.
           queuedRequest.abort()
           queuedRequest.abort()
           queuedRequest = this.requests.run(() => {
           queuedRequest = this.requests.run(() => {
             upload.start()
             upload.start()
@@ -330,7 +331,8 @@ module.exports = class AwsS3Multipart extends BasePlugin {
           queuedRequest.abort()
           queuedRequest.abort()
           socket.send('pause', {})
           socket.send('pause', {})
         } else {
         } else {
-          // Resuming an upload should be queued, else you could pause and then resume a queued upload to make it skip the queue.
+          // Resuming an upload should be queued, else you could pause and then
+          // resume a queued upload to make it skip the queue.
           queuedRequest.abort()
           queuedRequest.abort()
           queuedRequest = this.requests.run(() => {
           queuedRequest = this.requests.run(() => {
             socket.send('resume', {})
             socket.send('resume', {})

+ 6 - 8
packages/@uppy/companion-client/src/Provider.js

@@ -3,7 +3,7 @@
 const RequestClient = require('./RequestClient')
 const RequestClient = require('./RequestClient')
 const tokenStorage = require('./tokenStorage')
 const tokenStorage = require('./tokenStorage')
 
 
-const _getName = (id) => {
+const getName = (id) => {
   return id.split('-').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
   return id.split('-').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
 }
 }
 
 
@@ -12,7 +12,7 @@ module.exports = class Provider extends RequestClient {
     super(uppy, opts)
     super(uppy, opts)
     this.provider = opts.provider
     this.provider = opts.provider
     this.id = this.provider
     this.id = this.provider
-    this.name = this.opts.name || _getName(this.id)
+    this.name = this.opts.name || getName(this.id)
     this.pluginId = this.opts.pluginId
     this.pluginId = this.opts.pluginId
     this.tokenKey = `companion-${this.pluginId}-auth-token`
     this.tokenKey = `companion-${this.pluginId}-auth-token`
     this.companionKeysParams = this.opts.companionKeysParams
     this.companionKeysParams = this.opts.companionKeysParams
@@ -108,13 +108,11 @@ module.exports = class Provider extends RequestClient {
         throw new TypeError(`${plugin.id}: the option "companionAllowedHosts" must be one of string, Array, RegExp`)
         throw new TypeError(`${plugin.id}: the option "companionAllowedHosts" must be one of string, Array, RegExp`)
       }
       }
       plugin.opts.companionAllowedHosts = pattern
       plugin.opts.companionAllowedHosts = pattern
-    } else {
+    } else if (/^(?!https?:\/\/).*$/i.test(opts.companionUrl)) {
       // does not start with https://
       // does not start with https://
-      if (/^(?!https?:\/\/).*$/i.test(opts.companionUrl)) {
-        plugin.opts.companionAllowedHosts = `https://${opts.companionUrl.replace(/^\/\//, '')}`
-      } else {
-        plugin.opts.companionAllowedHosts = new URL(opts.companionUrl).origin
-      }
+      plugin.opts.companionAllowedHosts = `https://${opts.companionUrl.replace(/^\/\//, '')}`
+    } else {
+      plugin.opts.companionAllowedHosts = new URL(opts.companionUrl).origin
     }
     }
 
 
     plugin.storage = plugin.opts.storage || tokenStorage
     plugin.storage = plugin.opts.storage || tokenStorage

+ 2 - 2
packages/@uppy/companion-client/src/SearchProvider.js

@@ -2,7 +2,7 @@
 
 
 const RequestClient = require('./RequestClient')
 const RequestClient = require('./RequestClient')
 
 
-const _getName = (id) => {
+const getName = (id) => {
   return id.split('-').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
   return id.split('-').map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
 }
 }
 
 
@@ -11,7 +11,7 @@ module.exports = class SearchProvider extends RequestClient {
     super(uppy, opts)
     super(uppy, opts)
     this.provider = opts.provider
     this.provider = opts.provider
     this.id = this.provider
     this.id = this.provider
-    this.name = this.opts.name || _getName(this.id)
+    this.name = this.opts.name || getName(this.id)
     this.pluginId = this.opts.pluginId
     this.pluginId = this.opts.pluginId
   }
   }
 
 

+ 1 - 1
packages/@uppy/companion/src/companion.js

@@ -233,7 +233,7 @@ const validateConfig = (companionOptions) => {
   // validate that specified filePath is writeable/readable.
   // validate that specified filePath is writeable/readable.
   try {
   try {
     // @ts-ignore
     // @ts-ignore
-    fs.accessSync(`${companionOptions.filePath}`, fs.R_OK | fs.W_OK)
+    fs.accessSync(`${companionOptions.filePath}`, fs.R_OK | fs.W_OK) // eslint-disable-line no-bitwise
   } catch (err) {
   } catch (err) {
     throw new Error(
     throw new Error(
       `No access to "${companionOptions.filePath}". Please ensure the directory exists and with read/write permissions.`
       `No access to "${companionOptions.filePath}". Please ensure the directory exists and with read/write permissions.`

+ 2 - 0
packages/@uppy/companion/src/server/helpers/request.js

@@ -14,6 +14,7 @@ function isIPAddress (address) {
   return addressAsV6.isValid() || addressAsV4.isValid()
   return addressAsV6.isValid() || addressAsV4.isValid()
 }
 }
 
 
+/* eslint-disable max-len */
 /**
 /**
  * Determine if a IP address provided is a private one.
  * Determine if a IP address provided is a private one.
  * Return TRUE if it's the case, FALSE otherwise.
  * Return TRUE if it's the case, FALSE otherwise.
@@ -23,6 +24,7 @@ function isIPAddress (address) {
  * @param {string} ipAddress the ip address to validate
  * @param {string} ipAddress the ip address to validate
  * @returns {boolean}
  * @returns {boolean}
  */
  */
+/* eslint-enable max-len */
 function isPrivateIP (ipAddress) {
 function isPrivateIP (ipAddress) {
   let isPrivate = false
   let isPrivate = false
   // Build the list of IP prefix for V4 and V6 addresses
   // Build the list of IP prefix for V4 and V6 addresses

+ 1 - 1
packages/@uppy/companion/src/server/provider/credentials.js

@@ -13,7 +13,7 @@ const Provider = require('./Provider')
  *
  *
  * @param {Object.<string, (typeof Provider)>} providers provider classes enabled for this server
  * @param {Object.<string, (typeof Provider)>} providers provider classes enabled for this server
  * @param {object} companionOptions companion options object
  * @param {object} companionOptions companion options object
- * @returns {(req: object, res: object, next: function()) => void}
+ * @returns {(req: object, res: object, next: Function) => void}
  */
  */
 exports.getCredentialsOverrideMiddleware = (providers, companionOptions) => {
 exports.getCredentialsOverrideMiddleware = (providers, companionOptions) => {
   return (req, res, next) => {
   return (req, res, next) => {

+ 7 - 7
packages/@uppy/companion/src/server/provider/index.js

@@ -48,8 +48,8 @@ config.zoom = {
  * adds the desired provider module to the request object,
  * adds the desired provider module to the request object,
  * based on the providerName parameter specified
  * based on the providerName parameter specified
  *
  *
- * @param {Object.<string, (typeof Provider) | typeof SearchProvider>} providers
- * @param {boolean=} needsProviderCredentials
+ * @param {Record<string, (typeof Provider) | typeof SearchProvider>} providers
+ * @param {boolean} [needsProviderCredentials]
  */
  */
 module.exports.getProviderMiddleware = (providers, needsProviderCredentials) => {
 module.exports.getProviderMiddleware = (providers, needsProviderCredentials) => {
   /**
   /**
@@ -75,7 +75,7 @@ module.exports.getProviderMiddleware = (providers, needsProviderCredentials) =>
 }
 }
 
 
 /**
 /**
- * @returns {Object.<string, typeof Provider>}
+ * @returns {Record<string, typeof Provider>}
  */
  */
 module.exports.getDefaultProviders = () => {
 module.exports.getDefaultProviders = () => {
   const providers = { dropbox, box, drive, facebook, onedrive, zoom, instagram }
   const providers = { dropbox, box, drive, facebook, onedrive, zoom, instagram }
@@ -84,7 +84,7 @@ module.exports.getDefaultProviders = () => {
 }
 }
 
 
 /**
 /**
- * @returns {Object.<string, typeof SearchProvider>}
+ * @returns {Record<string, typeof SearchProvider>}
  */
  */
 module.exports.getSearchProviders = () => {
 module.exports.getSearchProviders = () => {
   return { unsplash }
   return { unsplash }
@@ -92,10 +92,10 @@ module.exports.getSearchProviders = () => {
 
 
 /**
 /**
  *
  *
- * @typedef {{module: typeof Provider, config: object}} CustomProvider
+ * @typedef {{'module': typeof Provider, config: Record<string,unknown>}} CustomProvider
  *
  *
- * @param {Object.<string, CustomProvider>} customProviders
- * @param {Object.<string, typeof Provider>} providers
+ * @param {Record<string, CustomProvider>} customProviders
+ * @param {Record<string, typeof Provider>} providers
  * @param {object} grantConfig
  * @param {object} grantConfig
  */
  */
 module.exports.addCustomProviders = (customProviders, providers, grantConfig) => {
 module.exports.addCustomProviders = (customProviders, providers, grantConfig) => {

+ 2 - 1
packages/@uppy/companion/src/server/provider/zoom/adapter.js

@@ -107,7 +107,8 @@ exports.getRequestPath = (item) => {
   } if (item.file_type) {
   } if (item.file_type) {
     return `${encodeURIComponent(item.meeting_id)}?recordingId=${encodeURIComponent(item.id)}`
     return `${encodeURIComponent(item.meeting_id)}?recordingId=${encodeURIComponent(item.id)}`
   }
   }
-  // Zoom meeting ids are reused so we need to use the UUID. Also, these UUIDs can contain `/` characters which require double encoding (see https://devforum.zoom.us/t/double-encode-meeting-uuids/23729)
+  // Zoom meeting ids are reused so we need to use the UUID. Also, these UUIDs can contain `/` characters which require
+  // double encoding (see https://devforum.zoom.us/t/double-encode-meeting-uuids/23729).
   return `${encodeURIComponent(encodeURIComponent(item.uuid))}`
   return `${encodeURIComponent(encodeURIComponent(item.uuid))}`
 }
 }
 
 

+ 5 - 2
packages/@uppy/companion/src/server/provider/zoom/index.js

@@ -213,7 +213,8 @@ class Zoom extends Provider {
       return { items: [] }
       return { items: [] }
     }
     }
 
 
-    // we query the zoom api by date (from 00:00 - 23:59 UTC) which may include extra results for 00:00 - 23:59 local time that we want to filter out
+    // we query the zoom api by date (from 00:00 - 23:59 UTC) which may include
+    // extra results for 00:00 - 23:59 local time that we want to filter out.
     const utcFrom = moment.tz(query.from, userResponse.timezone || 'UTC').startOf('day').tz('UTC')
     const utcFrom = moment.tz(query.from, userResponse.timezone || 'UTC').startOf('day').tz('UTC')
     const utcTo = moment.tz(query.to, userResponse.timezone || 'UTC').endOf('day').tz('UTC')
     const utcTo = moment.tz(query.to, userResponse.timezone || 'UTC').endOf('day').tz('UTC')
 
 
@@ -320,7 +321,9 @@ class Zoom extends Provider {
     if (resp) {
     if (resp) {
       const fallbackMsg = `request to ${this.authProvider} returned ${resp.statusCode}`
       const fallbackMsg = `request to ${this.authProvider} returned ${resp.statusCode}`
       const errMsg = (resp.body || {}).message ? resp.body.message : fallbackMsg
       const errMsg = (resp.body || {}).message ? resp.body.message : fallbackMsg
-      return authErrorCodes.indexOf(resp.statusCode) > -1 ? new ProviderAuthError() : new ProviderApiError(errMsg, resp.statusCode)
+      return authErrorCodes.indexOf(resp.statusCode) > -1
+        ? new ProviderAuthError()
+        : new ProviderApiError(errMsg, resp.statusCode)
     }
     }
     return err
     return err
   }
   }

+ 9 - 5
packages/@uppy/companion/src/standalone/index.js

@@ -29,16 +29,17 @@ module.exports = function server (inputCompanionOptions = {}) {
    *
    *
    * Returns a copy of the object with unknown types removed and sensitive values replaced by ***.
    * Returns a copy of the object with unknown types removed and sensitive values replaced by ***.
    *
    *
-   * The input type is more broad that it needs to be, this way typescript can help us guarantee that we're dealing with all possible inputs :)
+   * The input type is more broad that it needs to be, this way typescript can help us guarantee that we're dealing with all
+   * possible inputs :)
    *
    *
-   * @param {{ [key: string]: any }} rawQuery
+   * @param {Record<string, any>} rawQuery
    * @returns {{
    * @returns {{
-   *   query: { [key: string]: string },
+   *   query: Record<string, any>,
    *   censored: boolean
    *   censored: boolean
    * }}
    * }}
    */
    */
   function censorQuery (rawQuery) {
   function censorQuery (rawQuery) {
-    /** @type {{ [key: string]: string }} */
+    /** @type {Record<string, any>} */
     const query = {}
     const query = {}
     let censored = false
     let censored = false
     Object.keys(rawQuery).forEach((key) => {
     Object.keys(rawQuery).forEach((key) => {
@@ -82,7 +83,8 @@ module.exports = function server (inputCompanionOptions = {}) {
   // for server metrics tracking.
   // for server metrics tracking.
   // make app metrics available at '/metrics'.
   // make app metrics available at '/metrics'.
   // TODO for the next major version: use instead companion option "metrics": true and remove this code
   // TODO for the next major version: use instead companion option "metrics": true and remove this code
-  // Se discussion: https://github.com/transloadit/uppy/pull/2854/files/64be97205e4012818abfcc8b0b8b7fe09de91729#diff-68f5e3eb307c1c9d1fd02224fd7888e2f74718744e1b6e35d929fcab1cc50ed1
+  // eslint-disable-next-line max-len
+  // See discussion: https://github.com/transloadit/uppy/pull/2854/files/64be97205e4012818abfcc8b0b8b7fe09de91729#diff-68f5e3eb307c1c9d1fd02224fd7888e2f74718744e1b6e35d929fcab1cc50ed1
   if (process.env.COMPANION_HIDE_METRICS !== 'true') {
   if (process.env.COMPANION_HIDE_METRICS !== 'true') {
     app.use(middlewares.metrics())
     app.use(middlewares.metrics())
   }
   }
@@ -152,6 +154,7 @@ module.exports = function server (inputCompanionOptions = {}) {
     // initialize companion
     // initialize companion
     companionApp = companion.app(companionOptions)
     companionApp = companion.app(companionOptions)
   } catch (error) {
   } catch (error) {
+    // eslint-disable-next-line no-console
     console.error('\x1b[31m', error.message, '\x1b[0m')
     console.error('\x1b[31m', error.message, '\x1b[0m')
     process.exit(1)
     process.exit(1)
   }
   }
@@ -177,6 +180,7 @@ module.exports = function server (inputCompanionOptions = {}) {
       })
       })
       res.header('Content-Length', `${Buffer.byteLength(content, 'utf8')}`)
       res.header('Content-Length', `${Buffer.byteLength(content, 'utf8')}`)
       // use writeHead to prevent 'charset' from being appended
       // use writeHead to prevent 'charset' from being appended
+      // eslint-disable-next-line max-len
       // https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-configure-publisher-domain#to-select-a-verified-domain
       // https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-configure-publisher-domain#to-select-a-verified-domain
       res.writeHead(200, { 'Content-Type': 'application/json' })
       res.writeHead(200, { 'Content-Type': 'application/json' })
       res.write(content)
       res.write(content)

+ 1 - 0
packages/@uppy/companion/src/standalone/start-server.js

@@ -10,5 +10,6 @@ const { app } = standalone()
 
 
 companion.socket(app.listen(port))
 companion.socket(app.listen(port))
 
 
+/* eslint-disable no-console */
 console.log(`Welcome to Companion! v${version}`)
 console.log(`Welcome to Companion! v${version}`)
 console.log(`Listening on http://0.0.0.0:${port}`)
 console.log(`Listening on http://0.0.0.0:${port}`)

+ 1 - 0
packages/@uppy/core/src/loggers.js

@@ -1,3 +1,4 @@
+/* eslint-disable no-console */
 const getTimeStamp = require('@uppy/utils/lib/getTimeStamp')
 const getTimeStamp = require('@uppy/utils/lib/getTimeStamp')
 
 
 // Swallow all logs, except errors.
 // Swallow all logs, except errors.

+ 29 - 28
packages/@uppy/dashboard/src/components/AddFiles.js

@@ -21,33 +21,6 @@ class AddFiles extends Component {
     event.target.value = null
     event.target.value = null
   }
   }
 
 
-  renderPoweredByUppy () {
-    const { i18nArray } = this.props
-
-    const uppyBranding = (
-      <span>
-        <svg aria-hidden="true" focusable="false" className="uppy-c-icon uppy-Dashboard-poweredByIcon" width="11" height="11" viewBox="0 0 11 11">
-          <path d="M7.365 10.5l-.01-4.045h2.612L5.5.806l-4.467 5.65h2.604l.01 4.044h3.718z" fillRule="evenodd" />
-        </svg>
-        <span className="uppy-Dashboard-poweredByUppy">Uppy</span>
-      </span>
-    )
-
-    const linkText = i18nArray('poweredBy', { uppy: uppyBranding })
-
-    return (
-      <a
-        tabIndex="-1"
-        href="https://uppy.io"
-        rel="noreferrer noopener"
-        target="_blank"
-        className="uppy-Dashboard-poweredBy"
-      >
-        {linkText}
-      </a>
-    )
-  }
-
   renderHiddenInput = (isFolder, refCallback) => {
   renderHiddenInput = (isFolder, refCallback) => {
     return (
     return (
       <input
       <input
@@ -120,6 +93,7 @@ class AddFiles extends Component {
     return (
     return (
       <div class="uppy-Dashboard-AddFiles-title">
       <div class="uppy-Dashboard-AddFiles-title">
         {
         {
+          // eslint-disable-next-line no-nested-ternary
           this.props.disableLocalFiles ? this.props.i18n('importFiles')
           this.props.disableLocalFiles ? this.props.i18n('importFiles')
             : numberOfAcquirers > 0
             : numberOfAcquirers > 0
               ? this.props.i18nArray(`dropPasteImport${camelFMSelectionType}`, { browseFiles, browseFolders, browse: browseFiles })
               ? this.props.i18nArray(`dropPasteImport${camelFMSelectionType}`, { browseFiles, browseFolders, browse: browseFiles })
@@ -173,13 +147,40 @@ class AddFiles extends Component {
       <div className="uppy-Dashboard-AddFiles-list" role="tablist">
       <div className="uppy-Dashboard-AddFiles-list" role="tablist">
         {!disableLocalFiles && this.renderMyDeviceAcquirer()}
         {!disableLocalFiles && this.renderMyDeviceAcquirer()}
         {acquirersWithoutLastTwo.map((acquirer) => this.renderAcquirer(acquirer))}
         {acquirersWithoutLastTwo.map((acquirer) => this.renderAcquirer(acquirer))}
-        <span role="presentation" style="white-space: nowrap;">
+        <span role="presentation" style={{ 'white-space': 'nowrap' }}>
           {lastTwoAcquirers.map((acquirer) => this.renderAcquirer(acquirer))}
           {lastTwoAcquirers.map((acquirer) => this.renderAcquirer(acquirer))}
         </span>
         </span>
       </div>
       </div>
     )
     )
   }
   }
 
 
+  renderPoweredByUppy () {
+    const { i18nArray } = this.props
+
+    const uppyBranding = (
+      <span>
+        <svg aria-hidden="true" focusable="false" className="uppy-c-icon uppy-Dashboard-poweredByIcon" width="11" height="11" viewBox="0 0 11 11">
+          <path d="M7.365 10.5l-.01-4.045h2.612L5.5.806l-4.467 5.65h2.604l.01 4.044h3.718z" fillRule="evenodd" />
+        </svg>
+        <span className="uppy-Dashboard-poweredByUppy">Uppy</span>
+      </span>
+    )
+
+    const linkText = i18nArray('poweredBy', { uppy: uppyBranding })
+
+    return (
+      <a
+        tabIndex="-1"
+        href="https://uppy.io"
+        rel="noreferrer noopener"
+        target="_blank"
+        className="uppy-Dashboard-poweredBy"
+      >
+        {linkText}
+      </a>
+    )
+  }
+
   render () {
   render () {
     return (
     return (
       <div className="uppy-Dashboard-AddFiles">
       <div className="uppy-Dashboard-AddFiles">

+ 1 - 0
packages/@uppy/dashboard/src/components/Dashboard.js

@@ -81,6 +81,7 @@ module.exports = function Dashboard (props) {
       onDrop={props.handleDrop}
       onDrop={props.handleDrop}
     >
     >
       <div
       <div
+        aria-hidden="true"
         className="uppy-Dashboard-overlay"
         className="uppy-Dashboard-overlay"
         tabIndex={-1}
         tabIndex={-1}
         onClick={props.handleClickOutside}
         onClick={props.handleClickOutside}

+ 1 - 1
packages/@uppy/dashboard/src/components/EditorPanel.js

@@ -2,7 +2,7 @@ const { h } = require('preact')
 const classNames = require('classnames')
 const classNames = require('classnames')
 
 
 function EditorPanel (props) {
 function EditorPanel (props) {
-  const file = this.props.files[this.props.fileCardFor]
+  const file = props.files[props.fileCardFor]
 
 
   return (
   return (
     <div
     <div

+ 19 - 19
packages/@uppy/dashboard/src/components/FileCard/index.js

@@ -6,6 +6,8 @@ const ignoreEvent = require('../../utils/ignoreEvent.js')
 const FilePreview = require('../FilePreview')
 const FilePreview = require('../FilePreview')
 
 
 class FileCard extends Component {
 class FileCard extends Component {
+  form = document.createElement('form');
+
   constructor (props) {
   constructor (props) {
     super(props)
     super(props)
 
 
@@ -24,6 +26,23 @@ class FileCard extends Component {
     this.form.id = nanoid()
     this.form.id = nanoid()
   }
   }
 
 
+  // TODO(aduh95): move this to `UNSAFE_componentWillMount` when updating to Preact X+.
+  componentWillMount () { // eslint-disable-line react/no-deprecated
+    this.form.addEventListener('submit', this.handleSave)
+    document.body.appendChild(this.form)
+  }
+
+  componentWillUnmount () {
+    this.form.removeEventListener('submit', this.handleSave)
+    document.body.removeChild(this.form)
+  }
+
+  getMetaFields () {
+    return typeof this.props.metaFields === 'function'
+      ? this.props.metaFields(this.props.files[this.props.fileCardFor])
+      : this.props.metaFields
+  }
+
   updateMeta = (newVal, name) => {
   updateMeta = (newVal, name) => {
     this.setState(({ formState }) => ({
     this.setState(({ formState }) => ({
       formState: {
       formState: {
@@ -33,8 +52,6 @@ class FileCard extends Component {
     }))
     }))
   }
   }
 
 
-  form = document.createElement('form');
-
   handleSave = (e) => {
   handleSave = (e) => {
     e.preventDefault()
     e.preventDefault()
     const fileID = this.props.fileCardFor
     const fileID = this.props.fileCardFor
@@ -45,17 +62,6 @@ class FileCard extends Component {
     this.props.toggleFileCard(false)
     this.props.toggleFileCard(false)
   }
   }
 
 
-  // TODO(aduh95): move this to `UNSAFE_componentWillMount` when updating to Preact X+.
-  componentWillMount () {
-    this.form.addEventListener('submit', this.handleSave)
-    document.body.appendChild(this.form)
-  }
-
-  componentWillUnmount () {
-    this.form.removeEventListener('submit', this.handleSave)
-    document.body.removeChild(this.form)
-  }
-
   renderMetaFields = () => {
   renderMetaFields = () => {
     const metaFields = this.getMetaFields() || []
     const metaFields = this.getMetaFields() || []
     const fieldCSSClasses = {
     const fieldCSSClasses = {
@@ -94,12 +100,6 @@ class FileCard extends Component {
     })
     })
   }
   }
 
 
-  getMetaFields () {
-    return typeof this.props.metaFields === 'function'
-      ? this.props.metaFields(this.props.files[this.props.fileCardFor])
-      : this.props.metaFields
-  }
-
   render () {
   render () {
     const file = this.props.files[this.props.fileCardFor]
     const file = this.props.files[this.props.fileCardFor]
     const showEditButton = this.props.canEditFile(file)
     const showEditButton = this.props.canEditFile(file)

+ 5 - 4
packages/@uppy/dashboard/src/components/FileItem/FileInfo/index.js

@@ -51,16 +51,16 @@ const ReSelectButton = (props) => (
 const ErrorButton = ({ file, onClick }) => {
 const ErrorButton = ({ file, onClick }) => {
   if (file.error) {
   if (file.error) {
     return (
     return (
-      <span
+      <button
         className="uppy-Dashboard-Item-errorDetails"
         className="uppy-Dashboard-Item-errorDetails"
         aria-label={file.error}
         aria-label={file.error}
         data-microtip-position="bottom"
         data-microtip-position="bottom"
         data-microtip-size="medium"
         data-microtip-size="medium"
-        role="tooltip"
         onClick={onClick}
         onClick={onClick}
+        type="button"
       >
       >
         ?
         ?
-      </span>
+      </button>
     )
     )
   }
   }
   return null
   return null
@@ -75,7 +75,8 @@ module.exports = function FileInfo (props) {
         {ReSelectButton(props)}
         {ReSelectButton(props)}
         <ErrorButton
         <ErrorButton
           file={props.file}
           file={props.file}
-          onClick={() => alert(props.file.error)}
+          // eslint-disable-next-line no-alert
+          onClick={() => alert(props.file.error)} // TODO: move to a custom alert implementation
         />
         />
       </div>
       </div>
     </div>
     </div>

+ 3 - 1
packages/@uppy/dashboard/src/components/FileItem/FilePreviewAndLink/index.js

@@ -18,7 +18,9 @@ module.exports = function FilePreviewAndLink (props) {
             rel="noreferrer noopener"
             rel="noreferrer noopener"
             target="_blank"
             target="_blank"
             aria-label={props.file.meta.name}
             aria-label={props.file.meta.name}
-          />
+          >
+            <span hidden>props.file.meta.name</span>
+          </a>
           )
           )
       }
       }
       <FilePreview file={props.file} />
       <FilePreview file={props.file} />

+ 0 - 1
packages/@uppy/dashboard/src/components/FileItem/FileProgress/index.js

@@ -1,7 +1,6 @@
 const { h } = require('preact')
 const { h } = require('preact')
 
 
 function onPauseResumeCancelRetry (props) {
 function onPauseResumeCancelRetry (props) {
-  console.log(props.uppy)
   if (props.isUploaded) return
   if (props.isUploaded) return
 
 
   if (props.error && !props.hideRetryButton) {
   if (props.error && !props.hideRetryButton) {

+ 4 - 4
packages/@uppy/dashboard/src/components/FileItem/index.js

@@ -7,10 +7,6 @@ const FileInfo = require('./FileInfo')
 const Buttons = require('./Buttons')
 const Buttons = require('./Buttons')
 
 
 module.exports = class FileItem extends Component {
 module.exports = class FileItem extends Component {
-  shouldComponentUpdate (nextProps) {
-    return !shallowEqual(this.props, nextProps)
-  }
-
   componentDidMount () {
   componentDidMount () {
     const { file } = this.props
     const { file } = this.props
     if (!file.preview) {
     if (!file.preview) {
@@ -18,6 +14,10 @@ module.exports = class FileItem extends Component {
     }
     }
   }
   }
 
 
+  shouldComponentUpdate (nextProps) {
+    return !shallowEqual(this.props, nextProps)
+  }
+
   // VirtualList mounts FileItems again and they emit `thumbnail:request`
   // VirtualList mounts FileItems again and they emit `thumbnail:request`
   // Otherwise thumbnails are broken or missing after Golden Retriever restores files
   // Otherwise thumbnails are broken or missing after Golden Retriever restores files
   componentDidUpdate () {
   componentDidUpdate () {

+ 1 - 0
packages/@uppy/dashboard/src/components/FileItem/index.scss

@@ -141,6 +141,7 @@
 }
 }
 
 
 .uppy-Dashboard-Item-errorDetails {
 .uppy-Dashboard-Item-errorDetails {
+  appearance: none;
   line-height: 12px;
   line-height: 12px;
   width: 12px;
   width: 12px;
   height: 12px;
   height: 12px;

+ 2 - 1
packages/@uppy/dashboard/src/components/FileList.js

@@ -70,7 +70,8 @@ module.exports = (props) => {
 
 
   function renderRow (row) {
   function renderRow (row) {
     return (
     return (
-      // The `role="presentation` attribute ensures that the list items are properly associated with the `VirtualList` element
+      // The `role="presentation` attribute ensures that the list items are properly
+      // associated with the `VirtualList` element.
       // We use the first file ID as the key—this should not change across scroll rerenders
       // We use the first file ID as the key—this should not change across scroll rerenders
       <div role="presentation" key={row[0]}>
       <div role="presentation" key={row[0]}>
         {row.map((fileID) => (
         {row.map((fileID) => (

+ 65 - 39
packages/@uppy/dashboard/src/index.js

@@ -1,6 +1,5 @@
 const { h } = require('preact')
 const { h } = require('preact')
 const { UIPlugin } = require('@uppy/core')
 const { UIPlugin } = require('@uppy/core')
-const Translator = require('@uppy/utils/lib/Translator')
 const StatusBar = require('@uppy/status-bar')
 const StatusBar = require('@uppy/status-bar')
 const Informer = require('@uppy/informer')
 const Informer = require('@uppy/informer')
 const ThumbnailGenerator = require('@uppy/thumbnail-generator')
 const ThumbnailGenerator = require('@uppy/thumbnail-generator')
@@ -41,6 +40,8 @@ function defaultPickerIcon () {
 module.exports = class Dashboard extends UIPlugin {
 module.exports = class Dashboard extends UIPlugin {
   static VERSION = require('../package.json').version
   static VERSION = require('../package.json').version
 
 
+  #openFileEditorWhenFilesAdded
+
   constructor (uppy, opts) {
   constructor (uppy, opts) {
     super(uppy, opts)
     super(uppy, opts)
     this.id = this.opts.id || 'Dashboard'
     this.id = this.opts.id || 'Dashboard'
@@ -241,7 +242,7 @@ module.exports = class Dashboard extends UIPlugin {
 
 
   canEditFile = (file) => {
   canEditFile = (file) => {
     const { targets } = this.getPluginState()
     const { targets } = this.getPluginState()
-    const editors = this._getEditors(targets)
+    const editors = this.#getEditors(targets)
 
 
     return editors.some((target) => (
     return editors.some((target) => (
       this.uppy.getPlugin(target.id).canEditFile(file)
       this.uppy.getPlugin(target.id).canEditFile(file)
@@ -250,7 +251,7 @@ module.exports = class Dashboard extends UIPlugin {
 
 
   openFileEditor = (file) => {
   openFileEditor = (file) => {
     const { targets } = this.getPluginState()
     const { targets } = this.getPluginState()
-    const editors = this._getEditors(targets)
+    const editors = this.#getEditors(targets)
 
 
     this.setPluginState({
     this.setPluginState({
       showFileEditor: true,
       showFileEditor: true,
@@ -265,7 +266,7 @@ module.exports = class Dashboard extends UIPlugin {
 
 
   saveFileEditor = () => {
   saveFileEditor = () => {
     const { targets } = this.getPluginState()
     const { targets } = this.getPluginState()
-    const editors = this._getEditors(targets)
+    const editors = this.#getEditors(targets)
 
 
     editors.forEach((editor) => {
     editors.forEach((editor) => {
       this.uppy.getPlugin(editor.id).save()
       this.uppy.getPlugin(editor.id).save()
@@ -315,7 +316,8 @@ module.exports = class Dashboard extends UIPlugin {
 
 
   closeModal = (opts = {}) => {
   closeModal = (opts = {}) => {
     const {
     const {
-      manualClose = true, // Whether the modal is being closed by the user (`true`) or by other means (e.g. browser back button)
+      // Whether the modal is being closed by the user (`true`) or by other means (e.g. browser back button)
+      manualClose = true,
     } = opts
     } = opts
 
 
     const { isHidden, isClosing } = this.getPluginState()
     const { isHidden, isClosing } = this.getPluginState()
@@ -364,9 +366,11 @@ module.exports = class Dashboard extends UIPlugin {
     if (manualClose) {
     if (manualClose) {
       if (this.opts.browserBackButtonClose) {
       if (this.opts.browserBackButtonClose) {
         // Make sure that the latest entry in the history state is our modal name
         // Make sure that the latest entry in the history state is our modal name
-        if (history.state && history.state[this.modalName]) {
+        // eslint-disable-next-line no-restricted-globals
+        if (history.state?.[this.modalName]) {
           // Go back in history to clear out the entry we created (ultimately closing the modal)
           // Go back in history to clear out the entry we created (ultimately closing the modal)
-          history.go(-1)
+          // eslint-disable-next-line no-restricted-globals
+          history.back()
         }
         }
       }
       }
     }
     }
@@ -445,9 +449,11 @@ module.exports = class Dashboard extends UIPlugin {
   }
   }
 
 
   // ___Why make insides of Dashboard invisible until first ResizeObserver event is emitted?
   // ___Why make insides of Dashboard invisible until first ResizeObserver event is emitted?
-  //    ResizeOberserver doesn't emit the first resize event fast enough, users can see the jump from one .uppy-size-- to another (e.g. in Safari)
+  //    ResizeOberserver doesn't emit the first resize event fast enough, users can see the jump from one .uppy-size-- to
+  //    another (e.g. in Safari)
   // ___Why not apply visibility property to .uppy-Dashboard-inner?
   // ___Why not apply visibility property to .uppy-Dashboard-inner?
-  //    Because ideally, acc to specs, ResizeObserver should see invisible elements as of width 0. So even though applying invisibility to .uppy-Dashboard-inner works now, it may not work in the future.
+  //    Because ideally, acc to specs, ResizeObserver should see invisible elements as of width 0. So even though applying
+  //    invisibility to .uppy-Dashboard-inner works now, it may not work in the future.
   startListeningToResize = () => {
   startListeningToResize = () => {
     // Watch for Dashboard container (`.uppy-Dashboard-inner`) resize
     // Watch for Dashboard container (`.uppy-Dashboard-inner`) resize
     // and update containerWidth/containerHeight in plugin state accordingly.
     // and update containerWidth/containerHeight in plugin state accordingly.
@@ -531,9 +537,12 @@ module.exports = class Dashboard extends UIPlugin {
 
 
   updateBrowserHistory = () => {
   updateBrowserHistory = () => {
     // Ensure history state does not already contain our modal name to avoid double-pushing
     // Ensure history state does not already contain our modal name to avoid double-pushing
-    if (!history.state || !history.state[this.modalName]) {
+    // eslint-disable-next-line no-restricted-globals
+    if (!history.state?.[this.modalName]) {
       // Push to history so that the page is not lost on browser back button press
       // Push to history so that the page is not lost on browser back button press
+      // eslint-disable-next-line no-restricted-globals
       history.pushState({
       history.pushState({
+        // eslint-disable-next-line no-restricted-globals
         ...history.state,
         ...history.state,
         [this.modalName]: true,
         [this.modalName]: true,
       }, '')
       }, '')
@@ -549,11 +558,15 @@ module.exports = class Dashboard extends UIPlugin {
       this.closeModal({ manualClose: false })
       this.closeModal({ manualClose: false })
     }
     }
 
 
-    // When the browser back button is pressed and uppy is now the latest entry in the history but the modal is closed, fix the history by removing the uppy history entry
-    // This occurs when another entry is added into the history state while the modal is open, and then the modal gets manually closed
+    // When the browser back button is pressed and uppy is now the latest entry
+    // in the history but the modal is closed, fix the history by removing the
+    // uppy history entry.
+    // This occurs when another entry is added into the history state while the
+    // modal is open, and then the modal gets manually closed.
     // Solves PR #575 (https://github.com/transloadit/uppy/pull/575)
     // Solves PR #575 (https://github.com/transloadit/uppy/pull/575)
-    if (!this.isModalOpen() && event.state && event.state[this.modalName]) {
-      history.go(-1)
+    if (!this.isModalOpen() && event.state?.[this.modalName]) {
+      // eslint-disable-next-line no-restricted-globals
+      history.back()
     }
     }
   }
   }
 
 
@@ -597,7 +610,8 @@ module.exports = class Dashboard extends UIPlugin {
     }
     }
 
 
     // 1. Add a small (+) icon on drop
     // 1. Add a small (+) icon on drop
-    // (and prevent browsers from interpreting this as files being _moved_ into the browser, https://github.com/transloadit/uppy/issues/1978)
+    // (and prevent browsers from interpreting this as files being _moved_ into the
+    // browser, https://github.com/transloadit/uppy/issues/1978).
     event.dataTransfer.dropEffect = 'copy'
     event.dataTransfer.dropEffect = 'copy'
 
 
     clearTimeout(this.removeDragOverClassTimeout)
     clearTimeout(this.removeDragOverClassTimeout)
@@ -613,7 +627,8 @@ module.exports = class Dashboard extends UIPlugin {
     }
     }
 
 
     clearTimeout(this.removeDragOverClassTimeout)
     clearTimeout(this.removeDragOverClassTimeout)
-    // Timeout against flickering, this solution is taken from drag-drop library. Solution with 'pointer-events: none' didn't work across browsers.
+    // Timeout against flickering, this solution is taken from drag-drop library.
+    // Solution with 'pointer-events: none' didn't work across browsers.
     this.removeDragOverClassTimeout = setTimeout(() => {
     this.removeDragOverClassTimeout = setTimeout(() => {
       this.setPluginState({ isDraggingOver: false })
       this.setPluginState({ isDraggingOver: false })
     }, 50)
     }, 50)
@@ -668,7 +683,8 @@ module.exports = class Dashboard extends UIPlugin {
   }
   }
 
 
   /**
   /**
-   * We cancel thumbnail requests when a file item component unmounts to avoid clogging up the queue when the user scrolls past many elements.
+   * We cancel thumbnail requests when a file item component unmounts to avoid
+   * clogging up the queue when the user scrolls past many elements.
    */
    */
   handleCancelThumbnail = (file) => {
   handleCancelThumbnail = (file) => {
     if (!this.opts.waitForThumbnailsBeforeUpload) {
     if (!this.opts.waitForThumbnailsBeforeUpload) {
@@ -681,11 +697,14 @@ module.exports = class Dashboard extends UIPlugin {
     if (event.keyCode === TAB_KEY) trapFocus.forInline(event, this.getPluginState().activeOverlayType, this.el)
     if (event.keyCode === TAB_KEY) trapFocus.forInline(event, this.getPluginState().activeOverlayType, this.el)
   }
   }
 
 
-  // ___Why do we listen to the 'paste' event on a document instead of onPaste={props.handlePaste} prop, or this.el.addEventListener('paste')?
+  // ___Why do we listen to the 'paste' event on a document instead of onPaste={props.handlePaste} prop,
+  //    or this.el.addEventListener('paste')?
   //    Because (at least) Chrome doesn't handle paste if focus is on some button, e.g. 'My Device'.
   //    Because (at least) Chrome doesn't handle paste if focus is on some button, e.g. 'My Device'.
-  //    => Therefore, the best option is to listen to all 'paste' events, and only react to them when we are focused on our particular Uppy instance.
+  //    => Therefore, the best option is to listen to all 'paste' events, and only react to them when we are focused on our
+  //       particular Uppy instance.
   // ___Why do we still need onPaste={props.handlePaste} for the DashboardUi?
   // ___Why do we still need onPaste={props.handlePaste} for the DashboardUi?
-  //    Because if we click on the 'Drop files here' caption e.g., `document.activeElement` will be 'body'. Which means our standard determination of whether we're pasting into our Uppy instance won't work.
+  //    Because if we click on the 'Drop files here' caption e.g., `document.activeElement` will be 'body'. Which means our
+  //    standard determination of whether we're pasting into our Uppy instance won't work.
   //    => Therefore, we need a traditional onPaste={props.handlePaste} handler too.
   //    => Therefore, we need a traditional onPaste={props.handlePaste} handler too.
   handlePasteOnBody = (event) => {
   handlePasteOnBody = (event) => {
     const isFocusInOverlay = this.el.contains(document.activeElement)
     const isFocusInOverlay = this.el.contains(document.activeElement)
@@ -742,7 +761,7 @@ module.exports = class Dashboard extends UIPlugin {
     }
     }
 
 
     if (this.opts.autoOpenFileEditor) {
     if (this.opts.autoOpenFileEditor) {
-      this.uppy.on('files-added', this._openFileEditorWhenFilesAdded)
+      this.uppy.on('files-added', this.#openFileEditorWhenFilesAdded)
     }
     }
   }
   }
 
 
@@ -770,7 +789,7 @@ module.exports = class Dashboard extends UIPlugin {
     }
     }
 
 
     if (this.opts.autoOpenFileEditor) {
     if (this.opts.autoOpenFileEditor) {
-      this.uppy.off('files-added', this._openFileEditorWhenFilesAdded)
+      this.uppy.off('files-added', this.#openFileEditorWhenFilesAdded)
     }
     }
   }
   }
 
 
@@ -785,14 +804,20 @@ module.exports = class Dashboard extends UIPlugin {
       // If update is connected to showing the Informer - let the screen reader calmly read it.
       // If update is connected to showing the Informer - let the screen reader calmly read it.
       isInformerHidden
       isInformerHidden
       && (
       && (
-        // If we are in a modal - always superfocus without concern for other elements on the page (user is unlikely to want to interact with the rest of the page)
+        // If we are in a modal - always superfocus without concern for other elements
+        // on the page (user is unlikely to want to interact with the rest of the page)
         isModal
         isModal
         // If we are already inside of Uppy, or
         // If we are already inside of Uppy, or
         || isFocusInUppy
         || isFocusInUppy
         // If we are not focused on anything BUT we have already, at least once, focused on uppy
         // If we are not focused on anything BUT we have already, at least once, focused on uppy
-        //   1. We focus when isFocusNowhere, because when the element we were focused on disappears (e.g. an overlay), - focus gets lost. If user is typing something somewhere else on the page, - focus won't be 'nowhere'.
-        //   2. We only focus when focus is nowhere AND this.ifFocusedOnUppyRecently, to avoid focus jumps if we do something else on the page.
-        //   [Practical check] Without '&& this.ifFocusedOnUppyRecently', in Safari, in inline mode, when file is uploading, - navigate via tab to the checkbox, try to press space multiple times. Focus will jump to Uppy.
+        //   1. We focus when isFocusNowhere, because when the element we were focused
+        //      on disappears (e.g. an overlay), - focus gets lost. If user is typing
+        //      something somewhere else on the page, - focus won't be 'nowhere'.
+        //   2. We only focus when focus is nowhere AND this.ifFocusedOnUppyRecently,
+        //      to avoid focus jumps if we do something else on the page.
+        //   [Practical check] Without '&& this.ifFocusedOnUppyRecently', in Safari, in inline mode,
+        //                     when file is uploading, - navigate via tab to the checkbox,
+        //                     try to press space multiple times. Focus will jump to Uppy.
         || (isFocusNowhere && this.ifFocusedOnUppyRecently)
         || (isFocusNowhere && this.ifFocusedOnUppyRecently)
       )
       )
     ) {
     ) {
@@ -820,7 +845,7 @@ module.exports = class Dashboard extends UIPlugin {
     this.toggleFileCard(false, fileID)
     this.toggleFileCard(false, fileID)
   }
   }
 
 
-  _attachRenderFunctionToTarget = (target) => {
+  #attachRenderFunctionToTarget = (target) => {
     const plugin = this.uppy.getPlugin(target.id)
     const plugin = this.uppy.getPlugin(target.id)
     return {
     return {
       ...target,
       ...target,
@@ -829,7 +854,7 @@ module.exports = class Dashboard extends UIPlugin {
     }
     }
   }
   }
 
 
-  _isTargetSupported = (target) => {
+  #isTargetSupported = (target) => {
     const plugin = this.uppy.getPlugin(target.id)
     const plugin = this.uppy.getPlugin(target.id)
     // If the plugin does not provide a `supported` check, assume the plugin works everywhere.
     // If the plugin does not provide a `supported` check, assume the plugin works everywhere.
     if (typeof plugin.isSupported !== 'function') {
     if (typeof plugin.isSupported !== 'function') {
@@ -838,22 +863,22 @@ module.exports = class Dashboard extends UIPlugin {
     return plugin.isSupported()
     return plugin.isSupported()
   }
   }
 
 
-  _getAcquirers = memoize((targets) => {
+  #getAcquirers = memoize((targets) => {
     return targets
     return targets
-      .filter(target => target.type === 'acquirer' && this._isTargetSupported(target))
-      .map(this._attachRenderFunctionToTarget)
+      .filter(target => target.type === 'acquirer' && this.#isTargetSupported(target))
+      .map(this.#attachRenderFunctionToTarget)
   })
   })
 
 
-  _getProgressIndicators = memoize((targets) => {
+  #getProgressIndicators = memoize((targets) => {
     return targets
     return targets
       .filter(target => target.type === 'progressindicator')
       .filter(target => target.type === 'progressindicator')
-      .map(this._attachRenderFunctionToTarget)
+      .map(this.#attachRenderFunctionToTarget)
   })
   })
 
 
-  _getEditors = memoize((targets) => {
+  #getEditors = memoize((targets) => {
     return targets
     return targets
       .filter(target => target.type === 'editor')
       .filter(target => target.type === 'editor')
-      .map(this._attachRenderFunctionToTarget)
+      .map(this.#attachRenderFunctionToTarget)
   })
   })
 
 
   render = (state) => {
   render = (state) => {
@@ -874,9 +899,9 @@ module.exports = class Dashboard extends UIPlugin {
       isAllPaused,
       isAllPaused,
     } = this.uppy.getObjectOfFilesPerState()
     } = this.uppy.getObjectOfFilesPerState()
 
 
-    const acquirers = this._getAcquirers(pluginState.targets)
-    const progressindicators = this._getProgressIndicators(pluginState.targets)
-    const editors = this._getEditors(pluginState.targets)
+    const acquirers = this.#getAcquirers(pluginState.targets)
+    const progressindicators = this.#getProgressIndicators(pluginState.targets)
+    const editors = this.#getEditors(pluginState.targets)
 
 
     let theme
     let theme
     if (this.opts.theme === 'auto') {
     if (this.opts.theme === 'auto') {
@@ -887,7 +912,8 @@ module.exports = class Dashboard extends UIPlugin {
 
 
     if (['files', 'folders', 'both'].indexOf(this.opts.fileManagerSelectionType) < 0) {
     if (['files', 'folders', 'both'].indexOf(this.opts.fileManagerSelectionType) < 0) {
       this.opts.fileManagerSelectionType = 'files'
       this.opts.fileManagerSelectionType = 'files'
-      console.error(`Unsupported option for "fileManagerSelectionType". Using default of "${this.opts.fileManagerSelectionType}".`)
+      // eslint-disable-next-line no-console
+      console.warn(`Unsupported option for "fileManagerSelectionType". Using default of "${this.opts.fileManagerSelectionType}".`)
     }
     }
 
 
     return DashboardUI({
     return DashboardUI({

+ 1 - 0
packages/@uppy/dashboard/src/utils/copyToClipboard.js

@@ -32,6 +32,7 @@ module.exports = function copyToClipboard (textToCopy, fallbackString) {
 
 
     const magicCopyFailed = () => {
     const magicCopyFailed = () => {
       document.body.removeChild(textArea)
       document.body.removeChild(textArea)
+      // eslint-disable-next-line no-alert
       window.prompt(fallbackString, textToCopy)
       window.prompt(fallbackString, textToCopy)
       resolve()
       resolve()
     }
     }

+ 12 - 5
packages/@uppy/dashboard/src/utils/createSuperFocus.js

@@ -5,8 +5,12 @@ const getActiveOverlayEl = require('./getActiveOverlayEl')
 /*
 /*
   Focuses on some element in the currently topmost overlay.
   Focuses on some element in the currently topmost overlay.
 
 
-  1. If there are some [data-uppy-super-focusable] elements rendered already - focuses on the first superfocusable element, and leaves focus up to the control of a user (until currently focused element disappears from the screen [which can happen when overlay changes, or, e.g., when we click on a folder in googledrive]).
-  2. If there are no [data-uppy-super-focusable] elements yet (or ever) - focuses on the first focusable element, but switches focus if superfocusable elements appear on next render.
+  1. If there are some [data-uppy-super-focusable] elements rendered already - focuses
+     on the first superfocusable element, and leaves focus up to the control of
+     a user (until currently focused element disappears from the screen [which
+     can happen when overlay changes, or, e.g., when we click on a folder in googledrive]).
+  2. If there are no [data-uppy-super-focusable] elements yet (or ever) - focuses
+     on the first focusable element, but switches focus if superfocusable elements appear on next render.
 */
 */
 module.exports = function createSuperFocus () {
 module.exports = function createSuperFocus () {
   let lastFocusWasOnSuperFocusableEl = false
   let lastFocusWasOnSuperFocusableEl = false
@@ -15,13 +19,15 @@ module.exports = function createSuperFocus () {
     const overlayEl = getActiveOverlayEl(dashboardEl, activeOverlayType)
     const overlayEl = getActiveOverlayEl(dashboardEl, activeOverlayType)
 
 
     const isFocusInOverlay = overlayEl.contains(document.activeElement)
     const isFocusInOverlay = overlayEl.contains(document.activeElement)
-    // If focus is already in the topmost overlay, AND on last update we focused on the superfocusable element - then leave focus up to the user.
+    // If focus is already in the topmost overlay, AND on last update we focused on the superfocusable
+    // element - then leave focus up to the user.
     // [Practical check] without this line, typing in the search input in googledrive overlay won't work.
     // [Practical check] without this line, typing in the search input in googledrive overlay won't work.
     if (isFocusInOverlay && lastFocusWasOnSuperFocusableEl) return
     if (isFocusInOverlay && lastFocusWasOnSuperFocusableEl) return
 
 
     const superFocusableEl = overlayEl.querySelector('[data-uppy-super-focusable]')
     const superFocusableEl = overlayEl.querySelector('[data-uppy-super-focusable]')
     // If we are already in the topmost overlay, AND there are no super focusable elements yet, - leave focus up to the user.
     // If we are already in the topmost overlay, AND there are no super focusable elements yet, - leave focus up to the user.
-    // [Practical check] without this line, if you are in an empty folder in google drive, and something's uploading in the bg, - focus will be jumping to Done all the time.
+    // [Practical check] without this line, if you are in an empty folder in google drive, and something's uploading in the
+    // bg, - focus will be jumping to Done all the time.
     if (isFocusInOverlay && !superFocusableEl) return
     if (isFocusInOverlay && !superFocusableEl) return
 
 
     if (superFocusableEl) {
     if (superFocusableEl) {
@@ -35,7 +41,8 @@ module.exports = function createSuperFocus () {
   }
   }
 
 
   // ___Why do we need to debounce?
   // ___Why do we need to debounce?
-  //    1. To deal with animations: overlay changes via animations, which results in the DOM updating AFTER plugin.update() already executed.
+  //    1. To deal with animations: overlay changes via animations, which results in the DOM updating AFTER plugin.update()
+  //       already executed.
   //    [Practical check] without debounce, if we open the Url overlay, and click 'Done', Dashboard won't get focused again.
   //    [Practical check] without debounce, if we open the Url overlay, and click 'Done', Dashboard won't get focused again.
   //    [Practical check] if we delay 250ms instead of 260ms - IE11 won't get focused in same situation.
   //    [Practical check] if we delay 250ms instead of 260ms - IE11 won't get focused in same situation.
   //    2. Performance: there can be many state update()s in a second, and this function is called every time.
   //    2. Performance: there can be many state update()s in a second, and this function is called every time.

+ 11 - 5
packages/@uppy/dashboard/src/utils/trapFocus.js

@@ -19,8 +19,11 @@ function focusOnLastNode (event, nodes) {
 }
 }
 
 
 // ___Why not just use (focusedItemIndex === -1)?
 // ___Why not just use (focusedItemIndex === -1)?
-//    Firefox thinks <ul> is focusable, but we don't have <ul>s in our FOCUSABLE_ELEMENTS. Which means that if we tab into the <ul>, code will think that we are not in the active overlay, and we should focusOnFirstNode() of the currently active overlay!
-//    [Practical check] if we use (focusedItemIndex === -1), instagram provider in firefox will never get focus on its pics in the <ul>.
+//    Firefox thinks <ul> is focusable, but we don't have <ul>s in our FOCUSABLE_ELEMENTS. Which means that if we tab into
+//    the <ul>, code will think that we are not in the active overlay, and we should focusOnFirstNode() of the currently
+//    active overlay!
+//    [Practical check] if we use (focusedItemIndex === -1), instagram provider in firefox will never get focus on its pics
+//    in the <ul>.
 function isFocusInOverlay (activeOverlayEl) {
 function isFocusInOverlay (activeOverlayEl) {
   return activeOverlayEl.contains(document.activeElement)
   return activeOverlayEl.contains(document.activeElement)
 }
 }
@@ -31,8 +34,10 @@ function trapFocus (event, activeOverlayType, dashboardEl) {
 
 
   const focusedItemIndex = focusableNodes.indexOf(document.activeElement)
   const focusedItemIndex = focusableNodes.indexOf(document.activeElement)
 
 
-  // If we pressed tab, and focus is not yet within the current overlay - focus on the first element within the current overlay.
-  // This is a safety measure (for when user returns from another tab e.g.), most plugins will try to focus on some important element as it loads.
+  // If we pressed tab, and focus is not yet within the current overlay - focus on
+  // the first element within the current overlay.
+  // This is a safety measure (for when user returns from another tab e.g.), most
+  // plugins will try to focus on some important element as it loads.
   if (!isFocusInOverlay(activeOverlayEl)) {
   if (!isFocusInOverlay(activeOverlayEl)) {
     focusOnFirstNode(event, focusableNodes)
     focusOnFirstNode(event, focusableNodes)
   // If we pressed shift + tab, and we're on the first element of a modal
   // If we pressed shift + tab, and we're on the first element of a modal
@@ -45,7 +50,8 @@ function trapFocus (event, activeOverlayType, dashboardEl) {
 }
 }
 
 
 module.exports = {
 module.exports = {
-  // Traps focus inside of the currently open overlay (e.g. Dashboard, or e.g. Instagram), never lets focus disappear from the modal.
+  // Traps focus inside of the currently open overlay (e.g. Dashboard, or e.g. Instagram),
+  // never lets focus disappear from the modal.
   forModal: (event, activeOverlayType, dashboardEl) => {
   forModal: (event, activeOverlayType, dashboardEl) => {
     trapFocus(event, activeOverlayType, dashboardEl)
     trapFocus(event, activeOverlayType, dashboardEl)
   },
   },

+ 0 - 5
packages/@uppy/golden-retriever/src/IndexedDBStore.js

@@ -211,11 +211,6 @@ class IndexedDBStore {
           const cursor = event.target.result
           const cursor = event.target.result
           if (cursor) {
           if (cursor) {
             const entry = cursor.value
             const entry = cursor.value
-            console.log(
-              '[IndexedDBStore] Deleting record', entry.fileID,
-              'of size', prettierBytes(entry.data.size),
-              '- expired on', new Date(entry.expires)
-            )
             cursor.delete() // Ignoring return value … it's not terrible if this goes wrong.
             cursor.delete() // Ignoring return value … it's not terrible if this goes wrong.
             cursor.continue()
             cursor.continue()
           } else {
           } else {

+ 1 - 4
packages/@uppy/golden-retriever/src/ServiceWorker.js

@@ -1,4 +1,5 @@
 /* globals clients */
 /* globals clients */
+/* eslint-disable no-restricted-globals */
 
 
 const fileCache = Object.create(null)
 const fileCache = Object.create(null)
 
 
@@ -10,8 +11,6 @@ function getCache (name) {
 }
 }
 
 
 self.addEventListener('install', (event) => {
 self.addEventListener('install', (event) => {
-  console.log('Installing Uppy Service Worker...')
-
   event.waitUntil(Promise.resolve()
   event.waitUntil(Promise.resolve()
     .then(() => self.skipWaiting()))
     .then(() => self.skipWaiting()))
 })
 })
@@ -30,12 +29,10 @@ function sendMessageToAllClients (msg) {
 
 
 function addFile (store, file) {
 function addFile (store, file) {
   getCache(store)[file.id] = file.data
   getCache(store)[file.id] = file.data
-  console.log('Added file blob to service worker cache:', file.data)
 }
 }
 
 
 function removeFile (store, fileID) {
 function removeFile (store, fileID) {
   delete getCache(store)[fileID]
   delete getCache(store)[fileID]
-  console.log('Removed file blob from service worker cache:', fileID)
 }
 }
 
 
 function getFiles (store) {
 function getFiles (store) {

+ 1 - 1
packages/@uppy/informer/src/TransitionGroup.js

@@ -1,7 +1,7 @@
+/* eslint-disable */
 /**
 /**
  * @source https://github.com/developit/preact-transition-group
  * @source https://github.com/developit/preact-transition-group
  */
  */
-/* eslint-disable */
 'use strict'
 'use strict'
 
 
 const { Component, cloneElement, h, toChildArray } = require('preact')
 const { Component, cloneElement, h, toChildArray } = require('preact')

+ 1 - 1
packages/@uppy/locales/src/ar_SA.js

@@ -137,7 +137,7 @@ ar_SA.strings = {
   openFolderNamed: 'افتح المجلد %{name}',
   openFolderNamed: 'افتح المجلد %{name}',
 }
 }
 
 
-ar_SA.pluralize = function (n) {
+ar_SA.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/bg_BG.js

@@ -143,7 +143,7 @@ bg_BG.strings = {
   },
   },
 }
 }
 
 
-bg_BG.pluralize = function (count) {
+bg_BG.pluralize = function pluralize (count) {
   if (count === 1) {
   if (count === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/cs_CZ.js

@@ -138,7 +138,7 @@ cs_CZ.strings = {
   },
   },
 }
 }
 
 
-cs_CZ.pluralize = function (n) {
+cs_CZ.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/da_DK.js

@@ -137,7 +137,7 @@ da_DK.strings = {
   },
   },
 }
 }
 
 
-da_DK.pluralize = function (n) {
+da_DK.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/de_DE.js

@@ -177,7 +177,7 @@ de_DE.strings = {
   zoomOut: 'Verkleinern',
   zoomOut: 'Verkleinern',
 }
 }
 
 
-de_DE.pluralize = function (count) {
+de_DE.pluralize = function pluralize (count) {
   if (count === 1) {
   if (count === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/el_GR.js

@@ -137,7 +137,7 @@ el_GR.strings = {
   },
   },
 }
 }
 
 
-el_GR.pluralize = function (n) {
+el_GR.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 1
     return 1
   }
   }

+ 1 - 1
packages/@uppy/locales/src/en_US.js

@@ -178,7 +178,7 @@ en_US.strings = {
   zoomOut: 'Zoom out',
   zoomOut: 'Zoom out',
 }
 }
 
 
-en_US.pluralize = function (count) {
+en_US.pluralize = function pluralize (count) {
   if (count === 1) {
   if (count === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/es_ES.js

@@ -138,7 +138,7 @@ es_ES.strings = {
   openFolderNamed: 'Carpeta abierta %{name}',
   openFolderNamed: 'Carpeta abierta %{name}',
 }
 }
 
 
-es_ES.pluralize = function (n) {
+es_ES.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/fa_IR.js

@@ -171,7 +171,7 @@ fa_IR.strings = {
 
 
 }
 }
 
 
-fa_IR.pluralize = function (n) {
+fa_IR.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/fi_FI.js

@@ -144,7 +144,7 @@ fi_FI.strings = {
   recording: 'Tallennetaan',
   recording: 'Tallennetaan',
 }
 }
 
 
-fi_FI.pluralize = function (n) {
+fi_FI.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/fr_FR.js

@@ -154,7 +154,7 @@ fr_FR.strings = {
   },
   },
 }
 }
 
 
-fr_FR.pluralize = function (n) {
+fr_FR.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/gl_ES.js

@@ -137,7 +137,7 @@ gl_ES.strings = {
   openFolderNamed: 'Cartafol aberto %{name}',
   openFolderNamed: 'Cartafol aberto %{name}',
 }
 }
 
 
-gl_ES.pluralize = function (n) {
+gl_ES.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/he_IL.js

@@ -139,7 +139,7 @@ he_IL.strings = {
   },
   },
 }
 }
 
 
-he_IL.pluralize = function (n) {
+he_IL.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/hr_HR.js

@@ -137,7 +137,7 @@ hr_HR.strings = {
   openFolderNamed: 'Otvori mapu %{name}',
   openFolderNamed: 'Otvori mapu %{name}',
 }
 }
 
 
-hr_HR.pluralize = function (n) {
+hr_HR.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/hu_HU.js

@@ -139,7 +139,7 @@ hu_HU.strings = {
   openFolderNamed: 'Nyitott mappa %{name}',
   openFolderNamed: 'Nyitott mappa %{name}',
 }
 }
 
 
-hu_HU.pluralize = function (n) {
+hu_HU.pluralize = function pluralize (n) {
   return 0
   return 0
 }
 }
 
 

+ 1 - 1
packages/@uppy/locales/src/id_ID.js

@@ -137,7 +137,7 @@ id_ID.strings = {
   },
   },
 }
 }
 
 
-id_ID.pluralize = function (n) {
+id_ID.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/is_IS.js

@@ -144,7 +144,7 @@ is_IS.strings = {
   },
   },
 }
 }
 
 
-is_IS.pluralize = function (n) {
+is_IS.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/it_IT.js

@@ -137,7 +137,7 @@ it_IT.strings = {
   openFolderNamed: 'Cartella aperta %{name}',
   openFolderNamed: 'Cartella aperta %{name}',
 }
 }
 
 
-it_IT.pluralize = function (n) {
+it_IT.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/ja_JP.js

@@ -137,7 +137,7 @@ ja_JP.strings = {
   openFolderNamed: '開いたフォルダ %{name}',
   openFolderNamed: '開いたフォルダ %{name}',
 }
 }
 
 
-ja_JP.pluralize = function (n) {
+ja_JP.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/ko_KR.js

@@ -96,7 +96,7 @@ ko_KR.strings = {
   youHaveToAtLeastSelectX: '최소 %{smart_count}개의 파일을 선택해야 합니다',
   youHaveToAtLeastSelectX: '최소 %{smart_count}개의 파일을 선택해야 합니다',
 }
 }
 
 
-ko_KR.pluralize = function (n) {
+ko_KR.pluralize = function pluralize (n) {
   return 0
   return 0
 }
 }
 
 

+ 1 - 1
packages/@uppy/locales/src/nb_NO.js

@@ -158,7 +158,7 @@ nb_NO.strings = {
   zoomOut: 'Zoom ut',
   zoomOut: 'Zoom ut',
 }
 }
 
 
-nb_NO.pluralize = function (count) {
+nb_NO.pluralize = function pluralize (count) {
   if (count === 1) {
   if (count === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/nl_NL.js

@@ -130,7 +130,7 @@ nl_NL.strings = {
   openFolderNamed: 'Open map %{name}',
   openFolderNamed: 'Open map %{name}',
 }
 }
 
 
-nl_NL.pluralize = function (n) {
+nl_NL.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/pl_PL.js

@@ -146,7 +146,7 @@ pl_PL.strings = {
   },
   },
 }
 }
 
 
-pl_PL.pluralize = function (n) {
+pl_PL.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/pt_BR.js

@@ -131,7 +131,7 @@ pt_BR.strings = {
   openFolderNamed: 'Pasta aberta %{name}',
   openFolderNamed: 'Pasta aberta %{name}',
 }
 }
 
 
-pt_BR.pluralize = function (n) {
+pt_BR.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/pt_PT.js

@@ -131,7 +131,7 @@ pt_PT.strings = {
   openFolderNamed: 'Pasta aberta %{name}',
   openFolderNamed: 'Pasta aberta %{name}',
 }
 }
 
 
-pt_PT.pluralize = function (n) {
+pt_PT.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/ro_RO.js

@@ -136,7 +136,7 @@ ro_RO.strings = {
   },
   },
 }
 }
 
 
-ro_RO.pluralize = function (count) {
+ro_RO.pluralize = function pluralize (count) {
   if (count === 1) {
   if (count === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/ru_RU.js

@@ -139,7 +139,7 @@ ru_RU.strings = {
   openFolderNamed: 'Открыть папку %{name}',
   openFolderNamed: 'Открыть папку %{name}',
 }
 }
 
 
-ru_RU.pluralize = function (n) {
+ru_RU.pluralize = function pluralize (n) {
   if (n % 10 === 1 && n % 100 !== 11) {
   if (n % 10 === 1 && n % 100 !== 11) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/sk_SK.js

@@ -167,7 +167,7 @@ sk_SK.strings = {
   zoomOut: 'Oddialiť',
   zoomOut: 'Oddialiť',
 }
 }
 
 
-sk_SK.pluralize = function (count) {
+sk_SK.pluralize = function pluralize (count) {
   if (count === 1) {
   if (count === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/sr_RS_Cyrillic.js

@@ -137,7 +137,7 @@ sr_RS_Cyrillic.strings = {
   openFolderNamed: 'Отвори фолдер %{name}',
   openFolderNamed: 'Отвори фолдер %{name}',
 }
 }
 
 
-sr_RS_Cyrillic.pluralize = function (n) {
+sr_RS_Cyrillic.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/sr_RS_Latin.js

@@ -137,7 +137,7 @@ sr_RS_Latin.strings = {
   openFolderNamed: 'Otvori folder %{name}',
   openFolderNamed: 'Otvori folder %{name}',
 }
 }
 
 
-sr_RS_Latin.pluralize = function (n) {
+sr_RS_Latin.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/sv_SE.js

@@ -137,7 +137,7 @@ sv_SE.strings = {
   },
   },
 }
 }
 
 
-sv_SE.pluralize = function (n) {
+sv_SE.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/th_TH.js

@@ -156,7 +156,7 @@ th_TH.strings = {
   zoomOut: 'ซูมออก',
   zoomOut: 'ซูมออก',
 }
 }
 
 
-th_TH.pluralize = function (n) {
+th_TH.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/tr_TR.js

@@ -137,7 +137,7 @@ tr_TR.strings = {
   openFolderNamed: 'Açık dosya %{name}',
   openFolderNamed: 'Açık dosya %{name}',
 }
 }
 
 
-tr_TR.pluralize = function (n) {
+tr_TR.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/uk_UA.js

@@ -137,7 +137,7 @@ uk_UA.strings = {
   openFolderNamed: 'Відкрити теку %{name}',
   openFolderNamed: 'Відкрити теку %{name}',
 }
 }
 
 
-uk_UA.pluralize = function (n) {
+uk_UA.pluralize = function pluralize (n) {
   if (n % 10 === 1 && n % 100 !== 11) {
   if (n % 10 === 1 && n % 100 !== 11) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/vi_VN.js

@@ -140,7 +140,7 @@ vi_VN.strings = {
   },
   },
 }
 }
 
 
-vi_VN.pluralize = function (n) {
+vi_VN.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/src/zh_CN.js

@@ -110,7 +110,7 @@ zh_CN.strings = {
 }
 }
 
 
 // There is just one form.
 // There is just one form.
-zh_CN.pluralize = function (n) { return 0 }
+zh_CN.pluralize = function pluralize (n) { return 0 }
 
 
 if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
 if (typeof window !== 'undefined' && typeof window.Uppy !== 'undefined') {
   window.Uppy.locales.zh_CN = zh_CN
   window.Uppy.locales.zh_CN = zh_CN

+ 1 - 1
packages/@uppy/locales/src/zh_TW.js

@@ -146,7 +146,7 @@ zh_TW.strings = {
   },
   },
 }
 }
 
 
-zh_TW.pluralize = function (n) {
+zh_TW.pluralize = function pluralize (n) {
   if (n === 1) {
   if (n === 1) {
     return 0
     return 0
   }
   }

+ 1 - 1
packages/@uppy/locales/template.js

@@ -2,7 +2,7 @@ const en_US = {}
 
 
 en_US.strings = {}
 en_US.strings = {}
 
 
-en_US.pluralize = function (count) {
+en_US.pluralize = function pluralize (count) {
   if (count === 1) {
   if (count === 1) {
     return 0
     return 0
   }
   }

+ 2 - 2
packages/@uppy/provider-views/src/FooterActions.js

@@ -3,12 +3,12 @@ const { h } = require('preact')
 module.exports = (props) => {
 module.exports = (props) => {
   return (
   return (
     <div className="uppy-ProviderBrowser-footer">
     <div className="uppy-ProviderBrowser-footer">
-      <button className="uppy-u-reset uppy-c-btn uppy-c-btn-primary" onClick={props.done}>
+      <button className="uppy-u-reset uppy-c-btn uppy-c-btn-primary" onClick={props.done} type="button">
         {props.i18n('selectX', {
         {props.i18n('selectX', {
           smart_count: props.selected,
           smart_count: props.selected,
         })}
         })}
       </button>
       </button>
-      <button className="uppy-u-reset uppy-c-btn uppy-c-btn-link" onClick={props.cancel}>
+      <button className="uppy-u-reset uppy-c-btn uppy-c-btn-link" onClick={props.cancel} type="button">
         {props.i18n('cancel')}
         {props.i18n('cancel')}
       </button>
       </button>
     </div>
     </div>

+ 1 - 1
packages/@uppy/provider-views/src/Item/components/ItemIcon.js

@@ -36,6 +36,6 @@ module.exports = (props) => {
     case 'video':
     case 'video':
       return <VideoIcon />
       return <VideoIcon />
     default:
     default:
-      return <img src={props.itemIconString} />
+      return <img src={props.itemIconString} alt={props.alt} />
   }
   }
 }
 }

+ 23 - 25
packages/@uppy/provider-views/src/ProviderView/AuthView.js

@@ -1,30 +1,28 @@
-const { h, Component } = require('preact')
+const { h } = require('preact')
 
 
-class AuthView extends Component {
-  render () {
-    const pluginNameComponent = (
-      <span className="uppy-Provider-authTitleName">
-        {this.props.pluginName}
-        <br />
-      </span>
-    )
-    return (
-      <div className="uppy-Provider-auth">
-        <div className="uppy-Provider-authIcon">{this.props.pluginIcon()}</div>
-        <div className="uppy-Provider-authTitle">
-          {this.props.i18nArray('authenticateWithTitle', { pluginName: pluginNameComponent })}
-        </div>
-        <button
-          type="button"
-          className="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Provider-authBtn"
-          onClick={this.props.handleAuth}
-          data-uppy-super-focusable
-        >
-          {this.props.i18nArray('authenticateWith', { pluginName: this.props.pluginName })}
-        </button>
+function AuthView (props) {
+  const pluginNameComponent = (
+    <span className="uppy-Provider-authTitleName">
+      {props.pluginName}
+      <br />
+    </span>
+  )
+  return (
+    <div className="uppy-Provider-auth">
+      <div className="uppy-Provider-authIcon">{props.pluginIcon()}</div>
+      <div className="uppy-Provider-authTitle">
+        {props.i18nArray('authenticateWithTitle', { pluginName: pluginNameComponent })}
       </div>
       </div>
-    )
-  }
+      <button
+        type="button"
+        className="uppy-u-reset uppy-c-btn uppy-c-btn-primary uppy-Provider-authBtn"
+        onClick={props.handleAuth}
+        data-uppy-super-focusable
+      >
+        {props.i18nArray('authenticateWith', { pluginName: props.pluginName })}
+      </button>
+    </div>
+  )
 }
 }
 
 
 module.exports = AuthView
 module.exports = AuthView

+ 19 - 14
packages/@uppy/provider-views/src/ProviderView/ProviderView.js

@@ -11,6 +11,7 @@ const SharedHandler = require('../SharedHandler')
 const CloseWrapper = require('../CloseWrapper')
 const CloseWrapper = require('../CloseWrapper')
 
 
 function getOrigin () {
 function getOrigin () {
+  // eslint-disable-next-line no-restricted-globals
   return location.origin
   return location.origin
 }
 }
 
 
@@ -20,6 +21,10 @@ function getOrigin () {
 module.exports = class ProviderView {
 module.exports = class ProviderView {
   static VERSION = require('../../package.json').version
   static VERSION = require('../../package.json').version
 
 
+  #isHandlingScroll
+
+  #sharedHandler
+
   /**
   /**
    * @param {object} plugin instance of the plugin
    * @param {object} plugin instance of the plugin
    * @param {object} opts
    * @param {object} opts
@@ -27,7 +32,7 @@ module.exports = class ProviderView {
   constructor (plugin, opts) {
   constructor (plugin, opts) {
     this.plugin = plugin
     this.plugin = plugin
     this.provider = opts.provider
     this.provider = opts.provider
-    this._sharedHandler = new SharedHandler(plugin)
+    this.#sharedHandler = new SharedHandler(plugin)
 
 
     // set default options
     // set default options
     const defaultOptions = {
     const defaultOptions = {
@@ -75,7 +80,7 @@ module.exports = class ProviderView {
     // Nothing.
     // Nothing.
   }
   }
 
 
-  _updateFilesAndFolders (res, files, folders) {
+  #updateFilesAndFolders (res, files, folders) {
     this.nextPagePath = res.nextPagePath
     this.nextPagePath = res.nextPagePath
     res.items.forEach((item) => {
     res.items.forEach((item) => {
       if (item.isFolder) {
       if (item.isFolder) {
@@ -104,7 +109,7 @@ module.exports = class ProviderView {
    * @returns {Promise}   Folders/files in folder
    * @returns {Promise}   Folders/files in folder
    */
    */
   getFolder (id, name) {
   getFolder (id, name) {
-    return this._sharedHandler.loaderWrapper(
+    return this.#sharedHandler.loaderWrapper(
       this.provider.list(id),
       this.provider.list(id),
       (res) => {
       (res) => {
         const folders = []
         const folders = []
@@ -121,7 +126,7 @@ module.exports = class ProviderView {
         }
         }
 
 
         this.username = res.username || this.username
         this.username = res.username || this.username
-        this._updateFilesAndFolders(res, files, folders)
+        this.#updateFilesAndFolders(res, files, folders)
         this.plugin.setPluginState({ directories: updatedDirectories })
         this.plugin.setPluginState({ directories: updatedDirectories })
       },
       },
       this.handleError
       this.handleError
@@ -292,7 +297,7 @@ module.exports = class ProviderView {
 
 
     const authWindow = window.open(link, '_blank')
     const authWindow = window.open(link, '_blank')
     const handleToken = (e) => {
     const handleToken = (e) => {
-      if (!this._isOriginAllowed(e.origin, this.plugin.opts.companionAllowedHosts) || e.source !== authWindow) {
+      if (!this.#isOriginAllowed(e.origin, this.plugin.opts.companionAllowedHosts) || e.source !== authWindow) {
         this.plugin.uppy.log(`rejecting event from ${e.origin} vs allowed pattern ${this.plugin.opts.companionAllowedHosts}`)
         this.plugin.uppy.log(`rejecting event from ${e.origin} vs allowed pattern ${this.plugin.opts.companionAllowedHosts}`)
         return
         return
       }
       }
@@ -314,7 +319,7 @@ module.exports = class ProviderView {
     window.addEventListener('message', handleToken)
     window.addEventListener('message', handleToken)
   }
   }
 
 
-  _isOriginAllowed (origin, allowedOrigin) {
+  #isOriginAllowed (origin, allowedOrigin) {
     const getRegex = (value) => {
     const getRegex = (value) => {
       if (typeof value === 'string') {
       if (typeof value === 'string') {
         return new RegExp(`^${value}$`)
         return new RegExp(`^${value}$`)
@@ -343,15 +348,15 @@ module.exports = class ProviderView {
     const scrollPos = e.target.scrollHeight - (e.target.scrollTop + e.target.offsetHeight)
     const scrollPos = e.target.scrollHeight - (e.target.scrollTop + e.target.offsetHeight)
     const path = this.nextPagePath || null
     const path = this.nextPagePath || null
 
 
-    if (scrollPos < 50 && path && !this._isHandlingScroll) {
+    if (scrollPos < 50 && path && !this.#isHandlingScroll) {
       this.provider.list(path)
       this.provider.list(path)
         .then((res) => {
         .then((res) => {
           const { files, folders } = this.plugin.getPluginState()
           const { files, folders } = this.plugin.getPluginState()
-          this._updateFilesAndFolders(res, files, folders)
+          this.#updateFilesAndFolders(res, files, folders)
         }).catch(this.handleError)
         }).catch(this.handleError)
-        .then(() => { this._isHandlingScroll = false }) // always called
+        .then(() => { this.#isHandlingScroll = false }) // always called
 
 
-      this._isHandlingScroll = true
+      this.#isHandlingScroll = true
     }
     }
   }
   }
 
 
@@ -386,7 +391,7 @@ module.exports = class ProviderView {
       return this.addFile(file)
       return this.addFile(file)
     })
     })
 
 
-    this._sharedHandler.loaderWrapper(Promise.all(promises), () => {
+    this.#sharedHandler.loaderWrapper(Promise.all(promises), () => {
       this.clearSelection()
       this.clearSelection()
     }, () => {})
     }, () => {})
   }
   }
@@ -449,11 +454,11 @@ module.exports = class ProviderView {
       username: this.username,
       username: this.username,
       getNextFolder: this.getNextFolder,
       getNextFolder: this.getNextFolder,
       getFolder: this.getFolder,
       getFolder: this.getFolder,
-      filterItems: this._sharedHandler.filterItems,
+      filterItems: this.#sharedHandler.filterItems,
       filterQuery: this.filterQuery,
       filterQuery: this.filterQuery,
       logout: this.logout,
       logout: this.logout,
-      isChecked: this._sharedHandler.isChecked,
-      toggleCheckbox: this._sharedHandler.toggleCheckbox,
+      isChecked: this.#sharedHandler.isChecked,
+      toggleCheckbox: this.#sharedHandler.toggleCheckbox,
       handleScroll: this.handleScroll,
       handleScroll: this.handleScroll,
       listAllFiles: this.listAllFiles,
       listAllFiles: this.listAllFiles,
       done: this.donePicking,
       done: this.donePicking,

+ 20 - 14
packages/@uppy/provider-views/src/SearchProviderView/SearchProviderView.js

@@ -15,6 +15,12 @@ const CloseWrapper = require('../CloseWrapper')
 module.exports = class ProviderView {
 module.exports = class ProviderView {
   static VERSION = require('../../package.json').version
   static VERSION = require('../../package.json').version
 
 
+  #isHandlingScroll
+
+  #searchTerm
+
+  #sharedHandler
+
   /**
   /**
    * @param {object} plugin instance of the plugin
    * @param {object} plugin instance of the plugin
    * @param {object} opts
    * @param {object} opts
@@ -22,7 +28,7 @@ module.exports = class ProviderView {
   constructor (plugin, opts) {
   constructor (plugin, opts) {
     this.plugin = plugin
     this.plugin = plugin
     this.provider = opts.provider
     this.provider = opts.provider
-    this._sharedHandler = new SharedHandler(plugin)
+    this.#sharedHandler = new SharedHandler(plugin)
 
 
     // set default options
     // set default options
     const defaultOptions = {
     const defaultOptions = {
@@ -66,9 +72,9 @@ module.exports = class ProviderView {
     // Nothing.
     // Nothing.
   }
   }
 
 
-  _updateFilesAndInputMode (res, files) {
+  #updateFilesAndInputMode (res, files) {
     this.nextPageQuery = res.nextPageQuery
     this.nextPageQuery = res.nextPageQuery
-    this._searchTerm = res.searchedFor
+    this.#searchTerm = res.searchedFor
     res.items.forEach((item) => { files.push(item) })
     res.items.forEach((item) => { files.push(item) })
     this.plugin.setPluginState({ isInputMode: false, files })
     this.plugin.setPluginState({ isInputMode: false, files })
   }
   }
@@ -83,16 +89,16 @@ module.exports = class ProviderView {
   }
   }
 
 
   search (query) {
   search (query) {
-    if (query && query === this._searchTerm) {
+    if (query && query === this.#searchTerm) {
       // no need to search again as this is the same as the previous search
       // no need to search again as this is the same as the previous search
       this.plugin.setPluginState({ isInputMode: false })
       this.plugin.setPluginState({ isInputMode: false })
       return
       return
     }
     }
 
 
-    return this._sharedHandler.loaderWrapper(
+    return this.#sharedHandler.loaderWrapper(
       this.provider.search(query),
       this.provider.search(query),
       (res) => {
       (res) => {
-        this._updateFilesAndInputMode(res, [])
+        this.#updateFilesAndInputMode(res, [])
       },
       },
       this.handleError
       this.handleError
     )
     )
@@ -159,15 +165,15 @@ module.exports = class ProviderView {
     const scrollPos = e.target.scrollHeight - (e.target.scrollTop + e.target.offsetHeight)
     const scrollPos = e.target.scrollHeight - (e.target.scrollTop + e.target.offsetHeight)
     const query = this.nextPageQuery || null
     const query = this.nextPageQuery || null
 
 
-    if (scrollPos < 50 && query && !this._isHandlingScroll) {
-      this.provider.search(this._searchTerm, query)
+    if (scrollPos < 50 && query && !this.#isHandlingScroll) {
+      this.provider.search(this.#searchTerm, query)
         .then((res) => {
         .then((res) => {
           const { files } = this.plugin.getPluginState()
           const { files } = this.plugin.getPluginState()
-          this._updateFilesAndInputMode(res, files)
+          this.#updateFilesAndInputMode(res, files)
         }).catch(this.handleError)
         }).catch(this.handleError)
-        .then(() => { this._isHandlingScroll = false }) // always called
+        .then(() => { this.#isHandlingScroll = false }) // always called
 
 
-      this._isHandlingScroll = true
+      this.#isHandlingScroll = true
     }
     }
   }
   }
 
 
@@ -175,7 +181,7 @@ module.exports = class ProviderView {
     const { currentSelection } = this.plugin.getPluginState()
     const { currentSelection } = this.plugin.getPluginState()
     const promises = currentSelection.map((file) => this.addFile(file))
     const promises = currentSelection.map((file) => this.addFile(file))
 
 
-    this._sharedHandler.loaderWrapper(Promise.all(promises), () => {
+    this.#sharedHandler.loaderWrapper(Promise.all(promises), () => {
       this.clearSelection()
       this.clearSelection()
     }, () => {})
     }, () => {})
   }
   }
@@ -221,8 +227,8 @@ module.exports = class ProviderView {
     const targetViewOptions = { ...this.opts, ...viewOptions }
     const targetViewOptions = { ...this.opts, ...viewOptions }
     const browserProps = {
     const browserProps = {
       ...this.plugin.getPluginState(),
       ...this.plugin.getPluginState(),
-      isChecked: this._sharedHandler.isChecked,
-      toggleCheckbox: this._sharedHandler.toggleCheckbox,
+      isChecked: this.#sharedHandler.isChecked,
+      toggleCheckbox: this.#sharedHandler.toggleCheckbox,
       handleScroll: this.handleScroll,
       handleScroll: this.handleScroll,
       done: this.donePicking,
       done: this.donePicking,
       cancel: this.cancelPicking,
       cancel: this.cancelPicking,

+ 5 - 5
packages/@uppy/react-native/file-picker/index.js

@@ -46,6 +46,7 @@ export default class UppyReactNativeFilePicker extends React.Component {
       })
       })
       this.props.onRequestClose()
       this.props.onRequestClose()
     }).catch((err) => {
     }).catch((err) => {
+      // eslint-disable-next-line no-console
       console.log(err)
       console.log(err)
     })
     })
   }
   }
@@ -60,6 +61,7 @@ export default class UppyReactNativeFilePicker extends React.Component {
       })
       })
       this.props.onRequestClose()
       this.props.onRequestClose()
     }).catch((err) => {
     }).catch((err) => {
+      // eslint-disable-next-line no-console
       console.log(err)
       console.log(err)
     })
     })
   }
   }
@@ -73,20 +75,18 @@ export default class UppyReactNativeFilePicker extends React.Component {
       })
       })
       this.props.onRequestClose()
       this.props.onRequestClose()
     }).catch((err) => {
     }).catch((err) => {
+      // eslint-disable-next-line no-console
       console.log(err)
       console.log(err)
     })
     })
   }
   }
 
 
   openProvider (id) {
   openProvider (id) {
-    console.log('Open provider:', id)
     this.setState({
     this.setState({
       openProvider: id,
       openProvider: id,
     })
     })
   }
   }
 
 
   chooseProvider (id) {
   chooseProvider (id) {
-    console.log('Provider selected:', id)
-
     switch (id) {
     switch (id) {
       case 'LocalImages':
       case 'LocalImages':
         this.selectImage()
         this.selectImage()
@@ -107,11 +107,11 @@ export default class UppyReactNativeFilePicker extends React.Component {
       <ScrollView
       <ScrollView
         contentContainerStyle={styles.providerList}
         contentContainerStyle={styles.providerList}
       >
       >
-        {this.state.providers.map((item, index) => {
+        {this.state.providers.map((item) => {
           return (
           return (
             <TouchableOpacity
             <TouchableOpacity
               style={styles.providerButton}
               style={styles.providerButton}
-              key={index}
+              key={item.title}
               onPress={ev => this.chooseProvider(item.id)}
               onPress={ev => this.chooseProvider(item.id)}
             >
             >
               <Text style={styles.providerButtonText}>{item.title}</Text>
               <Text style={styles.providerButtonText}>{item.title}</Text>

+ 0 - 3
packages/@uppy/react-native/file-picker/instagram.js

@@ -78,7 +78,6 @@ export default class UppyRNInstagram extends React.Component {
   }
   }
 
 
   renderInstagram () {
   renderInstagram () {
-    console.log(this.state.authUrl)
     return (
     return (
       <WebView
       <WebView
         source={{ uri: this.state.authUrl }}
         source={{ uri: this.state.authUrl }}
@@ -86,9 +85,7 @@ export default class UppyRNInstagram extends React.Component {
         onNavigationStateChange={(ev) => {
         onNavigationStateChange={(ev) => {
           const { url } = ev
           const { url } = ev
           const token = getQueryParamValueFromUrl('uppyAuthToken', url)
           const token = getQueryParamValueFromUrl('uppyAuthToken', url)
-          console.log(token)
           this.plugin.provider.setAuthToken(token)
           this.plugin.provider.setAuthToken(token)
-          console.log(this.plugin.provider.list('recent'))
           // return this.renderGrid(this.state.instagram.items)
           // return this.renderGrid(this.state.instagram.items)
         }}
         }}
       />
       />

+ 1 - 0
packages/@uppy/react-native/file-picker/url.js

@@ -42,6 +42,7 @@ export default class UppyRNUrl extends React.Component {
     this.plugin.addFile(this.state.url)
     this.plugin.addFile(this.state.url)
       .then(this.props.onDone)
       .then(this.props.onDone)
       .catch((err) => {
       .catch((err) => {
+        // eslint-disable-next-line no-console
         console.log(err)
         console.log(err)
       })
       })
   }
   }

+ 1 - 1
packages/@uppy/react/src/propTypes.js

@@ -9,7 +9,7 @@ const plugins = PropTypes.arrayOf(PropTypes.string)
 
 
 // Language strings for this component.
 // Language strings for this component.
 const locale = PropTypes.shape({
 const locale = PropTypes.shape({
-  strings: PropTypes.object,
+  strings: PropTypes.object, // eslint-disable-line react/forbid-prop-types
   pluralize: PropTypes.func,
   pluralize: PropTypes.func,
 })
 })
 
 

+ 3 - 0
packages/@uppy/redux-dev-tools/src/index.js

@@ -1,11 +1,13 @@
 const { UIPlugin } = require('@uppy/core')
 const { UIPlugin } = require('@uppy/core')
 
 
+/* eslint-disable max-len */
 /**
 /**
  * Add Redux DevTools support to Uppy
  * Add Redux DevTools support to Uppy
  *
  *
  * See https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f
  * See https://medium.com/@zalmoxis/redux-devtools-without-redux-or-how-to-have-a-predictable-state-with-any-architecture-61c5f5a7716f
  * and https://github.com/zalmoxisus/mobx-remotedev/blob/master/src/monitorActions.js
  * and https://github.com/zalmoxisus/mobx-remotedev/blob/master/src/monitorActions.js
  */
  */
+/* eslint-enable max-len */
 module.exports = class ReduxDevTools extends UIPlugin {
 module.exports = class ReduxDevTools extends UIPlugin {
   static VERSION = require('../package.json').version
   static VERSION = require('../package.json').version
 
 
@@ -54,6 +56,7 @@ module.exports = class ReduxDevTools extends UIPlugin {
   }
   }
 
 
   install () {
   install () {
+    // eslint-disable-next-line no-underscore-dangle
     this.withDevTools = typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__
     this.withDevTools = typeof window !== 'undefined' && window.__REDUX_DEVTOOLS_EXTENSION__
     if (this.withDevTools) {
     if (this.withDevTools) {
       this.initDevTools()
       this.initDevTools()

+ 3 - 4
packages/@uppy/robodog/src/TransloaditResultsPlugin.js

@@ -9,17 +9,16 @@ class TransloaditResultsPlugin extends BasePlugin {
 
 
     this.type = 'modifier'
     this.type = 'modifier'
     this.id = this.opts.id || 'TransloaditResultsPlugin'
     this.id = this.opts.id || 'TransloaditResultsPlugin'
-    this._afterUpload = this._afterUpload.bind(this)
   }
   }
 
 
   install () {
   install () {
-    this.uppy.addPostProcessor(this._afterUpload)
+    this.uppy.addPostProcessor(this.#afterUpload)
   }
   }
 
 
-  _afterUpload (fileIDs, uploadID) {
+  #afterUpload = (fileIDs, uploadID) => {
     const { currentUploads } = this.uppy.getState()
     const { currentUploads } = this.uppy.getState()
     const { result } = currentUploads[uploadID]
     const { result } = currentUploads[uploadID]
-    const assemblies = result && Array.isArray(result.transloadit) ? result.transloadit : []
+    const assemblies = Array.isArray(result?.transloadit) ? result.transloadit : []
 
 
     // Merge the assembly.results[*] arrays and add `stepName` and
     // Merge the assembly.results[*] arrays and add `stepName` and
     // `assemblyId` properties.
     // `assemblyId` properties.

+ 2 - 0
packages/@uppy/robodog/types/index.test-d.ts

@@ -2,6 +2,8 @@ import { Transloadit } from 'uppy' // eslint-disable-line import/no-extraneous-d
 import { expectError } from 'tsd'
 import { expectError } from 'tsd'
 import Robodog from '.'
 import Robodog from '.'
 
 
+/* eslint-disable @typescript-eslint/no-unused-vars */
+
 async function performPick () {
 async function performPick () {
   const { successful, failed, transloadit, results } = await Robodog.pick({
   const { successful, failed, transloadit, results } = await Robodog.pick({
     target: 'test',
     target: 'test',

+ 2 - 1
packages/@uppy/screen-capture/src/CaptureScreen.js

@@ -39,7 +39,8 @@ class RecorderScreen extends Component {
       <div className="uppy uppy-ScreenCapture-container">
       <div className="uppy uppy-ScreenCapture-container">
         <div className="uppy-ScreenCapture-videoContainer">
         <div className="uppy-ScreenCapture-videoContainer">
           <StreamStatus {...this.props} />
           <StreamStatus {...this.props} />
-          <video ref={videoElement => (this.videoElement = videoElement)} className="uppy-ScreenCapture-video" {...videoProps} />
+          {/* eslint-disable-next-line jsx-a11y/media-has-caption */}
+          <video ref={videoElement => { this.videoElement = videoElement }} className="uppy-ScreenCapture-video" {...videoProps} />
           <StopWatch {...this.props} />
           <StopWatch {...this.props} />
         </div>
         </div>
 
 

+ 1 - 0
packages/@uppy/screen-capture/src/StopWatch.js

@@ -64,6 +64,7 @@ class Stopwatch extends Component {
   }
   }
 
 
   fmtMSS (s) {
   fmtMSS (s) {
+    // eslint-disable-next-line no-return-assign
     return (s - (s %= 60)) / 60 + (s > 9 ? ':' : ':0') + s
     return (s - (s %= 60)) / 60 + (s > 9 ? ':' : ':0') + s
   }
   }
 
 

+ 5 - 2
packages/@uppy/screen-capture/src/index.js

@@ -20,7 +20,8 @@ module.exports = class ScreenCapture extends UIPlugin {
   constructor (uppy, opts) {
   constructor (uppy, opts) {
     super(uppy, opts)
     super(uppy, opts)
     this.mediaDevices = getMediaDevices()
     this.mediaDevices = getMediaDevices()
-    this.protocol = location.protocol.match(/https/i) ? 'https' : 'http'
+    // eslint-disable-next-line no-restricted-globals
+    this.protocol = location.protocol === 'https:' ? 'https' : 'http'
     this.id = this.opts.id || 'ScreenCapture'
     this.id = this.opts.id || 'ScreenCapture'
     this.title = this.opts.title || 'Screencast'
     this.title = this.opts.title || 'Screencast'
     this.type = 'acquirer'
     this.type = 'acquirer'
@@ -209,7 +210,9 @@ module.exports = class ScreenCapture extends UIPlugin {
       .then((videoStream) => {
       .then((videoStream) => {
         // Attempt to use the passed preferredVideoMimeType (if any) during recording.
         // Attempt to use the passed preferredVideoMimeType (if any) during recording.
         // If the browser doesn't support it, we'll fall back to the browser default instead
         // If the browser doesn't support it, we'll fall back to the browser default instead
-        if (preferredVideoMimeType && MediaRecorder.isTypeSupported(preferredVideoMimeType) && getFileTypeExtension(preferredVideoMimeType)) {
+        if (preferredVideoMimeType
+            && MediaRecorder.isTypeSupported(preferredVideoMimeType)
+            && getFileTypeExtension(preferredVideoMimeType)) {
           options.mimeType = preferredVideoMimeType
           options.mimeType = preferredVideoMimeType
         }
         }
 
 

+ 8 - 4
packages/@uppy/status-bar/src/StatusBar.js

@@ -134,6 +134,8 @@ module.exports = (props) => {
         className={progressClassNames}
         className={progressClassNames}
         style={{ width: `${width}%` }}
         style={{ width: `${width}%` }}
         role="progressbar"
         role="progressbar"
+        aria-label={`${width}%`}
+        aria-valuetext={`${width}%`}
         aria-valuemin="0"
         aria-valuemin="0"
         aria-valuemax="100"
         aria-valuemax="100"
         aria-valuenow={progressValue}
         aria-valuenow={progressValue}
@@ -371,6 +373,7 @@ const ProgressBarUploading = (props) => {
         <div className="uppy-StatusBar-statusPrimary">
         <div className="uppy-StatusBar-statusPrimary">
           {props.supportsUploadProgress ? `${title}: ${props.totalProgress}%` : title}
           {props.supportsUploadProgress ? `${title}: ${props.totalProgress}%` : title}
         </div>
         </div>
+        {/* eslint-disable-next-line no-nested-ternary */}
         {!props.isAllPaused && !showUploadNewlyAddedFiles && props.showProgressDetails
         {!props.isAllPaused && !showUploadNewlyAddedFiles && props.showProgressDetails
           ? (props.supportsUploadProgress ? <ThrottledProgressDetails {...props} /> : <UnknownProgressDetails {...props} />)
           ? (props.supportsUploadProgress ? <ThrottledProgressDetails {...props} /> : <UnknownProgressDetails {...props} />)
           : null}
           : null}
@@ -398,7 +401,8 @@ const ProgressBarComplete = ({ totalProgress, i18n }) => {
 const ProgressBarError = ({ error, retryAll, hideRetryButton, i18n }) => {
 const ProgressBarError = ({ error, retryAll, hideRetryButton, i18n }) => {
   function displayErrorAlert () {
   function displayErrorAlert () {
     const errorMessage = `${i18n('uploadFailed')} \n\n ${error}`
     const errorMessage = `${i18n('uploadFailed')} \n\n ${error}`
-    alert(errorMessage)
+    // eslint-disable-next-line no-alert
+    alert(errorMessage) // TODO: move to custom alert implementation
   }
   }
 
 
   return (
   return (
@@ -411,16 +415,16 @@ const ProgressBarError = ({ error, retryAll, hideRetryButton, i18n }) => {
           {i18n('uploadFailed')}
           {i18n('uploadFailed')}
         </div>
         </div>
       </div>
       </div>
-      <span
+      <button
         className="uppy-StatusBar-details"
         className="uppy-StatusBar-details"
         aria-label={error}
         aria-label={error}
         data-microtip-position="top-right"
         data-microtip-position="top-right"
         data-microtip-size="medium"
         data-microtip-size="medium"
-        role="tooltip"
         onClick={displayErrorAlert}
         onClick={displayErrorAlert}
+        type="button"
       >
       >
         ?
         ?
-      </span>
+      </button>
     </div>
     </div>
   )
   )
 }
 }

+ 3 - 1
packages/@uppy/status-bar/src/index.js

@@ -133,7 +133,9 @@ module.exports = class StatusBar extends UIPlugin {
       }
       }
       // If NO files are being preprocessed or uploaded right now, but some files are
       // If NO files are being preprocessed or uploaded right now, but some files are
       // being postprocessed, show the postprocess state.
       // being postprocessed, show the postprocess state.
-      if (progress.postprocess && state !== statusBarStates.STATE_UPLOADING && state !== statusBarStates.STATE_PREPROCESSING) {
+      if (progress.postprocess
+          && state !== statusBarStates.STATE_UPLOADING
+          && state !== statusBarStates.STATE_PREPROCESSING) {
         state = statusBarStates.STATE_POSTPROCESSING
         state = statusBarStates.STATE_POSTPROCESSING
       }
       }
     }
     }

+ 1 - 0
packages/@uppy/status-bar/src/style.scss

@@ -385,6 +385,7 @@
 }
 }
 
 
 .uppy-StatusBar-details {
 .uppy-StatusBar-details {
+  appearance: none;
   line-height: 12px;
   line-height: 12px;
   width: 13px;
   width: 13px;
   height: 13px;
   height: 13px;

+ 2 - 2
packages/@uppy/store-default/src/index.js

@@ -18,7 +18,7 @@ class DefaultStore {
     const nextState = { ...this.state, ...patch }
     const nextState = { ...this.state, ...patch }
 
 
     this.state = nextState
     this.state = nextState
-    this._publish(prevState, nextState, patch)
+    this.#publish(prevState, nextState, patch)
   }
   }
 
 
   subscribe (listener) {
   subscribe (listener) {
@@ -32,7 +32,7 @@ class DefaultStore {
     }
     }
   }
   }
 
 
-  _publish (...args) {
+  #publish (...args) {
     this.callbacks.forEach((listener) => {
     this.callbacks.forEach((listener) => {
       listener(...args)
       listener(...args)
     })
     })

+ 17 - 7
packages/@uppy/store-redux/src/index.js

@@ -18,10 +18,16 @@ const defaultSelector = (id) => (state) => state.uppy[id]
 class ReduxStore {
 class ReduxStore {
   static VERSION = require('../package.json').version
   static VERSION = require('../package.json').version
 
 
+  #id
+
+  #selector
+
+  #store
+
   constructor (opts) {
   constructor (opts) {
-    this._store = opts.store
-    this._id = opts.id || nanoid()
-    this._selector = opts.selector || defaultSelector(this._id)
+    this.#store = opts.store
+    this.#id = opts.id || nanoid()
+    this.#selector = opts.selector || defaultSelector(this.#id)
 
 
     // Calling `setState` to dispatch an action to the Redux store.
     // Calling `setState` to dispatch an action to the Redux store.
     // The intent is to make sure that the reducer has run once.
     // The intent is to make sure that the reducer has run once.
@@ -29,20 +35,20 @@ class ReduxStore {
   }
   }
 
 
   setState (patch) {
   setState (patch) {
-    this._store.dispatch({
+    this.#store.dispatch({
       type: STATE_UPDATE,
       type: STATE_UPDATE,
-      id: this._id,
+      id: this.#id,
       payload: patch,
       payload: patch,
     })
     })
   }
   }
 
 
   getState () {
   getState () {
-    return this._selector(this._store.getState())
+    return this.#selector(this.#store.getState())
   }
   }
 
 
   subscribe (cb) {
   subscribe (cb) {
     let prevState = this.getState()
     let prevState = this.getState()
-    return this._store.subscribe(() => {
+    return this.#store.subscribe(() => {
       const nextState = this.getState()
       const nextState = this.getState()
       if (prevState !== nextState) {
       if (prevState !== nextState) {
         const patch = getPatch(prevState, nextState)
         const patch = getPatch(prevState, nextState)
@@ -51,6 +57,10 @@ class ReduxStore {
       }
       }
     })
     })
   }
   }
+
+  [Symbol.for('uppy test: get id')] () {
+    return this.#id
+  }
 }
 }
 
 
 function getPatch (prev, next) {
 function getPatch (prev, next) {

+ 1 - 1
packages/@uppy/store-redux/src/index.test.js

@@ -82,7 +82,7 @@ describe('ReduxStore', () => {
       type: 'SET',
       type: 'SET',
       payload: {
       payload: {
         uppy: {
         uppy: {
-          [store._id]: { b: 2 },
+          [store[Symbol.for('uppy test: get id')]()]: { b: 2 },
         },
         },
       },
       },
     })
     })

+ 3 - 3
packages/@uppy/thumbnail-generator/src/index.js

@@ -160,8 +160,8 @@ module.exports = class ThumbnailGenerator extends UIPlugin {
     if (steps < 1) {
     if (steps < 1) {
       steps = 1
       steps = 1
     }
     }
-    let sW = targetWidth * Math.pow(2, steps - 1)
-    let sH = targetHeight * Math.pow(2, steps - 1)
+    let sW = targetWidth * 2 ** (steps - 1)
+    let sH = targetHeight * 2 ** (steps - 1)
     const x = 2
     const x = 2
 
 
     while (steps--) {
     while (steps--) {
@@ -260,7 +260,7 @@ module.exports = class ThumbnailGenerator extends UIPlugin {
         return
         return
       }
       }
       return this.requestThumbnail(current)
       return this.requestThumbnail(current)
-        .catch(err => {}) // eslint-disable-line handle-callback-err
+        .catch(() => {}) // eslint-disable-line node/handle-callback-err
         .then(() => this.processQueue())
         .then(() => this.processQueue())
     }
     }
     this.queueProcessing = false
     this.queueProcessing = false

+ 2 - 1
packages/@uppy/transloadit/src/Assembly.js

@@ -12,7 +12,8 @@ const parseUrl = require('./parseUrl')
 let socketIo
 let socketIo
 function requireSocketIo () {
 function requireSocketIo () {
   // eslint-disable-next-line global-require
   // eslint-disable-next-line global-require
-  return socketIo ??= require('socket.io-client')
+  socketIo ??= require('socket.io-client')
+  return socketIo
 }
 }
 
 
 const ASSEMBLY_UPLOADING = 'ASSEMBLY_UPLOADING'
 const ASSEMBLY_UPLOADING = 'ASSEMBLY_UPLOADING'

+ 1 - 1
packages/@uppy/tus/src/getFingerprint.js

@@ -23,7 +23,7 @@ function isReactNative () {
 // For React Native and Cordova, we let tus-js-client’s default
 // For React Native and Cordova, we let tus-js-client’s default
 // fingerprint handling take charge.
 // fingerprint handling take charge.
 module.exports = function getFingerprint (uppyFileObj) {
 module.exports = function getFingerprint (uppyFileObj) {
-  return function (file, options) {
+  return (file, options) => {
     if (isCordova() || isReactNative()) {
     if (isCordova() || isReactNative()) {
       return tus.defaultOptions.fingerprint(file, options)
       return tus.defaultOptions.fingerprint(file, options)
     }
     }

Some files were not shown because too many files changed in this diff