Преглед изворни кода

Merge pull request #17525 from overleaf/ae-upgrade-prettier

Upgrade Prettier to v3

GitOrigin-RevId: 6f1338f196408f3edb4892d5220ad3665ff1a5bc
Alf Eaton пре 2 година
родитељ
комит
6cc2db3cdd
100 измењених фајлова са 987 додато и 938 уклоњено
  1. 16 24
      libraries/object-persistor/test/unit/FSPersistorTests.js
  2. 2 2
      libraries/overleaf-editor-core/lib/operation/text_operation.js
  3. 12 11
      package-lock.json
  4. 1 1
      package.json
  5. 1 3
      server-ce/config/settings.js
  6. 12 12
      server-ce/services.js
  7. 2 3
      services/chat/test/acceptance/js/DestroyingAProjectTests.js
  8. 6 9
      services/chat/test/acceptance/js/EditingAMessageTests.js
  9. 2 3
      services/chat/test/acceptance/js/GettingMessagesTests.js
  10. 4 6
      services/chat/test/acceptance/js/SendingAMessageTests.js
  11. 39 37
      services/clsi/app/js/ProjectPersistenceManager.js
  12. 2 3
      services/document-updater/app/js/UpdateManager.js
  13. 2 2
      services/document-updater/app/js/sharejs/types/text-tp2.js
  14. 6 3
      services/document-updater/test/stress/js/run.js
  15. 16 17
      services/history-v1/storage/lib/blob_hash.js
  16. 98 96
      services/project-history/app/js/ErrorRecorder.js
  17. 70 68
      services/project-history/app/js/FlushManager.js
  18. 2 3
      services/project-history/app/js/RetryManager.js
  19. 6 6
      services/project-history/app/js/UpdateCompressor.js
  20. 2 3
      services/project-history/scripts/clear_dangling_timestamps.js
  21. 4 4
      services/real-time/app/js/RedisClientManager.js
  22. 4 6
      services/web/app/src/Features/Authentication/AuthenticationManager.js
  23. 4 6
      services/web/app/src/Features/Collaborators/CollaboratorsInviteController.js
  24. 6 9
      services/web/app/src/Features/Editor/EditorHttpController.js
  25. 4 4
      services/web/app/src/Features/Email/Layouts/BaseWithHeaderEmailLayout.js
  26. 2 2
      services/web/app/src/Features/History/HistoryManager.js
  27. 4 6
      services/web/app/src/Features/Institutions/InstitutionsManager.js
  28. 2 3
      services/web/app/src/Features/PasswordReset/PasswordResetController.js
  29. 2 2
      services/web/app/src/Features/Project/ProjectController.js
  30. 2 3
      services/web/app/src/Features/Project/ProjectDeleter.js
  31. 2 3
      services/web/app/src/Features/Project/ProjectEntityMongoUpdateHandler.js
  32. 386 386
      services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js
  33. 11 12
      services/web/app/src/Features/ServerAdmin/AdminController.js
  34. 4 6
      services/web/app/src/Features/Subscription/FeaturesUpdater.js
  35. 2 3
      services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js
  36. 2 3
      services/web/app/src/Features/Subscription/SubscriptionHandler.js
  37. 2 3
      services/web/app/src/Features/Subscription/SubscriptionUpdater.js
  38. 2 3
      services/web/app/src/Features/Subscription/TeamInvitesController.js
  39. 2 3
      services/web/app/src/Features/Subscription/TeamInvitesHandler.js
  40. 2 3
      services/web/app/src/Features/ThirdPartyDataStore/UpdateMerger.js
  41. 4 6
      services/web/app/src/Features/Uploads/ProjectUploadManager.js
  42. 2 3
      services/web/app/src/Features/User/SAMLIdentityManager.js
  43. 2 3
      services/web/app/src/Features/User/UserDeleter.js
  44. 2 3
      services/web/app/src/Features/User/UserGetter.js
  45. 2 3
      services/web/app/src/Features/User/UserPostRegistrationAnalyticsManager.js
  46. 2 3
      services/web/app/src/Features/User/UserUpdater.js
  47. 3 5
      services/web/app/src/infrastructure/Modules.js
  48. 2 1
      services/web/frontend/fonts/font-awesome.css
  49. 8 4
      services/web/frontend/fonts/lato.css
  50. 5 1
      services/web/frontend/fonts/material-symbols.css
  51. 12 4
      services/web/frontend/fonts/merriweather.css
  52. 12 4
      services/web/frontend/fonts/open-sans.css
  53. 3 1
      services/web/frontend/fonts/source-code-pro.css
  54. 13 10
      services/web/frontend/js/features/history/utils/auto-select-file.ts
  55. 5 5
      services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts
  56. 2 2
      services/web/frontend/js/features/ide-react/hooks/use-editing-session-heartbeat.ts
  57. 2 2
      services/web/frontend/js/features/pdf-preview/components/faster-compiles-feedback.tsx
  58. 2 2
      services/web/frontend/js/features/pdf-preview/components/pdf-viewer.jsx
  59. 1 1
      services/web/frontend/js/features/project-list/components/notifications/groups-and-enterprise-banner.tsx
  60. 2 2
      services/web/frontend/js/features/project-list/components/table/project-tools/buttons/tags-dropdown.tsx
  61. 2 1
      services/web/frontend/js/features/settings/components/emails/add-email/country-input.tsx
  62. 3 1
      services/web/frontend/js/features/settings/domain-blocklist.ts
  63. 2 2
      services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal.tsx
  64. 2 2
      services/web/frontend/js/features/source-editor/components/figure-modal/file-sources/figure-modal-upload-source.tsx
  65. 1 1
      services/web/frontend/js/features/source-editor/components/review-panel/utils/compare-props-with-shallow-array-compare.ts
  66. 2 3
      services/web/frontend/js/features/source-editor/components/table-generator/toolbar/commands.ts
  67. 4 4
      services/web/frontend/js/features/source-editor/extensions/browser.ts
  68. 4 1
      services/web/frontend/js/features/source-editor/extensions/track-changes.ts
  69. 1 1
      services/web/frontend/js/features/source-editor/extensions/visual/paste-html.ts
  70. 4 2
      services/web/frontend/js/features/source-editor/extensions/visual/pasted-content.tsx
  71. 4 1
      services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/environment-line.ts
  72. 2 3
      services/web/frontend/js/ide/review-panel/controllers/ReviewPanelController.js
  73. 2 2
      services/web/frontend/js/main/event.js
  74. 2 2
      services/web/frontend/js/shared/context/project-context.tsx
  75. 1 1
      services/web/frontend/js/utils/functions.ts
  76. 3 1
      services/web/frontend/stylesheets/app/editor/logs.less
  77. 8 2
      services/web/frontend/stylesheets/app/editor/pdf.less
  78. 22 7
      services/web/frontend/stylesheets/app/editor/review-panel.less
  79. 3 1
      services/web/frontend/stylesheets/app/editor/toolbar.less
  80. 9 3
      services/web/frontend/stylesheets/app/plans/plans-v2.less
  81. 4 1
      services/web/frontend/stylesheets/app/project-list-react.less
  82. 14 6
      services/web/frontend/stylesheets/app/review-features-page.less
  83. 6 3
      services/web/frontend/stylesheets/app/website-redesign.less
  84. 4 2
      services/web/frontend/stylesheets/bootstrap-5/foundations/elevation.scss
  85. 3 1
      services/web/frontend/stylesheets/components/expand-collapse.less
  86. 2 1
      services/web/frontend/stylesheets/components/forms.less
  87. 7 2
      services/web/frontend/stylesheets/components/input-switch.less
  88. 2 1
      services/web/frontend/stylesheets/components/navbar.less
  89. 2 1
      services/web/frontend/stylesheets/components/ui-select.less
  90. 1 1
      services/web/frontend/stylesheets/core/type.less
  91. 2 1
      services/web/frontend/stylesheets/core/variables.less
  92. 2 1
      services/web/frontend/stylesheets/variables/all.less
  93. 2 1
      services/web/frontend/stylesheets/vendor/select/select.css
  94. 4 6
      services/web/scripts/analytics/sync_group_subscription_memberships.js
  95. 2 3
      services/web/scripts/delete_orphaned_docs_online_check.js
  96. 2 3
      services/web/scripts/restore_soft_deleted_docs.js
  97. 2 3
      services/web/scripts/sso_id_migration_check.js
  98. 2 3
      services/web/scripts/sso_id_remove_not_migrated.js
  99. 1 1
      services/web/test/frontend/components/shared/select.spec.tsx
  100. 6 9
      services/web/test/frontend/features/project-list/components/project-list-root.test.tsx

+ 16 - 24
libraries/object-persistor/test/unit/FSPersistorTests.js

@@ -114,9 +114,8 @@ describe('FSPersistorTests', function () {
             })
 
             it('should not write the target file', async function () {
-              await expect(
-                fsPromises.access(scenario.fsPath(files.wombat))
-              ).to.be.rejected
+              await expect(fsPromises.access(scenario.fsPath(files.wombat))).to
+                .be.rejected
             })
 
             it('should delete the temporary file', async function () {
@@ -135,9 +134,8 @@ describe('FSPersistorTests', function () {
               const contents = await fsPromises.readFile(
                 scenario.fsPath(files.wombat)
               )
-              expect(
-                contents.equals(localFiles['/uploads/info.txt'])
-              ).to.be.true
+              expect(contents.equals(localFiles['/uploads/info.txt'])).to.be
+                .true
             })
           })
 
@@ -151,9 +149,8 @@ describe('FSPersistorTests', function () {
             })
 
             it('should not write the target file', async function () {
-              await expect(
-                fsPromises.access(scenario.fsPath(files.wombat))
-              ).to.be.rejected
+              await expect(fsPromises.access(scenario.fsPath(files.wombat))).to
+                .be.rejected
             })
 
             it('should delete the temporary file', async function () {
@@ -203,9 +200,8 @@ describe('FSPersistorTests', function () {
               const contents = await fsPromises.readFile(
                 scenario.fsPath(files.wombat)
               )
-              expect(
-                contents.equals(localFiles['/uploads/info.txt'])
-              ).to.be.true
+              expect(contents.equals(localFiles['/uploads/info.txt'])).to.be
+                .true
             })
 
             it('should delete the temporary file', async function () {
@@ -224,9 +220,8 @@ describe('FSPersistorTests', function () {
               const contents = await fsPromises.readFile(
                 scenario.fsPath(files.wombat)
               )
-              expect(
-                contents.equals(localFiles['/uploads/other.txt'])
-              ).to.be.true
+              expect(contents.equals(localFiles['/uploads/other.txt'])).to.be
+                .true
             })
           })
 
@@ -243,9 +238,8 @@ describe('FSPersistorTests', function () {
               const contents = await fsPromises.readFile(
                 scenario.fsPath(files.wombat)
               )
-              expect(
-                contents.equals(localFiles['/uploads/info.txt'])
-              ).to.be.true
+              expect(contents.equals(localFiles['/uploads/info.txt'])).to.be
+                .true
             })
 
             it('should delete the temporary file', async function () {
@@ -280,9 +274,8 @@ describe('FSPersistorTests', function () {
           )
           const contents = await streamToBuffer(stream)
           // end is inclusive in ranges, but exclusive in slice()
-          expect(
-            contents.equals(localFiles['/uploads/info.txt'].slice(5, 17))
-          ).to.be.true
+          expect(contents.equals(localFiles['/uploads/info.txt'].slice(5, 17)))
+            .to.be.true
         })
 
         it('should give a NotFoundError if the file does not exist', async function () {
@@ -332,9 +325,8 @@ describe('FSPersistorTests', function () {
 
         it('should delete the file', async function () {
           await persistor.deleteObject(location, files.wombat)
-          await expect(
-            fsPromises.access(scenario.fsPath(files.wombat))
-          ).to.be.rejected
+          await expect(fsPromises.access(scenario.fsPath(files.wombat))).to.be
+            .rejected
         })
 
         it("should ignore files that don't exist", async function () {

+ 2 - 2
libraries/overleaf-editor-core/lib/operation/text_operation.js

@@ -792,8 +792,8 @@ function getSimpleOp(operation) {
       return ops[0] instanceof RetainOp
         ? ops[1]
         : ops[1] instanceof RetainOp
-        ? ops[0]
-        : null
+          ? ops[0]
+          : null
     case 3:
       if (ops[0] instanceof RetainOp && ops[2] instanceof RetainOp) {
         return ops[1]

+ 12 - 11
package-lock.json

@@ -49,7 +49,7 @@
         "eslint-plugin-node": "^11.1.0",
         "eslint-plugin-prettier": "^4.0.0",
         "eslint-plugin-promise": "^6.0.0",
-        "prettier": "2.5.1",
+        "prettier": "3.2.5",
         "resolve-url-loader": "^5.0.0",
         "sass": "^1.69.5",
         "sass-loader": "^13.3.2",
@@ -35248,15 +35248,18 @@
       }
     },
     "node_modules/prettier": {
-      "version": "2.5.1",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
-      "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
+      "version": "3.2.5",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
+      "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
       "dev": true,
       "bin": {
-        "prettier": "bin-prettier.js"
+        "prettier": "bin/prettier.cjs"
       },
       "engines": {
-        "node": ">=10.13.0"
+        "node": ">=14"
+      },
+      "funding": {
+        "url": "https://github.com/prettier/prettier?sponsor=1"
       }
     },
     "node_modules/prettier-linter-helpers": {
@@ -44299,7 +44302,6 @@
         "postcss-loader": "^7.3.0",
         "postcss-preset-env": "^8.3.2",
         "postcss-reporter": "^7.0.5",
-        "prettier": "^2.5.1",
         "react-test-renderer": "^16.7.0",
         "react-transform-hmr": "^1.0.4",
         "redux-mock-store": "1.5.0",
@@ -72046,7 +72048,6 @@
         "postcss-loader": "^7.3.0",
         "postcss-preset-env": "^8.3.2",
         "postcss-reporter": "^7.0.5",
-        "prettier": "^2.5.1",
         "prop-types": "^15.8.1",
         "react": "^16.8.6",
         "react-bootstrap": "^0.31.5",
@@ -77090,9 +77091,9 @@
       "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
     },
     "prettier": {
-      "version": "2.5.1",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
-      "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
+      "version": "3.2.5",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
+      "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
       "dev": true
     },
     "prettier-linter-helpers": {

+ 1 - 1
package.json

@@ -17,7 +17,7 @@
     "eslint-plugin-node": "^11.1.0",
     "eslint-plugin-prettier": "^4.0.0",
     "eslint-plugin-promise": "^6.0.0",
-    "prettier": "2.5.1",
+    "prettier": "3.2.5",
     "resolve-url-loader": "^5.0.0",
     "sass": "^1.69.5",
     "sass-loader": "^13.3.2",

+ 1 - 3
server-ce/config/settings.js

@@ -382,9 +382,7 @@ if (process.env.OVERLEAF_EMAIL_FROM_ADDRESS != null) {
 
 // i18n
 if (process.env.OVERLEAF_LANG_DOMAIN_MAPPING != null) {
-  settings.i18n.subdomainLang = parse(
-    process.env.OVERLEAF_LANG_DOMAIN_MAPPING
-  )
+  settings.i18n.subdomainLang = parse(process.env.OVERLEAF_LANG_DOMAIN_MAPPING)
 }
 
 // Password Settings

+ 12 - 12
server-ce/services.js

@@ -1,39 +1,39 @@
 module.exports = [
   {
-    name: 'web'
+    name: 'web',
   },
   {
-    name: 'real-time'
+    name: 'real-time',
   },
   {
-    name: 'document-updater'
+    name: 'document-updater',
   },
   {
-    name: 'clsi'
+    name: 'clsi',
   },
   {
-    name: 'filestore'
+    name: 'filestore',
   },
   {
-    name: 'docstore'
+    name: 'docstore',
   },
   {
-    name: 'chat'
+    name: 'chat',
   },
   {
-    name: 'spelling'
+    name: 'spelling',
   },
   {
-    name: 'contacts'
+    name: 'contacts',
   },
   {
-    name: 'notifications'
+    name: 'notifications',
   },
   {
-    name: 'project-history'
+    name: 'project-history',
   },
   {
-    name: 'history-v1'
+    name: 'history-v1',
   },
 ]
 

+ 2 - 3
services/chat/test/acceptance/js/DestroyingAProjectTests.js

@@ -47,9 +47,8 @@ describe('Destroying a project', async function () {
       const globalThreadMessage = await getMessage(this.globalThreadMessageId)
       expect(globalThreadMessage).to.exist
 
-      const { response: responseDestroy } = await ChatClient.destroyProject(
-        projectId
-      )
+      const { response: responseDestroy } =
+        await ChatClient.destroyProject(projectId)
       expect(responseDestroy.statusCode).to.equal(204)
     })
 

+ 6 - 9
services/chat/test/acceptance/js/EditingAMessageTests.js

@@ -43,9 +43,8 @@ describe('Editing a message', async function () {
       })
 
       it('should then list the updated message in the threads', async function () {
-        const { response, body: threads } = await ChatClient.getThreads(
-          projectId
-        )
+        const { response, body: threads } =
+          await ChatClient.getThreads(projectId)
         expect(response.statusCode).to.equal(200)
         expect(threads[threadId].messages.length).to.equal(1)
         expect(threads[threadId].messages[0].content).to.equal(newContent)
@@ -65,9 +64,8 @@ describe('Editing a message', async function () {
       })
 
       it('should then list the updated message in the threads', async function () {
-        const { response, body: threads } = await ChatClient.getThreads(
-          projectId
-        )
+        const { response, body: threads } =
+          await ChatClient.getThreads(projectId)
         expect(response.statusCode).to.equal(200)
         expect(threads[threadId].messages.length).to.equal(1)
         expect(threads[threadId].messages[0].content).to.equal(newContent)
@@ -87,9 +85,8 @@ describe('Editing a message', async function () {
       })
 
       it('should then list the old message in the threads', async function () {
-        const { response, body: threads } = await ChatClient.getThreads(
-          projectId
-        )
+        const { response, body: threads } =
+          await ChatClient.getThreads(projectId)
         expect(response.statusCode).to.equal(200)
         expect(threads[threadId].messages.length).to.equal(1)
         expect(threads[threadId].messages[0].content).to.equal(content)

+ 2 - 3
services/chat/test/acceptance/js/GettingMessagesTests.js

@@ -46,9 +46,8 @@ describe('Getting messages', async function () {
     })
 
     it('should contain the messages and populated users when getting the messages', async function () {
-      const { response, body: messages } = await ChatClient.getGlobalMessages(
-        projectId
-      )
+      const { response, body: messages } =
+        await ChatClient.getGlobalMessages(projectId)
       expect(response.statusCode).to.equal(200)
       expect(messages.length).to.equal(2)
       messages.reverse()

+ 4 - 6
services/chat/test/acceptance/js/SendingAMessageTests.js

@@ -26,9 +26,8 @@ describe('Sending a message', async function () {
     })
 
     it('should then list the message in the project messages', async function () {
-      const { response, body: messages } = await ChatClient.getGlobalMessages(
-        projectId
-      )
+      const { response, body: messages } =
+        await ChatClient.getGlobalMessages(projectId)
       expect(response.statusCode).to.equal(200)
       expect(messages.length).to.equal(1)
       expect(messages[0].content).to.equal(content)
@@ -61,9 +60,8 @@ describe('Sending a message', async function () {
     })
 
     it('should not appear in the global messages', async function () {
-      const { response, body: messages } = await ChatClient.getGlobalMessages(
-        projectId
-      )
+      const { response, body: messages } =
+        await ChatClient.getGlobalMessages(projectId)
       expect(response.statusCode).to.equal(200)
       expect(messages.length).to.equal(0)
     })

+ 39 - 37
services/clsi/app/js/ProjectPersistenceManager.js

@@ -88,15 +88,18 @@ module.exports = ProjectPersistenceManager = {
           })
         },
         () => {
-          setInterval(() => {
-            ProjectPersistenceManager.refreshExpiryTimeout(() => {
-              ProjectPersistenceManager.clearExpiredProjects(err => {
-                if (err) {
-                  logger.error({ err }, 'clearing expired projects failed')
-                }
+          setInterval(
+            () => {
+              ProjectPersistenceManager.refreshExpiryTimeout(() => {
+                ProjectPersistenceManager.clearExpiredProjects(err => {
+                  if (err) {
+                    logger.error({ err }, 'clearing expired projects failed')
+                  }
+                })
               })
-            })
-          }, 10 * 60 * 1000)
+            },
+            10 * 60 * 1000
+          )
         }
       )
     })
@@ -111,39 +114,38 @@ module.exports = ProjectPersistenceManager = {
     if (callback == null) {
       callback = function () {}
     }
-    return ProjectPersistenceManager._findExpiredProjectIds(function (
-      error,
-      projectIds
-    ) {
-      if (error != null) {
-        return callback(error)
-      }
-      logger.debug({ projectIds }, 'clearing expired projects')
-      const jobs = Array.from(projectIds || []).map(projectId =>
-        (
-          projectId => callback =>
-            ProjectPersistenceManager.clearProjectFromCache(
-              projectId,
-              { reason: 'expired' },
-              function (err) {
-                if (err != null) {
-                  logger.error({ err, projectId }, 'error clearing project')
-                }
-                return callback()
-              }
-            )
-        )(projectId)
-      )
-      return async.series(jobs, function (error) {
+    return ProjectPersistenceManager._findExpiredProjectIds(
+      function (error, projectIds) {
         if (error != null) {
           return callback(error)
         }
-        return CompileManager.clearExpiredProjects(
-          ProjectPersistenceManager.EXPIRY_TIMEOUT,
-          error => callback(error)
+        logger.debug({ projectIds }, 'clearing expired projects')
+        const jobs = Array.from(projectIds || []).map(projectId =>
+          (
+            projectId => callback =>
+              ProjectPersistenceManager.clearProjectFromCache(
+                projectId,
+                { reason: 'expired' },
+                function (err) {
+                  if (err != null) {
+                    logger.error({ err, projectId }, 'error clearing project')
+                  }
+                  return callback()
+                }
+              )
+          )(projectId)
         )
-      })
-    })
+        return async.series(jobs, function (error) {
+          if (error != null) {
+            return callback(error)
+          }
+          return CompileManager.clearExpiredProjects(
+            ProjectPersistenceManager.EXPIRY_TIMEOUT,
+            error => callback(error)
+          )
+        })
+      }
+    )
   }, // ignore any errors from deleting directories
 
   clearProject(projectId, userId, callback) {

+ 2 - 3
services/document-updater/app/js/UpdateManager.js

@@ -74,9 +74,8 @@ const UpdateManager = {
       doc_id: docId,
     })
 
-    const updates = await RealTimeRedisManager.promises.getPendingUpdatesForDoc(
-      docId
-    )
+    const updates =
+      await RealTimeRedisManager.promises.getPendingUpdatesForDoc(docId)
     logger.debug(
       { projectId, docId, count: updates.length },
       'processing updates'

+ 2 - 2
services/document-updater/app/js/sharejs/types/text-tp2.js

@@ -129,8 +129,8 @@ type._takeDoc = takeDoc = function (
         ? part.slice(position.offset, position.offset + maxlength)
         : part.slice(position.offset)
       : maxlength === undefined || tombsIndivisible
-      ? part - position.offset
-      : Math.min(maxlength, part - position.offset)
+        ? part - position.offset
+        : Math.min(maxlength, part - position.offset)
 
   const resultLen = result.length || result
 

+ 6 - 3
services/document-updater/test/stress/js/run.js

@@ -162,9 +162,12 @@ class StressTestClient {
   continue() {
     if (this.updateCount > 0) {
       this.updateCount--
-      return setTimeout(() => {
-        return this.sendUpdate()
-      }, this.options.updateDelay * (0.5 + Math.random()))
+      return setTimeout(
+        () => {
+          return this.sendUpdate()
+        },
+        this.options.updateDelay * (0.5 + Math.random())
+      )
     } else {
       return this.updateCallback()
     }

+ 16 - 17
services/history-v1/storage/lib/blob_hash.js

@@ -26,25 +26,24 @@ function getBlobHash(byteLength) {
  * @param  {stream.Readable} stream
  * @return {Promise.<string>} hexadecimal SHA-1 hash
  */
-exports.fromStream = BPromise.method(function blobHashFromStream(
-  byteLength,
-  stream
-) {
-  assert.integer(byteLength, 'blobHash: bad byteLength')
-  assert.object(stream, 'blobHash: bad stream')
+exports.fromStream = BPromise.method(
+  function blobHashFromStream(byteLength, stream) {
+    assert.integer(byteLength, 'blobHash: bad byteLength')
+    assert.object(stream, 'blobHash: bad stream')
 
-  const hash = getBlobHash(byteLength)
-  return new BPromise(function (resolve, reject) {
-    pipeline(stream, hash, function (err) {
-      if (err) {
-        reject(err)
-      } else {
-        hash.end()
-        resolve(hash.read())
-      }
+    const hash = getBlobHash(byteLength)
+    return new BPromise(function (resolve, reject) {
+      pipeline(stream, hash, function (err) {
+        if (err) {
+          reject(err)
+        } else {
+          hash.end()
+          resolve(hash.read())
+        }
+      })
     })
-  })
-})
+  }
+)
 
 /**
  * Compute the git blob hash for a blob with the given string content.

+ 98 - 96
services/project-history/app/js/ErrorRecorder.js

@@ -194,111 +194,113 @@ export function getFailures(callback) {
   if (callback == null) {
     callback = function () {}
   }
-  return getFailuresByType(function (
-    error,
-    failureCounts,
-    failureAttempts,
-    failureRequests,
-    maxQueueSize
-  ) {
-    let attempts, failureType, label, requests
-    if (error != null) {
-      return callback(OError.tag(error))
-    }
+  return getFailuresByType(
+    function (
+      error,
+      failureCounts,
+      failureAttempts,
+      failureRequests,
+      maxQueueSize
+    ) {
+      let attempts, failureType, label, requests
+      if (error != null) {
+        return callback(OError.tag(error))
+      }
 
-    const shortNames = {
-      'Error: bad response from filestore: 404': 'filestore-404',
-      'Error: bad response from filestore: 500': 'filestore-500',
-      'NotFoundError: got a 404 from web api': 'web-api-404',
-      'Error: history store a non-success status code: 413':
-        'history-store-413',
-      'Error: history store a non-success status code: 422':
-        'history-store-422',
-      'Error: history store a non-success status code: 500':
-        'history-store-500',
-      'Error: history store a non-success status code: 503':
-        'history-store-503',
-      'Error: web returned a non-success status code: 500 (attempts: 2)':
-        'web-500',
-      'Error: ESOCKETTIMEDOUT': 'socket-timeout',
-      'Error: no project found': 'no-project-found',
-      'OpsOutOfOrderError: project structure version out of order on incoming updates':
-        'incoming-project-version-out-of-order',
-      'OpsOutOfOrderError: doc version out of order on incoming updates':
-        'incoming-doc-version-out-of-order',
-      'OpsOutOfOrderError: project structure version out of order':
-        'chunk-project-version-out-of-order',
-      'OpsOutOfOrderError: doc version out of order':
-        'chunk-doc-version-out-of-order',
-      'Error: failed to extend lock': 'lock-overrun',
-      'Error: tried to release timed out lock': 'lock-overrun',
-      'Error: Timeout': 'lock-overrun',
-      'Error: sync ongoing': 'sync-ongoing',
-      'SyncError: unexpected resyncProjectStructure update': 'sync-error',
-      '[object Error]': 'unknown-error-object',
-      'UpdateWithUnknownFormatError: update with unknown format':
-        'unknown-format',
-      'Error: update with unknown format': 'unknown-format',
-      'TextOperationError: The base length of the second operation has to be the target length of the first operation':
-        'text-op-error',
-      'Error: ENOSPC: no space left on device, write': 'ENOSPC',
-      '*': 'other',
-    }
+      const shortNames = {
+        'Error: bad response from filestore: 404': 'filestore-404',
+        'Error: bad response from filestore: 500': 'filestore-500',
+        'NotFoundError: got a 404 from web api': 'web-api-404',
+        'Error: history store a non-success status code: 413':
+          'history-store-413',
+        'Error: history store a non-success status code: 422':
+          'history-store-422',
+        'Error: history store a non-success status code: 500':
+          'history-store-500',
+        'Error: history store a non-success status code: 503':
+          'history-store-503',
+        'Error: web returned a non-success status code: 500 (attempts: 2)':
+          'web-500',
+        'Error: ESOCKETTIMEDOUT': 'socket-timeout',
+        'Error: no project found': 'no-project-found',
+        'OpsOutOfOrderError: project structure version out of order on incoming updates':
+          'incoming-project-version-out-of-order',
+        'OpsOutOfOrderError: doc version out of order on incoming updates':
+          'incoming-doc-version-out-of-order',
+        'OpsOutOfOrderError: project structure version out of order':
+          'chunk-project-version-out-of-order',
+        'OpsOutOfOrderError: doc version out of order':
+          'chunk-doc-version-out-of-order',
+        'Error: failed to extend lock': 'lock-overrun',
+        'Error: tried to release timed out lock': 'lock-overrun',
+        'Error: Timeout': 'lock-overrun',
+        'Error: sync ongoing': 'sync-ongoing',
+        'SyncError: unexpected resyncProjectStructure update': 'sync-error',
+        '[object Error]': 'unknown-error-object',
+        'UpdateWithUnknownFormatError: update with unknown format':
+          'unknown-format',
+        'Error: update with unknown format': 'unknown-format',
+        'TextOperationError: The base length of the second operation has to be the target length of the first operation':
+          'text-op-error',
+        'Error: ENOSPC: no space left on device, write': 'ENOSPC',
+        '*': 'other',
+      }
 
-    // set all the known errors to zero if not present (otherwise gauges stay on their last value)
-    const summaryCounts = {}
-    const summaryAttempts = {}
-    const summaryRequests = {}
-    const summaryMaxQueueSize = {}
+      // set all the known errors to zero if not present (otherwise gauges stay on their last value)
+      const summaryCounts = {}
+      const summaryAttempts = {}
+      const summaryRequests = {}
+      const summaryMaxQueueSize = {}
 
-    for (failureType in shortNames) {
-      label = shortNames[failureType]
-      summaryCounts[label] = 0
-      summaryAttempts[label] = 0
-      summaryRequests[label] = 0
-      summaryMaxQueueSize[label] = 0
-    }
+      for (failureType in shortNames) {
+        label = shortNames[failureType]
+        summaryCounts[label] = 0
+        summaryAttempts[label] = 0
+        summaryRequests[label] = 0
+        summaryMaxQueueSize[label] = 0
+      }
 
-    // record a metric for each type of failure
-    for (failureType in failureCounts) {
-      const failureCount = failureCounts[failureType]
-      label = shortNames[failureType] || shortNames['*']
-      summaryCounts[label] += failureCount
-      summaryAttempts[label] += failureAttempts[failureType]
-      summaryRequests[label] += failureRequests[failureType]
-      summaryMaxQueueSize[label] = Math.max(
-        maxQueueSize[failureType],
-        summaryMaxQueueSize[label]
-      )
-    }
+      // record a metric for each type of failure
+      for (failureType in failureCounts) {
+        const failureCount = failureCounts[failureType]
+        label = shortNames[failureType] || shortNames['*']
+        summaryCounts[label] += failureCount
+        summaryAttempts[label] += failureAttempts[failureType]
+        summaryRequests[label] += failureRequests[failureType]
+        summaryMaxQueueSize[label] = Math.max(
+          maxQueueSize[failureType],
+          summaryMaxQueueSize[label]
+        )
+      }
 
-    for (label in summaryCounts) {
-      const count = summaryCounts[label]
-      metrics.globalGauge('failed', count, 1, { status: label })
-    }
+      for (label in summaryCounts) {
+        const count = summaryCounts[label]
+        metrics.globalGauge('failed', count, 1, { status: label })
+      }
 
-    for (label in summaryAttempts) {
-      attempts = summaryAttempts[label]
-      metrics.globalGauge('attempts', attempts, 1, { status: label })
-    }
+      for (label in summaryAttempts) {
+        attempts = summaryAttempts[label]
+        metrics.globalGauge('attempts', attempts, 1, { status: label })
+      }
 
-    for (label in summaryRequests) {
-      requests = summaryRequests[label]
-      metrics.globalGauge('requests', requests, 1, { status: label })
-    }
+      for (label in summaryRequests) {
+        requests = summaryRequests[label]
+        metrics.globalGauge('requests', requests, 1, { status: label })
+      }
 
-    for (label in summaryMaxQueueSize) {
-      const queueSize = summaryMaxQueueSize[label]
-      metrics.globalGauge('max-queue-size', queueSize, 1, { status: label })
-    }
+      for (label in summaryMaxQueueSize) {
+        const queueSize = summaryMaxQueueSize[label]
+        metrics.globalGauge('max-queue-size', queueSize, 1, { status: label })
+      }
 
-    return callback(null, {
-      counts: summaryCounts,
-      attempts: summaryAttempts,
-      requests: summaryRequests,
-      maxQueueSize: summaryMaxQueueSize,
-    })
-  })
+      return callback(null, {
+        counts: summaryCounts,
+        attempts: summaryAttempts,
+        requests: summaryRequests,
+        maxQueueSize: summaryMaxQueueSize,
+      })
+    }
+  )
 }
 
 export const promises = {

+ 70 - 68
services/project-history/app/js/FlushManager.js

@@ -59,79 +59,81 @@ export function flushOldOps(options, callback) {
       if (error != null) {
         return callback(OError.tag(error))
       }
-      return ErrorRecorder.getFailedProjects(function (
-        error,
-        projectHistoryFailures
-      ) {
-        if (error != null) {
-          return callback(OError.tag(error))
-        }
-        // exclude failed projects already in projectHistoryFailures
-        const failedProjects = new Set()
-        for (const entry of Array.from(projectHistoryFailures)) {
-          failedProjects.add(entry.project_id)
-        }
-        // randomise order so we get different projects if there is a limit
-        projectIds = _.shuffle(projectIds)
-        const maxAge = options.maxAge || 6 * 3600 // default to 6 hours
-        const cutoffTime = new Date(Date.now() - maxAge * 1000)
-        const startTime = new Date()
-        let count = 0
-        const jobs = projectIds.map(
-          projectId =>
-            function (cb) {
-              const timeTaken = new Date() - startTime
-              count++
-              if (
-                (options != null ? options.timeout : undefined) &&
-                timeTaken > options.timeout
-              ) {
-                // finish early due to timeout, return an error to bail out of the async iteration
-                logger.debug('background retries timed out')
-                return cb(new OError('retries timed out'))
-              }
-              if (
-                (options != null ? options.limit : undefined) &&
-                count > options.limit
-              ) {
-                // finish early due to reaching limit, return an error to bail out of the async iteration
-                logger.debug({ count }, 'background retries hit limit')
-                return cb(new OError('hit limit'))
-              }
-              if (failedProjects.has(projectId)) {
-                // skip failed projects
-                return setTimeout(cb, options.queueDelay || 100) // pause between flushes
+      return ErrorRecorder.getFailedProjects(
+        function (error, projectHistoryFailures) {
+          if (error != null) {
+            return callback(OError.tag(error))
+          }
+          // exclude failed projects already in projectHistoryFailures
+          const failedProjects = new Set()
+          for (const entry of Array.from(projectHistoryFailures)) {
+            failedProjects.add(entry.project_id)
+          }
+          // randomise order so we get different projects if there is a limit
+          projectIds = _.shuffle(projectIds)
+          const maxAge = options.maxAge || 6 * 3600 // default to 6 hours
+          const cutoffTime = new Date(Date.now() - maxAge * 1000)
+          const startTime = new Date()
+          let count = 0
+          const jobs = projectIds.map(
+            projectId =>
+              function (cb) {
+                const timeTaken = new Date() - startTime
+                count++
+                if (
+                  (options != null ? options.timeout : undefined) &&
+                  timeTaken > options.timeout
+                ) {
+                  // finish early due to timeout, return an error to bail out of the async iteration
+                  logger.debug('background retries timed out')
+                  return cb(new OError('retries timed out'))
+                }
+                if (
+                  (options != null ? options.limit : undefined) &&
+                  count > options.limit
+                ) {
+                  // finish early due to reaching limit, return an error to bail out of the async iteration
+                  logger.debug({ count }, 'background retries hit limit')
+                  return cb(new OError('hit limit'))
+                }
+                if (failedProjects.has(projectId)) {
+                  // skip failed projects
+                  return setTimeout(cb, options.queueDelay || 100) // pause between flushes
+                }
+                return flushIfOld(projectId, cutoffTime, function (err) {
+                  if (err != null) {
+                    logger.warn(
+                      { projectId, flushErr: err },
+                      'error flushing old project'
+                    )
+                  }
+                  return setTimeout(cb, options.queueDelay || 100)
+                })
               }
-              return flushIfOld(projectId, cutoffTime, function (err) {
-                if (err != null) {
-                  logger.warn(
-                    { projectId, flushErr: err },
-                    'error flushing old project'
+          ) // pause between flushes
+          return async.series(
+            async.reflectAll(jobs),
+            function (error, results) {
+              const success = []
+              const failure = []
+              results.forEach((result, i) => {
+                if (
+                  result.error != null &&
+                  !['retries timed out', 'hit limit'].includes(
+                    result?.error?.message
                   )
+                ) {
+                  // ignore expected errors
+                  return failure.push(projectIds[i])
+                } else {
+                  return success.push(projectIds[i])
                 }
-                return setTimeout(cb, options.queueDelay || 100)
               })
+              return callback(error, { success, failure, failedProjects })
             }
-        ) // pause between flushes
-        return async.series(async.reflectAll(jobs), function (error, results) {
-          const success = []
-          const failure = []
-          results.forEach((result, i) => {
-            if (
-              result.error != null &&
-              !['retries timed out', 'hit limit'].includes(
-                result?.error?.message
-              )
-            ) {
-              // ignore expected errors
-              return failure.push(projectIds[i])
-            } else {
-              return success.push(projectIds[i])
-            }
-          })
-          return callback(error, { success, failure, failedProjects })
-        })
-      })
+          )
+        }
+      )
     }
   )
 }

+ 2 - 3
services/project-history/app/js/RetryManager.js

@@ -175,9 +175,8 @@ async function checkProjectHasHistoryId(projectId) {
 
 async function waitUntilRedisQueueIsEmpty(projectId) {
   for (let attempts = 0; attempts < 30; attempts++) {
-    const updatesCount = await RedisManager.promises.countUnprocessedUpdates(
-      projectId
-    )
+    const updatesCount =
+      await RedisManager.promises.countUnprocessedUpdates(projectId)
     if (updatesCount === 0) {
       return
     }

+ 6 - 6
services/project-history/app/js/UpdateCompressor.js

@@ -252,12 +252,12 @@ function _concatTwoUpdates(firstUpdate, secondUpdate) {
     firstOp.p === secondOp.p
   ) {
     offset = firstOp.p
-    const diffUpdates = diffAsShareJsOps(firstOp.d, secondOp.i).map(function (
-      op
-    ) {
-      op.p += offset
-      return mergeUpdatesWithOp(firstUpdate, secondUpdate, op)
-    })
+    const diffUpdates = diffAsShareJsOps(firstOp.d, secondOp.i).map(
+      function (op) {
+        op.p += offset
+        return mergeUpdatesWithOp(firstUpdate, secondUpdate, op)
+      }
+    )
 
     // Doing a diff like this loses track of the doc lengths for each
     // update, so recalculate them

+ 2 - 3
services/project-history/scripts/clear_dangling_timestamps.js

@@ -25,9 +25,8 @@ async function main() {
   let clearedTimestamps = 0
   let processed = 0
   for (const projectId of projectIdsWithFirstOpTimestamps) {
-    const result = await RedisManager.promises.clearDanglingFirstOpTimestamp(
-      projectId
-    )
+    const result =
+      await RedisManager.promises.clearDanglingFirstOpTimestamp(projectId)
     processed++
     clearedTimestamps += result
     if (processed % 1000 === 0) {

+ 4 - 4
services/real-time/app/js/RedisClientManager.js

@@ -8,10 +8,10 @@ module.exports = {
       const redisType = x.cluster
         ? 'cluster'
         : x.sentinels
-        ? 'sentinel'
-        : x.host
-        ? 'single'
-        : 'unknown'
+          ? 'sentinel'
+          : x.host
+            ? 'single'
+            : 'unknown'
       logger.debug({ redis: redisType }, 'creating redis client')
       return redis.createClient(x)
     })

+ 4 - 6
services/web/app/src/Features/Authentication/AuthenticationManager.js

@@ -166,9 +166,8 @@ const AuthenticationManager = {
 
     let isPasswordReused
     try {
-      isPasswordReused = await HaveIBeenPwned.promises.checkPasswordForReuse(
-        password
-      )
+      isPasswordReused =
+        await HaveIBeenPwned.promises.checkPasswordForReuse(password)
     } catch (err) {
       logger.err({ err }, 'cannot check password for re-use')
     }
@@ -333,9 +332,8 @@ const AuthenticationManager = {
 
     let isPasswordReused
     try {
-      isPasswordReused = await HaveIBeenPwned.promises.checkPasswordForReuse(
-        password
-      )
+      isPasswordReused =
+        await HaveIBeenPwned.promises.checkPasswordForReuse(password)
     } catch (error) {
       logger.err({ error }, 'cannot check password for re-use')
     }

+ 4 - 6
services/web/app/src/Features/Collaborators/CollaboratorsInviteController.js

@@ -34,9 +34,8 @@ const CollaboratorsInviteController = {
   async getAllInvites(req, res) {
     const projectId = req.params.Project_id
     logger.debug({ projectId }, 'getting all active invites for project')
-    const invites = await CollaboratorsInviteHandler.promises.getAllInvites(
-      projectId
-    )
+    const invites =
+      await CollaboratorsInviteHandler.promises.getAllInvites(projectId)
     res.json({ invites })
   },
 
@@ -115,9 +114,8 @@ const CollaboratorsInviteController = {
       return res.status(400).json({ errorReason: 'invalid_email' })
     }
 
-    const underRateLimit = await CollaboratorsInviteController._checkRateLimit(
-      sendingUserId
-    )
+    const underRateLimit =
+      await CollaboratorsInviteController._checkRateLimit(sendingUserId)
     if (!underRateLimit) {
       return res.sendStatus(429)
     }

+ 6 - 9
services/web/app/src/Features/Editor/EditorHttpController.js

@@ -152,17 +152,15 @@ async function joinProject(req, res, next) {
 }
 
 async function _buildJoinProjectView(req, projectId, userId) {
-  const project = await ProjectGetter.promises.getProjectWithoutDocLines(
-    projectId
-  )
+  const project =
+    await ProjectGetter.promises.getProjectWithoutDocLines(projectId)
   if (project == null) {
     throw new Errors.NotFoundError('project not found')
   }
   let deletedDocsFromDocstore = []
   try {
-    deletedDocsFromDocstore = await DocstoreManager.promises.getAllDeletedDocs(
-      projectId
-    )
+    deletedDocsFromDocstore =
+      await DocstoreManager.promises.getAllDeletedDocs(projectId)
   } catch (err) {
     // The query in docstore is not optimized at this time and fails for
     // projects with many very large, deleted documents.
@@ -187,9 +185,8 @@ async function _buildJoinProjectView(req, projectId, userId) {
   if (privilegeLevel == null || privilegeLevel === PrivilegeLevels.NONE) {
     return { project: null, privilegeLevel: null, isRestrictedUser: false }
   }
-  const invites = await CollaboratorsInviteHandler.promises.getAllInvites(
-    projectId
-  )
+  const invites =
+    await CollaboratorsInviteHandler.promises.getAllInvites(projectId)
   const isTokenMember = await CollaboratorsHandler.promises.userIsTokenMember(
     userId,
     projectId

+ 4 - 4
services/web/app/src/Features/Email/Layouts/BaseWithHeaderEmailLayout.js

@@ -375,10 +375,10 @@ module.exports = _.template(`\
                       ? `${settings.email.template.customFooter}<br>`
                       : ''
                   }${settings.appName} &bull; <a href="${
-  settings.siteUrl
-}" style="Margin: 0; color: #0F7A06; font-family: Helvetica, Arial, sans-serif; font-weight: normal; line-height: 1.3; margin: 0; padding: 0; text-align: left; text-decoration: none;">${
-  settings.siteUrl
-}</a>
+                    settings.siteUrl
+                  }" style="Margin: 0; color: #0F7A06; font-family: Helvetica, Arial, sans-serif; font-weight: normal; line-height: 1.3; margin: 0; padding: 0; text-align: left; text-decoration: none;">${
+                    settings.siteUrl
+                  }</a>
 								</small></p>
 							</td></tr></table>
 						</td></tr></tbody></table>

+ 2 - 2
services/web/app/src/Features/History/HistoryManager.js

@@ -138,8 +138,8 @@ async function injectUserDetails(data) {
   const entries = Array.isArray(data.diff)
     ? data.diff
     : Array.isArray(data.updates)
-    ? data.updates
-    : []
+      ? data.updates
+      : []
   for (const entry of entries) {
     for (const user of (entry.meta && entry.meta.users) || []) {
       if (typeof user === 'string') {

+ 4 - 6
services/web/app/src/Features/Institutions/InstitutionsManager.js

@@ -105,9 +105,8 @@ const InstitutionsManager = {
   async refreshInstitutionUsers(institutionId, notify) {
     const refreshFunction = notify ? refreshFeaturesAndNotify : refreshFeatures
 
-    const { institution, affiliations } = await fetchInstitutionAndAffiliations(
-      institutionId
-    )
+    const { institution, affiliations } =
+      await fetchInstitutionAndAffiliations(institutionId)
 
     for (const affiliation of affiliations) {
       affiliation.institutionName = institution.name
@@ -316,9 +315,8 @@ async function refreshFeaturesAndNotify(affiliation) {
 
 const getUserInfo = async userId => {
   const user = await UserGetter.promises.getUser(userId, { _id: 1 })
-  const subscription = await SubscriptionLocator.promises.getUsersSubscription(
-    user
-  )
+  const subscription =
+    await SubscriptionLocator.promises.getUsersSubscription(user)
   return { user, subscription }
 }
 

+ 2 - 3
services/web/app/src/Features/PasswordReset/PasswordResetController.js

@@ -114,9 +114,8 @@ async function requestReset(req, res, next) {
 
   let status
   try {
-    status = await PasswordResetHandler.promises.generateAndEmailResetToken(
-      email
-    )
+    status =
+      await PasswordResetHandler.promises.generateAndEmailResetToken(email)
   } catch (err) {
     OError.tag(err, 'failed to generate and email password reset token', {
       email,

+ 2 - 2
services/web/app/src/Features/Project/ProjectController.js

@@ -751,8 +751,8 @@ const ProjectController = {
                 ? // TODO: Create React version of detached page
                   'project/editor_detached'
                 : idePageReact
-                ? 'project/ide-react'
-                : 'project/editor'
+                  ? 'project/ide-react'
+                  : 'project/editor'
 
             res.render(template, {
               title: project.name,

+ 2 - 3
services/web/app/src/Features/Project/ProjectDeleter.js

@@ -418,9 +418,8 @@ let deletedFilesProjectIdIndexExist
 async function doesDeletedFilesProjectIdIndexExist() {
   if (typeof deletedFilesProjectIdIndexExist !== 'boolean') {
     // Resolve this about once. No need for locking or retry handling.
-    deletedFilesProjectIdIndexExist = await db.deletedFiles.indexExists(
-      'projectId_1'
-    )
+    deletedFilesProjectIdIndexExist =
+      await db.deletedFiles.indexExists('projectId_1')
   }
   return deletedFilesProjectIdIndexExist
 }

+ 2 - 3
services/web/app/src/Features/Project/ProjectEntityMongoUpdateHandler.js

@@ -265,9 +265,8 @@ async function mkdirp(projectId, path, options = {}) {
   // to make matching case-sensitive
   const folders = path.split('/').filter(folder => folder.length !== 0)
 
-  const project = await ProjectGetter.promises.getProjectWithOnlyFolders(
-    projectId
-  )
+  const project =
+    await ProjectGetter.promises.getProjectWithOnlyFolders(projectId)
   if (path === '/') {
     return { newFolders: [], folder: project.rootFolder[0] }
   }

+ 386 - 386
services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js

@@ -645,153 +645,159 @@ const ProjectEntityUpdateHandler = {
     )
   },
 
-  upsertDoc: wrapWithLock(function (
-    projectId,
-    folderId,
-    docName,
-    docLines,
-    source,
-    userId,
-    callback
-  ) {
-    if (!SafePath.isCleanFilename(docName)) {
-      return callback(new Errors.InvalidNameError('invalid element name'))
-    }
-    ProjectLocator.findElement(
-      { project_id: projectId, element_id: folderId, type: 'folder' },
-      (error, folder, folderPath) => {
-        if (error != null) {
-          if (error instanceof Errors.NotFoundError && folder == null) {
-            return callback(new Error('folder_not_found'))
+  upsertDoc: wrapWithLock(
+    function (
+      projectId,
+      folderId,
+      docName,
+      docLines,
+      source,
+      userId,
+      callback
+    ) {
+      if (!SafePath.isCleanFilename(docName)) {
+        return callback(new Errors.InvalidNameError('invalid element name'))
+      }
+      ProjectLocator.findElement(
+        { project_id: projectId, element_id: folderId, type: 'folder' },
+        (error, folder, folderPath) => {
+          if (error != null) {
+            if (error instanceof Errors.NotFoundError && folder == null) {
+              return callback(new Error('folder_not_found'))
+            }
+            return callback(error)
           }
-          return callback(error)
-        }
-        if (folder == null) {
-          return callback(new Error("Couldn't find folder"))
-        }
-        const existingDoc = folder.docs.find(({ name }) => name === docName)
-        const existingFile = folder.fileRefs.find(
-          ({ name }) => name === docName
-        )
-        if (existingFile) {
-          const doc = new Doc({ name: docName })
-          const filePath = `${folderPath.fileSystem}/${existingFile.name}`
-          DocstoreManager.updateDoc(
-            projectId.toString(),
-            doc._id.toString(),
-            docLines,
-            0,
-            {},
-            (err, modified, rev) => {
-              if (err != null) {
-                return callback(err)
-              }
-              doc.rev = rev
-              ProjectEntityMongoUpdateHandler.replaceFileWithDoc(
-                projectId,
-                existingFile._id,
-                doc,
-                (err, project) => {
-                  if (err) {
-                    return callback(err)
-                  }
-                  TpdsUpdateSender.addDoc(
-                    {
-                      projectId,
-                      docId: doc._id,
-                      path: filePath,
-                      projectName: project.name,
-                      rev: existingFile.rev + 1,
-                      folderId,
-                    },
-                    err => {
-                      if (err) {
-                        return callback(err)
-                      }
-                      const projectHistoryId =
-                        project.overleaf &&
-                        project.overleaf.history &&
-                        project.overleaf.history.id
-                      const newDocs = [
-                        {
-                          doc,
-                          path: filePath,
-                          docLines: docLines.join('\n'),
-                        },
-                      ]
-                      const oldFiles = [
-                        {
-                          file: existingFile,
-                          path: filePath,
-                        },
-                      ]
-                      DocumentUpdaterHandler.updateProjectStructure(
+          if (folder == null) {
+            return callback(new Error("Couldn't find folder"))
+          }
+          const existingDoc = folder.docs.find(({ name }) => name === docName)
+          const existingFile = folder.fileRefs.find(
+            ({ name }) => name === docName
+          )
+          if (existingFile) {
+            const doc = new Doc({ name: docName })
+            const filePath = `${folderPath.fileSystem}/${existingFile.name}`
+            DocstoreManager.updateDoc(
+              projectId.toString(),
+              doc._id.toString(),
+              docLines,
+              0,
+              {},
+              (err, modified, rev) => {
+                if (err != null) {
+                  return callback(err)
+                }
+                doc.rev = rev
+                ProjectEntityMongoUpdateHandler.replaceFileWithDoc(
+                  projectId,
+                  existingFile._id,
+                  doc,
+                  (err, project) => {
+                    if (err) {
+                      return callback(err)
+                    }
+                    TpdsUpdateSender.addDoc(
+                      {
                         projectId,
-                        projectHistoryId,
-                        userId,
-                        { oldFiles, newDocs, newProject: project },
-                        source,
-                        error => {
-                          if (error != null) {
-                            return callback(error)
-                          }
-                          EditorRealTimeController.emitToRoom(
-                            projectId,
-                            'removeEntity',
-                            existingFile._id,
-                            'convertFileToDoc'
-                          )
-                          callback(null, doc, true)
+                        docId: doc._id,
+                        path: filePath,
+                        projectName: project.name,
+                        rev: existingFile.rev + 1,
+                        folderId,
+                      },
+                      err => {
+                        if (err) {
+                          return callback(err)
                         }
-                      )
-                    }
-                  )
+                        const projectHistoryId =
+                          project.overleaf &&
+                          project.overleaf.history &&
+                          project.overleaf.history.id
+                        const newDocs = [
+                          {
+                            doc,
+                            path: filePath,
+                            docLines: docLines.join('\n'),
+                          },
+                        ]
+                        const oldFiles = [
+                          {
+                            file: existingFile,
+                            path: filePath,
+                          },
+                        ]
+                        DocumentUpdaterHandler.updateProjectStructure(
+                          projectId,
+                          projectHistoryId,
+                          userId,
+                          { oldFiles, newDocs, newProject: project },
+                          source,
+                          error => {
+                            if (error != null) {
+                              return callback(error)
+                            }
+                            EditorRealTimeController.emitToRoom(
+                              projectId,
+                              'removeEntity',
+                              existingFile._id,
+                              'convertFileToDoc'
+                            )
+                            callback(null, doc, true)
+                          }
+                        )
+                      }
+                    )
+                  }
+                )
+              }
+            )
+          } else if (existingDoc) {
+            DocumentUpdaterHandler.setDocument(
+              projectId,
+              existingDoc._id,
+              userId,
+              docLines,
+              source,
+              (err, result) => {
+                if (err != null) {
+                  return callback(err)
                 }
-              )
-            }
-          )
-        } else if (existingDoc) {
-          DocumentUpdaterHandler.setDocument(
-            projectId,
-            existingDoc._id,
-            userId,
-            docLines,
-            source,
-            (err, result) => {
-              if (err != null) {
-                return callback(err)
+                logger.debug(
+                  { projectId, docId: existingDoc._id },
+                  'notifying users that the document has been updated'
+                )
+                // there is no need to flush the doc to mongo at this point as docupdater
+                // flushes it as part of setDoc.
+                //
+                // combine rev from response with existing doc metadata
+                callback(
+                  null,
+                  { ...existingDoc, ...result },
+                  existingDoc == null
+                )
               }
-              logger.debug(
-                { projectId, docId: existingDoc._id },
-                'notifying users that the document has been updated'
-              )
-              // there is no need to flush the doc to mongo at this point as docupdater
-              // flushes it as part of setDoc.
-              //
-              // combine rev from response with existing doc metadata
-              callback(null, { ...existingDoc, ...result }, existingDoc == null)
-            }
-          )
-        } else {
-          ProjectEntityUpdateHandler.addDocWithRanges.withoutLock(
-            projectId,
-            folderId,
-            docName,
-            docLines,
-            {},
-            userId,
-            source,
-            (err, doc) => {
-              if (err != null) {
-                return callback(err)
+            )
+          } else {
+            ProjectEntityUpdateHandler.addDocWithRanges.withoutLock(
+              projectId,
+              folderId,
+              docName,
+              docLines,
+              {},
+              userId,
+              source,
+              (err, doc) => {
+                if (err != null) {
+                  return callback(err)
+                }
+                callback(null, doc, existingDoc == null)
               }
-              callback(null, doc, existingDoc == null)
-            }
-          )
+            )
+          }
         }
-      }
-    )
-  }),
+      )
+    }
+  ),
 
   upsertFile: wrapWithLock({
     beforeLock(next) {
@@ -983,43 +989,38 @@ const ProjectEntityUpdateHandler = {
     },
   }),
 
-  upsertDocWithPath: wrapWithLock(function (
-    projectId,
-    elementPath,
-    docLines,
-    source,
-    userId,
-    callback
-  ) {
-    if (!SafePath.isCleanPath(elementPath)) {
-      return callback(new Errors.InvalidNameError('invalid element name'))
-    }
-    const docName = Path.basename(elementPath)
-    const folderPath = Path.dirname(elementPath)
-    ProjectEntityUpdateHandler.mkdirp.withoutLock(
-      projectId,
-      folderPath,
-      (err, newFolders, folder) => {
-        if (err != null) {
-          return callback(err)
-        }
-        ProjectEntityUpdateHandler.upsertDoc.withoutLock(
-          projectId,
-          folder._id,
-          docName,
-          docLines,
-          source,
-          userId,
-          (err, doc, isNewDoc) => {
-            if (err != null) {
-              return callback(err)
-            }
-            callback(null, doc, isNewDoc, newFolders, folder)
-          }
-        )
+  upsertDocWithPath: wrapWithLock(
+    function (projectId, elementPath, docLines, source, userId, callback) {
+      if (!SafePath.isCleanPath(elementPath)) {
+        return callback(new Errors.InvalidNameError('invalid element name'))
       }
-    )
-  }),
+      const docName = Path.basename(elementPath)
+      const folderPath = Path.dirname(elementPath)
+      ProjectEntityUpdateHandler.mkdirp.withoutLock(
+        projectId,
+        folderPath,
+        (err, newFolders, folder) => {
+          if (err != null) {
+            return callback(err)
+          }
+          ProjectEntityUpdateHandler.upsertDoc.withoutLock(
+            projectId,
+            folder._id,
+            docName,
+            docLines,
+            source,
+            userId,
+            (err, doc, isNewDoc) => {
+              if (err != null) {
+                return callback(err)
+              }
+              callback(null, doc, isNewDoc, newFolders, folder)
+            }
+          )
+        }
+      )
+    }
+  ),
 
   upsertFileWithPath: wrapWithLock({
     beforeLock(next) {
@@ -1115,64 +1116,62 @@ const ProjectEntityUpdateHandler = {
     },
   }),
 
-  deleteEntity: wrapWithLock(function (
-    projectId,
-    entityId,
-    entityType,
-    userId,
-    source,
-    callback
-  ) {
-    logger.debug({ entityId, entityType, projectId }, 'deleting project entity')
-    if (entityType == null) {
-      logger.warn({ err: 'No entityType set', projectId, entityId })
-      return callback(new Error('No entityType set'))
-    }
-    entityType = entityType.toLowerCase()
-    ProjectEntityMongoUpdateHandler.deleteEntity(
-      projectId,
-      entityId,
-      entityType,
-      (error, entity, path, projectBeforeDeletion, newProject) => {
-        if (error != null) {
-          return callback(error)
-        }
-        ProjectEntityUpdateHandler._cleanUpEntity(
-          projectBeforeDeletion,
-          newProject,
-          entity,
-          entityType,
-          path.fileSystem,
-          userId,
-          source,
-          (error, subtreeListing) => {
-            if (error != null) {
-              return callback(error)
-            }
-            const subtreeEntityIds = subtreeListing.map(entry =>
-              entry.entity._id.toString()
-            )
-            TpdsUpdateSender.deleteEntity(
-              {
-                projectId,
-                path: path.fileSystem,
-                projectName: projectBeforeDeletion.name,
-                entityId,
-                entityType,
-                subtreeEntityIds,
-              },
-              error => {
-                if (error != null) {
-                  return callback(error)
-                }
-                callback(null, entityId)
-              }
-            )
-          }
-        )
+  deleteEntity: wrapWithLock(
+    function (projectId, entityId, entityType, userId, source, callback) {
+      logger.debug(
+        { entityId, entityType, projectId },
+        'deleting project entity'
+      )
+      if (entityType == null) {
+        logger.warn({ err: 'No entityType set', projectId, entityId })
+        return callback(new Error('No entityType set'))
       }
-    )
-  }),
+      entityType = entityType.toLowerCase()
+      ProjectEntityMongoUpdateHandler.deleteEntity(
+        projectId,
+        entityId,
+        entityType,
+        (error, entity, path, projectBeforeDeletion, newProject) => {
+          if (error != null) {
+            return callback(error)
+          }
+          ProjectEntityUpdateHandler._cleanUpEntity(
+            projectBeforeDeletion,
+            newProject,
+            entity,
+            entityType,
+            path.fileSystem,
+            userId,
+            source,
+            (error, subtreeListing) => {
+              if (error != null) {
+                return callback(error)
+              }
+              const subtreeEntityIds = subtreeListing.map(entry =>
+                entry.entity._id.toString()
+              )
+              TpdsUpdateSender.deleteEntity(
+                {
+                  projectId,
+                  path: path.fileSystem,
+                  projectName: projectBeforeDeletion.name,
+                  entityId,
+                  entityType,
+                  subtreeEntityIds,
+                },
+                error => {
+                  if (error != null) {
+                    return callback(error)
+                  }
+                  callback(null, entityId)
+                }
+              )
+            }
+          )
+        }
+      )
+    }
+  ),
 
   deleteEntityWithPath: wrapWithLock(
     (projectId, path, userId, source, callback) =>
@@ -1225,166 +1224,167 @@ const ProjectEntityUpdateHandler = {
     )
   }),
 
-  addFolder: wrapWithLock(function (
-    projectId,
-    parentFolderId,
-    folderName,
-    callback
-  ) {
-    if (!SafePath.isCleanFilename(folderName)) {
-      return callback(new Errors.InvalidNameError('invalid element name'))
+  addFolder: wrapWithLock(
+    function (projectId, parentFolderId, folderName, callback) {
+      if (!SafePath.isCleanFilename(folderName)) {
+        return callback(new Errors.InvalidNameError('invalid element name'))
+      }
+      ProjectEntityMongoUpdateHandler.addFolder(
+        projectId,
+        parentFolderId,
+        folderName,
+        callback
+      )
     }
-    ProjectEntityMongoUpdateHandler.addFolder(
+  ),
+
+  moveEntity: wrapWithLock(
+    function (
       projectId,
-      parentFolderId,
-      folderName,
+      entityId,
+      destFolderId,
+      entityType,
+      userId,
+      source,
       callback
-    )
-  }),
-
-  moveEntity: wrapWithLock(function (
-    projectId,
-    entityId,
-    destFolderId,
-    entityType,
-    userId,
-    source,
-    callback
-  ) {
-    logger.debug(
-      { entityType, entityId, projectId, destFolderId },
-      'moving entity'
-    )
-    if (entityType == null) {
-      logger.warn({ err: 'No entityType set', projectId, entityId })
-      return callback(new Error('No entityType set'))
-    }
-    entityType = entityType.toLowerCase()
-    DocumentUpdaterHandler.flushProjectToMongo(projectId, err => {
-      if (err) {
-        return callback(err)
+    ) {
+      logger.debug(
+        { entityType, entityId, projectId, destFolderId },
+        'moving entity'
+      )
+      if (entityType == null) {
+        logger.warn({ err: 'No entityType set', projectId, entityId })
+        return callback(new Error('No entityType set'))
       }
-      ProjectEntityMongoUpdateHandler.moveEntity(
-        projectId,
-        entityId,
-        destFolderId,
-        entityType,
-        (err, project, startPath, endPath, rev, changes) => {
-          if (err != null) {
-            return callback(err)
-          }
-          const projectHistoryId =
-            project.overleaf &&
-            project.overleaf.history &&
-            project.overleaf.history.id
-          TpdsUpdateSender.moveEntity(
-            {
-              projectId,
-              projectName: project.name,
-              startPath,
-              endPath,
-              rev,
-              entityId,
-              entityType,
-              folderId: destFolderId,
-            },
-            err => {
-              if (err) {
-                logger.error({ err }, 'error sending tpds update')
-              }
-              DocumentUpdaterHandler.updateProjectStructure(
-                projectId,
-                projectHistoryId,
-                userId,
-                changes,
-                source,
-                callback
-              )
-            }
-          )
+      entityType = entityType.toLowerCase()
+      DocumentUpdaterHandler.flushProjectToMongo(projectId, err => {
+        if (err) {
+          return callback(err)
         }
-      )
-    })
-  }),
-
-  renameEntity: wrapWithLock(function (
-    projectId,
-    entityId,
-    entityType,
-    newName,
-    userId,
-    source,
-    callback
-  ) {
-    if (!newName || typeof newName !== 'string') {
-      const err = new OError('invalid newName value', {
-        value: newName,
-        type: typeof newName,
-        projectId,
-        entityId,
-        entityType,
-        userId,
-        source,
+        ProjectEntityMongoUpdateHandler.moveEntity(
+          projectId,
+          entityId,
+          destFolderId,
+          entityType,
+          (err, project, startPath, endPath, rev, changes) => {
+            if (err != null) {
+              return callback(err)
+            }
+            const projectHistoryId =
+              project.overleaf &&
+              project.overleaf.history &&
+              project.overleaf.history.id
+            TpdsUpdateSender.moveEntity(
+              {
+                projectId,
+                projectName: project.name,
+                startPath,
+                endPath,
+                rev,
+                entityId,
+                entityType,
+                folderId: destFolderId,
+              },
+              err => {
+                if (err) {
+                  logger.error({ err }, 'error sending tpds update')
+                }
+                DocumentUpdaterHandler.updateProjectStructure(
+                  projectId,
+                  projectHistoryId,
+                  userId,
+                  changes,
+                  source,
+                  callback
+                )
+              }
+            )
+          }
+        )
       })
-      logger.error({ err }, 'Invalid newName passed to renameEntity')
-      return callback(err)
-    }
-    if (!SafePath.isCleanFilename(newName)) {
-      return callback(new Errors.InvalidNameError('invalid element name'))
     }
-    logger.debug({ entityId, projectId }, `renaming ${entityType}`)
-    if (entityType == null) {
-      logger.warn({ err: 'No entityType set', projectId, entityId })
-      return callback(new Error('No entityType set'))
-    }
-    entityType = entityType.toLowerCase()
+  ),
 
-    DocumentUpdaterHandler.flushProjectToMongo(projectId, err => {
-      if (err) {
+  renameEntity: wrapWithLock(
+    function (
+      projectId,
+      entityId,
+      entityType,
+      newName,
+      userId,
+      source,
+      callback
+    ) {
+      if (!newName || typeof newName !== 'string') {
+        const err = new OError('invalid newName value', {
+          value: newName,
+          type: typeof newName,
+          projectId,
+          entityId,
+          entityType,
+          userId,
+          source,
+        })
+        logger.error({ err }, 'Invalid newName passed to renameEntity')
         return callback(err)
       }
-      ProjectEntityMongoUpdateHandler.renameEntity(
-        projectId,
-        entityId,
-        entityType,
-        newName,
-        (err, project, startPath, endPath, rev, changes) => {
-          if (err != null) {
-            return callback(err)
-          }
-          const projectHistoryId =
-            project.overleaf &&
-            project.overleaf.history &&
-            project.overleaf.history.id
-          TpdsUpdateSender.moveEntity(
-            {
-              projectId,
-              projectName: project.name,
-              startPath,
-              endPath,
-              rev,
-              entityId,
-              entityType,
-              folderId: null, // this means the folder has not changed
-            },
-            err => {
-              if (err) {
-                logger.error({ err }, 'error sending tpds update')
-              }
-              DocumentUpdaterHandler.updateProjectStructure(
-                projectId,
-                projectHistoryId,
-                userId,
-                changes,
-                source,
-                callback
-              )
-            }
-          )
+      if (!SafePath.isCleanFilename(newName)) {
+        return callback(new Errors.InvalidNameError('invalid element name'))
+      }
+      logger.debug({ entityId, projectId }, `renaming ${entityType}`)
+      if (entityType == null) {
+        logger.warn({ err: 'No entityType set', projectId, entityId })
+        return callback(new Error('No entityType set'))
+      }
+      entityType = entityType.toLowerCase()
+
+      DocumentUpdaterHandler.flushProjectToMongo(projectId, err => {
+        if (err) {
+          return callback(err)
         }
-      )
-    })
-  }),
+        ProjectEntityMongoUpdateHandler.renameEntity(
+          projectId,
+          entityId,
+          entityType,
+          newName,
+          (err, project, startPath, endPath, rev, changes) => {
+            if (err != null) {
+              return callback(err)
+            }
+            const projectHistoryId =
+              project.overleaf &&
+              project.overleaf.history &&
+              project.overleaf.history.id
+            TpdsUpdateSender.moveEntity(
+              {
+                projectId,
+                projectName: project.name,
+                startPath,
+                endPath,
+                rev,
+                entityId,
+                entityType,
+                folderId: null, // this means the folder has not changed
+              },
+              err => {
+                if (err) {
+                  logger.error({ err }, 'error sending tpds update')
+                }
+                DocumentUpdaterHandler.updateProjectStructure(
+                  projectId,
+                  projectHistoryId,
+                  userId,
+                  changes,
+                  source,
+                  callback
+                )
+              }
+            )
+          }
+        )
+      })
+    }
+  ),
 
   // This doesn't directly update project structure but we need to take the lock
   // to prevent anything else being queued before the resync update
@@ -1471,8 +1471,8 @@ const ProjectEntityUpdateHandler = {
       const originalName = entity.folder
         ? entity.folder.name
         : entity.doc
-        ? entity.doc.name
-        : entity.file.name
+          ? entity.doc.name
+          : entity.file.name
 
       let newPath = entity.path
       let newName = originalName
@@ -1540,8 +1540,8 @@ const ProjectEntityUpdateHandler = {
       const entityId = entity.folder
         ? entity.folder._id
         : entity.doc
-        ? entity.doc._id
-        : entity.file._id
+          ? entity.doc._id
+          : entity.file._id
       const entityType = entity.folder ? 'folder' : entity.doc ? 'doc' : 'file'
       ProjectEntityMongoUpdateHandler.renameEntity(
         projectId,

+ 11 - 12
services/web/app/src/Features/ServerAdmin/AdminController.js

@@ -93,19 +93,18 @@ const AdminController = {
       })()
     }
 
-    return SystemMessageManager.getMessagesFromDB(function (
-      error,
-      systemMessages
-    ) {
-      if (error != null) {
-        return next(error)
+    return SystemMessageManager.getMessagesFromDB(
+      function (error, systemMessages) {
+        if (error != null) {
+          return next(error)
+        }
+        return res.render('admin/index', {
+          title: 'System Admin',
+          openSockets,
+          systemMessages,
+        })
       }
-      return res.render('admin/index', {
-        title: 'System Admin',
-        openSockets,
-        systemMessages,
-      })
-    })
+    )
   },
 
   disconnectAllUsers: (req, res) => {

+ 4 - 6
services/web/app/src/Features/Subscription/FeaturesUpdater.js

@@ -114,16 +114,14 @@ async function computeFeatures(userId) {
 }
 
 async function _getIndividualFeatures(userId) {
-  const sub = await SubscriptionLocator.promises.getUserIndividualSubscription(
-    userId
-  )
+  const sub =
+    await SubscriptionLocator.promises.getUserIndividualSubscription(userId)
   return _subscriptionToFeatures(sub)
 }
 
 async function _getGroupFeatureSets(userId) {
-  const subs = await SubscriptionLocator.promises.getGroupSubscriptionsMemberOf(
-    userId
-  )
+  const subs =
+    await SubscriptionLocator.promises.getGroupSubscriptionsMemberOf(userId)
   return (subs || []).map(_subscriptionToFeatures)
 }
 

+ 2 - 3
services/web/app/src/Features/Subscription/SubscriptionGroupHandler.js

@@ -28,9 +28,8 @@ async function isUserPartOfGroup(userId, subscriptionId) {
 }
 
 async function getTotalConfirmedUsersInGroup(subscriptionId) {
-  const subscription = await SubscriptionLocator.promises.getSubscription(
-    subscriptionId
-  )
+  const subscription =
+    await SubscriptionLocator.promises.getSubscription(subscriptionId)
 
   return subscription?.member_ids?.length
 }

+ 2 - 3
services/web/app/src/Features/Subscription/SubscriptionHandler.js

@@ -207,9 +207,8 @@ async function syncSubscription(recurlySubscription, requesterData) {
 // This is used because Recurly doesn't always attempt collection of paast due
 // invoices after Paypal billing info were updated.
 async function attemptPaypalInvoiceCollection(recurlyAccountCode) {
-  const billingInfo = await RecurlyWrapper.promises.getBillingInfo(
-    recurlyAccountCode
-  )
+  const billingInfo =
+    await RecurlyWrapper.promises.getBillingInfo(recurlyAccountCode)
 
   if (!billingInfo.paypal_billing_agreement_id) {
     // this is not a Paypal user

+ 2 - 3
services/web/app/src/Features/Subscription/SubscriptionUpdater.js

@@ -47,9 +47,8 @@ async function syncSubscription(
   adminUserId,
   requesterData = {}
 ) {
-  let subscription = await SubscriptionLocator.promises.getUsersSubscription(
-    adminUserId
-  )
+  let subscription =
+    await SubscriptionLocator.promises.getUsersSubscription(adminUserId)
   if (subscription == null) {
     subscription = await _createNewSubscription(adminUserId)
   }

+ 2 - 3
services/web/app/src/Features/Subscription/TeamInvitesController.js

@@ -66,9 +66,8 @@ async function viewInvite(req, res, next) {
   const { token } = req.params
   const userId = SessionManager.getLoggedInUserId(req.session)
 
-  const { invite, subscription } = await TeamInvitesHandler.promises.getInvite(
-    token
-  )
+  const { invite, subscription } =
+    await TeamInvitesHandler.promises.getInvite(token)
   if (!invite) {
     return ErrorController.notFound(req, res)
   }

+ 2 - 3
services/web/app/src/Features/Subscription/TeamInvitesHandler.js

@@ -124,9 +124,8 @@ async function revokeInvite(teamManagerId, subscription, email) {
 // email is in Subscription.invited_emails when they join. We'll remove this
 // after a short while.
 async function createTeamInvitesForLegacyInvitedEmail(email) {
-  const teams = await SubscriptionLocator.promises.getGroupsWithEmailInvite(
-    email
-  )
+  const teams =
+    await SubscriptionLocator.promises.getGroupsWithEmailInvite(email)
   return Promise.all(
     teams.map(team => createInvite(team.admin_id, team, email))
   )

+ 2 - 3
services/web/app/src/Features/ThirdPartyDataStore/UpdateMerger.js

@@ -43,9 +43,8 @@ async function writeUpdateToDisk(projectId, updateStream) {
 }
 
 async function _findExistingFileType(projectId, path) {
-  const { docs, files } = await ProjectEntityHandler.promises.getAllEntities(
-    projectId
-  )
+  const { docs, files } =
+    await ProjectEntityHandler.promises.getAllEntities(projectId)
   if (_.some(docs, d => d.path === path)) {
     return 'doc'
   }

+ 4 - 6
services/web/app/src/Features/Uploads/ProjectUploadManager.js

@@ -119,12 +119,10 @@ async function _initializeProjectWithZipContents(
   project,
   contentsPath
 ) {
-  const topLevelDir = await ArchiveManager.promises.findTopLevelDirectory(
-    contentsPath
-  )
-  const importEntries = await FileSystemImportManager.promises.importDir(
-    topLevelDir
-  )
+  const topLevelDir =
+    await ArchiveManager.promises.findTopLevelDirectory(contentsPath)
+  const importEntries =
+    await FileSystemImportManager.promises.importDir(topLevelDir)
   const { fileEntries, docEntries } = await _createEntriesFromImports(
     project._id,
     importEntries

+ 2 - 3
services/web/app/src/Features/User/SAMLIdentityManager.js

@@ -32,9 +32,8 @@ async function _ensureCanAddIdentifier(userId, institutionEmail, providerId) {
     throw new Errors.SAMLAlreadyLinkedError()
   }
 
-  const userWithEmail = await UserGetter.promises.getUserByAnyEmail(
-    institutionEmail
-  )
+  const userWithEmail =
+    await UserGetter.promises.getUserByAnyEmail(institutionEmail)
 
   if (!userWithEmail) {
     // email doesn't exist; all good

+ 2 - 3
services/web/app/src/Features/User/UserDeleter.js

@@ -103,9 +103,8 @@ async function expireDeletedUsersAfterDuration() {
 }
 
 async function ensureCanDeleteUser(user) {
-  const subscription = await SubscriptionLocator.promises.getUsersSubscription(
-    user
-  )
+  const subscription =
+    await SubscriptionLocator.promises.getUsersSubscription(user)
   if (subscription) {
     throw new Errors.SubscriptionAdminDeletionError({})
   }

+ 2 - 3
services/web/app/src/Features/User/UserGetter.js

@@ -68,9 +68,8 @@ async function getUserFullEmails(userId) {
     return decorateFullEmails(user.email, user.emails, [], [])
   }
 
-  const affiliationsData = await InstitutionsAPIPromises.getUserAffiliations(
-    userId
-  )
+  const affiliationsData =
+    await InstitutionsAPIPromises.getUserAffiliations(userId)
 
   return decorateFullEmails(
     user.email,

+ 2 - 3
services/web/app/src/Features/User/UserPostRegistrationAnalyticsManager.js

@@ -24,9 +24,8 @@ async function postRegistrationAnalytics(userId) {
 }
 
 async function checkAffiliations(userId) {
-  const affiliationsData = await InstitutionsAPIPromises.getUserAffiliations(
-    userId
-  )
+  const affiliationsData =
+    await InstitutionsAPIPromises.getUserAffiliations(userId)
   const hasCommonsAccountAffiliation = affiliationsData.some(
     affiliationData =>
       affiliationData.institution && affiliationData.institution.commonsAccount

+ 2 - 3
services/web/app/src/Features/User/UserUpdater.js

@@ -354,9 +354,8 @@ async function maybeCreateRedundantSubscriptionNotification(userId, email) {
     return
   }
 
-  const affiliations = await InstitutionsAPI.promises.getUserAffiliations(
-    userId
-  )
+  const affiliations =
+    await InstitutionsAPI.promises.getUserAffiliations(userId)
   const confirmedAffiliation = affiliations.find(a => a.email === email)
   if (!confirmedAffiliation || confirmedAffiliation.licence === 'free') {
     return

+ 3 - 5
services/web/app/src/infrastructure/Modules.js

@@ -30,11 +30,9 @@ function loadModules() {
   }
 
   for (const moduleName of Settings.moduleImportSequence || []) {
-    const loadedModule = require(Path.join(
-      MODULE_BASE_PATH,
-      moduleName,
-      'index.js'
-    ))
+    const loadedModule = require(
+      Path.join(MODULE_BASE_PATH, moduleName, 'index.js')
+    )
     loadedModule.name = moduleName
     _modules.push(loadedModule)
   }

+ 2 - 1
services/web/frontend/fonts/font-awesome.css

@@ -4,7 +4,8 @@
  */
 @font-face {
   font-family: 'FontAwesome';
-  src: url('font-awesome-v470.woff2') format('woff2'),
+  src:
+    url('font-awesome-v470.woff2') format('woff2'),
     url('font-awesome-v470.woff') format('woff');
   font-weight: normal;
   font-style: normal;

+ 8 - 4
services/web/frontend/fonts/lato.css

@@ -2,7 +2,8 @@
   font-family: 'Lato';
   font-style: normal;
   font-weight: 400;
-  src: url('lato/lato-v2-latin-ext-regular.woff2') format('woff2'),
+  src:
+    url('lato/lato-v2-latin-ext-regular.woff2') format('woff2'),
     url('lato/lato-v2-latin-ext-regular.woff') format('woff');
 }
 
@@ -10,7 +11,8 @@
   font-family: 'Lato';
   font-style: italic;
   font-weight: 400;
-  src: url('lato/lato-v2-latin-ext-italic.woff2') format('woff2'),
+  src:
+    url('lato/lato-v2-latin-ext-italic.woff2') format('woff2'),
     url('lato/lato-v2-latin-ext-italic.woff') format('woff');
 }
 
@@ -18,7 +20,8 @@
   font-family: 'Lato';
   font-style: normal;
   font-weight: 700;
-  src: url('lato/lato-v2-latin-ext-700.woff2') format('woff2'),
+  src:
+    url('lato/lato-v2-latin-ext-700.woff2') format('woff2'),
     url('lato/lato-v2-latin-ext-700.woff') format('woff');
 }
 
@@ -26,6 +29,7 @@
   font-family: 'Lato';
   font-style: italic;
   font-weight: 700;
-  src: url('lato/lato-v2-latin-ext-700italic.woff2') format('woff2'),
+  src:
+    url('lato/lato-v2-latin-ext-700italic.woff2') format('woff2'),
     url('lato/lato-v2-latin-ext-700italic.woff') format('woff');
 }

+ 5 - 1
services/web/frontend/fonts/material-symbols.css

@@ -15,7 +15,11 @@
   font-weight: normal;
   font-style: normal;
   font-size: 20px;
-  font-variation-settings: 'FILL' 1, 'wght' 400, 'GRAD' 0, 'opsz' 20;
+  font-variation-settings:
+    'FILL' 1,
+    'wght' 400,
+    'GRAD' 0,
+    'opsz' 20;
   line-height: 1;
   letter-spacing: normal;
   text-transform: none;

+ 12 - 4
services/web/frontend/fonts/merriweather.css

@@ -2,7 +2,9 @@
   font-family: 'Merriweather';
   font-style: normal;
   font-weight: 400;
-  src: local('Merriweather Regular'), local('Merriweather-Regular'),
+  src:
+    local('Merriweather Regular'),
+    local('Merriweather-Regular'),
     url('merriweather-v21-latin-regular.woff2') format('woff2'),
     url('merriweather-v21-latin-regular.woff') format('woff');
 }
@@ -10,7 +12,9 @@
   font-family: 'Merriweather';
   font-style: italic;
   font-weight: 400;
-  src: local('Merriweather Italic'), local('Merriweather-Italic'),
+  src:
+    local('Merriweather Italic'),
+    local('Merriweather-Italic'),
     url('merriweather-v21-latin-italic.woff2') format('woff2'),
     url('merriweather-v21-latin-italic.woff') format('woff');
 }
@@ -18,7 +22,9 @@
   font-family: 'Merriweather';
   font-style: normal;
   font-weight: 700;
-  src: local('Merriweather Bold'), local('Merriweather-Bold'),
+  src:
+    local('Merriweather Bold'),
+    local('Merriweather-Bold'),
     url('merriweather-v21-latin-700.woff2') format('woff2'),
     url('merriweather-v21-latin-700.woff') format('woff');
 }
@@ -26,7 +32,9 @@
   font-family: 'Merriweather';
   font-style: italic;
   font-weight: 700;
-  src: local('Merriweather Bold Italic'), local('Merriweather-BoldItalic'),
+  src:
+    local('Merriweather Bold Italic'),
+    local('Merriweather-BoldItalic'),
     url('merriweather-v21-latin-700italic.woff2') format('woff2'),
     url('merriweather-v21-latin-700italic.woff') format('woff');
 }

+ 12 - 4
services/web/frontend/fonts/open-sans.css

@@ -2,7 +2,9 @@
   font-family: 'Open Sans';
   font-style: normal;
   font-weight: 300;
-  src: local('Open Sans Light'), local('OpenSans-Light'),
+  src:
+    local('Open Sans Light'),
+    local('OpenSans-Light'),
     url('open-sans-v17-latin-300.woff2') format('woff2'),
     url('open-sans-v17-latin-300.woff') format('woff');
 }
@@ -10,7 +12,9 @@
   font-family: 'Open Sans';
   font-style: normal;
   font-weight: 400;
-  src: local('Open Sans Regular'), local('OpenSans-Regular'),
+  src:
+    local('Open Sans Regular'),
+    local('OpenSans-Regular'),
     url('open-sans-v17-latin-regular.woff2') format('woff2'),
     url('open-sans-v17-latin-regular.woff') format('woff');
 }
@@ -18,7 +22,9 @@
   font-family: 'Open Sans';
   font-style: normal;
   font-weight: 600;
-  src: local('Open Sans SemiBold'), local('OpenSans-SemiBold'),
+  src:
+    local('Open Sans SemiBold'),
+    local('OpenSans-SemiBold'),
     url('open-sans-v17-latin-600.woff2') format('woff2'),
     url('open-sans-v17-latin-600.woff') format('woff');
 }
@@ -26,7 +32,9 @@
   font-family: 'Open Sans';
   font-style: normal;
   font-weight: 700;
-  src: local('Open Sans Bold'), local('OpenSans-Bold'),
+  src:
+    local('Open Sans Bold'),
+    local('OpenSans-Bold'),
     url('open-sans-v17-latin-700.woff2') format('woff2'),
     url('open-sans-v17-latin-700.woff') format('woff');
 }

+ 3 - 1
services/web/frontend/fonts/source-code-pro.css

@@ -2,7 +2,9 @@
   font-family: 'Source Code Pro';
   font-style: normal;
   font-weight: 400;
-  src: local('Source Code Pro Regular'), local('SourceCodePro-Regular'),
+  src:
+    local('Source Code Pro Regular'),
+    local('SourceCodePro-Regular'),
     url('source-code-pro-v13-latin-regular.woff2') format('woff2'),
     url('source-code-pro-v13-latin-regular.woff') format('woff');
 }

+ 13 - 10
services/web/frontend/js/features/history/utils/auto-select-file.ts

@@ -75,16 +75,19 @@ function getFilesWithOps(
 
     return filesWithOps
   } else {
-    const filesWithOps = files.reduce((curFilesWithOps, file) => {
-      if ('operation' in file) {
-        curFilesWithOps.push({
-          pathname: file.pathname,
-          editable: isFileEditable(file),
-          operation: file.operation,
-        })
-      }
-      return curFilesWithOps
-    }, <FileWithOps[]>[])
+    const filesWithOps = files.reduce(
+      (curFilesWithOps, file) => {
+        if ('operation' in file) {
+          curFilesWithOps.push({
+            pathname: file.pathname,
+            editable: isFileEditable(file),
+            operation: file.operation,
+          })
+        }
+        return curFilesWithOps
+      },
+      <FileWithOps[]>[]
+    )
 
     return filesWithOps
   }

+ 5 - 5
services/web/frontend/js/features/ide-react/context/review-panel/hooks/use-review-panel-state.ts

@@ -226,8 +226,8 @@ function useReviewPanelState(): ReviewPanel.ReviewPanelState {
             MergeAndOverride<
               ReviewPanelCommentThread,
               ReviewPanelCommentThreadsApi[ThreadId]
-            >
-          ]
+            >,
+          ],
         ]
         for (const [threadId, thread] of threadsEntries) {
           for (const comment of thread.messages) {
@@ -565,8 +565,8 @@ function useReviewPanelState(): ReviewPanel.ReviewPanelState {
           [
             UserId,
             NonNullable<
-              typeof trackChangesState[keyof typeof trackChangesState]
-            >
+              (typeof trackChangesState)[keyof typeof trackChangesState]
+            >,
           ]
         >
         for (const [userId, { value }] of entries) {
@@ -605,7 +605,7 @@ function useReviewPanelState(): ReviewPanel.ReviewPanelState {
       }
       const state =
         newTrackChangesState[userId] ??
-        ({} as NonNullable<typeof newTrackChangesState[UserId]>)
+        ({} as NonNullable<(typeof newTrackChangesState)[UserId]>)
       newTrackChangesState[userId] = state
 
       if (state.syncState == null || state.syncState === 'synced') {

+ 2 - 2
services/web/frontend/js/features/ide-react/hooks/use-editing-session-heartbeat.ts

@@ -69,8 +69,8 @@ export function useEditingSessionHeartbeat() {
       heartbeatsSent <= 2
         ? 30
         : heartbeatsSent <= 6
-        ? (heartbeatsSent - 2) * 60
-        : 300
+          ? (heartbeatsSent - 2) * 60
+          : 300
 
     setNextHeartbeatAt(moment().add(backoffSecs, 'seconds').toDate())
   }, [getEditorType, heartbeatsSent, nextHeartbeatAt, projectId])

+ 2 - 2
services/web/frontend/js/features/pdf-preview/components/faster-compiles-feedback.tsx

@@ -111,8 +111,8 @@ function FasterCompilesFeedbackContent() {
                 {feedback === 'faster'
                   ? t('faster_compiles_feedback_seems_faster')
                   : feedback === 'same'
-                  ? t('faster_compiles_feedback_seems_same')
-                  : t('faster_compiles_feedback_seems_slower')}
+                    ? t('faster_compiles_feedback_seems_same')
+                    : t('faster_compiles_feedback_seems_slower')}
               </Button>
             ))}
           </div>

+ 2 - 2
services/web/frontend/js/features/pdf-preview/components/pdf-viewer.jsx

@@ -1,8 +1,8 @@
 import { lazy, memo } from 'react'
 import { useDetachCompileContext as useCompileContext } from '../../../shared/context/detach-compile-context'
 
-const PdfJsViewer = lazy(() =>
-  import(/* webpackChunkName: "pdf-js-viewer" */ './pdf-js-viewer')
+const PdfJsViewer = lazy(
+  () => import(/* webpackChunkName: "pdf-js-viewer" */ './pdf-js-viewer')
 )
 
 function PdfViewer() {

+ 1 - 1
services/web/frontend/js/features/project-list/components/notifications/groups-and-enterprise-banner.tsx

@@ -7,7 +7,7 @@ import { useProjectListContext } from '../../context/project-list-context'
 import { useTranslation } from 'react-i18next'
 
 const variants = ['did-you-know', 'on-premise', 'people', 'FOMO'] as const
-type GroupsAndEnterpriseBannerVariant = typeof variants[number]
+type GroupsAndEnterpriseBannerVariant = (typeof variants)[number]
 
 let viewEventSent = false
 

+ 2 - 2
services/web/frontend/js/features/project-list/components/table/project-tools/buttons/tags-dropdown.tsx

@@ -116,8 +116,8 @@ function TagsDropdown() {
                       containsAllSelectedProjects(tag)
                         ? 'check-square-o'
                         : containsSomeSelectedProjects(tag)
-                        ? 'minus-square-o'
-                        : 'square-o'
+                          ? 'minus-square-o'
+                          : 'square-o'
                     }
                     className="tag-checkbox"
                   />{' '}

+ 2 - 1
services/web/frontend/js/features/settings/components/emails/add-email/country-input.tsx

@@ -9,7 +9,8 @@ type CountryInputProps = {
   inputRef?: React.ForwardedRef<HTMLInputElement>
 } & React.InputHTMLAttributes<HTMLInputElement>
 
-const itemToString = (item: typeof countries[number] | null) => item?.name ?? ''
+const itemToString = (item: (typeof countries)[number] | null) =>
+  item?.name ?? ''
 
 function Downshift({ setValue, inputRef }: CountryInputProps) {
   const { t } = useTranslation()

+ 3 - 1
services/web/frontend/js/features/settings/domain-blocklist.ts

@@ -58,4 +58,6 @@ for (const domain of commonDomains) {
   }
 }
 
-export default domainBlocklist as ReadonlyArray<typeof domainBlocklist[number]>
+export default domainBlocklist as ReadonlyArray<
+  (typeof domainBlocklist)[number]
+>

+ 2 - 2
services/web/frontend/js/features/source-editor/components/figure-modal/figure-modal.tsx

@@ -268,8 +268,8 @@ const FigureModalContent = () => {
           {helpShown
             ? t('help')
             : sourcePickerShown
-            ? t('replace_figure')
-            : getTitle(source)}{' '}
+              ? t('replace_figure')
+              : getTitle(source)}{' '}
           <FeedbackBadge
             id="figure-modal-feedback"
             url="https://forms.gle/PfEtwceYBNQ32DF4A"

+ 2 - 2
services/web/frontend/js/features/source-editor/components/figure-modal/file-sources/figure-modal-upload-source.tsx

@@ -209,8 +209,8 @@ export const FigureModalUploadFileSource: FC = () => {
               uploading
                 ? FileUploadStatus.UPLOADING
                 : uploadError
-                ? FileUploadStatus.ERROR
-                : FileUploadStatus.NOT_ATTEMPTED
+                  ? FileUploadStatus.ERROR
+                  : FileUploadStatus.NOT_ATTEMPTED
             }
             onDelete={() => {
               uppy.removeFile(file.id)

+ 1 - 1
services/web/frontend/js/features/source-editor/components/review-panel/utils/compare-props-with-shallow-array-compare.ts

@@ -4,7 +4,7 @@ const shallowEqual = (arr1: unknown[], arr2: unknown[]) =>
 // Compares props for a component, but comparing the specified props using
 // shallow array comparison rather than identity
 export default function comparePropsWithShallowArrayCompare<
-  T extends Record<string, unknown>
+  T extends Record<string, unknown>,
 >(...args: Array<keyof T>) {
   return (prevProps: T, nextProps: T) => {
     for (const k in prevProps) {

+ 2 - 3
services/web/frontend/js/features/source-editor/components/table-generator/toolbar/commands.ts

@@ -747,9 +747,8 @@ export const setColumnWidth = (
       const alignmentCharacter = getParagraphAlignmentCharacter(
         columnSpecification[i]
       )
-      columnSpecification[
-        i
-      ].content = `${alignmentCharacter}{${widthValue}${suffix}}`
+      columnSpecification[i].content =
+        `${alignmentCharacter}{${widthValue}${suffix}}`
     }
   }
   const newSpecification = generateColumnSpecification(columnSpecification)

+ 4 - 4
services/web/frontend/js/features/source-editor/extensions/browser.ts

@@ -28,10 +28,10 @@ export default {
   ie_version: ieUpTo10
     ? doc.documentMode || 6
     : ie11Up
-    ? +ie11Up[1]
-    : ieEdge
-    ? +ieEdge[1]
-    : 0,
+      ? +ie11Up[1]
+      : ieEdge
+        ? +ieEdge[1]
+        : 0,
   gecko,
   gecko_version: gecko
     ? +(/Firefox\/(\d+)/.exec(nav.userAgent) || [0, 0])[1]

+ 4 - 1
services/web/frontend/js/features/source-editor/extensions/track-changes.ts

@@ -221,7 +221,10 @@ class ChangeDeletedWidget extends WidgetType {
 }
 
 class ChangeCalloutWidget extends WidgetType {
-  constructor(public change: Change, public opType: string) {
+  constructor(
+    public change: Change,
+    public opType: string
+  ) {
     super()
   }
 

+ 1 - 1
services/web/frontend/js/features/source-editor/extensions/visual/paste-html.ts

@@ -542,7 +542,7 @@ const createSelector = <
   T extends string,
   E extends HTMLElement = T extends keyof HTMLElementTagNameMap
     ? HTMLElementTagNameMap[T]
-    : HTMLElement
+    : HTMLElement,
 >({
   selector,
   ...elementSelector

+ 4 - 2
services/web/frontend/js/features/source-editor/extensions/visual/pasted-content.tsx

@@ -13,8 +13,10 @@ import { SplitTestProvider } from '../../../../shared/context/split-test-context
 
 export type PastedContent = { latex: string; text: string }
 
-const pastedContentEffect =
-  StateEffect.define<{ content: PastedContent; formatted: boolean }>()
+const pastedContentEffect = StateEffect.define<{
+  content: PastedContent
+  formatted: boolean
+}>()
 
 export const insertPastedContent = (
   view: EditorView,

+ 4 - 1
services/web/frontend/js/features/source-editor/extensions/visual/visual-widgets/environment-line.ts

@@ -1,7 +1,10 @@
 import { EditorView, WidgetType } from '@codemirror/view'
 
 export class EnvironmentLineWidget extends WidgetType {
-  constructor(public environment: string, public line?: 'begin' | 'end') {
+  constructor(
+    public environment: string,
+    public line?: 'begin' | 'end'
+  ) {
     super()
   }
 

+ 2 - 3
services/web/frontend/js/ide/review-panel/controllers/ReviewPanelController.js

@@ -371,9 +371,8 @@ export default App.controller('ReviewPanelController', [
                 ide.$scope.reviewPanel.overview.docsCollapsedState[doc.id] ==
                 null
               ) {
-                ide.$scope.reviewPanel.overview.docsCollapsedState[
-                  doc.id
-                ] = false
+                ide.$scope.reviewPanel.overview.docsCollapsedState[doc.id] =
+                  false
               }
               if (doc.id !== $scope.editor.open_doc_id) {
                 // this is kept up to date in real-time, don't overwrite

+ 2 - 2
services/web/frontend/js/main/event.js

@@ -93,8 +93,8 @@ App.factory('eventTracking', [
           heartbeatsSent <= 2
             ? 30
             : heartbeatsSent <= 6
-            ? (heartbeatsSent - 2) * 60
-            : 300
+              ? (heartbeatsSent - 2) * 60
+              : 300
 
         nextHeartbeat = moment().add(backoffSecs, 'seconds').toDate()
       },

+ 2 - 2
services/web/frontend/js/shared/context/project-context.tsx

@@ -94,8 +94,8 @@ export const ProjectProvider: FC = ({ children }) => {
     forceNewCompileTimeout === 'active'
       ? 'active'
       : forceNewCompileTimeout === 'changing'
-      ? 'changing'
-      : undefined
+        ? 'changing'
+        : undefined
 
   const value = useMemo(() => {
     return {

+ 1 - 1
services/web/frontend/js/utils/functions.ts

@@ -1,6 +1,6 @@
 export function callFnsInSequence<
   Args,
-  Fn extends ((...args: Args[]) => void) | void
+  Fn extends ((...args: Args[]) => void) | void,
 >(...fns: Fn[]) {
   return (...args: Args[]) => fns.forEach(fn => fn?.(...args))
 }

+ 3 - 1
services/web/frontend/stylesheets/app/editor/logs.less

@@ -155,7 +155,9 @@
   position: relative;
   height: 40px;
   margin-top: 0;
-  transition: margin 0.15s ease-in-out, opacity 0.15s ease-in-out;
+  transition:
+    margin 0.15s ease-in-out,
+    opacity 0.15s ease-in-out;
   padding-bottom: @padding-sm;
   text-align: center;
   background-image: linear-gradient(0, @ol-blue-gray-1, transparent);

+ 8 - 2
services/web/frontend/stylesheets/app/editor/pdf.less

@@ -47,7 +47,11 @@
 }
 
 .toolbar-pdf-hybrid {
-  .btn:not(.detach-compile-button):not(.btn-orphan):not(.detach-synctex-control):not(.switch-to-editor-btn):not(.split-menu-dropdown-toggle):not(.split-menu-button) {
+  .btn:not(.detach-compile-button):not(.btn-orphan):not(
+      .detach-synctex-control
+    ):not(.switch-to-editor-btn):not(.split-menu-dropdown-toggle):not(
+      .split-menu-button
+    ) {
     display: inline-block;
     color: @toolbar-btn-color;
     background-color: transparent;
@@ -208,7 +212,9 @@
     z-index: 10; // above the PDF viewer
 
     .btn-group {
-      transition: opacity 0.5s ease, visibility 0 linear 0.5s;
+      transition:
+        opacity 0.5s ease,
+        visibility 0 linear 0.5s;
       visibility: hidden;
       opacity: 0;
     }

+ 22 - 7
services/web/frontend/stylesheets/app/editor/review-panel.less

@@ -231,10 +231,15 @@
   border-radius: 3px;
   color: #fff;
   cursor: pointer;
-  transition: top @rp-entry-animation-speed, left 0.1s, right 0.1s;
+  transition:
+    top @rp-entry-animation-speed,
+    left 0.1s,
+    right 0.1s;
 
   .no-animate & {
-    transition: left 0.1s, right 0.1s;
+    transition:
+      left 0.1s,
+      right 0.1s;
   }
 
   &-focused {
@@ -381,10 +386,15 @@
   border-left: solid @rp-entry-ribbon-width transparent;
   border-radius: 3px;
   background-color: #fff;
-  transition: top @rp-entry-animation-speed, left 0.1s, right 0.1s;
+  transition:
+    top @rp-entry-animation-speed,
+    left 0.1s,
+    right 0.1s;
 
   .no-animate & {
-    transition: left 0.1s, right 0.1s;
+    transition:
+      left 0.1s,
+      right 0.1s;
   }
 
   &-insert,
@@ -407,8 +417,11 @@
     z-index: 3;
     transform: scale(0.1);
     transform-origin: 0 0;
-    transition: top 0.35s ease-out, left 0.35s ease-out,
-      transform 0.35s ease-out, opacity 0.35s ease-out 0.2s;
+    transition:
+      top 0.35s ease-out,
+      left 0.35s ease-out,
+      transform 0.35s ease-out,
+      opacity 0.35s ease-out 0.2s;
   }
 
   &-comment-resolved {
@@ -654,7 +667,9 @@
 }
 
 .rp-entry-callout {
-  transition: top @rp-entry-animation-speed, height @rp-entry-animation-speed;
+  transition:
+    top @rp-entry-animation-speed,
+    height @rp-entry-animation-speed;
 
   .rp-state-current-file & {
     position: absolute;

+ 3 - 1
services/web/frontend/stylesheets/app/editor/toolbar.less

@@ -252,7 +252,9 @@
   user-select: none;
   color: @text-color;
   border-radius: @btn-border-radius-base;
-  transition: color 0.12s ease-out, background-color 0.12s ease-out,
+  transition:
+    color 0.12s ease-out,
+    background-color 0.12s ease-out,
     box-shadow 0.12s ease-out;
   overflow: hidden;
 

+ 9 - 3
services/web/frontend/stylesheets/app/plans/plans-v2.less

@@ -590,7 +590,9 @@ span.plans-v2-license-picker-educational-discount-learn-more-container {
     background-clip: padding-box; /* needed for firefox when there is bg color */
     text-align: center;
 
-    &:not(.plans-v2-table-cell-before-green-highlighted-column):not(.plans-v2-table-green-highlighted):not(.plans-v2-table-divider-highlighted) {
+    &:not(.plans-v2-table-cell-before-green-highlighted-column):not(
+        .plans-v2-table-green-highlighted
+      ):not(.plans-v2-table-divider-highlighted) {
       border-right: 1px solid @ol-blue-gray-0;
 
       @media (max-width: @screen-xs-max) {
@@ -623,7 +625,9 @@ span.plans-v2-license-picker-educational-discount-learn-more-container {
     vertical-align: middle;
     height: 100%;
 
-    &:last-child:not(.plans-v2-table-green-highlighted):not(.plans-v2-table-divider-highlighted) {
+    &:last-child:not(.plans-v2-table-green-highlighted):not(
+        .plans-v2-table-divider-highlighted
+      ) {
       border-right: 0;
     }
   }
@@ -649,7 +653,9 @@ span.plans-v2-license-picker-educational-discount-learn-more-container {
       }
     }
 
-    &:not(.plans-v2-table-row-last-row-per-section):not(.plans-v2-table-divider):not(:last-of-type) {
+    &:not(.plans-v2-table-row-last-row-per-section):not(
+        .plans-v2-table-divider
+      ):not(:last-of-type) {
       th > .plans-v2-table-th > .plans-v2-table-th-content,
       td > .plans-v2-table-feature-name,
       td > .plans-v2-table-cell > .plans-v2-table-cell-content {

+ 4 - 1
services/web/frontend/stylesheets/app/project-list-react.less

@@ -923,7 +923,10 @@
   vertical-align: middle;
 
   &:focus-visible {
-    box-shadow: 0 0 0 2px @white, 0 0 0 3px @blue-50, 0 0 0 5px @blue-30;
+    box-shadow:
+      0 0 0 2px @white,
+      0 0 0 3px @blue-50,
+      0 0 0 5px @blue-30;
   }
 
   &.more-button {

+ 14 - 6
services/web/frontend/stylesheets/app/review-features-page.less

@@ -214,7 +214,9 @@
   display: inline-block;
   transform: translate(150px, 0);
   opacity: 0;
-  transition: transform 0.8s ease 0s, opacity 0.8s ease 0s;
+  transition:
+    transform 0.8s ease 0s,
+    opacity 0.8s ease 0s;
   &:nth-child(2) {
     transition-delay: 0.5s, 0.5s;
   }
@@ -244,7 +246,9 @@
   margin-bottom: 2em;
   transform: translate(0, 100px);
   opacity: 0;
-  transition: transform 0.8s ease 1s, opacity 0.8s ease 1s;
+  transition:
+    transform 0.8s ease 1s,
+    opacity 0.8s ease 1s;
   box-shadow: none;
   max-width: none;
 
@@ -271,7 +275,9 @@
   }
 }
 .rfp-video-anim {
-  transition: transform 0.8s ease, opacity 0.8s ease;
+  transition:
+    transform 0.8s ease,
+    opacity 0.8s ease;
   transform: translate(100%, 0);
   opacity: 0;
 }
@@ -294,7 +300,7 @@
   border-left: 0;
   max-width: 30em;
   font-size: @rfp-lead-size;
-  quotes: '\201C''\201D';
+  quotes: '\201C' '\201D';
   box-shadow: @rfp-card-shadow;
   border-radius: @rfp-border-radius;
   background-color: #fff;
@@ -333,7 +339,7 @@
   border-left: 0;
   margin: 0 auto;
   padding: 0;
-  quotes: '\201C''\201D';
+  quotes: '\201C' '\201D';
   font-size: @rfp-lead-size;
   @media (min-width: @screen-md-min) {
     display: flex;
@@ -454,7 +460,9 @@
   position: absolute;
   left: 50%;
   text-transform: uppercase;
-  transition: opacity 0.25s, transform 0.25s;
+  transition:
+    opacity 0.25s,
+    transform 0.25s;
   transform: translate(-50%, 100%);
   opacity: 0;
   font-size: 0.5em;

+ 6 - 3
services/web/frontend/stylesheets/app/website-redesign.less

@@ -140,7 +140,9 @@
       .info-card {
         border-radius: 8px;
         height: 100%;
-        box-shadow: 0px 2px 4px 0px #1e253014, 0px 4px 12px 0px #1e25301f;
+        box-shadow:
+          0px 2px 4px 0px #1e253014,
+          0px 4px 12px 0px #1e25301f;
         border-top: 8px solid var(--sapphire-blue);
         padding: 32px 40px 32px 40px;
 
@@ -345,7 +347,8 @@
       }
 
       video {
-        box-shadow: 0px 4px 6px 0px rgba(30, 37, 48, 0.12),
+        box-shadow:
+          0px 4px 6px 0px rgba(30, 37, 48, 0.12),
           0px 8px 16px 0px rgba(30, 37, 48, 0.12);
         max-height: 100%;
         width: auto;
@@ -998,7 +1001,7 @@
     font-size: 1.875rem;
     line-height: 1.333;
     font-weight: 600;
-    quotes: '\201C''\201D'; // override default quotes
+    quotes: '\201C' '\201D'; // override default quotes
     padding: unset;
     margin: unset;
     font-family: 'Noto Sans', sans-serif;

+ 4 - 2
services/web/frontend/stylesheets/bootstrap-5/foundations/elevation.scss

@@ -5,12 +5,14 @@
 
 // Tooltips, Callouts, Dropdowns, etc.
 @mixin shadow-md {
-  box-shadow: 0px 4px 24px rgba(30, 37, 48, 0.12),
+  box-shadow:
+    0px 4px 24px rgba(30, 37, 48, 0.12),
     0px 1px 4px rgba(30, 37, 48, 0.08);
 }
 
 // Modals, drawers
 @mixin shadow-lg {
-  box-shadow: 0px 8px 24px rgba(30, 37, 48, 0.16),
+  box-shadow:
+    0px 8px 24px rgba(30, 37, 48, 0.16),
     0px 4px 8px rgba(30, 37, 48, 0.16);
 }

+ 3 - 1
services/web/frontend/stylesheets/components/expand-collapse.less

@@ -1,4 +1,6 @@
 .expand-collapse-container {
   overflow: hidden;
-  transition: height 0.15s ease-in-out, width 0.15s ease-in-out;
+  transition:
+    height 0.15s ease-in-out,
+    width 0.15s ease-in-out;
 }

+ 2 - 1
services/web/frontend/stylesheets/components/forms.less

@@ -331,7 +331,8 @@ input[type='checkbox'],
   ); // Redeclare so transitions work
   &:focus {
     border-color: darken(@state-danger-text, 10%);
-    @shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
+    @shadow:
+      inset 0 1px 1px rgba(0, 0, 0, 0.075),
       0 0 6px lighten(@state-danger-text, 20%);
     .box-shadow(@shadow);
   }

+ 7 - 2
services/web/frontend/stylesheets/components/input-switch.less

@@ -19,7 +19,9 @@
     padding: 1px;
     background-color: @rp-highlight-blue;
     border-radius: 0.875em;
-    transition: background 0.15s ease, border-color 0.15s ease;
+    transition:
+      background 0.15s ease,
+      border-color 0.15s ease;
 
     &::before {
       content: '';
@@ -31,7 +33,10 @@
       top: 1px;
       background-color: #fff;
       border-radius: 0.875em;
-      transition: background-color 0.15s ease, color 0.15s ease, left 0.15s ease;
+      transition:
+        background-color 0.15s ease,
+        color 0.15s ease,
+        left 0.15s ease;
     }
   }
 

+ 2 - 1
services/web/frontend/stylesheets/components/navbar.less

@@ -324,7 +324,8 @@
   padding: 10px @navbar-padding-horizontal;
   border-top: 1px solid transparent;
   border-bottom: 1px solid transparent;
-  @shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1),
+  @shadow:
+    inset 0 1px 0 rgba(255, 255, 255, 0.1),
     0 1px 0 rgba(255, 255, 255, 0.1);
   .box-shadow(@shadow);
 

+ 2 - 1
services/web/frontend/stylesheets/components/ui-select.less

@@ -34,7 +34,8 @@
       background-color: transparent;
       > .btn {
         border-color: @input-border-focus;
-        box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
+        box-shadow:
+          inset 0 1px 1px rgba(0, 0, 0, 0.075),
           0 0 8px fade(@input-border-focus, 60%);
         padding-top: @input-suggestion-v-offset;
       }

+ 1 - 1
services/web/frontend/stylesheets/core/type.less

@@ -328,7 +328,7 @@ blockquote {
   padding: (@line-height-computed / 2) @line-height-computed;
   margin: 0 0 @line-height-computed;
   font-size: @blockquote-font-size;
-  quotes: '\201C''\201D''\2018''\2019';
+  quotes: '\201C' '\201D' '\2018' '\2019';
   border-left: 5px solid @blockquote-border-color;
   &:before {
     content: open-quote;

+ 2 - 1
services/web/frontend/stylesheets/core/variables.less

@@ -962,7 +962,8 @@
 @btn-border-bottom-width: 0;
 
 // Shadows
-@box-shadow: 0px 4px 12px rgba(30, 37, 48, 0.12),
+@box-shadow:
+  0px 4px 12px rgba(30, 37, 48, 0.12),
   0px 2px 4px rgba(30, 37, 48, 0.08);
 
 // Cards

+ 2 - 1
services/web/frontend/stylesheets/variables/all.less

@@ -787,7 +787,8 @@
 @btn-secondary-hover-bg-color: @neutral-20;
 
 // Shadows
-@box-shadow: 0px 4px 12px rgba(30, 37, 48, 0.12),
+@box-shadow:
+  0px 4px 12px rgba(30, 37, 48, 0.12),
   0px 2px 4px rgba(30, 37, 48, 0.08);
 
 // Cards

+ 2 - 1
services/web/frontend/stylesheets/vendor/select/select.css

@@ -156,7 +156,8 @@ body > .select2-container.open {
   text-decoration: none;
   outline: 5px auto -webkit-focus-ring-color;
   outline-offset: -2px;
-  box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075),
+  box-shadow:
+    inset 0 1px 1px rgba(0, 0, 0, 0.075),
     0 0 8px rgba(102, 175, 233, 0.6);
 }
 

+ 4 - 6
services/web/scripts/analytics/sync_group_subscription_memberships.js

@@ -45,9 +45,8 @@ async function checkActiveSubscriptions() {
 
     if (subscriptions.length) {
       const groupIds = subscriptions.map(sub => sub._id)
-      const bigQueryGroupMemberships = await fetchBigQueryMembershipStatuses(
-        groupIds
-      )
+      const bigQueryGroupMemberships =
+        await fetchBigQueryMembershipStatuses(groupIds)
       const membershipsByGroupId = _.groupBy(
         bigQueryGroupMemberships,
         'group_id'
@@ -85,9 +84,8 @@ async function checkDeletedSubscriptions() {
 
     if (deletedSubscriptions.length) {
       const groupIds = deletedSubscriptions.map(sub => sub._id.toString())
-      const bigQueryGroupMemberships = await fetchBigQueryMembershipStatuses(
-        groupIds
-      )
+      const bigQueryGroupMemberships =
+        await fetchBigQueryMembershipStatuses(groupIds)
 
       const membershipsByGroupId = _.groupBy(
         bigQueryGroupMemberships,

+ 2 - 3
services/web/scripts/delete_orphaned_docs_online_check.js

@@ -68,9 +68,8 @@ async function main() {
         new Set(docs.map(doc => doc.project_id.toString()))
       ).map(id => new ObjectId(id))
       console.log('Checking projects', JSON.stringify(projectIds))
-      const { nProjectsWithOrphanedDocs, nDeletedDocs } = await processBatch(
-        projectIds
-      )
+      const { nProjectsWithOrphanedDocs, nDeletedDocs } =
+        await processBatch(projectIds)
       nProjectsProcessedTotal += projectIds.length
       nProjectsWithOrphanedDocsTotal += nProjectsWithOrphanedDocs
       nDeletedDocsTotal += nDeletedDocs

+ 2 - 3
services/web/scripts/restore_soft_deleted_docs.js

@@ -8,9 +8,8 @@ const PROJECT_ID = ARGV.shift()
 const FILE_NAMES_TO_RESTORE = ARGV
 
 async function main() {
-  const deletedDocs = await DocstoreManager.promises.getAllDeletedDocs(
-    PROJECT_ID
-  )
+  const deletedDocs =
+    await DocstoreManager.promises.getAllDeletedDocs(PROJECT_ID)
   const docsToRestore = deletedDocs.filter(doc =>
     FILE_NAMES_TO_RESTORE.includes(doc.name)
   )

+ 2 - 3
services/web/scripts/sso_id_migration_check.js

@@ -19,9 +19,8 @@ waitForDb()
   })
 
 async function main() {
-  const result = await SAMLUserIdMigrationHandler.promises.checkMigration(
-    institutionId
-  )
+  const result =
+    await SAMLUserIdMigrationHandler.promises.checkMigration(institutionId)
 
   if (emitUsers) {
     console.log(

+ 2 - 3
services/web/scripts/sso_id_remove_not_migrated.js

@@ -19,9 +19,8 @@ waitForDb()
   })
 
 async function main() {
-  const result = await SAMLUserIdMigrationHandler.promises.removeNotMigrated(
-    institutionId
-  )
+  const result =
+    await SAMLUserIdMigrationHandler.promises.removeNotMigrated(institutionId)
 
   if (emitUsers) {
     console.log(

+ 1 - 1
services/web/test/frontend/components/shared/select.spec.tsx

@@ -11,7 +11,7 @@ const testData = [1, 2, 3].map(index => ({
   sub: `Subtitle ${index}`,
 }))
 
-type RenderProps = Partial<SelectProps<typeof testData[number]>> & {
+type RenderProps = Partial<SelectProps<(typeof testData)[number]>> & {
   onSubmit?: (formData: object) => void
 }
 

+ 6 - 9
services/web/test/frontend/features/project-list/components/project-list-root.test.tsx

@@ -803,9 +803,8 @@ describe('<ProjectListRoot />', function () {
             fireEvent.click(allCheckboxes[1]) // select a project owned by the current user
 
             const actionsToolbar = screen.getAllByRole('toolbar')[0]
-            const moreDropdown = await within(
-              actionsToolbar
-            ).findByText<HTMLElement>('More')
+            const moreDropdown =
+              await within(actionsToolbar).findByText<HTMLElement>('More')
             fireEvent.click(moreDropdown)
 
             const editButton =
@@ -851,9 +850,8 @@ describe('<ProjectListRoot />', function () {
                 status: 200,
               }
             )
-            const moreDropdown = await within(
-              actionsToolbar
-            ).findByText<HTMLElement>('More')
+            const moreDropdown =
+              await within(actionsToolbar).findByText<HTMLElement>('More')
             fireEvent.click(moreDropdown)
 
             const renameButton =
@@ -888,9 +886,8 @@ describe('<ProjectListRoot />', function () {
             within(table).getByText(newProjectName)
             expect(within(table).queryByText(oldName)).to.be.null
 
-            const allCheckboxesInTable = await within(
-              table
-            ).findAllByRole<HTMLInputElement>('checkbox')
+            const allCheckboxesInTable =
+              await within(table).findAllByRole<HTMLInputElement>('checkbox')
             const allCheckboxesChecked = allCheckboxesInTable.filter(
               c => c.checked
             )

Неке датотеке нису приказане због велике количине промена