Bläddra i källkod

[web] Upgrade Prettier to match version in monorepo root (#6231)

GitOrigin-RevId: 02f97af1b9704782eee77a0b7dfc477ada23e34d
Alf Eaton 4 år sedan
förälder
incheckning
50df230846
100 ändrade filer med 1628 tillägg och 1733 borttagningar
  1. 8 4
      services/web/app.js
  2. 10 11
      services/web/app/src/Features/Authorization/AuthorizationManager.js
  3. 18 15
      services/web/app/src/Features/Authorization/AuthorizationMiddleware.js
  4. 1 2
      services/web/app/src/Features/Captcha/CaptchaMiddleware.js
  5. 25 30
      services/web/app/src/Features/Collaborators/CollaboratorsGetter.js
  6. 4 8
      services/web/app/src/Features/Collaborators/CollaboratorsHandler.js
  7. 2 3
      services/web/app/src/Features/Compile/ClsiStateManager.js
  8. 12 13
      services/web/app/src/Features/Editor/EditorHttpController.js
  9. 2 6
      services/web/app/src/Features/Errors/Errors.js
  10. 239 235
      services/web/app/src/Features/Exports/ExportsHandler.js
  11. 3 2
      services/web/app/src/Features/Helpers/EmailHelper.js
  12. 15 10
      services/web/app/src/Features/InactiveData/InactiveProjectManager.js
  13. 2 5
      services/web/app/src/Features/Institutions/InstitutionsManager.js
  14. 2 4
      services/web/app/src/Features/LinkedFiles/ProjectOutputFileAgent.js
  15. 8 16
      services/web/app/src/Features/Project/ProjectController.js
  16. 4 5
      services/web/app/src/Features/Project/ProjectDetailsHandler.js
  17. 6 5
      services/web/app/src/Features/Project/ProjectDuplicator.js
  18. 2 3
      services/web/app/src/Features/Project/ProjectEditorHandler.js
  19. 28 43
      services/web/app/src/Features/Project/ProjectEntityMongoUpdateHandler.js
  20. 2 5
      services/web/app/src/Features/Project/ProjectEntityUpdateHandler.js
  21. 3 2
      services/web/app/src/Features/References/ReferencesHandler.js
  22. 5 4
      services/web/app/src/Features/SplitTests/SplitTestMiddleware.js
  23. 2 6
      services/web/app/src/Features/SplitTests/SplitTestV2Handler.js
  24. 6 11
      services/web/app/src/Features/Subscription/FeaturesUpdater.js
  25. 2 3
      services/web/app/src/Features/Subscription/LimitationsManager.js
  26. 6 9
      services/web/app/src/Features/Subscription/RecurlyWrapper.js
  27. 27 30
      services/web/app/src/Features/Subscription/SubscriptionController.js
  28. 6 9
      services/web/app/src/Features/Subscription/SubscriptionUpdater.js
  29. 19 16
      services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js
  30. 4 6
      services/web/app/src/Features/ThirdPartyDataStore/TpdsUpdateSender.js
  31. 12 14
      services/web/app/src/Features/TokenAccess/TokenAccessController.js
  32. 10 11
      services/web/app/src/Features/Uploads/ProjectUploadManager.js
  33. 2 3
      services/web/app/src/Features/User/SAMLIdentityManager.js
  34. 2 3
      services/web/app/src/Features/User/UserGetter.js
  35. 13 12
      services/web/app/src/Features/User/UserSessionsManager.js
  36. 2 3
      services/web/app/src/Features/UserMembership/UserMembershipController.js
  37. 5 4
      services/web/app/src/Features/UserMembership/UserMembershipMiddleware.js
  38. 6 9
      services/web/app/src/infrastructure/Features.js
  39. 2 3
      services/web/app/src/infrastructure/Translations.js
  40. 9 7
      services/web/frontend/js/directives/onEnter.js
  41. 4 3
      services/web/frontend/js/features/clone-project-modal/components/clone-project-modal-content.js
  42. 2 3
      services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js
  43. 2 5
      services/web/frontend/js/features/file-tree/components/file-tree-context-menu.js
  44. 2 4
      services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.js
  45. 2 7
      services/web/frontend/js/features/file-tree/components/file-tree-item/file-tree-item-name.js
  46. 2 6
      services/web/frontend/js/features/file-tree/components/file-tree-toolbar.js
  47. 2 7
      services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-create-folder.js
  48. 98 95
      services/web/frontend/js/features/file-tree/controllers/file-tree-controller.js
  49. 2 6
      services/web/frontend/js/features/file-tree/hooks/file-tree-socket-listener.js
  50. 4 3
      services/web/frontend/js/features/pdf-preview/components/detach-compile-button.js
  51. 2 6
      services/web/frontend/js/features/pdf-preview/components/pdf-js-viewer.js
  52. 2 7
      services/web/frontend/js/features/pdf-preview/components/pdf-logs-button.js
  53. 2 7
      services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.js
  54. 2 6
      services/web/frontend/js/features/pdf-preview/hooks/use-compile-triggers.js
  55. 5 5
      services/web/frontend/js/features/plans/group-plan-modal/index.js
  56. 4 8
      services/web/frontend/js/features/preview/components/preview-logs-pane-entry.js
  57. 4 6
      services/web/frontend/js/features/preview/components/preview-pane.js
  58. 2 4
      services/web/frontend/js/features/share-project-modal/components/edit-member.js
  59. 6 4
      services/web/frontend/js/features/share-project-modal/components/select-collaborators.js
  60. 4 3
      services/web/frontend/js/features/share-project-modal/components/share-project-modal.js
  61. 2 1
      services/web/frontend/js/ide/connection/ConnectionManager.js
  62. 2 4
      services/web/frontend/js/ide/editor/ShareJsDoc.js
  63. 4 6
      services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.js
  64. 0 0
      services/web/frontend/js/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.js
  65. 8 16
      services/web/frontend/js/ide/editor/directives/aceEditor/track-changes/TrackChangesAdapter.js
  66. 4 8
      services/web/frontend/js/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.js
  67. 4 9
      services/web/frontend/js/ide/history/HistoryManager.js
  68. 11 12
      services/web/frontend/js/ide/history/HistoryV2Manager.js
  69. 6 3
      services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsRules.js
  70. 10 17
      services/web/frontend/js/ide/log-parser/bib-log-parser.js
  71. 2 2
      services/web/frontend/js/ide/pdfng/directives/pdfViewer.js
  72. 6 8
      services/web/frontend/js/ide/review-panel/controllers/ReviewPanelController.js
  73. 3 2
      services/web/frontend/js/ide/review-panel/filters/notEmpty.js
  74. 46 43
      services/web/frontend/js/main/account-settings.js
  75. 16 14
      services/web/frontend/js/main/affiliations/controllers/UserAffiliationsController.js
  76. 709 701
      services/web/frontend/js/main/project-list/project-list.js
  77. 2 3
      services/web/frontend/js/shared/context/compile-context.js
  78. 2 3
      services/web/frontend/js/shared/context/layout-context.js
  79. 2 6
      services/web/frontend/js/shared/hooks/use-detach-action.js
  80. 2 7
      services/web/frontend/js/shared/hooks/use-detach-layout.js
  81. 2 6
      services/web/frontend/js/shared/hooks/use-detach-state.js
  82. 15 16
      services/web/frontend/stories/modals/create-file/create-file-modal-decorator.js
  83. 1 2
      services/web/migrations/20190912145032_create_users_indexes.js
  84. 8 4
      services/web/modules/launchpad/test/unit/src/LaunchpadControllerTests.js
  85. 2 2
      services/web/modules/user-activate/test/unit/src/UserActivateControllerTests.js
  86. 15 3
      services/web/package-lock.json
  87. 1 1
      services/web/package.json
  88. 10 8
      services/web/scripts/clear_institution_notifications.js
  89. 2 3
      services/web/scripts/count_files_in_projects.js
  90. 2 2
      services/web/scripts/delete-orphaned-docs/delete-orphaned-docs.js
  91. 2 1
      services/web/scripts/history/count_project_history_categories.js
  92. 2 1
      services/web/scripts/history/upgrade_v1_without_conversion_if_created_after_fph_enabled.js
  93. 4 8
      services/web/scripts/sync-user-entitlements/sync-user-entitlements.js
  94. 5 4
      services/web/test/acceptance/src/ProjectInviteTests.js
  95. 7 6
      services/web/test/acceptance/src/TagsTests.js
  96. 4 4
      services/web/test/acceptance/src/helpers/DeletedSubscription.js
  97. 2 2
      services/web/test/acceptance/src/helpers/Institution.js
  98. 4 4
      services/web/test/acceptance/src/helpers/Subscription.js
  99. 2 3
      services/web/test/acceptance/src/helpers/UserHelper.js
  100. 8 3
      services/web/test/frontend/bootstrap.js

+ 8 - 4
services/web/app.js

@@ -15,10 +15,14 @@ const Settings = require('@overleaf/settings')
 const logger = require('@overleaf/logger')
 const logger = require('@overleaf/logger')
 const PlansLocator = require('./app/src/Features/Subscription/PlansLocator')
 const PlansLocator = require('./app/src/Features/Subscription/PlansLocator')
 logger.initialize(process.env.METRICS_APP_NAME || 'web')
 logger.initialize(process.env.METRICS_APP_NAME || 'web')
-logger.logger.serializers.user = require('./app/src/infrastructure/LoggerSerializers').user
-logger.logger.serializers.docs = require('./app/src/infrastructure/LoggerSerializers').docs
-logger.logger.serializers.files = require('./app/src/infrastructure/LoggerSerializers').files
-logger.logger.serializers.project = require('./app/src/infrastructure/LoggerSerializers').project
+logger.logger.serializers.user =
+  require('./app/src/infrastructure/LoggerSerializers').user
+logger.logger.serializers.docs =
+  require('./app/src/infrastructure/LoggerSerializers').docs
+logger.logger.serializers.files =
+  require('./app/src/infrastructure/LoggerSerializers').files
+logger.logger.serializers.project =
+  require('./app/src/infrastructure/LoggerSerializers').project
 if ((Settings.sentry != null ? Settings.sentry.dsn : undefined) != null) {
 if ((Settings.sentry != null ? Settings.sentry.dsn : undefined) != null) {
   logger.initializeErrorReporting(Settings.sentry.dsn)
   logger.initializeErrorReporting(Settings.sentry.dsn)
 }
 }

+ 10 - 11
services/web/app/src/Features/Authorization/AuthorizationManager.js

@@ -80,10 +80,11 @@ async function getPrivilegeLevelForProjectWithUser(
   token,
   token,
   opts = {}
   opts = {}
 ) {
 ) {
-  const privilegeLevel = await CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
-    userId,
-    projectId
-  )
+  const privilegeLevel =
+    await CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
+      userId,
+      projectId
+    )
   if (privilegeLevel && privilegeLevel !== PrivilegeLevels.NONE) {
   if (privilegeLevel && privilegeLevel !== PrivilegeLevels.NONE) {
     // The user has direct access
     // The user has direct access
     return privilegeLevel
     return privilegeLevel
@@ -140,13 +141,11 @@ async function getPrivilegeLevelForProjectWithToken(projectId, token) {
   // Anonymous users can have read-only access to token-based projects,
   // Anonymous users can have read-only access to token-based projects,
   // while read-write access must be logged in,
   // while read-write access must be logged in,
   // unless the `enableAnonymousReadAndWriteSharing` setting is enabled
   // unless the `enableAnonymousReadAndWriteSharing` setting is enabled
-  const {
-    isValidReadAndWrite,
-    isValidReadOnly,
-  } = await TokenAccessHandler.promises.validateTokenForAnonymousAccess(
-    projectId,
-    token
-  )
+  const { isValidReadAndWrite, isValidReadOnly } =
+    await TokenAccessHandler.promises.validateTokenForAnonymousAccess(
+      projectId,
+      token
+    )
   if (isValidReadOnly) {
   if (isValidReadOnly) {
     // Grant anonymous user read-only access
     // Grant anonymous user read-only access
     return PrivilegeLevels.READ_ONLY
     return PrivilegeLevels.READ_ONLY

+ 18 - 15
services/web/app/src/Features/Authorization/AuthorizationMiddleware.js

@@ -29,11 +29,12 @@ async function blockRestrictedUserFromProject(req, res, next) {
   const projectId = _getProjectId(req)
   const projectId = _getProjectId(req)
   const userId = _getUserId(req)
   const userId = _getUserId(req)
   const token = TokenAccessHandler.getRequestToken(req, projectId)
   const token = TokenAccessHandler.getRequestToken(req, projectId)
-  const isRestrictedUser = await AuthorizationManager.promises.isRestrictedUserForProject(
-    userId,
-    projectId,
-    token
-  )
+  const isRestrictedUser =
+    await AuthorizationManager.promises.isRestrictedUserForProject(
+      userId,
+      projectId,
+      token
+    )
   if (isRestrictedUser) {
   if (isRestrictedUser) {
     return HttpErrorHandler.forbidden(req, res)
     return HttpErrorHandler.forbidden(req, res)
   }
   }
@@ -75,11 +76,12 @@ async function ensureUserCanWriteProjectSettings(req, res, next) {
 
 
   const otherParams = Object.keys(req.body).filter(x => x !== 'name')
   const otherParams = Object.keys(req.body).filter(x => x !== 'name')
   if (otherParams.length > 0) {
   if (otherParams.length > 0) {
-    const canWrite = await AuthorizationManager.promises.canUserWriteProjectSettings(
-      userId,
-      projectId,
-      token
-    )
+    const canWrite =
+      await AuthorizationManager.promises.canUserWriteProjectSettings(
+        userId,
+        projectId,
+        token
+      )
     if (!canWrite) {
     if (!canWrite) {
       return HttpErrorHandler.forbidden(req, res)
       return HttpErrorHandler.forbidden(req, res)
     }
     }
@@ -92,11 +94,12 @@ async function ensureUserCanWriteProjectContent(req, res, next) {
   const projectId = _getProjectId(req)
   const projectId = _getProjectId(req)
   const userId = _getUserId(req)
   const userId = _getUserId(req)
   const token = TokenAccessHandler.getRequestToken(req, projectId)
   const token = TokenAccessHandler.getRequestToken(req, projectId)
-  const canWrite = await AuthorizationManager.promises.canUserWriteProjectContent(
-    userId,
-    projectId,
-    token
-  )
+  const canWrite =
+    await AuthorizationManager.promises.canUserWriteProjectContent(
+      userId,
+      projectId,
+      token
+    )
   if (canWrite) {
   if (canWrite) {
     logger.log(
     logger.log(
       { userId, projectId },
       { userId, projectId },

+ 1 - 2
services/web/app/src/Features/Captcha/CaptchaMiddleware.js

@@ -50,8 +50,7 @@ module.exports = CaptchaMiddleware = {
             return res.status(400).send({
             return res.status(400).send({
               errorReason: 'cannot_verify_user_not_robot',
               errorReason: 'cannot_verify_user_not_robot',
               message: {
               message: {
-                text:
-                  'Sorry, we could not verify that you are not a robot. Please check that Google reCAPTCHA is not being blocked by an ad blocker or firewall.',
+                text: 'Sorry, we could not verify that you are not a robot. Please check that Google reCAPTCHA is not being blocked by an ad blocker or firewall.',
               },
               },
             })
             })
           } else {
           } else {

+ 25 - 30
services/web/app/src/Features/Collaborators/CollaboratorsGetter.js

@@ -132,42 +132,37 @@ async function isUserInvitedMemberOfProject(userId, projectId) {
 
 
 async function getProjectsUserIsMemberOf(userId, fields) {
 async function getProjectsUserIsMemberOf(userId, fields) {
   const limit = pLimit(2)
   const limit = pLimit(2)
-  const [
-    readAndWrite,
-    readOnly,
-    tokenReadAndWrite,
-    tokenReadOnly,
-  ] = await Promise.all([
-    limit(() => Project.find({ collaberator_refs: userId }, fields).exec()),
-    limit(() => Project.find({ readOnly_refs: userId }, fields).exec()),
-    limit(() =>
-      Project.find(
-        {
-          tokenAccessReadAndWrite_refs: userId,
-          publicAccesLevel: PublicAccessLevels.TOKEN_BASED,
-        },
-        fields
-      ).exec()
-    ),
-    limit(() =>
-      Project.find(
-        {
-          tokenAccessReadOnly_refs: userId,
-          publicAccesLevel: PublicAccessLevels.TOKEN_BASED,
-        },
-        fields
-      ).exec()
-    ),
-  ])
+  const [readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly] =
+    await Promise.all([
+      limit(() => Project.find({ collaberator_refs: userId }, fields).exec()),
+      limit(() => Project.find({ readOnly_refs: userId }, fields).exec()),
+      limit(() =>
+        Project.find(
+          {
+            tokenAccessReadAndWrite_refs: userId,
+            publicAccesLevel: PublicAccessLevels.TOKEN_BASED,
+          },
+          fields
+        ).exec()
+      ),
+      limit(() =>
+        Project.find(
+          {
+            tokenAccessReadOnly_refs: userId,
+            publicAccesLevel: PublicAccessLevels.TOKEN_BASED,
+          },
+          fields
+        ).exec()
+      ),
+    ])
   return { readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly }
   return { readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly }
 }
 }
 
 
 async function getAllInvitedMembers(projectId) {
 async function getAllInvitedMembers(projectId) {
   try {
   try {
     const rawMembers = await getInvitedMembersWithPrivilegeLevels(projectId)
     const rawMembers = await getInvitedMembersWithPrivilegeLevels(projectId)
-    const { members } = ProjectEditorHandler.buildOwnerAndMembersViews(
-      rawMembers
-    )
+    const { members } =
+      ProjectEditorHandler.buildOwnerAndMembersViews(rawMembers)
     return members
     return members
   } catch (err) {
   } catch (err) {
     throw OError.tag(err, 'error getting members for project', { projectId })
     throw OError.tag(err, 'error getting members for project', { projectId })

+ 4 - 8
services/web/app/src/Features/Collaborators/CollaboratorsHandler.js

@@ -78,14 +78,10 @@ async function removeUserFromProject(projectId, userId) {
 }
 }
 
 
 async function removeUserFromAllProjects(userId) {
 async function removeUserFromAllProjects(userId) {
-  const {
-    readAndWrite,
-    readOnly,
-    tokenReadAndWrite,
-    tokenReadOnly,
-  } = await CollaboratorsGetter.promises.getProjectsUserIsMemberOf(userId, {
-    _id: 1,
-  })
+  const { readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly } =
+    await CollaboratorsGetter.promises.getProjectsUserIsMemberOf(userId, {
+      _id: 1,
+    })
   const allProjects = readAndWrite
   const allProjects = readAndWrite
     .concat(readOnly)
     .concat(readOnly)
     .concat(tokenReadAndWrite)
     .concat(tokenReadAndWrite)

+ 2 - 3
services/web/app/src/Features/Compile/ClsiStateManager.js

@@ -38,9 +38,8 @@ const buildState = s =>
 
 
 module.exports = ClsiStateManager = {
 module.exports = ClsiStateManager = {
   computeHash(project, options) {
   computeHash(project, options) {
-    const { docs, files } = ProjectEntityHandler.getAllEntitiesFromProject(
-      project
-    )
+    const { docs, files } =
+      ProjectEntityHandler.getAllEntitiesFromProject(project)
     const fileList = Array.from(files || []).map(
     const fileList = Array.from(files || []).map(
       f => `${f.file._id}:${f.file.rev}:${f.file.created}:${f.path}`
       f => `${f.file._id}:${f.file.rev}:${f.file.created}:${f.path}`
     )
     )

+ 12 - 13
services/web/app/src/Features/Editor/EditorHttpController.js

@@ -60,11 +60,8 @@ async function joinProject(req, res, next) {
     userId = null
     userId = null
   }
   }
   Metrics.inc('editor.join-project')
   Metrics.inc('editor.join-project')
-  const {
-    project,
-    privilegeLevel,
-    isRestrictedUser,
-  } = await _buildJoinProjectView(req, projectId, userId)
+  const { project, privilegeLevel, isRestrictedUser } =
+    await _buildJoinProjectView(req, projectId, userId)
   if (!project) {
   if (!project) {
     return res.sendStatus(403)
     return res.sendStatus(403)
   }
   }
@@ -116,15 +113,17 @@ async function _buildJoinProjectView(req, projectId, userId) {
       'soft-failure when fetching deletedDocs from docstore'
       'soft-failure when fetching deletedDocs from docstore'
     )
     )
   }
   }
-  const members = await CollaboratorsGetter.promises.getInvitedMembersWithPrivilegeLevels(
-    projectId
-  )
+  const members =
+    await CollaboratorsGetter.promises.getInvitedMembersWithPrivilegeLevels(
+      projectId
+    )
   const token = TokenAccessHandler.getRequestToken(req, projectId)
   const token = TokenAccessHandler.getRequestToken(req, projectId)
-  const privilegeLevel = await AuthorizationManager.promises.getPrivilegeLevelForProject(
-    userId,
-    projectId,
-    token
-  )
+  const privilegeLevel =
+    await AuthorizationManager.promises.getPrivilegeLevelForProject(
+      userId,
+      projectId,
+      token
+    )
   if (privilegeLevel == null || privilegeLevel === PrivilegeLevels.NONE) {
   if (privilegeLevel == null || privilegeLevel === PrivilegeLevels.NONE) {
     return { project: null, privilegeLevel: null, isRestrictedUser: false }
     return { project: null, privilegeLevel: null, isRestrictedUser: false }
   }
   }

+ 2 - 6
services/web/app/src/Features/Errors/Errors.js

@@ -99,12 +99,8 @@ class SAMLSessionDataMissing extends BackwardCompatibleError {
         ? arg.samlSession
         ? arg.samlSession
         : {}
         : {}
     this.tryAgain = true
     this.tryAgain = true
-    const {
-      universityId,
-      universityName,
-      externalUserId,
-      institutionEmail,
-    } = samlSession
+    const { universityId, universityName, externalUserId, institutionEmail } =
+      samlSession
 
 
     if (
     if (
       !universityId &&
       !universityId &&

+ 239 - 235
services/web/app/src/Features/Exports/ExportsHandler.js

@@ -27,260 +27,264 @@ let request = require('request')
 request = request.defaults()
 request = request.defaults()
 settings = require('@overleaf/settings')
 settings = require('@overleaf/settings')
 
 
-module.exports = ExportsHandler = self = {
-  exportProject(export_params, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return self._buildExport(export_params, function (err, export_data) {
-      if (err != null) {
-        return callback(err)
-      }
-      return self._requestExport(export_data, function (err, body) {
-        if (err != null) {
-          return callback(err)
+module.exports =
+  ExportsHandler =
+  self =
+    {
+      exportProject(export_params, callback) {
+        if (callback == null) {
+          callback = function () {}
         }
         }
-        export_data.v1_id = body.exportId
-        export_data.message = body.message
-        // TODO: possibly store the export data in Mongo
-        return callback(null, export_data)
-      })
-    })
-  },
-
-  _buildExport(export_params, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    const {
-      project_id,
-      user_id,
-      brand_variation_id,
-      title,
-      description,
-      author,
-      license,
-      show_source,
-    } = export_params
-    const jobs = {
-      project(cb) {
-        return ProjectGetter.getProject(project_id, cb)
-      },
-      // TODO: when we update async, signature will change from (cb, results) to (results, cb)
-      rootDoc: [
-        'project',
-        (cb, results) =>
-          ProjectRootDocManager.ensureRootDocumentIsValid(
-            project_id,
-            function (error) {
-              if (error != null) {
-                return callback(error)
-              }
-              return ProjectLocator.findRootDoc(
-                { project: results.project, project_id },
-                cb
-              )
-            }
-          ),
-      ],
-      user(cb) {
-        return UserGetter.getUser(
-          user_id,
-          { first_name: 1, last_name: 1, email: 1, overleaf: 1 },
-          cb
-        )
-      },
-      historyVersion(cb) {
-        return ProjectHistoryHandler.ensureHistoryExistsForProject(
-          project_id,
-          function (error) {
-            if (error != null) {
-              return callback(error)
-            }
-            return self._requestVersion(project_id, cb)
+        return self._buildExport(export_params, function (err, export_data) {
+          if (err != null) {
+            return callback(err)
           }
           }
-        )
+          return self._requestExport(export_data, function (err, body) {
+            if (err != null) {
+              return callback(err)
+            }
+            export_data.v1_id = body.exportId
+            export_data.message = body.message
+            // TODO: possibly store the export data in Mongo
+            return callback(null, export_data)
+          })
+        })
       },
       },
-    }
 
 
-    return async.auto(jobs, function (err, results) {
-      if (err != null) {
-        OError.tag(err, 'error building project export', {
+      _buildExport(export_params, callback) {
+        if (callback == null) {
+          callback = function () {}
+        }
+        const {
           project_id,
           project_id,
           user_id,
           user_id,
           brand_variation_id,
           brand_variation_id,
-        })
-        return callback(err)
-      }
+          title,
+          description,
+          author,
+          license,
+          show_source,
+        } = export_params
+        const jobs = {
+          project(cb) {
+            return ProjectGetter.getProject(project_id, cb)
+          },
+          // TODO: when we update async, signature will change from (cb, results) to (results, cb)
+          rootDoc: [
+            'project',
+            (cb, results) =>
+              ProjectRootDocManager.ensureRootDocumentIsValid(
+                project_id,
+                function (error) {
+                  if (error != null) {
+                    return callback(error)
+                  }
+                  return ProjectLocator.findRootDoc(
+                    { project: results.project, project_id },
+                    cb
+                  )
+                }
+              ),
+          ],
+          user(cb) {
+            return UserGetter.getUser(
+              user_id,
+              { first_name: 1, last_name: 1, email: 1, overleaf: 1 },
+              cb
+            )
+          },
+          historyVersion(cb) {
+            return ProjectHistoryHandler.ensureHistoryExistsForProject(
+              project_id,
+              function (error) {
+                if (error != null) {
+                  return callback(error)
+                }
+                return self._requestVersion(project_id, cb)
+              }
+            )
+          },
+        }
 
 
-      const { project, rootDoc, user, historyVersion } = results
-      if (!rootDoc || rootDoc[1] == null) {
-        err = new OError('cannot export project without root doc', {
-          project_id,
-        })
-        return callback(err)
-      }
+        return async.auto(jobs, function (err, results) {
+          if (err != null) {
+            OError.tag(err, 'error building project export', {
+              project_id,
+              user_id,
+              brand_variation_id,
+            })
+            return callback(err)
+          }
 
 
-      if (export_params.first_name && export_params.last_name) {
-        user.first_name = export_params.first_name
-        user.last_name = export_params.last_name
-      }
+          const { project, rootDoc, user, historyVersion } = results
+          if (!rootDoc || rootDoc[1] == null) {
+            err = new OError('cannot export project without root doc', {
+              project_id,
+            })
+            return callback(err)
+          }
 
 
-      const export_data = {
-        project: {
-          id: project_id,
-          rootDocPath: rootDoc[1] != null ? rootDoc[1].fileSystem : undefined,
-          historyId: __guard__(
-            project.overleaf != null ? project.overleaf.history : undefined,
-            x => x.id
-          ),
-          historyVersion,
-          v1ProjectId:
-            project.overleaf != null ? project.overleaf.id : undefined,
-          metadata: {
-            compiler: project.compiler,
-            imageName: project.imageName,
-            title,
-            description,
-            author,
-            license,
-            showSource: show_source,
-          },
-        },
-        user: {
-          id: user_id,
-          firstName: user.first_name,
-          lastName: user.last_name,
-          email: user.email,
-          orcidId: null, // until v2 gets ORCID
-          v1UserId: user.overleaf != null ? user.overleaf.id : undefined,
-        },
-        destination: {
-          brandVariationId: brand_variation_id,
-        },
-        options: {
-          callbackUrl: null,
-        }, // for now, until we want v1 to call us back
-      }
-      return callback(null, export_data)
-    })
-  },
+          if (export_params.first_name && export_params.last_name) {
+            user.first_name = export_params.first_name
+            user.last_name = export_params.last_name
+          }
 
 
-  _requestExport(export_data, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return request.post(
-      {
-        url: `${settings.apis.v1.url}/api/v1/sharelatex/exports`,
-        auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass },
-        json: export_data,
+          const export_data = {
+            project: {
+              id: project_id,
+              rootDocPath:
+                rootDoc[1] != null ? rootDoc[1].fileSystem : undefined,
+              historyId: __guard__(
+                project.overleaf != null ? project.overleaf.history : undefined,
+                x => x.id
+              ),
+              historyVersion,
+              v1ProjectId:
+                project.overleaf != null ? project.overleaf.id : undefined,
+              metadata: {
+                compiler: project.compiler,
+                imageName: project.imageName,
+                title,
+                description,
+                author,
+                license,
+                showSource: show_source,
+              },
+            },
+            user: {
+              id: user_id,
+              firstName: user.first_name,
+              lastName: user.last_name,
+              email: user.email,
+              orcidId: null, // until v2 gets ORCID
+              v1UserId: user.overleaf != null ? user.overleaf.id : undefined,
+            },
+            destination: {
+              brandVariationId: brand_variation_id,
+            },
+            options: {
+              callbackUrl: null,
+            }, // for now, until we want v1 to call us back
+          }
+          return callback(null, export_data)
+        })
       },
       },
-      function (err, res, body) {
-        if (err != null) {
-          OError.tag(err, 'error making request to v1 export', {
-            export: export_data,
-          })
-          return callback(err)
-        } else if (res.statusCode >= 200 && res.statusCode < 300) {
-          return callback(null, body)
-        } else {
-          logger.warn(
-            { export: export_data },
-            `v1 export returned failure; forwarding: ${body}`
-          )
-          // pass the v1 error along for the publish modal to handle
-          const err = { forwardResponse: body }
-          return callback(err)
-        }
-      }
-    )
-  },
 
 
-  _requestVersion(project_id, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return request.get(
-      {
-        url: `${settings.apis.project_history.url}/project/${project_id}/version`,
-        json: true,
-      },
-      function (err, res, body) {
-        if (err != null) {
-          OError.tag(err, 'error making request to project history', {
-            project_id,
-          })
-          return callback(err)
-        } else if (res.statusCode >= 200 && res.statusCode < 300) {
-          return callback(null, body.version)
-        } else {
-          err = new OError(
-            `project history version returned a failure status code: ${res.statusCode}`,
-            { project_id }
-          )
-          return callback(err)
+      _requestExport(export_data, callback) {
+        if (callback == null) {
+          callback = function () {}
         }
         }
-      }
-    )
-  },
-
-  fetchExport(export_id, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return request.get(
-      {
-        url: `${settings.apis.v1.url}/api/v1/sharelatex/exports/${export_id}`,
-        auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass },
+        return request.post(
+          {
+            url: `${settings.apis.v1.url}/api/v1/sharelatex/exports`,
+            auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass },
+            json: export_data,
+          },
+          function (err, res, body) {
+            if (err != null) {
+              OError.tag(err, 'error making request to v1 export', {
+                export: export_data,
+              })
+              return callback(err)
+            } else if (res.statusCode >= 200 && res.statusCode < 300) {
+              return callback(null, body)
+            } else {
+              logger.warn(
+                { export: export_data },
+                `v1 export returned failure; forwarding: ${body}`
+              )
+              // pass the v1 error along for the publish modal to handle
+              const err = { forwardResponse: body }
+              return callback(err)
+            }
+          }
+        )
       },
       },
-      function (err, res, body) {
-        if (err != null) {
-          OError.tag(err, 'error making request to v1 export', {
-            export: export_id,
-          })
-          return callback(err)
-        } else if (res.statusCode >= 200 && res.statusCode < 300) {
-          return callback(null, body)
-        } else {
-          err = new OError(
-            `v1 export returned a failure status code: ${res.statusCode}`,
-            { export: export_id }
-          )
-          return callback(err)
+
+      _requestVersion(project_id, callback) {
+        if (callback == null) {
+          callback = function () {}
         }
         }
-      }
-    )
-  },
+        return request.get(
+          {
+            url: `${settings.apis.project_history.url}/project/${project_id}/version`,
+            json: true,
+          },
+          function (err, res, body) {
+            if (err != null) {
+              OError.tag(err, 'error making request to project history', {
+                project_id,
+              })
+              return callback(err)
+            } else if (res.statusCode >= 200 && res.statusCode < 300) {
+              return callback(null, body.version)
+            } else {
+              err = new OError(
+                `project history version returned a failure status code: ${res.statusCode}`,
+                { project_id }
+              )
+              return callback(err)
+            }
+          }
+        )
+      },
 
 
-  fetchDownload(export_id, type, callback) {
-    if (callback == null) {
-      callback = function () {}
-    }
-    return request.get(
-      {
-        url: `${settings.apis.v1.url}/api/v1/sharelatex/exports/${export_id}/${type}_url`,
-        auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass },
+      fetchExport(export_id, callback) {
+        if (callback == null) {
+          callback = function () {}
+        }
+        return request.get(
+          {
+            url: `${settings.apis.v1.url}/api/v1/sharelatex/exports/${export_id}`,
+            auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass },
+          },
+          function (err, res, body) {
+            if (err != null) {
+              OError.tag(err, 'error making request to v1 export', {
+                export: export_id,
+              })
+              return callback(err)
+            } else if (res.statusCode >= 200 && res.statusCode < 300) {
+              return callback(null, body)
+            } else {
+              err = new OError(
+                `v1 export returned a failure status code: ${res.statusCode}`,
+                { export: export_id }
+              )
+              return callback(err)
+            }
+          }
+        )
       },
       },
-      function (err, res, body) {
-        if (err != null) {
-          OError.tag(err, 'error making request to v1 export', {
-            export: export_id,
-          })
-          return callback(err)
-        } else if (res.statusCode >= 200 && res.statusCode < 300) {
-          return callback(null, body)
-        } else {
-          err = new OError(
-            `v1 export returned a failure status code: ${res.statusCode}`,
-            { export: export_id }
-          )
-          return callback(err)
+
+      fetchDownload(export_id, type, callback) {
+        if (callback == null) {
+          callback = function () {}
         }
         }
-      }
-    )
-  },
-}
+        return request.get(
+          {
+            url: `${settings.apis.v1.url}/api/v1/sharelatex/exports/${export_id}/${type}_url`,
+            auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass },
+          },
+          function (err, res, body) {
+            if (err != null) {
+              OError.tag(err, 'error making request to v1 export', {
+                export: export_id,
+              })
+              return callback(err)
+            } else if (res.statusCode >= 200 && res.statusCode < 300) {
+              return callback(null, body)
+            } else {
+              err = new OError(
+                `v1 export returned a failure status code: ${res.statusCode}`,
+                { export: export_id }
+              )
+              return callback(err)
+            }
+          }
+        )
+      },
+    }
 
 
 function __guard__(value, transform) {
 function __guard__(value, transform) {
   return typeof value !== 'undefined' && value !== null
   return typeof value !== 'undefined' && value !== null

+ 3 - 2
services/web/app/src/Features/Helpers/EmailHelper.js

@@ -1,5 +1,6 @@
-// eslint-disable-next-line no-useless-escape
-const EMAIL_REGEXP = /^([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+const EMAIL_REGEXP =
+  // eslint-disable-next-line no-useless-escape
+  /^([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
 
 
 function getDomain(email) {
 function getDomain(email) {
   email = parseEmail(email)
   email = parseEmail(email)

+ 15 - 10
services/web/app/src/Features/InactiveData/InactiveProjectManager.js

@@ -78,16 +78,21 @@ module.exports = InactiveProjectManager = {
         if (err != null) {
         if (err != null) {
           logger.err({ err }, 'could not get projects for deactivating')
           logger.err({ err }, 'could not get projects for deactivating')
         }
         }
-        const jobs = _.map(projects, project => cb =>
-          InactiveProjectManager.deactivateProject(project._id, function (err) {
-            if (err) {
-              logger.err(
-                { project_id: project._id, err: err },
-                'unable to deactivate project'
-              )
-            }
-            cb()
-          })
+        const jobs = _.map(
+          projects,
+          project => cb =>
+            InactiveProjectManager.deactivateProject(
+              project._id,
+              function (err) {
+                if (err) {
+                  logger.err(
+                    { project_id: project._id, err: err },
+                    'unable to deactivate project'
+                  )
+                }
+                cb()
+              }
+            )
         )
         )
         logger.log(
         logger.log(
           { numberOfProjects: projects && projects.length },
           { numberOfProjects: projects && projects.length },

+ 2 - 5
services/web/app/src/Features/Institutions/InstitutionsManager.js

@@ -125,11 +125,8 @@ async function checkInstitutionUsers(institutionId, emitNonProUserIds) {
   )
   )
   result.ssoUsers.current.entitled = entitled
   result.ssoUsers.current.entitled = entitled
 
 
-  const {
-    allSsoUsers,
-    allSsoUsersByIds,
-    currentNotEntitledCount,
-  } = await _getSsoUsers(institutionId, lapsedUserIds)
+  const { allSsoUsers, allSsoUsersByIds, currentNotEntitledCount } =
+    await _getSsoUsers(institutionId, lapsedUserIds)
   result.ssoUsers.total = allSsoUsers.length
   result.ssoUsers.total = allSsoUsers.length
   result.ssoUsers.current.notEntitled = currentNotEntitledCount
   result.ssoUsers.current.notEntitled = currentNotEntitledCount
 
 

+ 2 - 4
services/web/app/src/Features/LinkedFiles/ProjectOutputFileAgent.js

@@ -168,10 +168,8 @@ function _checkAuth(projectId, data, currentUserId, callback) {
 
 
 function _getFileStream(linkedFileData, userId, callback) {
 function _getFileStream(linkedFileData, userId, callback) {
   callback = _.once(callback)
   callback = _.once(callback)
-  const {
-    source_output_file_path: sourceOutputFilePath,
-    build_id: buildId,
-  } = linkedFileData
+  const { source_output_file_path: sourceOutputFilePath, build_id: buildId } =
+    linkedFileData
   LinkedFilesHandler.getSourceProject(linkedFileData, (err, project) => {
   LinkedFilesHandler.getSourceProject(linkedFileData, (err, project) => {
     if (err) {
     if (err) {
       return callback(err)
       return callback(err)

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

@@ -357,9 +357,8 @@ const ProjectController = {
       if (err != null) {
       if (err != null) {
         return next(err)
         return next(err)
       }
       }
-      const { docs, files } = ProjectEntityHandler.getAllEntitiesFromProject(
-        project
-      )
+      const { docs, files } =
+        ProjectEntityHandler.getAllEntitiesFromProject(project)
       const entities = docs
       const entities = docs
         .concat(files)
         .concat(files)
         // Sort by path ascending
         // Sort by path ascending
@@ -553,9 +552,8 @@ const ProjectController = {
           delete req.session.saml
           delete req.session.saml
         }
         }
 
 
-        const portalTemplates = ProjectController._buildPortalTemplatesList(
-          userAffiliations
-        )
+        const portalTemplates =
+          ProjectController._buildPortalTemplatesList(userAffiliations)
         const projects = ProjectController._buildProjectList(
         const projects = ProjectController._buildProjectList(
           results.projects,
           results.projects,
           userId
           userId
@@ -798,9 +796,8 @@ const ProjectController = {
           req,
           req,
           projectId
           projectId
         )
         )
-        const allowedImageNames = ProjectHelper.getAllowedImagesForUser(
-          sessionUser
-        )
+        const allowedImageNames =
+          ProjectHelper.getAllowedImagesForUser(sessionUser)
 
 
         AuthorizationManager.getPrivilegeLevelForProject(
         AuthorizationManager.getPrivilegeLevelForProject(
           userId,
           userId,
@@ -1028,13 +1025,8 @@ const ProjectController = {
 
 
   _buildProjectList(allProjects, userId) {
   _buildProjectList(allProjects, userId) {
     let project
     let project
-    const {
-      owned,
-      readAndWrite,
-      readOnly,
-      tokenReadAndWrite,
-      tokenReadOnly,
-    } = allProjects
+    const { owned, readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly } =
+      allProjects
     const projects = []
     const projects = []
     for (project of owned) {
     for (project of owned) {
       projects.push(
       projects.push(

+ 4 - 5
services/web/app/src/Features/Project/ProjectDetailsHandler.js

@@ -144,10 +144,8 @@ async function validateProjectName(name) {
 // with a unique name.  But that requires thinking through how we would handle incoming projects from
 // with a unique name.  But that requires thinking through how we would handle incoming projects from
 // dropbox for example.
 // dropbox for example.
 async function generateUniqueName(userId, name, suffixes = []) {
 async function generateUniqueName(userId, name, suffixes = []) {
-  const allUsersProjectNames = await ProjectGetter.promises.findAllUsersProjects(
-    userId,
-    { name: 1 }
-  )
+  const allUsersProjectNames =
+    await ProjectGetter.promises.findAllUsersProjects(userId, { name: 1 })
   // allUsersProjectNames is returned as a hash {owned: [name1, name2, ...], readOnly: [....]}
   // allUsersProjectNames is returned as a hash {owned: [name1, name2, ...], readOnly: [....]}
   // collect all of the names and flatten them into a single array
   // collect all of the names and flatten them into a single array
   const projectNameList = _.pluck(
   const projectNameList = _.pluck(
@@ -241,6 +239,7 @@ async function _generateTokens(project, callback) {
     tokens.readAndWritePrefix = numericPrefix
     tokens.readAndWritePrefix = numericPrefix
   }
   }
   if (tokens.readOnly == null) {
   if (tokens.readOnly == null) {
-    tokens.readOnly = await TokenGenerator.promises.generateUniqueReadOnlyToken()
+    tokens.readOnly =
+      await TokenGenerator.promises.generateUniqueReadOnlyToken()
   }
   }
 }
 }

+ 6 - 5
services/web/app/src/Features/Project/ProjectDuplicator.js

@@ -66,11 +66,12 @@ async function duplicate(owner, originalProjectId, newProjectName) {
       _copyDocs(originalEntries.docEntries, originalProject, newProject),
       _copyDocs(originalEntries.docEntries, originalProject, newProject),
       _copyFiles(originalEntries.fileEntries, originalProject, newProject),
       _copyFiles(originalEntries.fileEntries, originalProject, newProject),
     ])
     ])
-    const projectVersion = await ProjectEntityMongoUpdateHandler.promises.createNewFolderStructure(
-      newProject._id,
-      docEntries,
-      fileEntries
-    )
+    const projectVersion =
+      await ProjectEntityMongoUpdateHandler.promises.createNewFolderStructure(
+        newProject._id,
+        docEntries,
+        fileEntries
+      )
     // Silently ignore the rootDoc in case it's not valid per the new limits.
     // Silently ignore the rootDoc in case it's not valid per the new limits.
     if (
     if (
       rootDocPath &&
       rootDocPath &&

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

@@ -62,9 +62,8 @@ module.exports = ProjectEditorHandler = {
     result.invites.forEach(invite => {
     result.invites.forEach(invite => {
       delete invite.token
       delete invite.token
     })
     })
-    ;({ owner, ownerFeatures, members } = this.buildOwnerAndMembersViews(
-      members
-    ))
+    ;({ owner, ownerFeatures, members } =
+      this.buildOwnerAndMembersViews(members))
     result.owner = owner
     result.owner = owner
     result.members = members
     result.members = members
 
 

+ 28 - 43
services/web/app/src/Features/Project/ProjectEntityMongoUpdateHandler.js

@@ -257,21 +257,18 @@ async function mkdirp(projectId, path, options = {}) {
   for (const folderName of folders) {
   for (const folderName of folders) {
     builtUpPath += `/${folderName}`
     builtUpPath += `/${folderName}`
     try {
     try {
-      const {
-        element: foundFolder,
-      } = await ProjectLocator.promises.findElementByPath({
-        project,
-        path: builtUpPath,
-        exactCaseMatch: options.exactCaseMatch,
-      })
+      const { element: foundFolder } =
+        await ProjectLocator.promises.findElementByPath({
+          project,
+          path: builtUpPath,
+          exactCaseMatch: options.exactCaseMatch,
+        })
       lastFolder = foundFolder
       lastFolder = foundFolder
     } catch (err) {
     } catch (err) {
       // Folder couldn't be found. Create it.
       // Folder couldn't be found. Create it.
       const parentFolderId = lastFolder && lastFolder._id
       const parentFolderId = lastFolder && lastFolder._id
-      const {
-        folder: newFolder,
-        parentFolderId: newParentFolderId,
-      } = await addFolder(projectId, parentFolderId, folderName)
+      const { folder: newFolder, parentFolderId: newParentFolderId } =
+        await addFolder(projectId, parentFolderId, folderName)
       newFolder.parentFolder_id = newParentFolderId
       newFolder.parentFolder_id = newParentFolderId
       lastFolder = newFolder
       lastFolder = newFolder
       newFolders.push(newFolder)
       newFolders.push(newFolder)
@@ -285,23 +282,19 @@ async function moveEntity(projectId, entityId, destFolderId, entityType) {
     projectId,
     projectId,
     { rootFolder: true, name: true, overleaf: true }
     { rootFolder: true, name: true, overleaf: true }
   )
   )
-  const {
-    element: entity,
-    path: entityPath,
-  } = await ProjectLocator.promises.findElement({
-    project,
-    element_id: entityId,
-    type: entityType,
-  })
+  const { element: entity, path: entityPath } =
+    await ProjectLocator.promises.findElement({
+      project,
+      element_id: entityId,
+      type: entityType,
+    })
   // Prevent top-level docs/files with reserved names (to match v1 behaviour)
   // Prevent top-level docs/files with reserved names (to match v1 behaviour)
   if (_blockedFilename(entityPath, entityType)) {
   if (_blockedFilename(entityPath, entityType)) {
     throw new Errors.InvalidNameError('blocked element name')
     throw new Errors.InvalidNameError('blocked element name')
   }
   }
   await _checkValidMove(project, entityType, entity, entityPath, destFolderId)
   await _checkValidMove(project, entityType, entity, entityPath, destFolderId)
-  const {
-    docs: oldDocs,
-    files: oldFiles,
-  } = ProjectEntityHandler.getAllEntitiesFromProject(project)
+  const { docs: oldDocs, files: oldFiles } =
+    ProjectEntityHandler.getAllEntitiesFromProject(project)
   // For safety, insert the entity in the destination
   // For safety, insert the entity in the destination
   // location first, and then remove the original.  If
   // location first, and then remove the original.  If
   // there is an error the entity may appear twice. This
   // there is an error the entity may appear twice. This
@@ -328,10 +321,8 @@ async function moveEntity(projectId, entityId, destFolderId, entityType) {
     entityPath.mongo,
     entityPath.mongo,
     entityId
     entityId
   )
   )
-  const {
-    docs: newDocs,
-    files: newFiles,
-  } = ProjectEntityHandler.getAllEntitiesFromProject(newProject)
+  const { docs: newDocs, files: newFiles } =
+    ProjectEntityHandler.getAllEntitiesFromProject(newProject)
   const startPath = entityPath.fileSystem
   const startPath = entityPath.fileSystem
   const endPath = result.path.fileSystem
   const endPath = result.path.fileSystem
   const changes = {
   const changes = {
@@ -418,10 +409,8 @@ async function renameEntity(
   // check if the new name already exists in the current folder
   // check if the new name already exists in the current folder
   _checkValidElementName(parentFolder, newName)
   _checkValidElementName(parentFolder, newName)
 
 
-  const {
-    docs: oldDocs,
-    files: oldFiles,
-  } = ProjectEntityHandler.getAllEntitiesFromProject(project)
+  const { docs: oldDocs, files: oldFiles } =
+    ProjectEntityHandler.getAllEntitiesFromProject(project)
 
 
   // we need to increment the project version number for any structure change
   // we need to increment the project version number for any structure change
   const newProject = await Project.findOneAndUpdate(
   const newProject = await Project.findOneAndUpdate(
@@ -430,10 +419,8 @@ async function renameEntity(
     { new: true }
     { new: true }
   ).exec()
   ).exec()
 
 
-  const {
-    docs: newDocs,
-    files: newFiles,
-  } = ProjectEntityHandler.getAllEntitiesFromProject(newProject)
+  const { docs: newDocs, files: newFiles } =
+    ProjectEntityHandler.getAllEntitiesFromProject(newProject)
   return {
   return {
     project,
     project,
     startPath,
     startPath,
@@ -618,14 +605,12 @@ async function _checkValidMove(
   entityPath,
   entityPath,
   destFolderId
   destFolderId
 ) {
 ) {
-  const {
-    element: destEntity,
-    path: destFolderPath,
-  } = await ProjectLocator.promises.findElement({
-    project,
-    element_id: destFolderId,
-    type: 'folder',
-  })
+  const { element: destEntity, path: destFolderPath } =
+    await ProjectLocator.promises.findElement({
+      project,
+      element_id: destFolderId,
+      type: 'folder',
+    })
   // check if there is already a doc/file/folder with the same name
   // check if there is already a doc/file/folder with the same name
   // in the destination folder
   // in the destination folder
   _checkValidElementName(destEntity, entity.name)
   _checkValidElementName(destEntity, entity.name)

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

@@ -1349,11 +1349,8 @@ const ProjectEntityUpdateHandler = {
           return callback(error)
           return callback(error)
         }
         }
 
 
-        let {
-          docs,
-          files,
-          folders,
-        } = ProjectEntityHandler.getAllEntitiesFromProject(project)
+        let { docs, files, folders } =
+          ProjectEntityHandler.getAllEntitiesFromProject(project)
         // _checkFileTree() must be passed the folders before docs and
         // _checkFileTree() must be passed the folders before docs and
         // files
         // files
         ProjectEntityUpdateHandler._checkFiletree(
         ProjectEntityUpdateHandler._checkFiletree(

+ 3 - 2
services/web/app/src/Features/References/ReferencesHandler.js

@@ -166,8 +166,9 @@ module.exports = ReferencesHandler = {
         'flushing docs to mongo before calling references service'
         'flushing docs to mongo before calling references service'
       )
       )
       return Async.series(
       return Async.series(
-        docIds.map(docId => cb =>
-          DocumentUpdaterHandler.flushDocToMongo(projectId, docId, cb)
+        docIds.map(
+          docId => cb =>
+            DocumentUpdaterHandler.flushDocToMongo(projectId, docId, cb)
         ),
         ),
         function (err) {
         function (err) {
           // continue
           // continue

+ 5 - 4
services/web/app/src/Features/SplitTests/SplitTestMiddleware.js

@@ -41,10 +41,11 @@ async function _loadAssignmentInLocals(splitTest, session, locals) {
     if (cachedVariant) {
     if (cachedVariant) {
       LocalsHelper.setSplitTestVariant(locals, splitTest.name, cachedVariant)
       LocalsHelper.setSplitTestVariant(locals, splitTest.name, cachedVariant)
     } else {
     } else {
-      const assignment = await SplitTestV2Handler.promises.getAssignmentForSession(
-        session,
-        splitTest.name
-      )
+      const assignment =
+        await SplitTestV2Handler.promises.getAssignmentForSession(
+          session,
+          splitTest.name
+        )
       session.cachedSplitTestAssignments[cacheKey] = assignment.variant
       session.cachedSplitTestAssignments[cacheKey] = assignment.variant
       LocalsHelper.setSplitTestVariant(
       LocalsHelper.setSplitTestVariant(
         locals,
         locals,

+ 2 - 6
services/web/app/src/Features/SplitTests/SplitTestV2Handler.js

@@ -138,12 +138,8 @@ async function _getAssignment(
   if (splitTest) {
   if (splitTest) {
     const currentVersion = splitTest.getCurrentVersion()
     const currentVersion = splitTest.getCurrentVersion()
     if (currentVersion.active) {
     if (currentVersion.active) {
-      const {
-        activeForUser,
-        selectedVariantName,
-        phase,
-        versionNumber,
-      } = await _getAssignmentMetadata(analyticsId, userId, splitTest)
+      const { activeForUser, selectedVariantName, phase, versionNumber } =
+        await _getAssignmentMetadata(analyticsId, userId, splitTest)
       if (activeForUser) {
       if (activeForUser) {
         const assignmentConfig = {
         const assignmentConfig = {
           userId,
           userId,

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

@@ -48,10 +48,8 @@ async function refreshFeatures(userId, reason) {
     matchedFeatureSet
     matchedFeatureSet
   )
   )
 
 
-  const {
-    features: newFeatures,
-    featuresChanged,
-  } = await UserFeaturesUpdater.promises.updateFeatures(userId, features)
+  const { features: newFeatures, featuresChanged } =
+    await UserFeaturesUpdater.promises.updateFeatures(userId, features)
   if (oldFeatures.dropbox === true && features.dropbox === false) {
   if (oldFeatures.dropbox === true && features.dropbox === false) {
     logger.log({ userId }, '[FeaturesUpdater] must unlink dropbox')
     logger.log({ userId }, '[FeaturesUpdater] must unlink dropbox')
     const Modules = require('../../infrastructure/Modules')
     const Modules = require('../../infrastructure/Modules')
@@ -70,9 +68,8 @@ async function refreshFeatures(userId, reason) {
 async function computeFeatures(userId) {
 async function computeFeatures(userId) {
   const individualFeatures = await _getIndividualFeatures(userId)
   const individualFeatures = await _getIndividualFeatures(userId)
   const groupFeatureSets = await _getGroupFeatureSets(userId)
   const groupFeatureSets = await _getGroupFeatureSets(userId)
-  const institutionFeatures = await InstitutionsFeatures.promises.getInstitutionsFeatures(
-    userId
-  )
+  const institutionFeatures =
+    await InstitutionsFeatures.promises.getInstitutionsFeatures(userId)
   const v1Features = await _getV1Features(userId)
   const v1Features = await _getV1Features(userId)
   const bonusFeatures = await ReferalFeatures.promises.getBonusFeatures(userId)
   const bonusFeatures = await ReferalFeatures.promises.getBonusFeatures(userId)
   const featuresOverrides = await _getFeaturesOverrides(userId)
   const featuresOverrides = await _getFeaturesOverrides(userId)
@@ -144,10 +141,8 @@ async function _getFeaturesOverrides(userId) {
 async function _getV1Features(userId) {
 async function _getV1Features(userId) {
   let planCode, v1Id
   let planCode, v1Id
   try {
   try {
-    ;({
-      planCode,
-      v1Id,
-    } = await V1SubscriptionManager.promises.getPlanCodeFromV1(userId))
+    ;({ planCode, v1Id } =
+      await V1SubscriptionManager.promises.getPlanCodeFromV1(userId))
   } catch (err) {
   } catch (err) {
     if (err.name === 'NotFoundError') {
     if (err.name === 'NotFoundError') {
       return {}
       return {}

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

@@ -197,9 +197,8 @@ const LimitationsManager = {
           return callback(new Error('no subscription found'))
           return callback(new Error('no subscription found'))
         }
         }
 
 
-        const limitReached = LimitationsManager.teamHasReachedMemberLimit(
-          subscription
-        )
+        const limitReached =
+          LimitationsManager.teamHasReachedMemberLimit(subscription)
         callback(err, limitReached, subscription)
         callback(err, limitReached, subscription)
       }
       }
     )
     )

+ 6 - 9
services/web/app/src/Features/Subscription/RecurlyWrapper.js

@@ -283,9 +283,8 @@ const RecurlyWrapper = {
           account_code: user._id,
           account_code: user._id,
         },
         },
       }
       }
-      const customFields = getCustomFieldsFromSubscriptionDetails(
-        subscriptionDetails
-      )
+      const customFields =
+        getCustomFieldsFromSubscriptionDetails(subscriptionDetails)
       if (customFields) {
       if (customFields) {
         data.custom_fields = customFields
         data.custom_fields = customFields
       }
       }
@@ -395,9 +394,8 @@ const RecurlyWrapper = {
       data.account.billing_info.three_d_secure_action_result_token_id =
       data.account.billing_info.three_d_secure_action_result_token_id =
         recurlyTokenIds.threeDSecureActionResult
         recurlyTokenIds.threeDSecureActionResult
     }
     }
-    const customFields = getCustomFieldsFromSubscriptionDetails(
-      subscriptionDetails
-    )
+    const customFields =
+      getCustomFieldsFromSubscriptionDetails(subscriptionDetails)
     if (customFields) {
     if (customFields) {
       data.custom_fields = customFields
       data.custom_fields = customFields
     }
     }
@@ -525,9 +523,8 @@ const RecurlyWrapper = {
                 recurlySubscription.account != null &&
                 recurlySubscription.account != null &&
                 recurlySubscription.account.url != null
                 recurlySubscription.account.url != null
               ) {
               ) {
-                accountId = recurlySubscription.account.url.match(
-                  /accounts\/(.*)/
-                )[1]
+                accountId =
+                  recurlySubscription.account.url.match(/accounts\/(.*)/)[1]
               } else {
               } else {
                 return callback(
                 return callback(
                   new Error("I don't understand the response from Recurly")
                   new Error("I don't understand the response from Recurly")

+ 27 - 30
services/web/app/src/Features/Subscription/SubscriptionController.js

@@ -30,11 +30,10 @@ const validGroupPlanModalOptions = {
 async function plansPage(req, res) {
 async function plansPage(req, res) {
   const plans = SubscriptionViewModelBuilder.buildPlansList()
   const plans = SubscriptionViewModelBuilder.buildPlansList()
 
 
-  const {
-    currencyCode: recommendedCurrency,
-  } = await GeoIpLookup.promises.getCurrencyCode(
-    (req.query ? req.query.ip : undefined) || req.ip
-  )
+  const { currencyCode: recommendedCurrency } =
+    await GeoIpLookup.promises.getCurrencyCode(
+      (req.query ? req.query.ip : undefined) || req.ip
+    )
 
 
   function getDefault(param, category, defaultValue) {
   function getDefault(param, category, defaultValue) {
     const v = req.query && req.query[param]
     const v = req.query && req.query[param]
@@ -77,17 +76,17 @@ async function paymentPage(req, res) {
   if (!plan) {
   if (!plan) {
     return HttpErrorHandler.unprocessableEntity(req, res, 'Plan not found')
     return HttpErrorHandler.unprocessableEntity(req, res, 'Plan not found')
   }
   }
-  const hasSubscription = await LimitationsManager.promises.userHasV1OrV2Subscription(
-    user
-  )
+  const hasSubscription =
+    await LimitationsManager.promises.userHasV1OrV2Subscription(user)
   if (hasSubscription) {
   if (hasSubscription) {
     res.redirect('/user/subscription?hasSubscription=true')
     res.redirect('/user/subscription?hasSubscription=true')
   } else {
   } else {
     // LimitationsManager.userHasV2Subscription only checks Mongo. Double check with
     // LimitationsManager.userHasV2Subscription only checks Mongo. Double check with
     // Recurly as well at this point (we don't do this most places for speed).
     // Recurly as well at this point (we don't do this most places for speed).
-    const valid = await SubscriptionHandler.promises.validateNoSubscriptionInRecurly(
-      user._id
-    )
+    const valid =
+      await SubscriptionHandler.promises.validateNoSubscriptionInRecurly(
+        user._id
+      )
     if (!valid) {
     if (!valid) {
       res.redirect('/user/subscription?hasSubscription=true')
       res.redirect('/user/subscription?hasSubscription=true')
     } else {
     } else {
@@ -98,12 +97,10 @@ async function paymentPage(req, res) {
           currency = queryCurrency
           currency = queryCurrency
         }
         }
       }
       }
-      const {
-        currencyCode: recommendedCurrency,
-        countryCode,
-      } = await GeoIpLookup.promises.getCurrencyCode(
-        (req.query ? req.query.ip : undefined) || req.ip
-      )
+      const { currencyCode: recommendedCurrency, countryCode } =
+        await GeoIpLookup.promises.getCurrencyCode(
+          (req.query ? req.query.ip : undefined) || req.ip
+        )
       if (recommendedCurrency && currency == null) {
       if (recommendedCurrency && currency == null) {
         currency = recommendedCurrency
         currency = recommendedCurrency
       }
       }
@@ -127,9 +124,10 @@ async function paymentPage(req, res) {
 
 
 async function userSubscriptionPage(req, res) {
 async function userSubscriptionPage(req, res) {
   const user = SessionManager.getSessionUser(req.session)
   const user = SessionManager.getSessionUser(req.session)
-  const results = await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
-    user
-  )
+  const results =
+    await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
+      user
+    )
   const {
   const {
     personalSubscription,
     personalSubscription,
     memberGroupSubscriptions,
     memberGroupSubscriptions,
@@ -139,9 +137,8 @@ async function userSubscriptionPage(req, res) {
     managedPublishers,
     managedPublishers,
     v1SubscriptionStatus,
     v1SubscriptionStatus,
   } = results
   } = results
-  const hasSubscription = await LimitationsManager.promises.userHasV1OrV2Subscription(
-    user
-  )
+  const hasSubscription =
+    await LimitationsManager.promises.userHasV1OrV2Subscription(user)
   const fromPlansPage = req.query.hasSubscription
   const fromPlansPage = req.query.hasSubscription
   const plans = SubscriptionViewModelBuilder.buildPlansList(
   const plans = SubscriptionViewModelBuilder.buildPlansList(
     personalSubscription ? personalSubscription.plan : undefined
     personalSubscription ? personalSubscription.plan : undefined
@@ -446,9 +443,8 @@ function processUpgradeToAnnualPlan(req, res, next) {
 
 
 async function extendTrial(req, res) {
 async function extendTrial(req, res) {
   const user = SessionManager.getSessionUser(req.session)
   const user = SessionManager.getSessionUser(req.session)
-  const {
-    subscription,
-  } = await LimitationsManager.promises.userHasV2Subscription(user)
+  const { subscription } =
+    await LimitationsManager.promises.userHasV2Subscription(user)
 
 
   try {
   try {
     await SubscriptionHandler.promises.extendTrial(subscription, 14)
     await SubscriptionHandler.promises.extendTrial(subscription, 14)
@@ -485,10 +481,11 @@ async function refreshUserFeatures(req, res) {
 async function redirectToHostedPage(req, res) {
 async function redirectToHostedPage(req, res) {
   const userId = SessionManager.getLoggedInUserId(req.session)
   const userId = SessionManager.getLoggedInUserId(req.session)
   const { pageType } = req.params
   const { pageType } = req.params
-  const url = await SubscriptionViewModelBuilder.promises.getRedirectToHostedPage(
-    userId,
-    pageType
-  )
+  const url =
+    await SubscriptionViewModelBuilder.promises.getRedirectToHostedPage(
+      userId,
+      pageType
+    )
   logger.warn({ userId, pageType }, 'redirecting to recurly hosted page')
   logger.warn({ userId, pageType }, 'redirecting to recurly hosted page')
   res.redirect(url)
   res.redirect(url)
 }
 }

+ 6 - 9
services/web/app/src/Features/Subscription/SubscriptionUpdater.js

@@ -79,9 +79,8 @@ async function removeUserFromGroup(subscriptionId, userId) {
 }
 }
 
 
 async function removeUserFromAllGroups(userId) {
 async function removeUserFromAllGroups(userId) {
-  const subscriptions = await SubscriptionLocator.promises.getMemberSubscriptions(
-    userId
-  )
+  const subscriptions =
+    await SubscriptionLocator.promises.getMemberSubscriptions(userId)
   if (subscriptions.length === 0) {
   if (subscriptions.length === 0) {
     return
     return
   }
   }
@@ -114,9 +113,8 @@ async function deleteSubscription(subscription, deleterData) {
 }
 }
 
 
 async function restoreSubscription(subscriptionId) {
 async function restoreSubscription(subscriptionId) {
-  const deletedSubscription = await SubscriptionLocator.promises.getDeletedSubscription(
-    subscriptionId
-  )
+  const deletedSubscription =
+    await SubscriptionLocator.promises.getDeletedSubscription(subscriptionId)
   const subscription = deletedSubscription.subscription
   const subscription = deletedSubscription.subscription
 
 
   // 1. upsert subscription
   // 1. upsert subscription
@@ -249,9 +247,8 @@ async function updateSubscriptionFromRecurly(
 
 
 async function _sendUserGroupPlanCodeUserProperty(userId) {
 async function _sendUserGroupPlanCodeUserProperty(userId) {
   try {
   try {
-    const subscriptions = await SubscriptionLocator.promises.getMemberSubscriptions(
-      userId
-    )
+    const subscriptions =
+      await SubscriptionLocator.promises.getMemberSubscriptions(userId)
     let bestPlanCode = null
     let bestPlanCode = null
     let bestFeatures = {}
     let bestFeatures = {}
     for (const subscription of subscriptions) {
     for (const subscription of subscriptions) {

+ 19 - 16
services/web/app/src/Features/Subscription/SubscriptionViewModelBuilder.js

@@ -21,9 +21,8 @@ async function getRedirectToHostedPage(userId, pageType) {
   if (!['billing-details', 'account-management'].includes(pageType)) {
   if (!['billing-details', 'account-management'].includes(pageType)) {
     throw new InvalidError('unexpected page type')
     throw new InvalidError('unexpected page type')
   }
   }
-  const personalSubscription = await SubscriptionLocator.promises.getUsersSubscription(
-    userId
-  )
+  const personalSubscription =
+    await SubscriptionLocator.promises.getUsersSubscription(userId)
   const recurlySubscriptionId = personalSubscription?.recurlySubscription_id
   const recurlySubscriptionId = personalSubscription?.recurlySubscription_id
   if (!recurlySubscriptionId) {
   if (!recurlySubscriptionId) {
     throw new NotFoundError('not a recurly subscription')
     throw new NotFoundError('not a recurly subscription')
@@ -253,23 +252,27 @@ function buildUsersSubscriptionViewModel(user, callback) {
           const pendingSubscriptionTax =
           const pendingSubscriptionTax =
             personalSubscription.recurly.taxRate *
             personalSubscription.recurly.taxRate *
             recurlySubscription.pending_subscription.unit_amount_in_cents
             recurlySubscription.pending_subscription.unit_amount_in_cents
-          personalSubscription.recurly.price = SubscriptionFormatters.formatPrice(
-            recurlySubscription.pending_subscription.unit_amount_in_cents +
-              pendingAddOnPrice +
-              pendingAddOnTax +
-              pendingSubscriptionTax,
-            recurlySubscription.currency
-          )
+          personalSubscription.recurly.price =
+            SubscriptionFormatters.formatPrice(
+              recurlySubscription.pending_subscription.unit_amount_in_cents +
+                pendingAddOnPrice +
+                pendingAddOnTax +
+                pendingSubscriptionTax,
+              recurlySubscription.currency
+            )
           const pendingTotalLicenses =
           const pendingTotalLicenses =
             (pendingPlan.membersLimit || 0) + pendingAdditionalLicenses
             (pendingPlan.membersLimit || 0) + pendingAdditionalLicenses
-          personalSubscription.recurly.pendingAdditionalLicenses = pendingAdditionalLicenses
-          personalSubscription.recurly.pendingTotalLicenses = pendingTotalLicenses
+          personalSubscription.recurly.pendingAdditionalLicenses =
+            pendingAdditionalLicenses
+          personalSubscription.recurly.pendingTotalLicenses =
+            pendingTotalLicenses
           personalSubscription.pendingPlan = pendingPlan
           personalSubscription.pendingPlan = pendingPlan
         } else {
         } else {
-          personalSubscription.recurly.price = SubscriptionFormatters.formatPrice(
-            recurlySubscription.unit_amount_in_cents + addOnPrice + tax,
-            recurlySubscription.currency
-          )
+          personalSubscription.recurly.price =
+            SubscriptionFormatters.formatPrice(
+              recurlySubscription.unit_amount_in_cents + addOnPrice + tax,
+              recurlySubscription.currency
+            )
         }
         }
       }
       }
 
 

+ 4 - 6
services/web/app/src/Features/ThirdPartyDataStore/TpdsUpdateSender.js

@@ -7,8 +7,8 @@ const path = require('path')
 const request = require('request-promise-native')
 const request = require('request-promise-native')
 const settings = require('@overleaf/settings')
 const settings = require('@overleaf/settings')
 
 
-const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
-  .promises
+const CollaboratorsGetter =
+  require('../Collaborators/CollaboratorsGetter').promises
 const UserGetter = require('../User/UserGetter.js').promises
 const UserGetter = require('../User/UserGetter.js').promises
 
 
 const tpdsUrl = _.get(settings, ['apis', 'thirdPartyDataStore', 'url'])
 const tpdsUrl = _.get(settings, ['apis', 'thirdPartyDataStore', 'url'])
@@ -149,10 +149,8 @@ async function enqueue(group, method, job) {
 async function getProjectUsersIds(projectId) {
 async function getProjectUsersIds(projectId) {
   // get list of all user ids with access to project. project owner
   // get list of all user ids with access to project. project owner
   // will always be the first entry in the list.
   // will always be the first entry in the list.
-  const [
-    ownerUserId,
-    ...invitedUserIds
-  ] = await CollaboratorsGetter.getInvitedMemberIds(projectId)
+  const [ownerUserId, ...invitedUserIds] =
+    await CollaboratorsGetter.getInvitedMemberIds(projectId)
   // if there are no invited users, always return the owner
   // if there are no invited users, always return the owner
   if (!invitedUserIds.length) {
   if (!invitedUserIds.length) {
     return [ownerUserId]
     return [ownerUserId]

+ 12 - 14
services/web/app/src/Features/TokenAccess/TokenAccessController.js

@@ -25,11 +25,12 @@ async function _userAlreadyHasHigherPrivilege(
   if (!Object.values(TokenAccessHandler.TOKEN_TYPES).includes(tokenType)) {
   if (!Object.values(TokenAccessHandler.TOKEN_TYPES).includes(tokenType)) {
     throw new Error('bad token type')
     throw new Error('bad token type')
   }
   }
-  const privilegeLevel = await AuthorizationManager.promises.getPrivilegeLevelForProject(
-    userId,
-    projectId,
-    token
-  )
+  const privilegeLevel =
+    await AuthorizationManager.promises.getPrivilegeLevelForProject(
+      userId,
+      projectId,
+      token
+    )
   return (
   return (
     orderedPrivilegeLevels.indexOf(privilegeLevel) >=
     orderedPrivilegeLevels.indexOf(privilegeLevel) >=
     orderedPrivilegeLevels.indexOf(tokenType)
     orderedPrivilegeLevels.indexOf(tokenType)
@@ -84,9 +85,8 @@ async function tokenAccessPage(req, res, next) {
   }
   }
   try {
   try {
     if (TokenAccessHandler.isReadOnlyToken(token)) {
     if (TokenAccessHandler.isReadOnlyToken(token)) {
-      const docPublishedInfo = await TokenAccessHandler.promises.getV1DocPublishedInfo(
-        token
-      )
+      const docPublishedInfo =
+        await TokenAccessHandler.promises.getV1DocPublishedInfo(token)
       if (docPublishedInfo.allow === false) {
       if (docPublishedInfo.allow === false) {
         return res.redirect(302, docPublishedInfo.published_path)
         return res.redirect(302, docPublishedInfo.published_path)
       }
       }
@@ -135,9 +135,8 @@ async function checkAndGetProjectOrResponseAction(
 
 
   const projectId = project._id
   const projectId = project._id
   const isAnonymousUser = !userId
   const isAnonymousUser = !userId
-  const tokenAccessEnabled = TokenAccessHandler.tokenAccessEnabledForProject(
-    project
-  )
+  const tokenAccessEnabled =
+    TokenAccessHandler.tokenAccessEnabledForProject(project)
   if (isAnonymousUser && tokenAccessEnabled) {
   if (isAnonymousUser && tokenAccessEnabled) {
     if (tokenType === TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE) {
     if (tokenType === TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE) {
       if (TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED) {
       if (TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED) {
@@ -260,9 +259,8 @@ async function grantTokenAccessReadOnly(req, res, next) {
     return res.sendStatus(400)
     return res.sendStatus(400)
   }
   }
   const tokenType = TokenAccessHandler.TOKEN_TYPES.READ_ONLY
   const tokenType = TokenAccessHandler.TOKEN_TYPES.READ_ONLY
-  const docPublishedInfo = await TokenAccessHandler.promises.getV1DocPublishedInfo(
-    token
-  )
+  const docPublishedInfo =
+    await TokenAccessHandler.promises.getV1DocPublishedInfo(token)
   if (docPublishedInfo.allow === false) {
   if (docPublishedInfo.allow === false) {
     return res.json({ redirect: docPublishedInfo.published_path })
     return res.json({ redirect: docPublishedInfo.published_path })
   }
   }

+ 10 - 11
services/web/app/src/Features/Uploads/ProjectUploadManager.js

@@ -29,12 +29,10 @@ module.exports = {
 
 
 async function createProjectFromZipArchive(ownerId, defaultName, zipPath) {
 async function createProjectFromZipArchive(ownerId, defaultName, zipPath) {
   const contentsPath = await _extractZip(zipPath)
   const contentsPath = await _extractZip(zipPath)
-  const {
-    path,
-    content,
-  } = await ProjectRootDocManager.promises.findRootDocFileFromDirectory(
-    contentsPath
-  )
+  const { path, content } =
+    await ProjectRootDocManager.promises.findRootDocFileFromDirectory(
+      contentsPath
+    )
 
 
   const projectName =
   const projectName =
     DocumentHelper.getTitleFromTexContent(content || '') || defaultName
     DocumentHelper.getTitleFromTexContent(content || '') || defaultName
@@ -131,11 +129,12 @@ async function _initializeProjectWithZipContents(
     project._id,
     project._id,
     importEntries
     importEntries
   )
   )
-  const projectVersion = await ProjectEntityMongoUpdateHandler.promises.createNewFolderStructure(
-    project._id,
-    docEntries,
-    fileEntries
-  )
+  const projectVersion =
+    await ProjectEntityMongoUpdateHandler.promises.createNewFolderStructure(
+      project._id,
+      docEntries,
+      fileEntries
+    )
   await _notifyDocumentUpdater(project, ownerId, {
   await _notifyDocumentUpdater(project, ownerId, {
     newFiles: fileEntries,
     newFiles: fileEntries,
     newDocs: docEntries,
     newDocs: docEntries,

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

@@ -223,9 +223,8 @@ async function getUser(providerId, externalUserId) {
 }
 }
 
 
 async function redundantSubscription(userId, providerId, providerName) {
 async function redundantSubscription(userId, providerId, providerName) {
-  const subscription = await SubscriptionLocator.promises.getUserIndividualSubscription(
-    userId
-  )
+  const subscription =
+    await SubscriptionLocator.promises.getUserIndividualSubscription(userId)
 
 
   if (subscription) {
   if (subscription) {
     await NotificationsBuilder.promises
     await NotificationsBuilder.promises

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

@@ -264,9 +264,8 @@ const decorateFullEmails = (
       )
       )
     }
     }
 
 
-    emailData.emailHasInstitutionLicence = InstitutionsHelper.emailHasLicence(
-      emailData
-    )
+    emailData.emailHasInstitutionLicence =
+      InstitutionsHelper.emailHasLicence(emailData)
   })
   })
 
 
   return emailsData
   return emailsData

+ 13 - 12
services/web/app/src/Features/User/UserSessionsManager.js

@@ -217,19 +217,20 @@ const UserSessionsManager = {
         return callback(err)
         return callback(err)
       }
       }
       Async.series(
       Async.series(
-        sessionKeys.map(key => next =>
-          rclient.get(key, function (err, val) {
-            if (err) {
-              return next(err)
-            }
-            if (!val) {
-              rclient.srem(sessionSetKey, key, function (err, result) {
+        sessionKeys.map(
+          key => next =>
+            rclient.get(key, function (err, val) {
+              if (err) {
                 return next(err)
                 return next(err)
-              })
-            } else {
-              next()
-            }
-          })
+              }
+              if (!val) {
+                rclient.srem(sessionSetKey, key, function (err, result) {
+                  return next(err)
+                })
+              } else {
+                next()
+              }
+            })
         ),
         ),
         function (err, results) {
         function (err, results) {
           callback(err)
           callback(err)

+ 2 - 3
services/web/app/src/Features/UserMembership/UserMembershipController.js

@@ -31,9 +31,8 @@ module.exports = {
           if (error != null) {
           if (error != null) {
             return next(error)
             return next(error)
           }
           }
-          const entityPrimaryKey = entity[
-            entityConfig.fields.primaryKey
-          ].toString()
+          const entityPrimaryKey =
+            entity[entityConfig.fields.primaryKey].toString()
           if (entityConfig.fields.name) {
           if (entityConfig.fields.name) {
             entityName = entity[entityConfig.fields.name]
             entityName = entity[entityConfig.fields.name]
           }
           }

+ 5 - 4
services/web/app/src/Features/UserMembership/UserMembershipMiddleware.js

@@ -198,10 +198,11 @@ function fetchEntityConfig(entityName) {
 // fetch the entity with id and config, and set it in the request
 // fetch the entity with id and config, and set it in the request
 function fetchEntity() {
 function fetchEntity() {
   return expressify(async (req, res, next) => {
   return expressify(async (req, res, next) => {
-    const entity = await UserMembershipHandler.promises.getEntityWithoutAuthorizationCheck(
-      req.params.id,
-      req.entityConfig
-    )
+    const entity =
+      await UserMembershipHandler.promises.getEntityWithoutAuthorizationCheck(
+        req.params.id,
+        req.entityConfig
+      )
     req.entity = entity
     req.entity = entity
     next()
     next()
   })
   })

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

@@ -1,19 +1,16 @@
 const _ = require('lodash')
 const _ = require('lodash')
 const Settings = require('@overleaf/settings')
 const Settings = require('@overleaf/settings')
 
 
-const publicRegistrationModuleAvailable = Settings.moduleImportSequence.includes(
-  'public-registration'
-)
+const publicRegistrationModuleAvailable =
+  Settings.moduleImportSequence.includes('public-registration')
 
 
 const supportModuleAvailable = Settings.moduleImportSequence.includes('support')
 const supportModuleAvailable = Settings.moduleImportSequence.includes('support')
 
 
-const historyV1ModuleAvailable = Settings.moduleImportSequence.includes(
-  'history-v1'
-)
+const historyV1ModuleAvailable =
+  Settings.moduleImportSequence.includes('history-v1')
 
 
-const trackChangesModuleAvailable = Settings.moduleImportSequence.includes(
-  'track-changes'
-)
+const trackChangesModuleAvailable =
+  Settings.moduleImportSequence.includes('track-changes')
 
 
 /**
 /**
  * @typedef {Object} Settings
  * @typedef {Object} Settings

+ 2 - 3
services/web/app/src/infrastructure/Translations.js

@@ -77,9 +77,8 @@ function setLangBasedOnDomainMiddleware(req, res, next) {
   // offering to switch to the appropriate library
   // offering to switch to the appropriate library
   const detectedLanguageCode = headerLangDetector.detect(req, res)
   const detectedLanguageCode = headerLangDetector.detect(req, res)
   if (req.language !== detectedLanguageCode) {
   if (req.language !== detectedLanguageCode) {
-    res.locals.suggestedLanguageSubdomainConfig = subdomainConfigs.get(
-      detectedLanguageCode
-    )
+    res.locals.suggestedLanguageSubdomainConfig =
+      subdomainConfigs.get(detectedLanguageCode)
   }
   }
 
 
   // Decorate req.i18n with translate function alias for backwards
   // Decorate req.i18n with translate function alias for backwards

+ 9 - 7
services/web/frontend/js/directives/onEnter.js

@@ -7,11 +7,13 @@
  */
  */
 import App from '../base'
 import App from '../base'
 
 
-export default App.directive('onEnter', () => (scope, element, attrs) =>
-  element.bind('keydown keypress', function (event) {
-    if (event.which === 13) {
-      scope.$apply(() => scope.$eval(attrs.onEnter, { event }))
-      return event.preventDefault()
-    }
-  })
+export default App.directive(
+  'onEnter',
+  () => (scope, element, attrs) =>
+    element.bind('keydown keypress', function (event) {
+      if (event.which === 13) {
+        scope.$apply(() => scope.$eval(attrs.onEnter, { event }))
+        return event.preventDefault()
+      }
+    })
 )
 )

+ 4 - 3
services/web/frontend/js/features/clone-project-modal/components/clone-project-modal-content.js

@@ -27,9 +27,10 @@ export default function CloneProjectModalContent({
   )
   )
 
 
   // valid if the cloned project has a name
   // valid if the cloned project has a name
-  const valid = useMemo(() => clonedProjectName.trim().length > 0, [
-    clonedProjectName,
-  ])
+  const valid = useMemo(
+    () => clonedProjectName.trim().length > 0,
+    [clonedProjectName]
+  )
 
 
   // form submission: clone the project if the name is valid
   // form submission: clone the project if the name is valid
   const handleSubmit = event => {
   const handleSubmit = event => {

+ 2 - 3
services/web/frontend/js/features/editor-navigation-toolbar/components/editor-navigation-toolbar-root.js

@@ -69,9 +69,8 @@ const EditorNavigationToolbarRoot = React.memo(
       pdfLayout,
       pdfLayout,
     } = useLayoutContext(layoutContextPropTypes)
     } = useLayoutContext(layoutContextPropTypes)
 
 
-    const { markMessagesAsRead, unreadMessageCount } = useChatContext(
-      chatContextPropTypes
-    )
+    const { markMessagesAsRead, unreadMessageCount } =
+      useChatContext(chatContextPropTypes)
 
 
     const toggleChatOpen = useCallback(() => {
     const toggleChatOpen = useCallback(() => {
       if (!chatIsOpen) {
       if (!chatIsOpen) {

+ 2 - 5
services/web/frontend/js/features/file-tree/components/file-tree-context-menu.js

@@ -7,11 +7,8 @@ import { useFileTreeMainContext } from '../contexts/file-tree-main'
 import FileTreeItemMenuItems from './file-tree-item/file-tree-item-menu-items'
 import FileTreeItemMenuItems from './file-tree-item/file-tree-item-menu-items'
 
 
 function FileTreeContextMenu() {
 function FileTreeContextMenu() {
-  const {
-    hasWritePermissions,
-    contextMenuCoords,
-    setContextMenuCoords,
-  } = useFileTreeMainContext()
+  const { hasWritePermissions, contextMenuCoords, setContextMenuCoords } =
+    useFileTreeMainContext()
 
 
   if (!hasWritePermissions || !contextMenuCoords) return null
   if (!hasWritePermissions || !contextMenuCoords) return null
 
 

+ 2 - 4
services/web/frontend/js/features/file-tree/components/file-tree-create/modes/file-tree-import-from-project.js

@@ -16,10 +16,8 @@ import ErrorMessage from '../error-message'
 export default function FileTreeImportFromProject() {
 export default function FileTreeImportFromProject() {
   const { t } = useTranslation()
   const { t } = useTranslation()
 
 
-  const {
-    hasLinkedProjectFileFeature,
-    hasLinkedProjectOutputFileFeature,
-  } = window.ExposedSettings
+  const { hasLinkedProjectFileFeature, hasLinkedProjectOutputFileFeature } =
+    window.ExposedSettings
   const canSwitchOutputFilesMode =
   const canSwitchOutputFilesMode =
     hasLinkedProjectFileFeature && hasLinkedProjectOutputFileFeature
     hasLinkedProjectFileFeature && hasLinkedProjectOutputFileFeature
 
 

+ 2 - 7
services/web/frontend/js/features/file-tree/components/file-tree-item/file-tree-item-name.js

@@ -9,13 +9,8 @@ import { useFileTreeMainContext } from '../../contexts/file-tree-main'
 function FileTreeItemName({ name, isSelected, setIsDraggable }) {
 function FileTreeItemName({ name, isSelected, setIsDraggable }) {
   const { hasWritePermissions } = useFileTreeMainContext()
   const { hasWritePermissions } = useFileTreeMainContext()
 
 
-  const {
-    isRenaming,
-    startRenaming,
-    finishRenaming,
-    error,
-    cancel,
-  } = useFileTreeActionable()
+  const { isRenaming, startRenaming, finishRenaming, error, cancel } =
+    useFileTreeActionable()
 
 
   const isRenamingEntity = isRenaming && isSelected && !error
   const isRenamingEntity = isRenaming && isSelected && !error
 
 

+ 2 - 6
services/web/frontend/js/features/file-tree/components/file-tree-toolbar.js

@@ -63,12 +63,8 @@ function FileTreeToolbarLeft() {
 
 
 function FileTreeToolbarRight() {
 function FileTreeToolbarRight() {
   const { t } = useTranslation()
   const { t } = useTranslation()
-  const {
-    canRename,
-    canDelete,
-    startRenaming,
-    startDeleting,
-  } = useFileTreeActionable()
+  const { canRename, canDelete, startRenaming, startDeleting } =
+    useFileTreeActionable()
 
 
   if (!canRename && !canDelete) {
   if (!canRename && !canDelete) {
     return null
     return null

+ 2 - 7
services/web/frontend/js/features/file-tree/components/modals/file-tree-modal-create-folder.js

@@ -18,13 +18,8 @@ function FileTreeModalCreateFolder() {
   const [name, setName] = useState('')
   const [name, setName] = useState('')
   const [validName, setValidName] = useState(true)
   const [validName, setValidName] = useState(true)
 
 
-  const {
-    isCreatingFolder,
-    inFlight,
-    finishCreatingFolder,
-    cancel,
-    error,
-  } = useFileTreeActionable()
+  const { isCreatingFolder, inFlight, finishCreatingFolder, cancel, error } =
+    useFileTreeActionable()
 
 
   if (!isCreatingFolder) return null // the modal will not be rendered; return early
   if (!isCreatingFolder) return null // the modal will not be rendered; return early
 
 

+ 98 - 95
services/web/frontend/js/features/file-tree/controllers/file-tree-controller.js

@@ -5,116 +5,119 @@ import { cloneDeep } from 'lodash'
 import FileTreeRoot from '../components/file-tree-root'
 import FileTreeRoot from '../components/file-tree-root'
 import { rootContext } from '../../../shared/context/root-context'
 import { rootContext } from '../../../shared/context/root-context'
 
 
-App.controller('ReactFileTreeController', function (
-  $scope,
-  $timeout,
-  ide
-  // eventTracking
-) {
-  $scope.projectId = ide.project_id
-  $scope.rootFolder = null
-  $scope.rootDocId = null
-  $scope.hasWritePermissions = false
-  $scope.isConnected = true
-
-  $scope.$on('project:joined', () => {
-    $scope.rootFolder = $scope.project.rootFolder
-    $scope.rootDocId = $scope.project.rootDoc_id
-    $scope.$emit('file-tree:initialized')
-  })
-
-  $scope.$watch('permissions.write', hasWritePermissions => {
-    $scope.hasWritePermissions = hasWritePermissions
-  })
-
-  $scope.$watch('editor.open_doc_id', openDocId => {
-    window.dispatchEvent(
-      new CustomEvent('editor.openDoc', { detail: openDocId })
-    )
-  })
-
-  // Set isConnected to true if:
-  // - connection state is 'ready', OR
-  // - connection state is 'waitingCountdown' and reconnection_countdown is null
-  // The added complexity is needed  because in Firefox on page reload the
-  // connection state goes into 'waitingCountdown' before being hidden and we
-  // don't want to show a disconnect UI.
-  function updateIsConnected() {
-    const isReady = $scope.connection.state === 'ready'
-    const willStartCountdown =
-      $scope.connection.state === 'waitingCountdown' &&
-      $scope.connection.reconnection_countdown === null
-    $scope.isConnected = isReady || willStartCountdown
-  }
+App.controller(
+  'ReactFileTreeController',
+  function (
+    $scope,
+    $timeout,
+    ide
+    // eventTracking
+  ) {
+    $scope.projectId = ide.project_id
+    $scope.rootFolder = null
+    $scope.rootDocId = null
+    $scope.hasWritePermissions = false
+    $scope.isConnected = true
+
+    $scope.$on('project:joined', () => {
+      $scope.rootFolder = $scope.project.rootFolder
+      $scope.rootDocId = $scope.project.rootDoc_id
+      $scope.$emit('file-tree:initialized')
+    })
 
 
-  $scope.$watch('connection.state', updateIsConnected)
-  $scope.$watch('connection.reconnection_countdown', updateIsConnected)
+    $scope.$watch('permissions.write', hasWritePermissions => {
+      $scope.hasWritePermissions = hasWritePermissions
+    })
 
 
-  $scope.onInit = () => {
-    // HACK: resize the vertical pane on init after a 0ms timeout. We do not
-    // understand why this is necessary but without this the resized handle is
-    // stuck at the bottom. The vertical resize will soon be migrated to React
-    // so we accept to live with this hack for now.
-    $timeout(() => {
-      $scope.$emit('left-pane-resize-all')
+    $scope.$watch('editor.open_doc_id', openDocId => {
+      window.dispatchEvent(
+        new CustomEvent('editor.openDoc', { detail: openDocId })
+      )
     })
     })
-  }
 
 
-  $scope.onSelect = selectedEntities => {
-    if (selectedEntities.length === 1) {
-      const selectedEntity = selectedEntities[0]
-      const type =
-        selectedEntity.type === 'fileRef' ? 'file' : selectedEntity.type
-      $scope.$emit('entity:selected', {
-        ...selectedEntity.entity,
-        id: selectedEntity.entity._id,
-        type,
-      })
+    // Set isConnected to true if:
+    // - connection state is 'ready', OR
+    // - connection state is 'waitingCountdown' and reconnection_countdown is null
+    // The added complexity is needed  because in Firefox on page reload the
+    // connection state goes into 'waitingCountdown' before being hidden and we
+    // don't want to show a disconnect UI.
+    function updateIsConnected() {
+      const isReady = $scope.connection.state === 'ready'
+      const willStartCountdown =
+        $scope.connection.state === 'waitingCountdown' &&
+        $scope.connection.reconnection_countdown === null
+      $scope.isConnected = isReady || willStartCountdown
+    }
+
+    $scope.$watch('connection.state', updateIsConnected)
+    $scope.$watch('connection.reconnection_countdown', updateIsConnected)
 
 
-      // in the react implementation there is no such concept as "1
-      // multi-selected entity" so here we pass a count of 0
-      $scope.$emit('entities:multiSelected', { count: 0 })
-    } else if (selectedEntities.length > 1) {
-      $scope.$emit('entities:multiSelected', {
-        count: selectedEntities.length,
+    $scope.onInit = () => {
+      // HACK: resize the vertical pane on init after a 0ms timeout. We do not
+      // understand why this is necessary but without this the resized handle is
+      // stuck at the bottom. The vertical resize will soon be migrated to React
+      // so we accept to live with this hack for now.
+      $timeout(() => {
+        $scope.$emit('left-pane-resize-all')
       })
       })
-    } else {
-      $scope.$emit('entity:no-selection')
     }
     }
-  }
 
 
-  $scope.userHasFeature = feature => ide.$scope.user.features[feature]
+    $scope.onSelect = selectedEntities => {
+      if (selectedEntities.length === 1) {
+        const selectedEntity = selectedEntities[0]
+        const type =
+          selectedEntity.type === 'fileRef' ? 'file' : selectedEntity.type
+        $scope.$emit('entity:selected', {
+          ...selectedEntity.entity,
+          id: selectedEntity.entity._id,
+          type,
+        })
+
+        // in the react implementation there is no such concept as "1
+        // multi-selected entity" so here we pass a count of 0
+        $scope.$emit('entities:multiSelected', { count: 0 })
+      } else if (selectedEntities.length > 1) {
+        $scope.$emit('entities:multiSelected', {
+          count: selectedEntities.length,
+        })
+      } else {
+        $scope.$emit('entity:no-selection')
+      }
+    }
 
 
-  $scope.$watch('permissions.write', hasWritePermissions => {
-    $scope.hasWritePermissions = hasWritePermissions
-  })
+    $scope.userHasFeature = feature => ide.$scope.user.features[feature]
 
 
-  $scope.refProviders = ide.$scope.user.refProviders || {}
+    $scope.$watch('permissions.write', hasWritePermissions => {
+      $scope.hasWritePermissions = hasWritePermissions
+    })
 
 
-  ide.$scope.$watch(
-    'user.refProviders',
-    refProviders => {
-      $scope.refProviders = cloneDeep(refProviders)
-    },
-    true
-  )
+    $scope.refProviders = ide.$scope.user.refProviders || {}
 
 
-  $scope.setRefProviderEnabled = provider => {
-    ide.$scope.$applyAsync(() => {
-      ide.$scope.user.refProviders[provider] = true
-    })
-  }
+    ide.$scope.$watch(
+      'user.refProviders',
+      refProviders => {
+        $scope.refProviders = cloneDeep(refProviders)
+      },
+      true
+    )
 
 
-  $scope.setStartedFreeTrial = started => {
-    $scope.$applyAsync(() => {
-      $scope.startedFreeTrial = started
-    })
-  }
+    $scope.setRefProviderEnabled = provider => {
+      ide.$scope.$applyAsync(() => {
+        ide.$scope.user.refProviders[provider] = true
+      })
+    }
+
+    $scope.setStartedFreeTrial = started => {
+      $scope.$applyAsync(() => {
+        $scope.startedFreeTrial = started
+      })
+    }
 
 
-  $scope.reindexReferences = () => {
-    ide.$scope.$emit('references:should-reindex', {})
+    $scope.reindexReferences = () => {
+      ide.$scope.$emit('references:should-reindex', {})
+    }
   }
   }
-})
+)
 
 
 App.component(
 App.component(
   'fileTreeRoot',
   'fileTreeRoot',

+ 2 - 6
services/web/frontend/js/features/file-tree/hooks/file-tree-socket-listener.js

@@ -19,12 +19,8 @@ export function useFileTreeSocketListener() {
     dispatchCreateFile,
     dispatchCreateFile,
     fileTreeData,
     fileTreeData,
   } = useFileTreeMutable()
   } = useFileTreeMutable()
-  const {
-    selectedEntityIds,
-    selectedEntityParentIds,
-    select,
-    unselect,
-  } = useFileTreeSelectable()
+  const { selectedEntityIds, selectedEntityParentIds, select, unselect } =
+    useFileTreeSelectable()
   const socket = window._ide && window._ide.socket
   const socket = window._ide && window._ide.socket
 
 
   const selectEntityIfCreatedByUser = useCallback(
   const selectEntityIfCreatedByUser = useCallback(

+ 4 - 3
services/web/frontend/js/features/pdf-preview/components/detach-compile-button.js

@@ -16,9 +16,10 @@ export function DetachCompileButton() {
     'detached'
     'detached'
   )
   )
 
 
-  const handleStartCompile = useCallback(() => startOrTriggerCompile(), [
-    startOrTriggerCompile,
-  ])
+  const handleStartCompile = useCallback(
+    () => startOrTriggerCompile(),
+    [startOrTriggerCompile]
+  )
 
 
   return (
   return (
     <div
     <div

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

@@ -14,12 +14,8 @@ import getMeta from '../../../utils/meta'
 function PdfJsViewer({ url }) {
 function PdfJsViewer({ url }) {
   const { _id: projectId } = useProjectContext()
   const { _id: projectId } = useProjectContext()
 
 
-  const {
-    setError,
-    firstRenderDone,
-    highlights,
-    setPosition,
-  } = useCompileContext()
+  const { setError, firstRenderDone, highlights, setPosition } =
+    useCompileContext()
   const [timePDFFetched, setTimePDFFetched] = useState()
   const [timePDFFetched, setTimePDFFetched] = useState()
 
 
   // state values persisted in localStorage to restore on load
   // state values persisted in localStorage to restore on load

+ 2 - 7
services/web/frontend/js/features/pdf-preview/components/pdf-logs-button.js

@@ -5,13 +5,8 @@ import { sendMBOnce } from '../../../infrastructure/event-tracking'
 import { useCompileContext } from '../../../shared/context/compile-context'
 import { useCompileContext } from '../../../shared/context/compile-context'
 
 
 function PdfLogsButton() {
 function PdfLogsButton() {
-  const {
-    codeCheckFailed,
-    error,
-    logEntries,
-    showLogs,
-    setShowLogs,
-  } = useCompileContext()
+  const { codeCheckFailed, error, logEntries, showLogs, setShowLogs } =
+    useCompileContext()
 
 
   const buttonStyle = useMemo(() => {
   const buttonStyle = useMemo(() => {
     if (showLogs) {
     if (showLogs) {

+ 2 - 7
services/web/frontend/js/features/pdf-preview/components/pdf-synctex-controls.js

@@ -103,13 +103,8 @@ function PdfSynctexControls() {
 
 
   const { detachRole } = useLayoutContext()
   const { detachRole } = useLayoutContext()
 
 
-  const {
-    clsiServerId,
-    pdfUrl,
-    pdfViewer,
-    position,
-    setHighlights,
-  } = useCompileContext()
+  const { clsiServerId, pdfUrl, pdfViewer, position, setHighlights } =
+    useCompileContext()
 
 
   const [cursorPosition, setCursorPosition] = useState(() => {
   const [cursorPosition, setCursorPosition] = useState(() => {
     const position = localStorage.getItem(
     const position = localStorage.getItem(

+ 2 - 6
services/web/frontend/js/features/pdf-preview/hooks/use-compile-triggers.js

@@ -10,12 +10,8 @@ const showPdfDetach = getMeta('ol-showPdfDetach')
 const debugPdfDetach = getMeta('ol-debugPdfDetach')
 const debugPdfDetach = getMeta('ol-debugPdfDetach')
 
 
 export default function useCompileTriggers() {
 export default function useCompileTriggers() {
-  const {
-    startCompile,
-    setChangedAt,
-    cleanupCompileResult,
-    setError,
-  } = useCompileContext()
+  const { startCompile, setChangedAt, cleanupCompileResult, setError } =
+    useCompileContext()
   const { role: detachRole } = useDetachContext()
   const { role: detachRole } = useDetachContext()
 
 
   // recompile on key press
   // recompile on key press

+ 5 - 5
services/web/frontend/js/features/plans/group-plan-modal/index.js

@@ -4,8 +4,9 @@ import * as eventTracking from '../../../infrastructure/event-tracking'
 
 
 function getFormValues() {
 function getFormValues() {
   const modalEl = document.querySelector('[data-ol-group-plan-modal]')
   const modalEl = document.querySelector('[data-ol-group-plan-modal]')
-  const planCode = modalEl.querySelector('input[name="plan_code"]:checked')
-    .value
+  const planCode = modalEl.querySelector(
+    'input[name="plan_code"]:checked'
+  ).value
   const size = modalEl.querySelector('#size').value
   const size = modalEl.querySelector('#size').value
   const currency = modalEl.querySelector('#currency').value
   const currency = modalEl.querySelector('#currency').value
   const usage = modalEl.querySelector('#usage').checked
   const usage = modalEl.querySelector('#usage').checked
@@ -32,9 +33,8 @@ function updateGroupPlanView() {
   modalEl.querySelectorAll('[data-ol-group-plan-usage]').forEach(el => {
   modalEl.querySelectorAll('[data-ol-group-plan-usage]').forEach(el => {
     el.hidden = el.getAttribute('data-ol-group-plan-usage') !== usage
     el.hidden = el.getAttribute('data-ol-group-plan-usage') !== usage
   })
   })
-  modalEl.querySelector(
-    '[data-ol-group-plan-display-price]'
-  ).innerText = displayPrice
+  modalEl.querySelector('[data-ol-group-plan-display-price]').innerText =
+    displayPrice
   modalEl.querySelector(
   modalEl.querySelector(
     '[data-ol-group-plan-price-per-user]'
     '[data-ol-group-plan-price-per-user]'
   ).innerText = `${currencySymbol}${perUserPrice} per user`
   ).innerText = `${currencySymbol}${perUserPrice} per user`

+ 4 - 8
services/web/frontend/js/features/preview/components/preview-logs-pane-entry.js

@@ -58,14 +58,10 @@ function PreviewLogEntryContent({
   formattedContent,
   formattedContent,
   extraInfoURL,
   extraInfoURL,
 }) {
 }) {
-  const {
-    isExpanded,
-    needsExpandCollapse,
-    expandableProps,
-    toggleProps,
-  } = useExpandCollapse({
-    collapsedSize: 150,
-  })
+  const { isExpanded, needsExpandCollapse, expandableProps, toggleProps } =
+    useExpandCollapse({
+      collapsedSize: 150,
+    })
 
 
   const buttonContainerClasses = classNames(
   const buttonContainerClasses = classNames(
     'log-entry-content-button-container',
     'log-entry-content-button-container',

+ 4 - 6
services/web/frontend/js/features/preview/components/preview-pane.js

@@ -30,12 +30,10 @@ function PreviewPane({
   const [lastCompileTimestamp, setLastCompileTimestamp] = useState(
   const [lastCompileTimestamp, setLastCompileTimestamp] = useState(
     compilerState.lastCompileTimestamp
     compilerState.lastCompileTimestamp
   )
   )
-  const [seenLogsForCurrentCompile, setSeenLogsForCurrentCompile] = useState(
-    false
-  )
-  const [dismissedFirstErrorPopUp, setDismissedFirstErrorPopUp] = useState(
-    false
-  )
+  const [seenLogsForCurrentCompile, setSeenLogsForCurrentCompile] =
+    useState(false)
+  const [dismissedFirstErrorPopUp, setDismissedFirstErrorPopUp] =
+    useState(false)
 
 
   if (lastCompileTimestamp < compilerState.lastCompileTimestamp) {
   if (lastCompileTimestamp < compilerState.lastCompileTimestamp) {
     setLastCompileTimestamp(compilerState.lastCompileTimestamp)
     setLastCompileTimestamp(compilerState.lastCompileTimestamp)

+ 2 - 4
services/web/frontend/js/features/share-project-modal/components/edit-member.js

@@ -18,10 +18,8 @@ import { useProjectContext } from '../../../shared/context/project-context'
 
 
 export default function EditMember({ member }) {
 export default function EditMember({ member }) {
   const [privileges, setPrivileges] = useState(member.privileges)
   const [privileges, setPrivileges] = useState(member.privileges)
-  const [
-    confirmingOwnershipTransfer,
-    setConfirmingOwnershipTransfer,
-  ] = useState(false)
+  const [confirmingOwnershipTransfer, setConfirmingOwnershipTransfer] =
+    useState(false)
 
 
   // update the local state if the member's privileges change externally
   // update the local state if the member's privileges change externally
   useEffect(() => {
   useEffect(() => {

+ 6 - 4
services/web/frontend/js/features/share-project-modal/components/select-collaborators.js

@@ -10,7 +10,8 @@ import Icon from '../../../shared/components/icon'
 // Unicode characters in these Unicode groups:
 // Unicode characters in these Unicode groups:
 //  "General Punctuation — Spaces"
 //  "General Punctuation — Spaces"
 //  "General Punctuation — Format character" (including zero-width spaces)
 //  "General Punctuation — Format character" (including zero-width spaces)
-const matchAllSpaces = /[\u061C\u2000-\u200F\u202A-\u202E\u2060\u2066-\u2069\u2028\u2029\u202F]/g
+const matchAllSpaces =
+  /[\u061C\u2000-\u200F\u202A-\u202E\u2060\u2066-\u2069\u2028\u2029\u202F]/g
 
 
 export default function SelectCollaborators({
 export default function SelectCollaborators({
   loading,
   loading,
@@ -28,9 +29,10 @@ export default function SelectCollaborators({
 
 
   const [inputValue, setInputValue] = useState('')
   const [inputValue, setInputValue] = useState('')
 
 
-  const selectedEmails = useMemo(() => selectedItems.map(item => item.email), [
-    selectedItems,
-  ])
+  const selectedEmails = useMemo(
+    () => selectedItems.map(item => item.email),
+    [selectedItems]
+  )
 
 
   const unselectedOptions = useMemo(
   const unselectedOptions = useMemo(
     () => options.filter(option => !selectedEmails.includes(option.email)),
     () => options.filter(option => !selectedEmails.includes(option.email)),

+ 4 - 3
services/web/frontend/js/features/share-project-modal/components/share-project-modal.js

@@ -124,9 +124,10 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
   }, [])
   }, [])
 
 
   // merge the new data with the old project data
   // merge the new data with the old project data
-  const updateProject = useCallback(data => Object.assign(project, data), [
-    project,
-  ])
+  const updateProject = useCallback(
+    data => Object.assign(project, data),
+    [project]
+  )
 
 
   if (!project) {
   if (!project) {
     return null
     return null

+ 2 - 1
services/web/frontend/js/ide/connection/ConnectionManager.js

@@ -426,7 +426,8 @@ Something went wrong connecting to your project. Please refresh if this continue
                 this.joinProjectRetryInterval <
                 this.joinProjectRetryInterval <
                 this.JOIN_PROJECT_MAX_RETRY_INTERVAL
                 this.JOIN_PROJECT_MAX_RETRY_INTERVAL
               ) {
               ) {
-                this.joinProjectRetryInterval += this.JOIN_PROJECT_RETRY_INTERVAL
+                this.joinProjectRetryInterval +=
+                  this.JOIN_PROJECT_RETRY_INTERVAL
               }
               }
               return
               return
             } else {
             } else {

+ 2 - 4
services/web/frontend/js/ide/editor/ShareJsDoc.js

@@ -335,10 +335,8 @@ export default ShareJsDoc = (function () {
       // end-to-end check for edits -> acks, for this very ShareJsdoc
       // end-to-end check for edits -> acks, for this very ShareJsdoc
       // This will catch a broken connection and missing UX-blocker for the
       // This will catch a broken connection and missing UX-blocker for the
       //  user, allowing them to keep editing.
       //  user, allowing them to keep editing.
-      this._detachEditorWatchdogManager = this.EditorWatchdogManager.attachToEditor(
-        editorName,
-        editor
-      )
+      this._detachEditorWatchdogManager =
+        this.EditorWatchdogManager.attachToEditor(editorName, editor)
     }
     }
 
 
     _attachToEditor(editorName, editor, attachToShareJs) {
     _attachToEditor(editorName, editor, attachToShareJs) {

+ 4 - 6
services/web/frontend/js/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.js

@@ -329,9 +329,8 @@ class AutoCompleteManager {
                     )
                     )
                   )
                   )
                 // Delete back to command start, as appropriate
                 // Delete back to command start, as appropriate
-                const commandStartIndex = Helpers.getLastCommandFragmentIndex(
-                  lineUpToCursor
-                )
+                const commandStartIndex =
+                  Helpers.getLastCommandFragmentIndex(lineUpToCursor)
                 if (commandStartIndex !== -1) {
                 if (commandStartIndex !== -1) {
                   leftRange.start.column = commandStartIndex
                   leftRange.start.column = commandStartIndex
                 } else {
                 } else {
@@ -351,9 +350,8 @@ class AutoCompleteManager {
                   )
                   )
 
 
                 if (lineBeyondCursor) {
                 if (lineBeyondCursor) {
-                  const partialCommandMatch = lineBeyondCursor.match(
-                    /^([a-zA-Z0-9]+)\{/
-                  )
+                  const partialCommandMatch =
+                    lineBeyondCursor.match(/^([a-zA-Z0-9]+)\{/)
                   if (partialCommandMatch) {
                   if (partialCommandMatch) {
                     // We've got a partial command after the cursor
                     // We've got a partial command after the cursor
                     const commandTail = partialCommandMatch[1]
                     const commandTail = partialCommandMatch[1]

Filskillnaden har hållts tillbaka eftersom den är för stor
+ 0 - 0
services/web/frontend/js/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.js


+ 8 - 16
services/web/frontend/js/ide/editor/directives/aceEditor/track-changes/TrackChangesAdapter.js

@@ -89,10 +89,8 @@ class TrackChangesAdapter {
     if (this.changeIdToMarkerIdMap[change.id] == null) {
     if (this.changeIdToMarkerIdMap[change.id] == null) {
       return
       return
     }
     }
-    const {
-      background_marker_id,
-      callout_marker_id,
-    } = this.changeIdToMarkerIdMap[change.id]
+    const { background_marker_id, callout_marker_id } =
+      this.changeIdToMarkerIdMap[change.id]
     delete this.changeIdToMarkerIdMap[change.id]
     delete this.changeIdToMarkerIdMap[change.id]
     const session = this.editor.getSession()
     const session = this.editor.getSession()
     session.removeMarker(background_marker_id)
     session.removeMarker(background_marker_id)
@@ -103,10 +101,8 @@ class TrackChangesAdapter {
     if (this.changeIdToMarkerIdMap[change.id] == null) {
     if (this.changeIdToMarkerIdMap[change.id] == null) {
       return
       return
     }
     }
-    const {
-      background_marker_id,
-      callout_marker_id,
-    } = this.changeIdToMarkerIdMap[change.id]
+    const { background_marker_id, callout_marker_id } =
+      this.changeIdToMarkerIdMap[change.id]
     delete this.changeIdToMarkerIdMap[change.id]
     delete this.changeIdToMarkerIdMap[change.id]
 
 
     const session = this.editor.getSession()
     const session = this.editor.getSession()
@@ -166,10 +162,8 @@ class TrackChangesAdapter {
   onCommentRemoved(comment) {
   onCommentRemoved(comment) {
     if (this.changeIdToMarkerIdMap[comment.id] != null) {
     if (this.changeIdToMarkerIdMap[comment.id] != null) {
       // Resolved comments may not have marker ids
       // Resolved comments may not have marker ids
-      const {
-        background_marker_id,
-        callout_marker_id,
-      } = this.changeIdToMarkerIdMap[comment.id]
+      const { background_marker_id, callout_marker_id } =
+        this.changeIdToMarkerIdMap[comment.id]
       delete this.changeIdToMarkerIdMap[comment.id]
       delete this.changeIdToMarkerIdMap[comment.id]
       const session = this.editor.getSession()
       const session = this.editor.getSession()
       session.removeMarker(background_marker_id)
       session.removeMarker(background_marker_id)
@@ -183,10 +177,8 @@ class TrackChangesAdapter {
     }
     }
     const session = this.editor.getSession()
     const session = this.editor.getSession()
     const markers = session.getMarkers()
     const markers = session.getMarkers()
-    const {
-      background_marker_id,
-      callout_marker_id,
-    } = this.changeIdToMarkerIdMap[change_id]
+    const { background_marker_id, callout_marker_id } =
+      this.changeIdToMarkerIdMap[change_id]
     if (background_marker_id != null && markers[background_marker_id] != null) {
     if (background_marker_id != null && markers[background_marker_id] != null) {
       const background_marker = markers[background_marker_id]
       const background_marker = markers[background_marker_id]
       background_marker.range.start = start
       background_marker.range.start = start

+ 4 - 8
services/web/frontend/js/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.js

@@ -496,10 +496,8 @@ class TrackChangesManager {
     for (const change of Array.from(this.rangesTracker.changes)) {
     for (const change of Array.from(this.rangesTracker.changes)) {
       if (this.adapter.changeIdToMarkerIdMap[change.id] != null) {
       if (this.adapter.changeIdToMarkerIdMap[change.id] != null) {
         ;({ op } = change)
         ;({ op } = change)
-        ;({
-          background_marker_id,
-          callout_marker_id,
-        } = this.adapter.changeIdToMarkerIdMap[change.id])
+        ;({ background_marker_id, callout_marker_id } =
+          this.adapter.changeIdToMarkerIdMap[change.id])
         start = this.adapter.shareJsOffsetToRowColumn(op.p)
         start = this.adapter.shareJsOffsetToRowColumn(op.p)
         if (op.i != null) {
         if (op.i != null) {
           end = this.adapter.shareJsOffsetToRowColumn(op.p + op.i.length)
           end = this.adapter.shareJsOffsetToRowColumn(op.p + op.i.length)
@@ -521,10 +519,8 @@ class TrackChangesManager {
 
 
     for (const comment of Array.from(this.rangesTracker.comments)) {
     for (const comment of Array.from(this.rangesTracker.comments)) {
       if (this.adapter.changeIdToMarkerIdMap[comment.id] != null) {
       if (this.adapter.changeIdToMarkerIdMap[comment.id] != null) {
-        ;({
-          background_marker_id,
-          callout_marker_id,
-        } = this.adapter.changeIdToMarkerIdMap[comment.id])
+        ;({ background_marker_id, callout_marker_id } =
+          this.adapter.changeIdToMarkerIdMap[comment.id])
         start = this.adapter.shareJsOffsetToRowColumn(comment.op.p)
         start = this.adapter.shareJsOffsetToRowColumn(comment.op.p)
         end = this.adapter.shareJsOffsetToRowColumn(
         end = this.adapter.shareJsOffsetToRowColumn(
           comment.op.p + comment.op.c.length
           comment.op.p + comment.op.c.length

+ 4 - 9
services/web/frontend/js/ide/history/HistoryManager.js

@@ -130,12 +130,8 @@ export default HistoryManager = (function () {
     reloadDiff() {
     reloadDiff() {
       let { diff } = this.$scope.history
       let { diff } = this.$scope.history
       const { updates, doc } = this.$scope.history.selection
       const { updates, doc } = this.$scope.history.selection
-      const {
-        fromV,
-        toV,
-        start_ts,
-        end_ts,
-      } = this._calculateRangeFromSelection()
+      const { fromV, toV, start_ts, end_ts } =
+        this._calculateRangeFromSelection()
 
 
       if (doc == null) {
       if (doc == null) {
         return
         return
@@ -269,9 +265,8 @@ export default HistoryManager = (function () {
       if (updates == null) {
       if (updates == null) {
         updates = []
         updates = []
       }
       }
-      let previousUpdate = this.$scope.history.updates[
-        this.$scope.history.updates.length - 1
-      ]
+      let previousUpdate =
+        this.$scope.history.updates[this.$scope.history.updates.length - 1]
 
 
       for (const update of Array.from(updates)) {
       for (const update of Array.from(updates)) {
         update.pathnames = [] // Used for display
         update.pathnames = [] // Used for display

+ 11 - 12
services/web/frontend/js/ide/history/HistoryV2Manager.js

@@ -323,8 +323,8 @@ export default HistoryManager = (function () {
 
 
     selectFile(file) {
     selectFile(file) {
       if (file != null && file.pathname != null) {
       if (file != null && file.pathname != null) {
-        this.$scope.history.selection.pathname = this._previouslySelectedPathname =
-          file.pathname
+        this.$scope.history.selection.pathname =
+          this._previouslySelectedPathname = file.pathname
         this.$scope.history.selection.file = file
         this.$scope.history.selection.file = file
         if (this.$scope.history.viewMode === HistoryViewModes.POINT_IN_TIME) {
         if (this.$scope.history.viewMode === HistoryViewModes.POINT_IN_TIME) {
           this.loadFileAtPointInTime()
           this.loadFileAtPointInTime()
@@ -659,13 +659,13 @@ export default HistoryManager = (function () {
 
 
     _loadLabels(labels, lastUpdateToV) {
     _loadLabels(labels, lastUpdateToV) {
       const sortedLabels = this._sortLabelsByVersionAndDate(labels)
       const sortedLabels = this._sortLabelsByVersionAndDate(labels)
-      const labelsWithoutPseudoLabel = this._deletePseudoCurrentStateLabelIfExistent(
-        sortedLabels
-      )
-      const labelsWithPseudoLabelIfNeeded = this._addPseudoCurrentStateLabelIfNeeded(
-        labelsWithoutPseudoLabel,
-        lastUpdateToV
-      )
+      const labelsWithoutPseudoLabel =
+        this._deletePseudoCurrentStateLabelIfExistent(sortedLabels)
+      const labelsWithPseudoLabelIfNeeded =
+        this._addPseudoCurrentStateLabelIfNeeded(
+          labelsWithoutPseudoLabel,
+          lastUpdateToV
+        )
       return labelsWithPseudoLabelIfNeeded
       return labelsWithPseudoLabelIfNeeded
     }
     }
 
 
@@ -902,9 +902,8 @@ export default HistoryManager = (function () {
       if (updates == null) {
       if (updates == null) {
         updates = []
         updates = []
       }
       }
-      let previousUpdate = this.$scope.history.updates[
-        this.$scope.history.updates.length - 1
-      ]
+      let previousUpdate =
+        this.$scope.history.updates[this.$scope.history.updates.length - 1]
       const dateTimeNow = new Date()
       const dateTimeNow = new Date()
       const timestamp24hoursAgo = dateTimeNow.setDate(dateTimeNow.getDate() - 1)
       const timestamp24hoursAgo = dateTimeNow.setDate(dateTimeNow.getDate() - 1)
       let cutOffIndex = null
       let cutOffIndex = null

+ 6 - 3
services/web/frontend/js/ide/human-readable-logs/HumanReadableLogsRules.js

@@ -500,7 +500,8 @@ const rules = [
     ruleId: 'hint_mismatched_environment2',
     ruleId: 'hint_mismatched_environment2',
     types: ['environment'],
     types: ['environment'],
     cascadesFrom: ['environment'],
     cascadesFrom: ['environment'],
-    regexToMatch: /Error: `\\end\{([^\}]+)\}' expected but found `\\end\{([^\}]+)\}'.*/,
+    regexToMatch:
+      /Error: `\\end\{([^\}]+)\}' expected but found `\\end\{([^\}]+)\}'.*/,
     newMessage: 'Error: environments do not match: \\begin{$1} ... \\end{$2}',
     newMessage: 'Error: environments do not match: \\begin{$1} ... \\end{$2}',
     humanReadableHintComponent: (
     humanReadableHintComponent: (
       <>
       <>
@@ -516,7 +517,8 @@ const rules = [
     ruleId: 'hint_mismatched_environment3',
     ruleId: 'hint_mismatched_environment3',
     types: ['environment'],
     types: ['environment'],
     cascadesFrom: ['environment'],
     cascadesFrom: ['environment'],
-    regexToMatch: /Warning: No matching \\end found for `\\begin\{([^\}]+)\}'.*/,
+    regexToMatch:
+      /Warning: No matching \\end found for `\\begin\{([^\}]+)\}'.*/,
     newMessage: 'Warning: No matching \\end found for \\begin{$1}',
     newMessage: 'Warning: No matching \\end found for \\begin{$1}',
     humanReadableHintComponent: (
     humanReadableHintComponent: (
       <>
       <>
@@ -532,7 +534,8 @@ const rules = [
     ruleId: 'hint_mismatched_environment4',
     ruleId: 'hint_mismatched_environment4',
     types: ['environment'],
     types: ['environment'],
     cascadesFrom: ['environment'],
     cascadesFrom: ['environment'],
-    regexToMatch: /Error: Found `\\end\{([^\}]+)\}' without corresponding \\begin.*/,
+    regexToMatch:
+      /Error: Found `\\end\{([^\}]+)\}' without corresponding \\begin.*/,
     newMessage: 'Error: found \\end{$1} without a corresponding \\begin{$1}',
     newMessage: 'Error: found \\end{$1} without a corresponding \\begin{$1}',
     humanReadableHintComponent: (
     humanReadableHintComponent: (
       <>
       <>

+ 10 - 17
services/web/frontend/js/ide/log-parser/bib-log-parser.js

@@ -3,9 +3,12 @@ const LINE_SPLITTER_REGEX = /^\[(\d+)].*>\s(INFO|WARN|ERROR)\s-\s(.*)$/
 
 
 const MULTILINE_WARNING_REGEX = /^Warning--(.+)\n--line (\d+) of file (.+)$/m
 const MULTILINE_WARNING_REGEX = /^Warning--(.+)\n--line (\d+) of file (.+)$/m
 const SINGLELINE_WARNING_REGEX = /^Warning--(.+)$/m
 const SINGLELINE_WARNING_REGEX = /^Warning--(.+)$/m
-const MULTILINE_ERROR_REGEX = /^(.*)---line (\d+) of file (.*)\n([^]+?)\nI'm skipping whatever remains of this entry$/m
-const BAD_CROSS_REFERENCE_REGEX = /^(A bad cross reference---entry ".+?"\nrefers to entry.+?, which doesn't exist)$/m
-const MULTILINE_COMMAND_ERROR_REGEX = /^(.*)\n?---line (\d+) of file (.*)\n([^]+?)\nI'm skipping whatever remains of this command$/m
+const MULTILINE_ERROR_REGEX =
+  /^(.*)---line (\d+) of file (.*)\n([^]+?)\nI'm skipping whatever remains of this entry$/m
+const BAD_CROSS_REFERENCE_REGEX =
+  /^(A bad cross reference---entry ".+?"\nrefers to entry.+?, which doesn't exist)$/m
+const MULTILINE_COMMAND_ERROR_REGEX =
+  /^(.*)\n?---line (\d+) of file (.*)\n([^]+?)\nI'm skipping whatever remains of this command$/m
 // Errors hit in BST file have a slightly different format
 // Errors hit in BST file have a slightly different format
 const BST_ERROR_REGEX = /^(.*?)\nwhile executing---line (\d+) of file (.*)/m
 const BST_ERROR_REGEX = /^(.*?)\nwhile executing---line (\d+) of file (.*)/m
 
 
@@ -103,13 +106,8 @@ export default class BibLogParser {
       [
       [
         MULTILINE_ERROR_REGEX,
         MULTILINE_ERROR_REGEX,
         function (match) {
         function (match) {
-          const [
-            fullMatch,
-            firstMessage,
-            lineNumber,
-            fileName,
-            secondMessage,
-          ] = match
+          const [fullMatch, firstMessage, lineNumber, fileName, secondMessage] =
+            match
           return {
           return {
             file: fileName,
             file: fileName,
             level: 'error',
             level: 'error',
@@ -135,13 +133,8 @@ export default class BibLogParser {
       [
       [
         MULTILINE_COMMAND_ERROR_REGEX,
         MULTILINE_COMMAND_ERROR_REGEX,
         function (match) {
         function (match) {
-          const [
-            fullMatch,
-            firstMessage,
-            lineNumber,
-            fileName,
-            secondMessage,
-          ] = match
+          const [fullMatch, firstMessage, lineNumber, fileName, secondMessage] =
+            match
           return {
           return {
             file: fileName,
             file: fileName,
             level: 'error',
             level: 'error',

+ 2 - 2
services/web/frontend/js/ide/pdfng/directives/pdfViewer.js

@@ -620,8 +620,8 @@ export default App.directive('pdfViewer', ($q, $timeout, pdfSpinner) => ({
       if (selection.rangeCount === 0) {
       if (selection.rangeCount === 0) {
         return false
         return false
       }
       }
-      const selectionAncestorNode = selection.getRangeAt(0)
-        .commonAncestorContainer
+      const selectionAncestorNode =
+        selection.getRangeAt(0).commonAncestorContainer
       return (
       return (
         element.find(selectionAncestorNode).length > 0 ||
         element.find(selectionAncestorNode).length > 0 ||
         element.is(selectionAncestorNode)
         element.is(selectionAncestorNode)

+ 6 - 8
services/web/frontend/js/ide/review-panel/controllers/ReviewPanelController.js

@@ -150,9 +150,8 @@ export default App.controller(
     $scope.$watch('project.members', function (members) {
     $scope.$watch('project.members', function (members) {
       $scope.reviewPanel.formattedProjectMembers = {}
       $scope.reviewPanel.formattedProjectMembers = {}
       if (($scope.project != null ? $scope.project.owner : undefined) != null) {
       if (($scope.project != null ? $scope.project.owner : undefined) != null) {
-        $scope.reviewPanel.formattedProjectMembers[
-          $scope.project.owner._id
-        ] = formatUser($scope.project.owner)
+        $scope.reviewPanel.formattedProjectMembers[$scope.project.owner._id] =
+          formatUser($scope.project.owner)
       }
       }
       if (
       if (
         ($scope.project != null ? $scope.project.members : undefined) != null
         ($scope.project != null ? $scope.project.members : undefined) != null
@@ -170,9 +169,8 @@ export default App.controller(
                 )
                 )
               }
               }
               result.push(
               result.push(
-                ($scope.reviewPanel.formattedProjectMembers[
-                  member._id
-                ] = formatUser(member))
+                ($scope.reviewPanel.formattedProjectMembers[member._id] =
+                  formatUser(member))
               )
               )
             } else {
             } else {
               result.push(undefined)
               result.push(undefined)
@@ -900,8 +898,8 @@ export default App.controller(
 
 
     $scope.toggleFullTCStateCollapse = function () {
     $scope.toggleFullTCStateCollapse = function () {
       if ($scope.project.features.trackChanges) {
       if ($scope.project.features.trackChanges) {
-        return ($scope.reviewPanel.fullTCStateCollapsed = !$scope.reviewPanel
-          .fullTCStateCollapsed)
+        return ($scope.reviewPanel.fullTCStateCollapsed =
+          !$scope.reviewPanel.fullTCStateCollapsed)
       } else {
       } else {
         _sendAnalytics()
         _sendAnalytics()
         return $scope.openTrackChangesUpgradeModal()
         return $scope.openTrackChangesUpgradeModal()

+ 3 - 2
services/web/frontend/js/ide/review-panel/filters/notEmpty.js

@@ -6,6 +6,7 @@
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  */
  */
 import App from '../../../base'
 import App from '../../../base'
-export default App.filter('notEmpty', () => object =>
-  !angular.equals({}, object)
+export default App.filter(
+  'notEmpty',
+  () => object => !angular.equals({}, object)
 )
 )

+ 46 - 43
services/web/frontend/js/main/account-settings.js

@@ -1,52 +1,55 @@
 import App from '../base'
 import App from '../base'
-App.controller('AccountSettingsController', function (
-  $scope,
-  $http,
-  $modal,
-  // eslint-disable-next-line camelcase
-  eventTracking,
-  UserAffiliationsDataService
-) {
-  $scope.subscribed = true
+App.controller(
+  'AccountSettingsController',
+  function (
+    $scope,
+    $http,
+    $modal,
+    // eslint-disable-next-line camelcase
+    eventTracking,
+    UserAffiliationsDataService
+  ) {
+    $scope.subscribed = true
 
 
-  $scope.unsubscribe = function () {
-    $scope.unsubscribing = true
-    return $http({
-      method: 'DELETE',
-      url: '/user/newsletter/unsubscribe',
-      headers: {
-        'X-CSRF-Token': window.csrfToken,
-      },
-    })
-      .then(function () {
-        $scope.unsubscribing = false
-        $scope.subscribed = false
+    $scope.unsubscribe = function () {
+      $scope.unsubscribing = true
+      return $http({
+        method: 'DELETE',
+        url: '/user/newsletter/unsubscribe',
+        headers: {
+          'X-CSRF-Token': window.csrfToken,
+        },
       })
       })
-      .catch(() => ($scope.unsubscribing = true))
-  }
+        .then(function () {
+          $scope.unsubscribing = false
+          $scope.subscribed = false
+        })
+        .catch(() => ($scope.unsubscribing = true))
+    }
 
 
-  $scope.deleteAccount = function () {
-    $modal.open({
-      templateUrl: 'deleteAccountModalTemplate',
-      controller: 'DeleteAccountModalController',
-      resolve: {
-        userDefaultEmail() {
-          return UserAffiliationsDataService.getUserDefaultEmail()
-            .then(
-              defaultEmailDetails =>
-                (defaultEmailDetails != null
-                  ? defaultEmailDetails.email
-                  : undefined) || null
-            )
-            .catch(() => null)
+    $scope.deleteAccount = function () {
+      $modal.open({
+        templateUrl: 'deleteAccountModalTemplate',
+        controller: 'DeleteAccountModalController',
+        resolve: {
+          userDefaultEmail() {
+            return UserAffiliationsDataService.getUserDefaultEmail()
+              .then(
+                defaultEmailDetails =>
+                  (defaultEmailDetails != null
+                    ? defaultEmailDetails.email
+                    : undefined) || null
+              )
+              .catch(() => null)
+          },
         },
         },
-      },
-    })
-  }
+      })
+    }
 
 
-  $scope.upgradeIntegration = service =>
-    eventTracking.send('subscription-funnel', 'settings-page', service)
-})
+    $scope.upgradeIntegration = service =>
+      eventTracking.send('subscription-funnel', 'settings-page', service)
+  }
+)
 
 
 App.controller(
 App.controller(
   'DeleteAccountModalController',
   'DeleteAccountModalController',

+ 16 - 14
services/web/frontend/js/main/affiliations/controllers/UserAffiliationsController.js

@@ -41,7 +41,8 @@ export default App.controller(
 
 
     const ONE_WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000
     const ONE_WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000
     const LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/
     const LOCAL_AND_DOMAIN_REGEX = /([^@]+)@(.+)/
-    const EMAIL_REGEX = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\ ".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA -Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
+    const EMAIL_REGEX =
+      /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\ ".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA -Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
 
 
     const _matchLocalAndDomain = function (userEmailInput) {
     const _matchLocalAndDomain = function (userEmailInput) {
       const match = userEmailInput
       const match = userEmailInput
@@ -84,9 +85,10 @@ export default App.controller(
       $scope.ui.isBlacklistedEmail = false
       $scope.ui.isBlacklistedEmail = false
       $scope.ui.showManualUniversitySelectionUI = false
       $scope.ui.showManualUniversitySelectionUI = false
       if (userInputLocalAndDomain.domain) {
       if (userInputLocalAndDomain.domain) {
-        $scope.ui.isBlacklistedEmail = UserAffiliationsDataService.isDomainBlacklisted(
-          userInputLocalAndDomain.domain
-        )
+        $scope.ui.isBlacklistedEmail =
+          UserAffiliationsDataService.isDomainBlacklisted(
+            userInputLocalAndDomain.domain
+          )
         return UserAffiliationsDataService.getUniversityDomainFromPartialDomainInput(
         return UserAffiliationsDataService.getUniversityDomainFromPartialDomainInput(
           userInputLocalAndDomain.domain
           userInputLocalAndDomain.domain
         )
         )
@@ -100,9 +102,8 @@ export default App.controller(
             ) {
             ) {
               $scope.newAffiliation.university = universityDomain.university
               $scope.newAffiliation.university = universityDomain.university
               $scope.newAffiliation.department = universityDomain.department
               $scope.newAffiliation.department = universityDomain.department
-              $scope.newAffiliation.ssoAvailable = _ssoAvailableForDomain(
-                universityDomain
-              )
+              $scope.newAffiliation.ssoAvailable =
+                _ssoAvailableForDomain(universityDomain)
             } else {
             } else {
               _resetAffiliationSuggestion()
               _resetAffiliationSuggestion()
             }
             }
@@ -175,13 +176,14 @@ export default App.controller(
         )
         )
       } else {
       } else {
         if ($scope.newAffiliation.university.isUserSuggested) {
         if ($scope.newAffiliation.university.isUserSuggested) {
-          addEmailPromise = UserAffiliationsDataService.addUserAffiliationWithUnknownUniversity(
-            $scope.newAffiliation.email,
-            $scope.newAffiliation.university.name,
-            $scope.newAffiliation.country.code,
-            $scope.newAffiliation.role,
-            $scope.newAffiliation.department
-          )
+          addEmailPromise =
+            UserAffiliationsDataService.addUserAffiliationWithUnknownUniversity(
+              $scope.newAffiliation.email,
+              $scope.newAffiliation.university.name,
+              $scope.newAffiliation.country.code,
+              $scope.newAffiliation.role,
+              $scope.newAffiliation.department
+            )
         } else {
         } else {
           addEmailPromise = UserAffiliationsDataService.addUserAffiliation(
           addEmailPromise = UserAffiliationsDataService.addUserAffiliation(
             $scope.newAffiliation.email,
             $scope.newAffiliation.email,

+ 709 - 701
services/web/frontend/js/main/project-list/project-list.js

@@ -1,857 +1,865 @@
 import _ from 'lodash'
 import _ from 'lodash'
 import App from '../../base'
 import App from '../../base'
 import './services/project-list'
 import './services/project-list'
-App.controller('ProjectPageController', function (
-  $scope,
-  $modal,
-  $window,
-  queuedHttp,
-  eventTracking, // eslint-disable-line camelcase
-  $timeout,
-  localStorage,
-  ProjectListService
-) {
-  $scope.projects = window.data.projects
-  $scope.tags = window.data.tags
-  $scope.notifications = window.data.notifications
-  $scope.notificationsInstitution = window.data.notificationsInstitution
-  $scope.allSelected = false
-  $scope.selectedProjects = []
-  $scope.filter = 'all'
-  $scope.predicate = 'lastUpdated'
-  $scope.nUntagged = 0
-  $scope.reverse = true
-  $scope.searchText = { value: '' }
-  $scope.$watch('predicate', function (newValue) {
-    $scope.comparator =
-      newValue === 'ownerName' ? ownerNameComparator : defaultComparator
-  })
-
-  $scope.shouldShowSurveyLink = true
-
-  if (localStorage('dismissed-2021-future-survey') === true) {
-    $scope.shouldShowSurveyLink = false
-  }
-
-  $scope.dismissSurvey = () => {
-    localStorage('dismissed-2021-future-survey', true)
-    $scope.shouldShowSurveyLink = false
-  }
+App.controller(
+  'ProjectPageController',
+  function (
+    $scope,
+    $modal,
+    $window,
+    queuedHttp,
+    eventTracking, // eslint-disable-line camelcase
+    $timeout,
+    localStorage,
+    ProjectListService
+  ) {
+    $scope.projects = window.data.projects
+    $scope.tags = window.data.tags
+    $scope.notifications = window.data.notifications
+    $scope.notificationsInstitution = window.data.notificationsInstitution
+    $scope.allSelected = false
+    $scope.selectedProjects = []
+    $scope.filter = 'all'
+    $scope.predicate = 'lastUpdated'
+    $scope.nUntagged = 0
+    $scope.reverse = true
+    $scope.searchText = { value: '' }
+    $scope.$watch('predicate', function (newValue) {
+      $scope.comparator =
+        newValue === 'ownerName' ? ownerNameComparator : defaultComparator
+    })
 
 
-  $timeout(() => recalculateProjectListHeight(), 10)
-
-  $scope.$watch(
-    () =>
-      $scope.projects.filter(
-        project =>
-          (project.tags == null || project.tags.length === 0) &&
-          !project.archived &&
-          !project.trashed
-      ).length,
-    newVal => ($scope.nUntagged = newVal)
-  )
-
-  function recalculateProjectListHeight() {
-    const $projListCard = $('.project-list-card')
-    if (!$projListCard || !$projListCard.offset()) return
-
-    const topOffset = $projListCard.offset().top
-    const cardPadding = $projListCard.outerHeight() - $projListCard.height()
-    const bottomOffset = $('footer').outerHeight()
-    const height = $window.innerHeight - topOffset - bottomOffset - cardPadding
-    $scope.projectListHeight = height
-  }
+    $scope.shouldShowSurveyLink = true
 
 
-  function defaultComparator(v1, v2) {
-    let result = 0
-    const type1 = v1.type
-    const type2 = v2.type
+    if (localStorage('dismissed-2021-future-survey') === true) {
+      $scope.shouldShowSurveyLink = false
+    }
 
 
-    if ($scope.predicate === 'ownerName') {
-      return
+    $scope.dismissSurvey = () => {
+      localStorage('dismissed-2021-future-survey', true)
+      $scope.shouldShowSurveyLink = false
     }
     }
 
 
-    if (type1 === type2) {
-      let value1 = v1.value
-      let value2 = v2.value
+    $timeout(() => recalculateProjectListHeight(), 10)
 
 
-      if (type1 === 'string') {
-        // Compare strings case-insensitively
-        value1 = value1.toLowerCase()
-        value2 = value2.toLowerCase()
-      } else if (type1 === 'object') {
-        // For basic objects, use the position of the object
-        // in the collection instead of the value
-        if (angular.isObject(value1)) value1 = v1.index
-        if (angular.isObject(value2)) value2 = v2.index
-      }
+    $scope.$watch(
+      () =>
+        $scope.projects.filter(
+          project =>
+            (project.tags == null || project.tags.length === 0) &&
+            !project.archived &&
+            !project.trashed
+        ).length,
+      newVal => ($scope.nUntagged = newVal)
+    )
 
 
-      if (value1 !== value2) {
-        result = value1 < value2 ? -1 : 1
-      }
-    } else {
-      result = type1 < type2 ? -1 : 1
+    function recalculateProjectListHeight() {
+      const $projListCard = $('.project-list-card')
+      if (!$projListCard || !$projListCard.offset()) return
+
+      const topOffset = $projListCard.offset().top
+      const cardPadding = $projListCard.outerHeight() - $projListCard.height()
+      const bottomOffset = $('footer').outerHeight()
+      const height =
+        $window.innerHeight - topOffset - bottomOffset - cardPadding
+      $scope.projectListHeight = height
     }
     }
 
 
-    return result
-  }
+    function defaultComparator(v1, v2) {
+      let result = 0
+      const type1 = v1.type
+      const type2 = v2.type
 
 
-  function ownerNameComparator(v1, v2) {
-    if ($scope.predicate !== 'ownerName') {
-      return
-    }
-    if (v1.value === 'You') {
-      if (v2.value === 'You') {
-        return v1.index < v2.index ? -1 : 1
-      } else {
-        return 1
+      if ($scope.predicate === 'ownerName') {
+        return
       }
       }
-    } else if (v1.value === 'An Overleaf v1 User' || v1.value === 'None') {
-      if (v2.value === 'An Overleaf v1 User' || v2.value === 'None') {
-        return v1.index < v2.index ? -1 : 1
+
+      if (type1 === type2) {
+        let value1 = v1.value
+        let value2 = v2.value
+
+        if (type1 === 'string') {
+          // Compare strings case-insensitively
+          value1 = value1.toLowerCase()
+          value2 = value2.toLowerCase()
+        } else if (type1 === 'object') {
+          // For basic objects, use the position of the object
+          // in the collection instead of the value
+          if (angular.isObject(value1)) value1 = v1.index
+          if (angular.isObject(value2)) value2 = v2.index
+        }
+
+        if (value1 !== value2) {
+          result = value1 < value2 ? -1 : 1
+        }
       } else {
       } else {
-        return -1
+        result = type1 < type2 ? -1 : 1
       }
       }
-    } else {
-      if (v2.value === 'You') {
-        return -1
-      } else if (v2.value === 'An Overleaf v1 User' || v2.value === 'None') {
-        return 1
+
+      return result
+    }
+
+    function ownerNameComparator(v1, v2) {
+      if ($scope.predicate !== 'ownerName') {
+        return
+      }
+      if (v1.value === 'You') {
+        if (v2.value === 'You') {
+          return v1.index < v2.index ? -1 : 1
+        } else {
+          return 1
+        }
+      } else if (v1.value === 'An Overleaf v1 User' || v1.value === 'None') {
+        if (v2.value === 'An Overleaf v1 User' || v2.value === 'None') {
+          return v1.index < v2.index ? -1 : 1
+        } else {
+          return -1
+        }
       } else {
       } else {
-        return v1.value > v2.value ? -1 : 1
+        if (v2.value === 'You') {
+          return -1
+        } else if (v2.value === 'An Overleaf v1 User' || v2.value === 'None') {
+          return 1
+        } else {
+          return v1.value > v2.value ? -1 : 1
+        }
       }
       }
     }
     }
-  }
 
 
-  angular.element($window).bind('resize', function () {
-    recalculateProjectListHeight()
-    $scope.$apply()
-  })
+    angular.element($window).bind('resize', function () {
+      recalculateProjectListHeight()
+      $scope.$apply()
+    })
 
 
-  $scope.$on('project-list:notifications-received', () =>
-    $scope.$applyAsync(() => recalculateProjectListHeight())
-  )
+    $scope.$on('project-list:notifications-received', () =>
+      $scope.$applyAsync(() => recalculateProjectListHeight())
+    )
 
 
-  // Allow tags to be accessed on projects as well
-  const projectsById = {}
-  for (const project of $scope.projects) {
-    projectsById[project.id] = project
-  }
+    // Allow tags to be accessed on projects as well
+    const projectsById = {}
+    for (const project of $scope.projects) {
+      projectsById[project.id] = project
+    }
 
 
-  $scope.getProjectById = id => projectsById[id]
+    $scope.getProjectById = id => projectsById[id]
 
 
-  for (const tag of $scope.tags) {
-    for (const projectId of tag.project_ids || []) {
-      const project = projectsById[projectId]
-      if (project) {
-        if (!project.tags) {
-          project.tags = []
+    for (const tag of $scope.tags) {
+      for (const projectId of tag.project_ids || []) {
+        const project = projectsById[projectId]
+        if (project) {
+          if (!project.tags) {
+            project.tags = []
+          }
+          project.tags.push(tag)
         }
         }
-        project.tags.push(tag)
       }
       }
     }
     }
-  }
 
 
-  $scope.changePredicate = function (newPredicate) {
-    if ($scope.predicate === newPredicate) {
-      $scope.reverse = !$scope.reverse
+    $scope.changePredicate = function (newPredicate) {
+      if ($scope.predicate === newPredicate) {
+        $scope.reverse = !$scope.reverse
+      }
+      $scope.predicate = newPredicate
     }
     }
-    $scope.predicate = newPredicate
-  }
 
 
-  $scope.getSortIconClass = function (column) {
-    if (column === $scope.predicate && $scope.reverse) {
-      return 'fa-caret-down'
-    } else if (column === $scope.predicate && !$scope.reverse) {
-      return 'fa-caret-up'
-    } else {
-      return ''
+    $scope.getSortIconClass = function (column) {
+      if (column === $scope.predicate && $scope.reverse) {
+        return 'fa-caret-down'
+      } else if (column === $scope.predicate && !$scope.reverse) {
+        return 'fa-caret-up'
+      } else {
+        return ''
+      }
     }
     }
-  }
 
 
-  $scope.searchProjects = function () {
-    eventTracking.send(
-      'project-list-page-interaction',
-      'project-search',
-      'keydown'
-    )
-    $scope.updateVisibleProjects()
-  }
-
-  $scope.clearSearchText = function () {
-    $scope.searchText.value = ''
-    $scope.filter = 'all'
-    $scope.$emit('search:clear')
-    $scope.updateVisibleProjects()
-  }
-
-  $scope.setFilter = function (filter) {
-    $scope.filter = filter
-    $scope.updateVisibleProjects()
-  }
-
-  $scope.updateSelectedProjects = function () {
-    $scope.selectedProjects = $scope.projects.filter(
-      project => project.selected
-    )
-  }
-
-  $scope.getSelectedProjects = () => $scope.selectedProjects
-
-  $scope.getSelectedProjectIds = () =>
-    $scope.selectedProjects.map(project => project.id)
+    $scope.searchProjects = function () {
+      eventTracking.send(
+        'project-list-page-interaction',
+        'project-search',
+        'keydown'
+      )
+      $scope.updateVisibleProjects()
+    }
 
 
-  $scope.getFirstSelectedProject = () => $scope.selectedProjects[0]
+    $scope.clearSearchText = function () {
+      $scope.searchText.value = ''
+      $scope.filter = 'all'
+      $scope.$emit('search:clear')
+      $scope.updateVisibleProjects()
+    }
 
 
-  $scope.hasLeavableProjectsSelected = () =>
-    _.some(
-      $scope.getSelectedProjects(),
-      project => project.accessLevel !== 'owner' && project.trashed
-    )
+    $scope.setFilter = function (filter) {
+      $scope.filter = filter
+      $scope.updateVisibleProjects()
+    }
 
 
-  $scope.hasDeletableProjectsSelected = () =>
-    _.some(
-      $scope.getSelectedProjects(),
-      project => project.accessLevel === 'owner' && project.trashed
-    )
+    $scope.updateSelectedProjects = function () {
+      $scope.selectedProjects = $scope.projects.filter(
+        project => project.selected
+      )
+    }
+
+    $scope.getSelectedProjects = () => $scope.selectedProjects
+
+    $scope.getSelectedProjectIds = () =>
+      $scope.selectedProjects.map(project => project.id)
+
+    $scope.getFirstSelectedProject = () => $scope.selectedProjects[0]
+
+    $scope.hasLeavableProjectsSelected = () =>
+      _.some(
+        $scope.getSelectedProjects(),
+        project => project.accessLevel !== 'owner' && project.trashed
+      )
+
+    $scope.hasDeletableProjectsSelected = () =>
+      _.some(
+        $scope.getSelectedProjects(),
+        project => project.accessLevel === 'owner' && project.trashed
+      )
+
+    $scope.updateVisibleProjects = function () {
+      $scope.visibleProjects = []
+      const selectedTag = $scope.getSelectedTag()
+      for (const project of $scope.projects) {
+        let visible = true
+        // Only show if it matches any search text
+        if ($scope.searchText.value !== '') {
+          if (
+            project.name
+              .toLowerCase()
+              .indexOf($scope.searchText.value.toLowerCase()) === -1
+          ) {
+            visible = false
+          }
+        }
+        // Only show if it matches the selected tag
+        if (
+          $scope.filter === 'tag' &&
+          selectedTag != null &&
+          !selectedTag.project_ids.includes(project.id)
+        ) {
+          visible = false
+        }
 
 
-  $scope.updateVisibleProjects = function () {
-    $scope.visibleProjects = []
-    const selectedTag = $scope.getSelectedTag()
-    for (const project of $scope.projects) {
-      let visible = true
-      // Only show if it matches any search text
-      if ($scope.searchText.value !== '') {
+        // Hide tagged projects if we only want to see the uncategorized ones
         if (
         if (
-          project.name
-            .toLowerCase()
-            .indexOf($scope.searchText.value.toLowerCase()) === -1
+          $scope.filter === 'untagged' &&
+          (project.tags != null ? project.tags.length : undefined) > 0
         ) {
         ) {
           visible = false
           visible = false
         }
         }
-      }
-      // Only show if it matches the selected tag
-      if (
-        $scope.filter === 'tag' &&
-        selectedTag != null &&
-        !selectedTag.project_ids.includes(project.id)
-      ) {
-        visible = false
-      }
 
 
-      // Hide tagged projects if we only want to see the uncategorized ones
-      if (
-        $scope.filter === 'untagged' &&
-        (project.tags != null ? project.tags.length : undefined) > 0
-      ) {
-        visible = false
-      }
+        // Hide projects we own if we only want to see shared projects
+        if ($scope.filter === 'shared' && project.accessLevel === 'owner') {
+          visible = false
+        }
 
 
-      // Hide projects we own if we only want to see shared projects
-      if ($scope.filter === 'shared' && project.accessLevel === 'owner') {
-        visible = false
-      }
+        // Hide projects we don't own if we only want to see owned projects
+        if ($scope.filter === 'owned' && project.accessLevel !== 'owner') {
+          visible = false
+        }
 
 
-      // Hide projects we don't own if we only want to see owned projects
-      if ($scope.filter === 'owned' && project.accessLevel !== 'owner') {
-        visible = false
-      }
+        if ($scope.filter === 'archived') {
+          // Only show archived projects
+          if (!project.archived) {
+            visible = false
+          }
+        } else {
+          // Only show non-archived projects
+          if (project.archived) {
+            visible = false
+          }
+        }
 
 
-      if ($scope.filter === 'archived') {
-        // Only show archived projects
-        if (!project.archived) {
-          visible = false
+        if ($scope.filter === 'trashed') {
+          // Only show trashed projects
+          if (!project.trashed) {
+            visible = false
+          }
+        } else {
+          // Only show non-trashed projects
+          if (project.trashed) {
+            visible = false
+          }
         }
         }
-      } else {
-        // Only show non-archived projects
-        if (project.archived) {
-          visible = false
+
+        if (visible) {
+          $scope.visibleProjects.push(project)
+        } else {
+          // We don't want hidden selections
+          project.selected = false
         }
         }
       }
       }
 
 
-      if ($scope.filter === 'trashed') {
-        // Only show trashed projects
-        if (!project.trashed) {
-          visible = false
+      localStorage(
+        'project_list',
+        JSON.stringify({
+          filter: $scope.filter,
+          selectedTagId: selectedTag != null ? selectedTag._id : undefined,
+        })
+      )
+      $scope.updateSelectedProjects()
+    }
+
+    $scope.getSelectedTag = function () {
+      for (const tag of $scope.tags) {
+        if (tag.selected) {
+          return tag
         }
         }
-      } else {
-        // Only show non-trashed projects
-        if (project.trashed) {
-          visible = false
+      }
+      return null
+    }
+
+    $scope._removeProjectIdsFromTagArray = function (tag, removeProjectIds) {
+      // Remove project_id from tag.project_ids
+      const remainingProjectIds = []
+      const removedProjectIds = []
+      for (const projectId of tag.project_ids) {
+        if (!removeProjectIds.includes(projectId)) {
+          remainingProjectIds.push(projectId)
+        } else {
+          removedProjectIds.push(projectId)
         }
         }
       }
       }
+      tag.project_ids = remainingProjectIds
+      return removedProjectIds
+    }
 
 
-      if (visible) {
-        $scope.visibleProjects.push(project)
-      } else {
-        // We don't want hidden selections
-        project.selected = false
+    $scope._removeProjectFromList = function (project) {
+      const index = $scope.projects.indexOf(project)
+      if (index > -1) {
+        $scope.projects.splice(index, 1)
       }
       }
     }
     }
 
 
-    localStorage(
-      'project_list',
-      JSON.stringify({
-        filter: $scope.filter,
-        selectedTagId: selectedTag != null ? selectedTag._id : undefined,
-      })
-    )
-    $scope.updateSelectedProjects()
-  }
+    $scope.removeSelectedProjectsFromTag = function (tag) {
+      tag.showWhenEmpty = true
 
 
-  $scope.getSelectedTag = function () {
-    for (const tag of $scope.tags) {
-      if (tag.selected) {
-        return tag
+      const selectedProjectIds = $scope.getSelectedProjectIds()
+      const selectedProjects = $scope.getSelectedProjects()
+
+      const removedProjectIds = $scope._removeProjectIdsFromTagArray(
+        tag,
+        selectedProjectIds
+      )
+
+      // Remove tag from project.tags
+      for (const project of selectedProjects) {
+        if (!project.tags) {
+          project.tags = []
+        }
+        const index = project.tags.indexOf(tag)
+        if (index > -1) {
+          project.tags.splice(index, 1)
+        }
       }
       }
-    }
-    return null
-  }
 
 
-  $scope._removeProjectIdsFromTagArray = function (tag, removeProjectIds) {
-    // Remove project_id from tag.project_ids
-    const remainingProjectIds = []
-    const removedProjectIds = []
-    for (const projectId of tag.project_ids) {
-      if (!removeProjectIds.includes(projectId)) {
-        remainingProjectIds.push(projectId)
-      } else {
-        removedProjectIds.push(projectId)
+      for (const projectId of removedProjectIds) {
+        queuedHttp({
+          method: 'DELETE',
+          url: `/tag/${tag._id}/project/${projectId}`,
+          headers: {
+            'X-CSRF-Token': window.csrfToken,
+          },
+        })
       }
       }
-    }
-    tag.project_ids = remainingProjectIds
-    return removedProjectIds
-  }
 
 
-  $scope._removeProjectFromList = function (project) {
-    const index = $scope.projects.indexOf(project)
-    if (index > -1) {
-      $scope.projects.splice(index, 1)
+      // If we're filtering by this tag then we need to remove
+      // the projects from view
+      $scope.updateVisibleProjects()
     }
     }
-  }
 
 
-  $scope.removeSelectedProjectsFromTag = function (tag) {
-    tag.showWhenEmpty = true
+    $scope.removeProjectFromTag = function (project, tag) {
+      tag.showWhenEmpty = true
 
 
-    const selectedProjectIds = $scope.getSelectedProjectIds()
-    const selectedProjects = $scope.getSelectedProjects()
-
-    const removedProjectIds = $scope._removeProjectIdsFromTagArray(
-      tag,
-      selectedProjectIds
-    )
-
-    // Remove tag from project.tags
-    for (const project of selectedProjects) {
       if (!project.tags) {
       if (!project.tags) {
         project.tags = []
         project.tags = []
       }
       }
       const index = project.tags.indexOf(tag)
       const index = project.tags.indexOf(tag)
+
       if (index > -1) {
       if (index > -1) {
+        $scope._removeProjectIdsFromTagArray(tag, [project.id])
         project.tags.splice(index, 1)
         project.tags.splice(index, 1)
+        queuedHttp({
+          method: 'DELETE',
+          url: `/tag/${tag._id}/project/${project.id}`,
+          headers: {
+            'X-CSRF-Token': window.csrfToken,
+          },
+        })
+        $scope.updateVisibleProjects()
       }
       }
     }
     }
 
 
-    for (const projectId of removedProjectIds) {
-      queuedHttp({
-        method: 'DELETE',
-        url: `/tag/${tag._id}/project/${projectId}`,
-        headers: {
-          'X-CSRF-Token': window.csrfToken,
-        },
-      })
-    }
-
-    // If we're filtering by this tag then we need to remove
-    // the projects from view
-    $scope.updateVisibleProjects()
-  }
-
-  $scope.removeProjectFromTag = function (project, tag) {
-    tag.showWhenEmpty = true
-
-    if (!project.tags) {
-      project.tags = []
-    }
-    const index = project.tags.indexOf(tag)
-
-    if (index > -1) {
-      $scope._removeProjectIdsFromTagArray(tag, [project.id])
-      project.tags.splice(index, 1)
-      queuedHttp({
-        method: 'DELETE',
-        url: `/tag/${tag._id}/project/${project.id}`,
-        headers: {
-          'X-CSRF-Token': window.csrfToken,
-        },
-      })
-      $scope.updateVisibleProjects()
-    }
-  }
-
-  $scope.addSelectedProjectsToTag = function (tag) {
-    const selectedProjects = $scope.getSelectedProjects()
-    eventTracking.send(
-      'project-list-page-interaction',
-      'project action',
-      'addSelectedProjectsToTag'
-    )
-
-    // Add project_ids into tag.project_ids
-    const addedProjectIds = []
-    for (const projectId of $scope.getSelectedProjectIds()) {
-      if (!tag.project_ids.includes(projectId)) {
-        tag.project_ids.push(projectId)
-        addedProjectIds.push(projectId)
+    $scope.addSelectedProjectsToTag = function (tag) {
+      const selectedProjects = $scope.getSelectedProjects()
+      eventTracking.send(
+        'project-list-page-interaction',
+        'project action',
+        'addSelectedProjectsToTag'
+      )
+
+      // Add project_ids into tag.project_ids
+      const addedProjectIds = []
+      for (const projectId of $scope.getSelectedProjectIds()) {
+        if (!tag.project_ids.includes(projectId)) {
+          tag.project_ids.push(projectId)
+          addedProjectIds.push(projectId)
+        }
       }
       }
-    }
 
 
-    // Add tag into each project.tags
-    for (const project of selectedProjects) {
-      if (!project.tags) {
-        project.tags = []
+      // Add tag into each project.tags
+      for (const project of selectedProjects) {
+        if (!project.tags) {
+          project.tags = []
+        }
+        if (!project.tags.includes(tag)) {
+          project.tags.push(tag)
+        }
       }
       }
-      if (!project.tags.includes(tag)) {
-        project.tags.push(tag)
+
+      for (const projectId of addedProjectIds) {
+        queuedHttp.post(`/tag/${tag._id}/project/${projectId}`, {
+          _csrf: window.csrfToken,
+        })
       }
       }
     }
     }
 
 
-    for (const projectId of addedProjectIds) {
-      queuedHttp.post(`/tag/${tag._id}/project/${projectId}`, {
-        _csrf: window.csrfToken,
+    $scope.openNewTagModal = function (e) {
+      const modalInstance = $modal.open({
+        templateUrl: 'newTagModalTemplate',
+        controller: 'NewTagModalController',
       })
       })
-    }
-  }
 
 
-  $scope.openNewTagModal = function (e) {
-    const modalInstance = $modal.open({
-      templateUrl: 'newTagModalTemplate',
-      controller: 'NewTagModalController',
-    })
+      modalInstance.result.then(function (tag) {
+        const tagIsDuplicate = $scope.tags.find(function (existingTag) {
+          return tag.name === existingTag.name
+        })
 
 
-    modalInstance.result.then(function (tag) {
-      const tagIsDuplicate = $scope.tags.find(function (existingTag) {
-        return tag.name === existingTag.name
+        if (!tagIsDuplicate) {
+          $scope.tags.push(tag)
+          $scope.addSelectedProjectsToTag(tag)
+        }
       })
       })
+    }
 
 
-      if (!tagIsDuplicate) {
-        $scope.tags.push(tag)
-        $scope.addSelectedProjectsToTag(tag)
+    $scope.createProject = function (name, template) {
+      if (template == null) {
+        template = 'none'
       }
       }
-    })
-  }
-
-  $scope.createProject = function (name, template) {
-    if (template == null) {
-      template = 'none'
-    }
-    return queuedHttp
-      .post('/project/new', {
-        _csrf: window.csrfToken,
-        projectName: name,
-        template,
-      })
-      .then(function (response) {
-        const { data } = response
-        $scope.projects.push({
-          name,
-          id: data.project_id,
-          accessLevel: 'owner',
-          owner: data.owner,
-          // TODO: Check access level if correct after adding it in
-          // to the rest of the app
+      return queuedHttp
+        .post('/project/new', {
+          _csrf: window.csrfToken,
+          projectName: name,
+          template,
         })
         })
-        $scope.updateVisibleProjects()
-      })
-  }
+        .then(function (response) {
+          const { data } = response
+          $scope.projects.push({
+            name,
+            id: data.project_id,
+            accessLevel: 'owner',
+            owner: data.owner,
+            // TODO: Check access level if correct after adding it in
+            // to the rest of the app
+          })
+          $scope.updateVisibleProjects()
+        })
+    }
 
 
-  $scope.openCreateProjectModal = function (template) {
-    if (template == null) {
-      template = 'none'
-    }
-    eventTracking.send('project-list-page-interaction', 'new-project', template)
-    const modalInstance = $modal.open({
-      templateUrl: 'newProjectModalTemplate',
-      controller: 'NewProjectModalController',
-      resolve: {
-        template() {
-          return template
+    $scope.openCreateProjectModal = function (template) {
+      if (template == null) {
+        template = 'none'
+      }
+      eventTracking.send(
+        'project-list-page-interaction',
+        'new-project',
+        template
+      )
+      const modalInstance = $modal.open({
+        templateUrl: 'newProjectModalTemplate',
+        controller: 'NewProjectModalController',
+        resolve: {
+          template() {
+            return template
+          },
         },
         },
-      },
-      scope: $scope,
-    })
-
-    modalInstance.result.then(
-      projectId => (window.location = `/project/${projectId}`)
-    )
-  }
-
-  $scope.renameProject = (project, newName) =>
-    queuedHttp
-      .post(`/project/${project.id}/rename`, {
-        newProjectName: newName,
-        _csrf: window.csrfToken,
+        scope: $scope,
       })
       })
-      .then(() => (project.name = newName))
 
 
-  $scope.openRenameProjectModal = function () {
-    const project = $scope.getFirstSelectedProject()
-    if (!project || project.accessLevel !== 'owner') {
-      return
+      modalInstance.result.then(
+        projectId => (window.location = `/project/${projectId}`)
+      )
     }
     }
-    eventTracking.send(
-      'project-list-page-interaction',
-      'project action',
-      'Rename'
-    )
-    $modal.open({
-      templateUrl: 'renameProjectModalTemplate',
-      controller: 'RenameProjectModalController',
-      resolve: {
-        project() {
-          return project
-        },
-      },
-      scope: $scope,
-    })
-  }
 
 
-  $scope.cloneProject = function (project, cloneName) {
-    eventTracking.send(
-      'project-list-page-interaction',
-      'project action',
-      'Clone'
-    )
-    return queuedHttp
-      .post(`/project/${project.id}/clone`, {
-        _csrf: window.csrfToken,
-        projectName: cloneName,
-      })
-      .then(function (response) {
-        const { data } = response
-        $scope.projects.push({
-          name: data.name,
-          id: data.project_id,
-          accessLevel: 'owner',
-          owner: data.owner,
-          // TODO: Check access level if correct after adding it in
-          // to the rest of the app
+    $scope.renameProject = (project, newName) =>
+      queuedHttp
+        .post(`/project/${project.id}/rename`, {
+          newProjectName: newName,
+          _csrf: window.csrfToken,
         })
         })
-        $scope.updateVisibleProjects()
+        .then(() => (project.name = newName))
+
+    $scope.openRenameProjectModal = function () {
+      const project = $scope.getFirstSelectedProject()
+      if (!project || project.accessLevel !== 'owner') {
+        return
+      }
+      eventTracking.send(
+        'project-list-page-interaction',
+        'project action',
+        'Rename'
+      )
+      $modal.open({
+        templateUrl: 'renameProjectModalTemplate',
+        controller: 'RenameProjectModalController',
+        resolve: {
+          project() {
+            return project
+          },
+        },
+        scope: $scope,
       })
       })
-  }
+    }
 
 
-  $scope.openCloneProjectModal = function (project) {
-    if (!project) {
-      return
+    $scope.cloneProject = function (project, cloneName) {
+      eventTracking.send(
+        'project-list-page-interaction',
+        'project action',
+        'Clone'
+      )
+      return queuedHttp
+        .post(`/project/${project.id}/clone`, {
+          _csrf: window.csrfToken,
+          projectName: cloneName,
+        })
+        .then(function (response) {
+          const { data } = response
+          $scope.projects.push({
+            name: data.name,
+            id: data.project_id,
+            accessLevel: 'owner',
+            owner: data.owner,
+            // TODO: Check access level if correct after adding it in
+            // to the rest of the app
+          })
+          $scope.updateVisibleProjects()
+        })
     }
     }
 
 
-    $modal.open({
-      templateUrl: 'cloneProjectModalTemplate',
-      controller: 'CloneProjectModalController',
-      resolve: {
-        project() {
-          return project
-        },
-      },
-      scope: $scope,
-    })
-  }
+    $scope.openCloneProjectModal = function (project) {
+      if (!project) {
+        return
+      }
 
 
-  // Methods to create modals for archiving, trashing, leaving and deleting projects
-  const _createArchiveTrashLeaveOrDeleteProjectsModal = function (
-    action,
-    projects
-  ) {
-    eventTracking.send(
-      'project-list-page-interaction',
-      'project action',
-      action
-    )
-    return $modal.open({
-      templateUrl: 'archiveTrashLeaveOrDeleteProjectsModalTemplate',
-      controller: 'ArchiveTrashLeaveOrDeleteProjectsModalController',
-      resolve: {
-        projects() {
-          return projects
+      $modal.open({
+        templateUrl: 'cloneProjectModalTemplate',
+        controller: 'CloneProjectModalController',
+        resolve: {
+          project() {
+            return project
+          },
         },
         },
-        action() {
-          return action
-        },
-      },
-    })
-  }
+        scope: $scope,
+      })
+    }
 
 
-  $scope.createArchiveProjectsModal = function (projects) {
-    return _createArchiveTrashLeaveOrDeleteProjectsModal('archive', projects)
-  }
+    // Methods to create modals for archiving, trashing, leaving and deleting projects
+    const _createArchiveTrashLeaveOrDeleteProjectsModal = function (
+      action,
+      projects
+    ) {
+      eventTracking.send(
+        'project-list-page-interaction',
+        'project action',
+        action
+      )
+      return $modal.open({
+        templateUrl: 'archiveTrashLeaveOrDeleteProjectsModalTemplate',
+        controller: 'ArchiveTrashLeaveOrDeleteProjectsModalController',
+        resolve: {
+          projects() {
+            return projects
+          },
+          action() {
+            return action
+          },
+        },
+      })
+    }
 
 
-  $scope.createTrashProjectsModal = function (projects) {
-    return _createArchiveTrashLeaveOrDeleteProjectsModal('trash', projects)
-  }
+    $scope.createArchiveProjectsModal = function (projects) {
+      return _createArchiveTrashLeaveOrDeleteProjectsModal('archive', projects)
+    }
 
 
-  $scope.createLeaveProjectsModal = function (projects) {
-    return _createArchiveTrashLeaveOrDeleteProjectsModal('leave', projects)
-  }
+    $scope.createTrashProjectsModal = function (projects) {
+      return _createArchiveTrashLeaveOrDeleteProjectsModal('trash', projects)
+    }
 
 
-  $scope.createDeleteProjectsModal = function (projects) {
-    return _createArchiveTrashLeaveOrDeleteProjectsModal('delete', projects)
-  }
+    $scope.createLeaveProjectsModal = function (projects) {
+      return _createArchiveTrashLeaveOrDeleteProjectsModal('leave', projects)
+    }
 
 
-  $scope.createLeaveOrDeleteProjectsModal = function (projects) {
-    return _createArchiveTrashLeaveOrDeleteProjectsModal(
-      'leaveOrDelete',
-      projects
-    )
-  }
+    $scope.createDeleteProjectsModal = function (projects) {
+      return _createArchiveTrashLeaveOrDeleteProjectsModal('delete', projects)
+    }
 
 
-  //
-  $scope.openArchiveProjectsModal = function () {
-    const modalInstance = $scope.createArchiveProjectsModal(
-      $scope.getSelectedProjects()
-    )
-    modalInstance.result.then(() => $scope.archiveSelectedProjects())
-  }
+    $scope.createLeaveOrDeleteProjectsModal = function (projects) {
+      return _createArchiveTrashLeaveOrDeleteProjectsModal(
+        'leaveOrDelete',
+        projects
+      )
+    }
 
 
-  $scope.openTrashProjectsModal = function () {
-    const modalInstance = $scope.createTrashProjectsModal(
-      $scope.getSelectedProjects()
-    )
+    //
+    $scope.openArchiveProjectsModal = function () {
+      const modalInstance = $scope.createArchiveProjectsModal(
+        $scope.getSelectedProjects()
+      )
+      modalInstance.result.then(() => $scope.archiveSelectedProjects())
+    }
 
 
-    modalInstance.result.then(() => $scope.trashSelectedProjects())
-  }
+    $scope.openTrashProjectsModal = function () {
+      const modalInstance = $scope.createTrashProjectsModal(
+        $scope.getSelectedProjects()
+      )
 
 
-  $scope.openLeaveProjectsModal = function () {
-    const modalInstance = $scope.createLeaveProjectsModal(
-      $scope.getSelectedProjects()
-    )
-    modalInstance.result.then(() => $scope.leaveSelectedProjects())
-  }
+      modalInstance.result.then(() => $scope.trashSelectedProjects())
+    }
 
 
-  $scope.openDeleteProjectsModal = function () {
-    const modalInstance = $scope.createDeleteProjectsModal(
-      $scope.getSelectedProjects()
-    )
-    modalInstance.result.then(() => $scope.deleteSelectedProjects())
-  }
+    $scope.openLeaveProjectsModal = function () {
+      const modalInstance = $scope.createLeaveProjectsModal(
+        $scope.getSelectedProjects()
+      )
+      modalInstance.result.then(() => $scope.leaveSelectedProjects())
+    }
 
 
-  $scope.openLeaveOrDeleteProjectsModal = function () {
-    const modalInstance = $scope.createLeaveOrDeleteProjectsModal(
-      $scope.getSelectedProjects()
-    )
-    modalInstance.result.then(() => $scope.leaveOrDeleteSelectedProjects())
-  }
+    $scope.openDeleteProjectsModal = function () {
+      const modalInstance = $scope.createDeleteProjectsModal(
+        $scope.getSelectedProjects()
+      )
+      modalInstance.result.then(() => $scope.deleteSelectedProjects())
+    }
 
 
-  //
-  $scope.archiveSelectedProjects = () =>
-    $scope.archiveProjects($scope.getSelectedProjects())
+    $scope.openLeaveOrDeleteProjectsModal = function () {
+      const modalInstance = $scope.createLeaveOrDeleteProjectsModal(
+        $scope.getSelectedProjects()
+      )
+      modalInstance.result.then(() => $scope.leaveOrDeleteSelectedProjects())
+    }
 
 
-  $scope.unarchiveSelectedProjects = () =>
-    $scope.unarchiveProjects($scope.getSelectedProjects())
+    //
+    $scope.archiveSelectedProjects = () =>
+      $scope.archiveProjects($scope.getSelectedProjects())
 
 
-  $scope.trashSelectedProjects = () =>
-    $scope.trashProjects($scope.getSelectedProjects())
+    $scope.unarchiveSelectedProjects = () =>
+      $scope.unarchiveProjects($scope.getSelectedProjects())
 
 
-  $scope.untrashSelectedProjects = () =>
-    $scope.untrashProjects($scope.getSelectedProjects())
+    $scope.trashSelectedProjects = () =>
+      $scope.trashProjects($scope.getSelectedProjects())
 
 
-  $scope.leaveSelectedProjects = () =>
-    $scope.leaveProjects($scope.getSelectedProjects())
+    $scope.untrashSelectedProjects = () =>
+      $scope.untrashProjects($scope.getSelectedProjects())
 
 
-  $scope.deleteSelectedProjects = () =>
-    $scope.deleteProjects($scope.getSelectedProjects())
+    $scope.leaveSelectedProjects = () =>
+      $scope.leaveProjects($scope.getSelectedProjects())
 
 
-  $scope.leaveOrDeleteSelectedProjects = () =>
-    $scope.leaveOrDeleteProjects($scope.getSelectedProjects())
+    $scope.deleteSelectedProjects = () =>
+      $scope.deleteProjects($scope.getSelectedProjects())
 
 
-  //
-  $scope.archiveProjects = function (projects) {
-    for (const project of projects) {
-      project.archived = true
-      project.trashed = false
-      _archiveProject(project)
-    }
-    $scope.updateVisibleProjects()
-  }
+    $scope.leaveOrDeleteSelectedProjects = () =>
+      $scope.leaveOrDeleteProjects($scope.getSelectedProjects())
 
 
-  $scope.unarchiveProjects = function (projects) {
-    for (const project of projects) {
-      project.archived = false
-      _unarchiveProject(project)
+    //
+    $scope.archiveProjects = function (projects) {
+      for (const project of projects) {
+        project.archived = true
+        project.trashed = false
+        _archiveProject(project)
+      }
+      $scope.updateVisibleProjects()
     }
     }
-    $scope.updateVisibleProjects()
-  }
 
 
-  $scope.trashProjects = function (projects) {
-    for (const project of projects) {
-      project.trashed = true
-      project.archived = false
-      _trashProject(project)
+    $scope.unarchiveProjects = function (projects) {
+      for (const project of projects) {
+        project.archived = false
+        _unarchiveProject(project)
+      }
+      $scope.updateVisibleProjects()
     }
     }
-    $scope.updateVisibleProjects()
-  }
 
 
-  $scope.untrashProjects = function (projects) {
-    for (const project of projects) {
-      project.trashed = false
-      _untrashProject(project)
+    $scope.trashProjects = function (projects) {
+      for (const project of projects) {
+        project.trashed = true
+        project.archived = false
+        _trashProject(project)
+      }
+      $scope.updateVisibleProjects()
     }
     }
-    $scope.updateVisibleProjects()
-  }
 
 
-  $scope.leaveProjects = function (projects) {
-    _deleteOrLeaveProjectsLocally(projects)
-    for (const project of projects) {
-      _leaveProject(project)
+    $scope.untrashProjects = function (projects) {
+      for (const project of projects) {
+        project.trashed = false
+        _untrashProject(project)
+      }
+      $scope.updateVisibleProjects()
     }
     }
-    $scope.updateVisibleProjects()
-  }
 
 
-  $scope.deleteProjects = function (projects) {
-    _deleteOrLeaveProjectsLocally(projects)
-    for (const project of projects) {
-      _deleteProject(project)
+    $scope.leaveProjects = function (projects) {
+      _deleteOrLeaveProjectsLocally(projects)
+      for (const project of projects) {
+        _leaveProject(project)
+      }
+      $scope.updateVisibleProjects()
     }
     }
-    $scope.updateVisibleProjects()
-  }
 
 
-  $scope.leaveOrDeleteProjects = function (projects) {
-    _deleteOrLeaveProjectsLocally(projects)
-    for (const project of projects) {
-      if (project.accessLevel === 'owner') {
+    $scope.deleteProjects = function (projects) {
+      _deleteOrLeaveProjectsLocally(projects)
+      for (const project of projects) {
         _deleteProject(project)
         _deleteProject(project)
-      } else {
-        _leaveProject(project)
       }
       }
+      $scope.updateVisibleProjects()
     }
     }
-    $scope.updateVisibleProjects()
-  }
 
 
-  // Actual interaction with the backend---we could move this into a service
-  const _archiveProject = function (project) {
-    return queuedHttp({
-      method: 'POST',
-      url: `/project/${project.id}/archive`,
-      headers: {
-        'X-CSRF-Token': window.csrfToken,
-      },
-    })
-  }
+    $scope.leaveOrDeleteProjects = function (projects) {
+      _deleteOrLeaveProjectsLocally(projects)
+      for (const project of projects) {
+        if (project.accessLevel === 'owner') {
+          _deleteProject(project)
+        } else {
+          _leaveProject(project)
+        }
+      }
+      $scope.updateVisibleProjects()
+    }
 
 
-  const _unarchiveProject = function (project) {
-    return queuedHttp({
-      method: 'DELETE',
-      url: `/project/${project.id}/archive`,
-      headers: {
-        'X-CSRF-Token': window.csrfToken,
-      },
-    })
-  }
+    // Actual interaction with the backend---we could move this into a service
+    const _archiveProject = function (project) {
+      return queuedHttp({
+        method: 'POST',
+        url: `/project/${project.id}/archive`,
+        headers: {
+          'X-CSRF-Token': window.csrfToken,
+        },
+      })
+    }
 
 
-  const _trashProject = function (project) {
-    return queuedHttp({
-      method: 'POST',
-      url: `/project/${project.id}/trash`,
-      headers: {
-        'X-CSRF-Token': window.csrfToken,
-      },
-    })
-  }
+    const _unarchiveProject = function (project) {
+      return queuedHttp({
+        method: 'DELETE',
+        url: `/project/${project.id}/archive`,
+        headers: {
+          'X-CSRF-Token': window.csrfToken,
+        },
+      })
+    }
 
 
-  const _untrashProject = function (project) {
-    return queuedHttp({
-      method: 'DELETE',
-      url: `/project/${project.id}/trash`,
-      headers: {
-        'X-CSRF-Token': window.csrfToken,
-      },
-    })
-  }
+    const _trashProject = function (project) {
+      return queuedHttp({
+        method: 'POST',
+        url: `/project/${project.id}/trash`,
+        headers: {
+          'X-CSRF-Token': window.csrfToken,
+        },
+      })
+    }
 
 
-  const _leaveProject = function (project) {
-    return queuedHttp({
-      method: 'POST',
-      url: `/project/${project.id}/leave`,
-      headers: {
-        'X-CSRF-Token': window.csrfToken,
-      },
-    })
-  }
+    const _untrashProject = function (project) {
+      return queuedHttp({
+        method: 'DELETE',
+        url: `/project/${project.id}/trash`,
+        headers: {
+          'X-CSRF-Token': window.csrfToken,
+        },
+      })
+    }
 
 
-  const _deleteProject = function (project) {
-    return queuedHttp({
-      method: 'DELETE',
-      url: `/project/${project.id}`,
-      headers: {
-        'X-CSRF-Token': window.csrfToken,
-      },
-    })
-  }
+    const _leaveProject = function (project) {
+      return queuedHttp({
+        method: 'POST',
+        url: `/project/${project.id}/leave`,
+        headers: {
+          'X-CSRF-Token': window.csrfToken,
+        },
+      })
+    }
 
 
-  const _deleteOrLeaveProjectsLocally = function (projects) {
-    const projectIds = projects.map(p => p.id)
-    for (const tag of $scope.tags || []) {
-      $scope._removeProjectIdsFromTagArray(tag, projectIds)
+    const _deleteProject = function (project) {
+      return queuedHttp({
+        method: 'DELETE',
+        url: `/project/${project.id}`,
+        headers: {
+          'X-CSRF-Token': window.csrfToken,
+        },
+      })
     }
     }
-    for (const project of projects || []) {
-      $scope._removeProjectFromList(project)
+
+    const _deleteOrLeaveProjectsLocally = function (projects) {
+      const projectIds = projects.map(p => p.id)
+      for (const tag of $scope.tags || []) {
+        $scope._removeProjectIdsFromTagArray(tag, projectIds)
+      }
+      for (const project of projects || []) {
+        $scope._removeProjectFromList(project)
+      }
     }
     }
-  }
 
 
-  $scope.getValueForCurrentPredicate = function (project) {
-    if ($scope.predicate === 'ownerName') {
-      return ProjectListService.getOwnerName(project)
-    } else {
-      return project[$scope.predicate]
+    $scope.getValueForCurrentPredicate = function (project) {
+      if ($scope.predicate === 'ownerName') {
+        return ProjectListService.getOwnerName(project)
+      } else {
+        return project[$scope.predicate]
+      }
     }
     }
-  }
 
 
-  $scope.openUploadProjectModal = function () {
-    $modal.open({
-      templateUrl: 'uploadProjectModalTemplate',
-      controller: 'UploadProjectModalController',
-    })
-  }
+    $scope.openUploadProjectModal = function () {
+      $modal.open({
+        templateUrl: 'uploadProjectModalTemplate',
+        controller: 'UploadProjectModalController',
+      })
+    }
 
 
-  $scope.downloadSelectedProjects = () =>
-    $scope.downloadProjectsById($scope.getSelectedProjectIds())
+    $scope.downloadSelectedProjects = () =>
+      $scope.downloadProjectsById($scope.getSelectedProjectIds())
 
 
-  $scope.downloadProjectsById = function (projectIds) {
-    let path
-    eventTracking.send(
-      'project-list-page-interaction',
-      'project action',
-      'Download Zip'
-    )
-    if (projectIds.length > 1) {
-      path = `/project/download/zip?project_ids=${projectIds.join(',')}`
-    } else {
-      path = `/project/${projectIds[0]}/download/zip`
+    $scope.downloadProjectsById = function (projectIds) {
+      let path
+      eventTracking.send(
+        'project-list-page-interaction',
+        'project action',
+        'Download Zip'
+      )
+      if (projectIds.length > 1) {
+        path = `/project/download/zip?project_ids=${projectIds.join(',')}`
+      } else {
+        path = `/project/${projectIds[0]}/download/zip`
+      }
+      return (window.location = path)
     }
     }
-    return (window.location = path)
-  }
 
 
-  const markTagAsSelected = id => {
-    for (const tag of $scope.tags) {
-      if (tag._id === id) {
-        tag.selected = true
-      } else {
-        tag.selected = false
+    const markTagAsSelected = id => {
+      for (const tag of $scope.tags) {
+        if (tag._id === id) {
+          tag.selected = true
+        } else {
+          tag.selected = false
+        }
       }
       }
     }
     }
-  }
 
 
-  const storedUIOpts = JSON.parse(localStorage('project_list'))
+    const storedUIOpts = JSON.parse(localStorage('project_list'))
 
 
-  if (storedUIOpts && storedUIOpts.filter) {
-    if (storedUIOpts.filter === 'tag' && storedUIOpts.selectedTagId) {
-      markTagAsSelected(storedUIOpts.selectedTagId)
+    if (storedUIOpts && storedUIOpts.filter) {
+      if (storedUIOpts.filter === 'tag' && storedUIOpts.selectedTagId) {
+        markTagAsSelected(storedUIOpts.selectedTagId)
+      }
+      $scope.setFilter(storedUIOpts.filter)
+    } else {
+      $scope.updateVisibleProjects()
     }
     }
-    $scope.setFilter(storedUIOpts.filter)
-  } else {
-    $scope.updateVisibleProjects()
   }
   }
-})
+)
 
 
 App.controller(
 App.controller(
   'ProjectListItemController',
   'ProjectListItemController',

+ 2 - 3
services/web/frontend/js/shared/context/compile-context.js

@@ -87,9 +87,8 @@ export function CompileProvider({ children }) {
   const [pdfViewer] = useScopeValue('settings.pdfViewer')
   const [pdfViewer] = useScopeValue('settings.pdfViewer')
 
 
   // the URL for downloading the PDF
   // the URL for downloading the PDF
-  const [pdfDownloadUrl, setPdfDownloadUrl] = useScopeValueSetterOnly(
-    'pdf.downloadUrl'
-  )
+  const [pdfDownloadUrl, setPdfDownloadUrl] =
+    useScopeValueSetterOnly('pdf.downloadUrl')
 
 
   // the URL for loading the PDF in the preview pane
   // the URL for loading the PDF in the preview pane
   const [pdfUrl, setPdfUrl] = useScopeValueSetterOnly('pdf.url')
   const [pdfUrl, setPdfUrl] = useScopeValueSetterOnly('pdf.url')

+ 2 - 3
services/web/frontend/js/shared/context/layout-context.js

@@ -66,9 +66,8 @@ export function LayoutProvider({ children }) {
   const [chatIsOpen, setChatIsOpen] = useScopeValue('ui.chatOpen')
   const [chatIsOpen, setChatIsOpen] = useScopeValue('ui.chatOpen')
 
 
   // whether the review pane is open
   // whether the review pane is open
-  const [reviewPanelOpen, setReviewPanelOpen] = useScopeValue(
-    'ui.reviewPanelOpen'
-  )
+  const [reviewPanelOpen, setReviewPanelOpen] =
+    useScopeValue('ui.reviewPanelOpen')
 
 
   // whether the menu pane is open
   // whether the menu pane is open
   const [leftMenuShown, setLeftMenuShown] = useScopeValue('ui.leftMenuShown')
   const [leftMenuShown, setLeftMenuShown] = useScopeValue('ui.leftMenuShown')

+ 2 - 6
services/web/frontend/js/shared/hooks/use-detach-action.js

@@ -10,12 +10,8 @@ export default function useDetachAction(
   senderRole,
   senderRole,
   targetRole
   targetRole
 ) {
 ) {
-  const {
-    role,
-    broadcastEvent,
-    addEventHandler,
-    deleteEventHandler,
-  } = useDetachContext()
+  const { role, broadcastEvent, addEventHandler, deleteEventHandler } =
+    useDetachContext()
 
 
   const eventName = `action-${actionName}`
   const eventName = `action-${actionName}`
 
 

+ 2 - 7
services/web/frontend/js/shared/hooks/use-detach-layout.js

@@ -11,13 +11,8 @@ const LINKING_TIMEOUT = 60000
 const RELINK_TIMEOUT = 10000
 const RELINK_TIMEOUT = 10000
 
 
 export default function useDetachLayout() {
 export default function useDetachLayout() {
-  const {
-    role,
-    setRole,
-    broadcastEvent,
-    addEventHandler,
-    deleteEventHandler,
-  } = useDetachContext()
+  const { role, setRole, broadcastEvent, addEventHandler, deleteEventHandler } =
+    useDetachContext()
 
 
   // isLinking: when the tab expects to be linked soon (e.g. on detach)
   // isLinking: when the tab expects to be linked soon (e.g. on detach)
   const [isLinking, setIsLinking] = useState(false)
   const [isLinking, setIsLinking] = useState(false)

+ 2 - 6
services/web/frontend/js/shared/hooks/use-detach-state.js

@@ -12,12 +12,8 @@ export default function useDetachState(
 ) {
 ) {
   const [value, setValue] = useState(defaultValue)
   const [value, setValue] = useState(defaultValue)
 
 
-  const {
-    role,
-    broadcastEvent,
-    addEventHandler,
-    deleteEventHandler,
-  } = useDetachContext()
+  const { role, broadcastEvent, addEventHandler, deleteEventHandler } =
+    useDetachContext()
 
 
   const eventName = `state-${key}`
   const eventName = `state-${key}`
 
 

+ 15 - 16
services/web/frontend/stories/modals/create-file/create-file-modal-decorator.js

@@ -98,23 +98,22 @@ export const mockCreateFileModalFetch = fetchMock =>
       return 204
       return 204
     })
     })
 
 
-export const createFileModalDecorator = (
-  contextProps = {},
-  createMode = 'doc'
+export const createFileModalDecorator =
+  (contextProps = {}, createMode = 'doc') =>
   // eslint-disable-next-line react/display-name
   // eslint-disable-next-line react/display-name
-) => Story => {
-  return (
-    <FileTreeContext {...defaultContextProps} {...contextProps}>
-      <FileTreeCreateNameProvider>
-        <FileTreeCreateFormProvider>
-          <OpenCreateFileModal createMode={createMode}>
-            <Story />
-          </OpenCreateFileModal>
-        </FileTreeCreateFormProvider>
-      </FileTreeCreateNameProvider>
-    </FileTreeContext>
-  )
-}
+  Story => {
+    return (
+      <FileTreeContext {...defaultContextProps} {...contextProps}>
+        <FileTreeCreateNameProvider>
+          <FileTreeCreateFormProvider>
+            <OpenCreateFileModal createMode={createMode}>
+              <Story />
+            </OpenCreateFileModal>
+          </FileTreeCreateFormProvider>
+        </FileTreeCreateNameProvider>
+      </FileTreeContext>
+    )
+  }
 
 
 function OpenCreateFileModal({ children, createMode }) {
 function OpenCreateFileModal({ children, createMode }) {
   const { startCreatingFile } = useFileTreeActionable()
   const { startCreatingFile } = useFileTreeActionable()

+ 1 - 2
services/web/migrations/20190912145032_create_users_indexes.js

@@ -48,8 +48,7 @@ const indexes = [
       'thirdPartyIdentifiers.externalUserId': 1,
       'thirdPartyIdentifiers.externalUserId': 1,
       'thirdPartyIdentifiers.providerId': 1,
       'thirdPartyIdentifiers.providerId': 1,
     },
     },
-    name:
-      'thirdPartyIdentifiers.externalUserId_1_thirdPartyIdentifiers.providerId_1',
+    name: 'thirdPartyIdentifiers.externalUserId_1_thirdPartyIdentifiers.providerId_1',
     sparse: true,
     sparse: true,
   },
   },
   {
   {

+ 8 - 4
services/web/modules/launchpad/test/unit/src/LaunchpadControllerTests.js

@@ -33,12 +33,16 @@ describe('LaunchpadController', function () {
       requires: {
       requires: {
         '@overleaf/settings': (this.Settings = {}),
         '@overleaf/settings': (this.Settings = {}),
         '@overleaf/metrics': (this.Metrics = {}),
         '@overleaf/metrics': (this.Metrics = {}),
-        '../../../../app/src/Features/User/UserRegistrationHandler': (this.UserRegistrationHandler = {}),
-        '../../../../app/src/Features/Email/EmailHandler': (this.EmailHandler = {}),
+        '../../../../app/src/Features/User/UserRegistrationHandler':
+          (this.UserRegistrationHandler = {}),
+        '../../../../app/src/Features/Email/EmailHandler': (this.EmailHandler =
+          {}),
         '../../../../app/src/Features/User/UserGetter': (this.UserGetter = {}),
         '../../../../app/src/Features/User/UserGetter': (this.UserGetter = {}),
         '../../../../app/src/models/User': { User: this.User },
         '../../../../app/src/models/User': { User: this.User },
-        '../../../../app/src/Features/Authentication/AuthenticationController': (this.AuthenticationController = {}),
-        '../../../../app/src/Features/Authentication/SessionManager': (this.SessionManager = {}),
+        '../../../../app/src/Features/Authentication/AuthenticationController':
+          (this.AuthenticationController = {}),
+        '../../../../app/src/Features/Authentication/SessionManager':
+          (this.SessionManager = {}),
       },
       },
     })
     })
 
 

+ 2 - 2
services/web/modules/user-activate/test/unit/src/UserActivateControllerTests.js

@@ -21,8 +21,8 @@ describe('UserActivateController', function () {
     this.UserActivateController = SandboxedModule.require(MODULE_PATH, {
     this.UserActivateController = SandboxedModule.require(MODULE_PATH, {
       requires: {
       requires: {
         '../../../../app/src/Features/User/UserGetter': this.UserGetter,
         '../../../../app/src/Features/User/UserGetter': this.UserGetter,
-        '../../../../app/src/Features/Errors/ErrorController': this
-          .ErrorController,
+        '../../../../app/src/Features/Errors/ErrorController':
+          this.ErrorController,
       },
       },
     })
     })
     this.req = {
     this.req = {

+ 15 - 3
services/web/package-lock.json

@@ -7644,6 +7644,12 @@
           "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
           "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
           "dev": true
           "dev": true
         },
         },
+        "prettier": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
+          "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
+          "dev": true
+        },
         "regenerator-runtime": {
         "regenerator-runtime": {
           "version": "0.13.7",
           "version": "0.13.7",
           "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
           "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
@@ -10606,6 +10612,12 @@
           "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
           "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
           "dev": true
           "dev": true
         },
         },
+        "prettier": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
+          "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
+          "dev": true
+        },
         "regenerator-runtime": {
         "regenerator-runtime": {
           "version": "0.13.7",
           "version": "0.13.7",
           "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
           "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
@@ -31176,9 +31188,9 @@
       "dev": true
       "dev": true
     },
     },
     "prettier": {
     "prettier": {
-      "version": "2.2.1",
-      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.2.1.tgz",
-      "integrity": "sha512-PqyhM2yCjg/oKkFPtTGUojv7gnZAoG80ttl45O6x2Ug/rMJw4wcc9k6aaf2hibP7BGVCCM33gZoGjyvt9mm16Q==",
+      "version": "2.5.1",
+      "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz",
+      "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==",
       "dev": true
       "dev": true
     },
     },
     "pretty-error": {
     "pretty-error": {

+ 1 - 1
services/web/package.json

@@ -270,7 +270,7 @@
     "optimize-css-assets-webpack-plugin": "^5.0.3",
     "optimize-css-assets-webpack-plugin": "^5.0.3",
     "pirates": "^4.0.1",
     "pirates": "^4.0.1",
     "postcss-loader": "^3.0.0",
     "postcss-loader": "^3.0.0",
-    "prettier": "^2.2.1",
+    "prettier": "^2.5.1",
     "requirejs": "^2.3.6",
     "requirejs": "^2.3.6",
     "samlp": "^3.4.1",
     "samlp": "^3.4.1",
     "sandboxed-module": "^2.0.4",
     "sandboxed-module": "^2.0.4",

+ 10 - 8
services/web/scripts/clear_institution_notifications.js

@@ -9,10 +9,11 @@ async function main() {
 
 
   console.log('Deleting notifications of institution', institutionId)
   console.log('Deleting notifications of institution', institutionId)
 
 
-  const preview = await InstitutionsManager.promises.clearInstitutionNotifications(
-    institutionId,
-    true
-  )
+  const preview =
+    await InstitutionsManager.promises.clearInstitutionNotifications(
+      institutionId,
+      true
+    )
   console.log('--- Preview ---')
   console.log('--- Preview ---')
   console.log(JSON.stringify(preview, null, 4))
   console.log(JSON.stringify(preview, null, 4))
   console.log('---------------')
   console.log('---------------')
@@ -25,10 +26,11 @@ async function main() {
   console.log('Exit in the next 10s in case these numbers are off.')
   console.log('Exit in the next 10s in case these numbers are off.')
   await sleep(10 * 1000)
   await sleep(10 * 1000)
 
 
-  const cleared = await InstitutionsManager.promises.clearInstitutionNotifications(
-    institutionId,
-    false
-  )
+  const cleared =
+    await InstitutionsManager.promises.clearInstitutionNotifications(
+      institutionId,
+      false
+    )
   console.log('--- Cleared ---')
   console.log('--- Cleared ---')
   console.log(JSON.stringify(cleared, null, 4))
   console.log(JSON.stringify(cleared, null, 4))
   console.log('---------------')
   console.log('---------------')

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

@@ -17,9 +17,8 @@ async function countFiles() {
       if (!project) {
       if (!project) {
         throw new Errors.NotFoundError('project not found')
         throw new Errors.NotFoundError('project not found')
       }
       }
-      const { files, docs } = ProjectEntityHandler.getAllEntitiesFromProject(
-        project
-      )
+      const { files, docs } =
+        ProjectEntityHandler.getAllEntitiesFromProject(project)
       console.error(
       console.error(
         projectId,
         projectId,
         files.length,
         files.length,

+ 2 - 2
services/web/scripts/delete-orphaned-docs/delete-orphaned-docs.js

@@ -9,8 +9,8 @@ const {
   ObjectId,
   ObjectId,
   waitForDb,
   waitForDb,
 } = require('../../app/src/infrastructure/mongodb')
 } = require('../../app/src/infrastructure/mongodb')
-const DocstoreManager = require('../../app/src/Features/Docstore/DocstoreManager')
-  .promises
+const DocstoreManager =
+  require('../../app/src/Features/Docstore/DocstoreManager').promises
 
 
 const argv = minimist(process.argv.slice(2))
 const argv = minimist(process.argv.slice(2))
 const commit = argv.commit !== undefined
 const commit = argv.commit !== undefined

+ 2 - 1
services/web/scripts/history/count_project_history_categories.js

@@ -30,7 +30,8 @@ const ID_WHEN_FULL_PROJECT_HISTORY_ENABLED = '5a8d8a370000000000000000'
 const OBJECT_ID_WHEN_FULL_PROJECT_HISTORY_ENABLED = new ObjectId(
 const OBJECT_ID_WHEN_FULL_PROJECT_HISTORY_ENABLED = new ObjectId(
   ID_WHEN_FULL_PROJECT_HISTORY_ENABLED
   ID_WHEN_FULL_PROJECT_HISTORY_ENABLED
 )
 )
-const DATETIME_WHEN_FULL_PROJECT_HISTORY_ENABLED = OBJECT_ID_WHEN_FULL_PROJECT_HISTORY_ENABLED.getTimestamp()
+const DATETIME_WHEN_FULL_PROJECT_HISTORY_ENABLED =
+  OBJECT_ID_WHEN_FULL_PROJECT_HISTORY_ENABLED.getTimestamp()
 
 
 async function processBatch(_, projects) {
 async function processBatch(_, projects) {
   await promiseMapWithLimit(WRITE_CONCURRENCY, projects, processProject)
   await promiseMapWithLimit(WRITE_CONCURRENCY, projects, processProject)

+ 2 - 1
services/web/scripts/history/upgrade_v1_without_conversion_if_created_after_fph_enabled.js

@@ -32,7 +32,8 @@ const ID_WHEN_FULL_PROJECT_HISTORY_ENABLED = '5a8d8a370000000000000000'
 const OBJECT_ID_WHEN_FULL_PROJECT_HISTORY_ENABLED = new ObjectId(
 const OBJECT_ID_WHEN_FULL_PROJECT_HISTORY_ENABLED = new ObjectId(
   ID_WHEN_FULL_PROJECT_HISTORY_ENABLED
   ID_WHEN_FULL_PROJECT_HISTORY_ENABLED
 )
 )
-const DATETIME_WHEN_FULL_PROJECT_HISTORY_ENABLED = OBJECT_ID_WHEN_FULL_PROJECT_HISTORY_ENABLED.getTimestamp()
+const DATETIME_WHEN_FULL_PROJECT_HISTORY_ENABLED =
+  OBJECT_ID_WHEN_FULL_PROJECT_HISTORY_ENABLED.getTimestamp()
 
 
 // set a default BATCH_LAST_ID at our cutoff point if none set
 // set a default BATCH_LAST_ID at our cutoff point if none set
 // we still check against this cut off point later, even if
 // we still check against this cut off point later, even if

+ 4 - 8
services/web/scripts/sync-user-entitlements/sync-user-entitlements.js

@@ -3,8 +3,8 @@
 const fs = require('fs')
 const fs = require('fs')
 const minimist = require('minimist')
 const minimist = require('minimist')
 
 
-const InstitutionsAPI = require('../../app/src/Features/Institutions/InstitutionsAPI')
-  .promises
+const InstitutionsAPI =
+  require('../../app/src/Features/Institutions/InstitutionsAPI').promises
 
 
 const argv = minimist(process.argv.slice(2))
 const argv = minimist(process.argv.slice(2))
 const commit = argv.commit !== undefined
 const commit = argv.commit !== undefined
@@ -175,12 +175,8 @@ function loadCachedEntitlements(cachedEntitlementsFilename) {
 
 
   for (const cachedEntitlementLine of cachedEntitlementsData) {
   for (const cachedEntitlementLine of cachedEntitlementsData) {
     // this is safe because comma is not an allowed value for any column
     // this is safe because comma is not an allowed value for any column
-    const [
-      userId,
-      email,
-      hasEntitlement,
-      providerId,
-    ] = cachedEntitlementLine.split(',')
+    const [userId, email, hasEntitlement, providerId] =
+      cachedEntitlementLine.split(',')
     let hasEntitlementBoolean
     let hasEntitlementBoolean
     if (ignoreNulls) {
     if (ignoreNulls) {
       hasEntitlementBoolean = hasEntitlement === 't'
       hasEntitlementBoolean = hasEntitlement === 't'

+ 5 - 4
services/web/test/acceptance/src/ProjectInviteTests.js

@@ -563,10 +563,11 @@ describe('ProjectInviteTests', function () {
                           throw err
                           throw err
                         }
                         }
                         this.secondInvite = invite
                         this.secondInvite = invite
-                        this.secondLink = CollaboratorsEmailHandler._buildInviteUrl(
-                          this.fakeProject,
-                          invite
-                        )
+                        this.secondLink =
+                          CollaboratorsEmailHandler._buildInviteUrl(
+                            this.fakeProject,
+                            invite
+                          )
                         cb()
                         cb()
                       }
                       }
                     )
                     )

+ 7 - 6
services/web/test/acceptance/src/TagsTests.js

@@ -31,12 +31,13 @@ const _createTag = (user, name, callback) => {
 const _createTags = (user, tagNames, callback) => {
 const _createTags = (user, tagNames, callback) => {
   const tags = []
   const tags = []
   async.series(
   async.series(
-    tagNames.map(tagName => cb =>
-      _createTag(user, tagName, (err, response, body) => {
-        _expect200(err, response)
-        tags.push(body)
-        cb()
-      })
+    tagNames.map(
+      tagName => cb =>
+        _createTag(user, tagName, (err, response, body) => {
+          _expect200(err, response)
+          tags.push(body)
+          cb()
+        })
     ),
     ),
     err => {
     err => {
       callback(err, tags)
       callback(err, tags)

+ 4 - 4
services/web/test/acceptance/src/helpers/DeletedSubscription.js

@@ -1,10 +1,10 @@
 const { expect } = require('chai')
 const { expect } = require('chai')
 const MockSubscription = require('./Subscription')
 const MockSubscription = require('./Subscription')
 const SubscriptionUpdater = require('../../../../app/src/Features/Subscription/SubscriptionUpdater')
 const SubscriptionUpdater = require('../../../../app/src/Features/Subscription/SubscriptionUpdater')
-const SubscriptionModel = require('../../../../app/src/models/Subscription')
-  .Subscription
-const DeletedSubscriptionModel = require(`../../../../app/src/models/DeletedSubscription`)
-  .DeletedSubscription
+const SubscriptionModel =
+  require('../../../../app/src/models/Subscription').Subscription
+const DeletedSubscriptionModel =
+  require(`../../../../app/src/models/DeletedSubscription`).DeletedSubscription
 
 
 class DeletedSubscription {
 class DeletedSubscription {
   constructor(options = {}) {
   constructor(options = {}) {

+ 2 - 2
services/web/test/acceptance/src/helpers/Institution.js

@@ -1,6 +1,6 @@
 const { ObjectId } = require('mongodb')
 const { ObjectId } = require('mongodb')
-const InstitutionModel = require('../../../../app/src/models/Institution')
-  .Institution
+const InstitutionModel =
+  require('../../../../app/src/models/Institution').Institution
 
 
 let count = parseInt(Math.random() * 999999)
 let count = parseInt(Math.random() * 999999)
 
 

+ 4 - 4
services/web/test/acceptance/src/helpers/Subscription.js

@@ -1,10 +1,10 @@
 const { db, ObjectId } = require('../../../../app/src/infrastructure/mongodb')
 const { db, ObjectId } = require('../../../../app/src/infrastructure/mongodb')
 const { expect } = require('chai')
 const { expect } = require('chai')
 const SubscriptionUpdater = require('../../../../app/src/Features/Subscription/SubscriptionUpdater')
 const SubscriptionUpdater = require('../../../../app/src/Features/Subscription/SubscriptionUpdater')
-const SubscriptionModel = require('../../../../app/src/models/Subscription')
-  .Subscription
-const DeletedSubscriptionModel = require(`../../../../app/src/models/DeletedSubscription`)
-  .DeletedSubscription
+const SubscriptionModel =
+  require('../../../../app/src/models/Subscription').Subscription
+const DeletedSubscriptionModel =
+  require(`../../../../app/src/models/DeletedSubscription`).DeletedSubscription
 
 
 class Subscription {
 class Subscription {
   constructor(options = {}) {
   constructor(options = {}) {

+ 2 - 3
services/web/test/acceptance/src/helpers/UserHelper.js

@@ -178,9 +178,8 @@ class UserHelper {
 
 
     // hash password and delete plaintext if set
     // hash password and delete plaintext if set
     if (attributes.password) {
     if (attributes.password) {
-      attributes.hashedPassword = await AuthenticationManager.promises.hashPassword(
-        attributes.password
-      )
+      attributes.hashedPassword =
+        await AuthenticationManager.promises.hashPassword(attributes.password)
       delete attributes.password
       delete attributes.password
     }
     }
 
 

+ 8 - 3
services/web/test/frontend/bootstrap.js

@@ -91,12 +91,17 @@ globalThis.requestAnimationFrame = global.requestAnimationFrame =
 globalThis.sessionStorage = global.sessionStorage = window.sessionStorage
 globalThis.sessionStorage = global.sessionStorage = window.sessionStorage
 
 
 // add polyfill for ResizeObserver
 // add polyfill for ResizeObserver
-globalThis.ResizeObserver = global.ResizeObserver = window.ResizeObserver = require('@juggle/resize-observer').ResizeObserver
+globalThis.ResizeObserver =
+  global.ResizeObserver =
+  window.ResizeObserver =
+    require('@juggle/resize-observer').ResizeObserver
 
 
 // node-fetch doesn't accept relative URL's: https://github.com/node-fetch/node-fetch/blob/master/docs/v2-LIMITS.md#known-differences
 // node-fetch doesn't accept relative URL's: https://github.com/node-fetch/node-fetch/blob/master/docs/v2-LIMITS.md#known-differences
 const fetch = require('node-fetch')
 const fetch = require('node-fetch')
-globalThis.fetch = global.fetch = window.fetch = (url, ...options) =>
-  fetch(new URL(url, 'http://localhost'), ...options)
+globalThis.fetch =
+  global.fetch =
+  window.fetch =
+    (url, ...options) => fetch(new URL(url, 'http://localhost'), ...options)
 
 
 // ignore CSS files
 // ignore CSS files
 const { addHook } = require('pirates')
 const { addHook } = require('pirates')

Vissa filer visades inte eftersom för många filer har ändrats