[web] Upgrade Prettier to match version in monorepo root (#6231)
GitOrigin-RevId: 02f97af1b9704782eee77a0b7dfc477ada23e34d
This commit is contained in:
+8
-4
@@ -15,10 +15,14 @@ const Settings = require('@overleaf/settings')
|
||||
const logger = require('@overleaf/logger')
|
||||
const PlansLocator = require('./app/src/Features/Subscription/PlansLocator')
|
||||
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) {
|
||||
logger.initializeErrorReporting(Settings.sentry.dsn)
|
||||
}
|
||||
|
||||
@@ -80,10 +80,11 @@ async function getPrivilegeLevelForProjectWithUser(
|
||||
token,
|
||||
opts = {}
|
||||
) {
|
||||
const privilegeLevel = await CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
|
||||
userId,
|
||||
projectId
|
||||
)
|
||||
const privilegeLevel =
|
||||
await CollaboratorsGetter.promises.getMemberIdPrivilegeLevel(
|
||||
userId,
|
||||
projectId
|
||||
)
|
||||
if (privilegeLevel && privilegeLevel !== PrivilegeLevels.NONE) {
|
||||
// The user has direct access
|
||||
return privilegeLevel
|
||||
@@ -140,13 +141,11 @@ async function getPrivilegeLevelForProjectWithToken(projectId, token) {
|
||||
// Anonymous users can have read-only access to token-based projects,
|
||||
// while read-write access must be logged in,
|
||||
// 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) {
|
||||
// Grant anonymous user read-only access
|
||||
return PrivilegeLevels.READ_ONLY
|
||||
|
||||
@@ -29,11 +29,12 @@ async function blockRestrictedUserFromProject(req, res, next) {
|
||||
const projectId = _getProjectId(req)
|
||||
const userId = _getUserId(req)
|
||||
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) {
|
||||
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')
|
||||
if (otherParams.length > 0) {
|
||||
const canWrite = await AuthorizationManager.promises.canUserWriteProjectSettings(
|
||||
userId,
|
||||
projectId,
|
||||
token
|
||||
)
|
||||
const canWrite =
|
||||
await AuthorizationManager.promises.canUserWriteProjectSettings(
|
||||
userId,
|
||||
projectId,
|
||||
token
|
||||
)
|
||||
if (!canWrite) {
|
||||
return HttpErrorHandler.forbidden(req, res)
|
||||
}
|
||||
@@ -92,11 +94,12 @@ async function ensureUserCanWriteProjectContent(req, res, next) {
|
||||
const projectId = _getProjectId(req)
|
||||
const userId = _getUserId(req)
|
||||
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) {
|
||||
logger.log(
|
||||
{ userId, projectId },
|
||||
|
||||
@@ -50,8 +50,7 @@ module.exports = CaptchaMiddleware = {
|
||||
return res.status(400).send({
|
||||
errorReason: 'cannot_verify_user_not_robot',
|
||||
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 {
|
||||
|
||||
@@ -132,42 +132,37 @@ async function isUserInvitedMemberOfProject(userId, projectId) {
|
||||
|
||||
async function getProjectsUserIsMemberOf(userId, fields) {
|
||||
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 }
|
||||
}
|
||||
|
||||
async function getAllInvitedMembers(projectId) {
|
||||
try {
|
||||
const rawMembers = await getInvitedMembersWithPrivilegeLevels(projectId)
|
||||
const { members } = ProjectEditorHandler.buildOwnerAndMembersViews(
|
||||
rawMembers
|
||||
)
|
||||
const { members } =
|
||||
ProjectEditorHandler.buildOwnerAndMembersViews(rawMembers)
|
||||
return members
|
||||
} catch (err) {
|
||||
throw OError.tag(err, 'error getting members for project', { projectId })
|
||||
|
||||
@@ -78,14 +78,10 @@ async function removeUserFromProject(projectId, 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
|
||||
.concat(readOnly)
|
||||
.concat(tokenReadAndWrite)
|
||||
|
||||
@@ -38,9 +38,8 @@ const buildState = s =>
|
||||
|
||||
module.exports = ClsiStateManager = {
|
||||
computeHash(project, options) {
|
||||
const { docs, files } = ProjectEntityHandler.getAllEntitiesFromProject(
|
||||
project
|
||||
)
|
||||
const { docs, files } =
|
||||
ProjectEntityHandler.getAllEntitiesFromProject(project)
|
||||
const fileList = Array.from(files || []).map(
|
||||
f => `${f.file._id}:${f.file.rev}:${f.file.created}:${f.path}`
|
||||
)
|
||||
|
||||
@@ -60,11 +60,8 @@ async function joinProject(req, res, next) {
|
||||
userId = null
|
||||
}
|
||||
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) {
|
||||
return res.sendStatus(403)
|
||||
}
|
||||
@@ -116,15 +113,17 @@ async function _buildJoinProjectView(req, projectId, userId) {
|
||||
'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 privilegeLevel = await AuthorizationManager.promises.getPrivilegeLevelForProject(
|
||||
userId,
|
||||
projectId,
|
||||
token
|
||||
)
|
||||
const privilegeLevel =
|
||||
await AuthorizationManager.promises.getPrivilegeLevelForProject(
|
||||
userId,
|
||||
projectId,
|
||||
token
|
||||
)
|
||||
if (privilegeLevel == null || privilegeLevel === PrivilegeLevels.NONE) {
|
||||
return { project: null, privilegeLevel: null, isRestrictedUser: false }
|
||||
}
|
||||
|
||||
@@ -99,12 +99,8 @@ class SAMLSessionDataMissing extends BackwardCompatibleError {
|
||||
? arg.samlSession
|
||||
: {}
|
||||
this.tryAgain = true
|
||||
const {
|
||||
universityId,
|
||||
universityName,
|
||||
externalUserId,
|
||||
institutionEmail,
|
||||
} = samlSession
|
||||
const { universityId, universityName, externalUserId, institutionEmail } =
|
||||
samlSession
|
||||
|
||||
if (
|
||||
!universityId &&
|
||||
|
||||
@@ -27,261 +27,265 @@ let request = require('request')
|
||||
request = request.defaults()
|
||||
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
|
||||
)
|
||||
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)
|
||||
}
|
||||
),
|
||||
],
|
||||
user(cb) {
|
||||
return UserGetter.getUser(
|
||||
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,
|
||||
{ first_name: 1, last_name: 1, email: 1, overleaf: 1 },
|
||||
cb
|
||||
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 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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if (export_params.first_name && export_params.last_name) {
|
||||
user.first_name = export_params.first_name
|
||||
user.last_name = export_params.last_name
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
},
|
||||
|
||||
_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,
|
||||
},
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
historyVersion(cb) {
|
||||
return ProjectHistoryHandler.ensureHistoryExistsForProject(
|
||||
project_id,
|
||||
function (error) {
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
|
||||
_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)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
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)
|
||||
}
|
||||
return self._requestVersion(project_id, cb)
|
||||
}
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
if (export_params.first_name && export_params.last_name) {
|
||||
user.first_name = export_params.first_name
|
||||
user.last_name = export_params.last_name
|
||||
}
|
||||
|
||||
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)
|
||||
})
|
||||
},
|
||||
|
||||
_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,
|
||||
},
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
)
|
||||
},
|
||||
|
||||
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) {
|
||||
return typeof value !== 'undefined' && value !== null
|
||||
? transform(value)
|
||||
|
||||
@@ -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) {
|
||||
email = parseEmail(email)
|
||||
|
||||
@@ -78,16 +78,21 @@ module.exports = InactiveProjectManager = {
|
||||
if (err != null) {
|
||||
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(
|
||||
{ numberOfProjects: projects && projects.length },
|
||||
|
||||
@@ -125,11 +125,8 @@ async function checkInstitutionUsers(institutionId, emitNonProUserIds) {
|
||||
)
|
||||
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.current.notEntitled = currentNotEntitledCount
|
||||
|
||||
|
||||
@@ -168,10 +168,8 @@ function _checkAuth(projectId, data, currentUserId, callback) {
|
||||
|
||||
function _getFileStream(linkedFileData, userId, 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) => {
|
||||
if (err) {
|
||||
return callback(err)
|
||||
|
||||
@@ -357,9 +357,8 @@ const ProjectController = {
|
||||
if (err != null) {
|
||||
return next(err)
|
||||
}
|
||||
const { docs, files } = ProjectEntityHandler.getAllEntitiesFromProject(
|
||||
project
|
||||
)
|
||||
const { docs, files } =
|
||||
ProjectEntityHandler.getAllEntitiesFromProject(project)
|
||||
const entities = docs
|
||||
.concat(files)
|
||||
// Sort by path ascending
|
||||
@@ -553,9 +552,8 @@ const ProjectController = {
|
||||
delete req.session.saml
|
||||
}
|
||||
|
||||
const portalTemplates = ProjectController._buildPortalTemplatesList(
|
||||
userAffiliations
|
||||
)
|
||||
const portalTemplates =
|
||||
ProjectController._buildPortalTemplatesList(userAffiliations)
|
||||
const projects = ProjectController._buildProjectList(
|
||||
results.projects,
|
||||
userId
|
||||
@@ -798,9 +796,8 @@ const ProjectController = {
|
||||
req,
|
||||
projectId
|
||||
)
|
||||
const allowedImageNames = ProjectHelper.getAllowedImagesForUser(
|
||||
sessionUser
|
||||
)
|
||||
const allowedImageNames =
|
||||
ProjectHelper.getAllowedImagesForUser(sessionUser)
|
||||
|
||||
AuthorizationManager.getPrivilegeLevelForProject(
|
||||
userId,
|
||||
@@ -1028,13 +1025,8 @@ const ProjectController = {
|
||||
|
||||
_buildProjectList(allProjects, userId) {
|
||||
let project
|
||||
const {
|
||||
owned,
|
||||
readAndWrite,
|
||||
readOnly,
|
||||
tokenReadAndWrite,
|
||||
tokenReadOnly,
|
||||
} = allProjects
|
||||
const { owned, readAndWrite, readOnly, tokenReadAndWrite, tokenReadOnly } =
|
||||
allProjects
|
||||
const projects = []
|
||||
for (project of owned) {
|
||||
projects.push(
|
||||
|
||||
@@ -144,10 +144,8 @@ async function validateProjectName(name) {
|
||||
// with a unique name. But that requires thinking through how we would handle incoming projects from
|
||||
// dropbox for example.
|
||||
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: [....]}
|
||||
// collect all of the names and flatten them into a single array
|
||||
const projectNameList = _.pluck(
|
||||
@@ -241,6 +239,7 @@ async function _generateTokens(project, callback) {
|
||||
tokens.readAndWritePrefix = numericPrefix
|
||||
}
|
||||
if (tokens.readOnly == null) {
|
||||
tokens.readOnly = await TokenGenerator.promises.generateUniqueReadOnlyToken()
|
||||
tokens.readOnly =
|
||||
await TokenGenerator.promises.generateUniqueReadOnlyToken()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,11 +66,12 @@ async function duplicate(owner, originalProjectId, newProjectName) {
|
||||
_copyDocs(originalEntries.docEntries, 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.
|
||||
if (
|
||||
rootDocPath &&
|
||||
|
||||
@@ -62,9 +62,8 @@ module.exports = ProjectEditorHandler = {
|
||||
result.invites.forEach(invite => {
|
||||
delete invite.token
|
||||
})
|
||||
;({ owner, ownerFeatures, members } = this.buildOwnerAndMembersViews(
|
||||
members
|
||||
))
|
||||
;({ owner, ownerFeatures, members } =
|
||||
this.buildOwnerAndMembersViews(members))
|
||||
result.owner = owner
|
||||
result.members = members
|
||||
|
||||
|
||||
@@ -257,21 +257,18 @@ async function mkdirp(projectId, path, options = {}) {
|
||||
for (const folderName of folders) {
|
||||
builtUpPath += `/${folderName}`
|
||||
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
|
||||
} catch (err) {
|
||||
// Folder couldn't be found. Create it.
|
||||
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
|
||||
lastFolder = newFolder
|
||||
newFolders.push(newFolder)
|
||||
@@ -285,23 +282,19 @@ async function moveEntity(projectId, entityId, destFolderId, entityType) {
|
||||
projectId,
|
||||
{ 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)
|
||||
if (_blockedFilename(entityPath, entityType)) {
|
||||
throw new Errors.InvalidNameError('blocked element name')
|
||||
}
|
||||
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
|
||||
// location first, and then remove the original. If
|
||||
// there is an error the entity may appear twice. This
|
||||
@@ -328,10 +321,8 @@ async function moveEntity(projectId, entityId, destFolderId, entityType) {
|
||||
entityPath.mongo,
|
||||
entityId
|
||||
)
|
||||
const {
|
||||
docs: newDocs,
|
||||
files: newFiles,
|
||||
} = ProjectEntityHandler.getAllEntitiesFromProject(newProject)
|
||||
const { docs: newDocs, files: newFiles } =
|
||||
ProjectEntityHandler.getAllEntitiesFromProject(newProject)
|
||||
const startPath = entityPath.fileSystem
|
||||
const endPath = result.path.fileSystem
|
||||
const changes = {
|
||||
@@ -418,10 +409,8 @@ async function renameEntity(
|
||||
// check if the new name already exists in the current folder
|
||||
_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
|
||||
const newProject = await Project.findOneAndUpdate(
|
||||
@@ -430,10 +419,8 @@ async function renameEntity(
|
||||
{ new: true }
|
||||
).exec()
|
||||
|
||||
const {
|
||||
docs: newDocs,
|
||||
files: newFiles,
|
||||
} = ProjectEntityHandler.getAllEntitiesFromProject(newProject)
|
||||
const { docs: newDocs, files: newFiles } =
|
||||
ProjectEntityHandler.getAllEntitiesFromProject(newProject)
|
||||
return {
|
||||
project,
|
||||
startPath,
|
||||
@@ -618,14 +605,12 @@ async function _checkValidMove(
|
||||
entityPath,
|
||||
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
|
||||
// in the destination folder
|
||||
_checkValidElementName(destEntity, entity.name)
|
||||
|
||||
@@ -1349,11 +1349,8 @@ const ProjectEntityUpdateHandler = {
|
||||
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
|
||||
// files
|
||||
ProjectEntityUpdateHandler._checkFiletree(
|
||||
|
||||
@@ -166,8 +166,9 @@ module.exports = ReferencesHandler = {
|
||||
'flushing docs to mongo before calling references service'
|
||||
)
|
||||
return Async.series(
|
||||
docIds.map(docId => cb =>
|
||||
DocumentUpdaterHandler.flushDocToMongo(projectId, docId, cb)
|
||||
docIds.map(
|
||||
docId => cb =>
|
||||
DocumentUpdaterHandler.flushDocToMongo(projectId, docId, cb)
|
||||
),
|
||||
function (err) {
|
||||
// continue
|
||||
|
||||
@@ -41,10 +41,11 @@ async function _loadAssignmentInLocals(splitTest, session, locals) {
|
||||
if (cachedVariant) {
|
||||
LocalsHelper.setSplitTestVariant(locals, splitTest.name, cachedVariant)
|
||||
} else {
|
||||
const assignment = await SplitTestV2Handler.promises.getAssignmentForSession(
|
||||
session,
|
||||
splitTest.name
|
||||
)
|
||||
const assignment =
|
||||
await SplitTestV2Handler.promises.getAssignmentForSession(
|
||||
session,
|
||||
splitTest.name
|
||||
)
|
||||
session.cachedSplitTestAssignments[cacheKey] = assignment.variant
|
||||
LocalsHelper.setSplitTestVariant(
|
||||
locals,
|
||||
|
||||
@@ -138,12 +138,8 @@ async function _getAssignment(
|
||||
if (splitTest) {
|
||||
const currentVersion = splitTest.getCurrentVersion()
|
||||
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) {
|
||||
const assignmentConfig = {
|
||||
userId,
|
||||
|
||||
@@ -48,10 +48,8 @@ async function refreshFeatures(userId, reason) {
|
||||
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) {
|
||||
logger.log({ userId }, '[FeaturesUpdater] must unlink dropbox')
|
||||
const Modules = require('../../infrastructure/Modules')
|
||||
@@ -70,9 +68,8 @@ async function refreshFeatures(userId, reason) {
|
||||
async function computeFeatures(userId) {
|
||||
const individualFeatures = await _getIndividualFeatures(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 bonusFeatures = await ReferalFeatures.promises.getBonusFeatures(userId)
|
||||
const featuresOverrides = await _getFeaturesOverrides(userId)
|
||||
@@ -144,10 +141,8 @@ async function _getFeaturesOverrides(userId) {
|
||||
async function _getV1Features(userId) {
|
||||
let planCode, v1Id
|
||||
try {
|
||||
;({
|
||||
planCode,
|
||||
v1Id,
|
||||
} = await V1SubscriptionManager.promises.getPlanCodeFromV1(userId))
|
||||
;({ planCode, v1Id } =
|
||||
await V1SubscriptionManager.promises.getPlanCodeFromV1(userId))
|
||||
} catch (err) {
|
||||
if (err.name === 'NotFoundError') {
|
||||
return {}
|
||||
|
||||
@@ -197,9 +197,8 @@ const LimitationsManager = {
|
||||
return callback(new Error('no subscription found'))
|
||||
}
|
||||
|
||||
const limitReached = LimitationsManager.teamHasReachedMemberLimit(
|
||||
subscription
|
||||
)
|
||||
const limitReached =
|
||||
LimitationsManager.teamHasReachedMemberLimit(subscription)
|
||||
callback(err, limitReached, subscription)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -283,9 +283,8 @@ const RecurlyWrapper = {
|
||||
account_code: user._id,
|
||||
},
|
||||
}
|
||||
const customFields = getCustomFieldsFromSubscriptionDetails(
|
||||
subscriptionDetails
|
||||
)
|
||||
const customFields =
|
||||
getCustomFieldsFromSubscriptionDetails(subscriptionDetails)
|
||||
if (customFields) {
|
||||
data.custom_fields = customFields
|
||||
}
|
||||
@@ -395,9 +394,8 @@ const RecurlyWrapper = {
|
||||
data.account.billing_info.three_d_secure_action_result_token_id =
|
||||
recurlyTokenIds.threeDSecureActionResult
|
||||
}
|
||||
const customFields = getCustomFieldsFromSubscriptionDetails(
|
||||
subscriptionDetails
|
||||
)
|
||||
const customFields =
|
||||
getCustomFieldsFromSubscriptionDetails(subscriptionDetails)
|
||||
if (customFields) {
|
||||
data.custom_fields = customFields
|
||||
}
|
||||
@@ -525,9 +523,8 @@ const RecurlyWrapper = {
|
||||
recurlySubscription.account != null &&
|
||||
recurlySubscription.account.url != null
|
||||
) {
|
||||
accountId = recurlySubscription.account.url.match(
|
||||
/accounts\/(.*)/
|
||||
)[1]
|
||||
accountId =
|
||||
recurlySubscription.account.url.match(/accounts\/(.*)/)[1]
|
||||
} else {
|
||||
return callback(
|
||||
new Error("I don't understand the response from Recurly")
|
||||
|
||||
@@ -30,11 +30,10 @@ const validGroupPlanModalOptions = {
|
||||
async function plansPage(req, res) {
|
||||
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) {
|
||||
const v = req.query && req.query[param]
|
||||
@@ -77,17 +76,17 @@ async function paymentPage(req, res) {
|
||||
if (!plan) {
|
||||
return HttpErrorHandler.unprocessableEntity(req, res, 'Plan not found')
|
||||
}
|
||||
const hasSubscription = await LimitationsManager.promises.userHasV1OrV2Subscription(
|
||||
user
|
||||
)
|
||||
const hasSubscription =
|
||||
await LimitationsManager.promises.userHasV1OrV2Subscription(user)
|
||||
if (hasSubscription) {
|
||||
res.redirect('/user/subscription?hasSubscription=true')
|
||||
} else {
|
||||
// LimitationsManager.userHasV2Subscription only checks Mongo. Double check with
|
||||
// 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) {
|
||||
res.redirect('/user/subscription?hasSubscription=true')
|
||||
} else {
|
||||
@@ -98,12 +97,10 @@ async function paymentPage(req, res) {
|
||||
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) {
|
||||
currency = recommendedCurrency
|
||||
}
|
||||
@@ -127,9 +124,10 @@ async function paymentPage(req, res) {
|
||||
|
||||
async function userSubscriptionPage(req, res) {
|
||||
const user = SessionManager.getSessionUser(req.session)
|
||||
const results = await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
|
||||
user
|
||||
)
|
||||
const results =
|
||||
await SubscriptionViewModelBuilder.promises.buildUsersSubscriptionViewModel(
|
||||
user
|
||||
)
|
||||
const {
|
||||
personalSubscription,
|
||||
memberGroupSubscriptions,
|
||||
@@ -139,9 +137,8 @@ async function userSubscriptionPage(req, res) {
|
||||
managedPublishers,
|
||||
v1SubscriptionStatus,
|
||||
} = results
|
||||
const hasSubscription = await LimitationsManager.promises.userHasV1OrV2Subscription(
|
||||
user
|
||||
)
|
||||
const hasSubscription =
|
||||
await LimitationsManager.promises.userHasV1OrV2Subscription(user)
|
||||
const fromPlansPage = req.query.hasSubscription
|
||||
const plans = SubscriptionViewModelBuilder.buildPlansList(
|
||||
personalSubscription ? personalSubscription.plan : undefined
|
||||
@@ -446,9 +443,8 @@ function processUpgradeToAnnualPlan(req, res, next) {
|
||||
|
||||
async function extendTrial(req, res) {
|
||||
const user = SessionManager.getSessionUser(req.session)
|
||||
const {
|
||||
subscription,
|
||||
} = await LimitationsManager.promises.userHasV2Subscription(user)
|
||||
const { subscription } =
|
||||
await LimitationsManager.promises.userHasV2Subscription(user)
|
||||
|
||||
try {
|
||||
await SubscriptionHandler.promises.extendTrial(subscription, 14)
|
||||
@@ -485,10 +481,11 @@ async function refreshUserFeatures(req, res) {
|
||||
async function redirectToHostedPage(req, res) {
|
||||
const userId = SessionManager.getLoggedInUserId(req.session)
|
||||
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')
|
||||
res.redirect(url)
|
||||
}
|
||||
|
||||
@@ -79,9 +79,8 @@ async function removeUserFromGroup(subscriptionId, userId) {
|
||||
}
|
||||
|
||||
async function removeUserFromAllGroups(userId) {
|
||||
const subscriptions = await SubscriptionLocator.promises.getMemberSubscriptions(
|
||||
userId
|
||||
)
|
||||
const subscriptions =
|
||||
await SubscriptionLocator.promises.getMemberSubscriptions(userId)
|
||||
if (subscriptions.length === 0) {
|
||||
return
|
||||
}
|
||||
@@ -114,9 +113,8 @@ async function deleteSubscription(subscription, deleterData) {
|
||||
}
|
||||
|
||||
async function restoreSubscription(subscriptionId) {
|
||||
const deletedSubscription = await SubscriptionLocator.promises.getDeletedSubscription(
|
||||
subscriptionId
|
||||
)
|
||||
const deletedSubscription =
|
||||
await SubscriptionLocator.promises.getDeletedSubscription(subscriptionId)
|
||||
const subscription = deletedSubscription.subscription
|
||||
|
||||
// 1. upsert subscription
|
||||
@@ -249,9 +247,8 @@ async function updateSubscriptionFromRecurly(
|
||||
|
||||
async function _sendUserGroupPlanCodeUserProperty(userId) {
|
||||
try {
|
||||
const subscriptions = await SubscriptionLocator.promises.getMemberSubscriptions(
|
||||
userId
|
||||
)
|
||||
const subscriptions =
|
||||
await SubscriptionLocator.promises.getMemberSubscriptions(userId)
|
||||
let bestPlanCode = null
|
||||
let bestFeatures = {}
|
||||
for (const subscription of subscriptions) {
|
||||
|
||||
@@ -21,9 +21,8 @@ async function getRedirectToHostedPage(userId, pageType) {
|
||||
if (!['billing-details', 'account-management'].includes(pageType)) {
|
||||
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
|
||||
if (!recurlySubscriptionId) {
|
||||
throw new NotFoundError('not a recurly subscription')
|
||||
@@ -253,23 +252,27 @@ function buildUsersSubscriptionViewModel(user, callback) {
|
||||
const pendingSubscriptionTax =
|
||||
personalSubscription.recurly.taxRate *
|
||||
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 =
|
||||
(pendingPlan.membersLimit || 0) + pendingAdditionalLicenses
|
||||
personalSubscription.recurly.pendingAdditionalLicenses = pendingAdditionalLicenses
|
||||
personalSubscription.recurly.pendingTotalLicenses = pendingTotalLicenses
|
||||
personalSubscription.recurly.pendingAdditionalLicenses =
|
||||
pendingAdditionalLicenses
|
||||
personalSubscription.recurly.pendingTotalLicenses =
|
||||
pendingTotalLicenses
|
||||
personalSubscription.pendingPlan = pendingPlan
|
||||
} 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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,8 +7,8 @@ const path = require('path')
|
||||
const request = require('request-promise-native')
|
||||
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 tpdsUrl = _.get(settings, ['apis', 'thirdPartyDataStore', 'url'])
|
||||
@@ -149,10 +149,8 @@ async function enqueue(group, method, job) {
|
||||
async function getProjectUsersIds(projectId) {
|
||||
// get list of all user ids with access to project. project owner
|
||||
// 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 (!invitedUserIds.length) {
|
||||
return [ownerUserId]
|
||||
|
||||
@@ -25,11 +25,12 @@ async function _userAlreadyHasHigherPrivilege(
|
||||
if (!Object.values(TokenAccessHandler.TOKEN_TYPES).includes(tokenType)) {
|
||||
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 (
|
||||
orderedPrivilegeLevels.indexOf(privilegeLevel) >=
|
||||
orderedPrivilegeLevels.indexOf(tokenType)
|
||||
@@ -84,9 +85,8 @@ async function tokenAccessPage(req, res, next) {
|
||||
}
|
||||
try {
|
||||
if (TokenAccessHandler.isReadOnlyToken(token)) {
|
||||
const docPublishedInfo = await TokenAccessHandler.promises.getV1DocPublishedInfo(
|
||||
token
|
||||
)
|
||||
const docPublishedInfo =
|
||||
await TokenAccessHandler.promises.getV1DocPublishedInfo(token)
|
||||
if (docPublishedInfo.allow === false) {
|
||||
return res.redirect(302, docPublishedInfo.published_path)
|
||||
}
|
||||
@@ -135,9 +135,8 @@ async function checkAndGetProjectOrResponseAction(
|
||||
|
||||
const projectId = project._id
|
||||
const isAnonymousUser = !userId
|
||||
const tokenAccessEnabled = TokenAccessHandler.tokenAccessEnabledForProject(
|
||||
project
|
||||
)
|
||||
const tokenAccessEnabled =
|
||||
TokenAccessHandler.tokenAccessEnabledForProject(project)
|
||||
if (isAnonymousUser && tokenAccessEnabled) {
|
||||
if (tokenType === TokenAccessHandler.TOKEN_TYPES.READ_AND_WRITE) {
|
||||
if (TokenAccessHandler.ANONYMOUS_READ_AND_WRITE_ENABLED) {
|
||||
@@ -260,9 +259,8 @@ async function grantTokenAccessReadOnly(req, res, next) {
|
||||
return res.sendStatus(400)
|
||||
}
|
||||
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) {
|
||||
return res.json({ redirect: docPublishedInfo.published_path })
|
||||
}
|
||||
|
||||
@@ -29,12 +29,10 @@ module.exports = {
|
||||
|
||||
async function createProjectFromZipArchive(ownerId, defaultName, zipPath) {
|
||||
const contentsPath = await _extractZip(zipPath)
|
||||
const {
|
||||
path,
|
||||
content,
|
||||
} = await ProjectRootDocManager.promises.findRootDocFileFromDirectory(
|
||||
contentsPath
|
||||
)
|
||||
const { path, content } =
|
||||
await ProjectRootDocManager.promises.findRootDocFileFromDirectory(
|
||||
contentsPath
|
||||
)
|
||||
|
||||
const projectName =
|
||||
DocumentHelper.getTitleFromTexContent(content || '') || defaultName
|
||||
@@ -131,11 +129,12 @@ async function _initializeProjectWithZipContents(
|
||||
project._id,
|
||||
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, {
|
||||
newFiles: fileEntries,
|
||||
newDocs: docEntries,
|
||||
|
||||
@@ -223,9 +223,8 @@ async function getUser(providerId, externalUserId) {
|
||||
}
|
||||
|
||||
async function redundantSubscription(userId, providerId, providerName) {
|
||||
const subscription = await SubscriptionLocator.promises.getUserIndividualSubscription(
|
||||
userId
|
||||
)
|
||||
const subscription =
|
||||
await SubscriptionLocator.promises.getUserIndividualSubscription(userId)
|
||||
|
||||
if (subscription) {
|
||||
await NotificationsBuilder.promises
|
||||
|
||||
@@ -264,9 +264,8 @@ const decorateFullEmails = (
|
||||
)
|
||||
}
|
||||
|
||||
emailData.emailHasInstitutionLicence = InstitutionsHelper.emailHasLicence(
|
||||
emailData
|
||||
)
|
||||
emailData.emailHasInstitutionLicence =
|
||||
InstitutionsHelper.emailHasLicence(emailData)
|
||||
})
|
||||
|
||||
return emailsData
|
||||
|
||||
@@ -217,19 +217,20 @@ const UserSessionsManager = {
|
||||
return callback(err)
|
||||
}
|
||||
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)
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
}
|
||||
if (!val) {
|
||||
rclient.srem(sessionSetKey, key, function (err, result) {
|
||||
return next(err)
|
||||
})
|
||||
} else {
|
||||
next()
|
||||
}
|
||||
})
|
||||
),
|
||||
function (err, results) {
|
||||
callback(err)
|
||||
|
||||
@@ -31,9 +31,8 @@ module.exports = {
|
||||
if (error != null) {
|
||||
return next(error)
|
||||
}
|
||||
const entityPrimaryKey = entity[
|
||||
entityConfig.fields.primaryKey
|
||||
].toString()
|
||||
const entityPrimaryKey =
|
||||
entity[entityConfig.fields.primaryKey].toString()
|
||||
if (entityConfig.fields.name) {
|
||||
entityName = entity[entityConfig.fields.name]
|
||||
}
|
||||
|
||||
@@ -198,10 +198,11 @@ function fetchEntityConfig(entityName) {
|
||||
// fetch the entity with id and config, and set it in the request
|
||||
function fetchEntity() {
|
||||
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
|
||||
next()
|
||||
})
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
const _ = require('lodash')
|
||||
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 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
|
||||
|
||||
@@ -77,9 +77,8 @@ function setLangBasedOnDomainMiddleware(req, res, next) {
|
||||
// offering to switch to the appropriate library
|
||||
const detectedLanguageCode = headerLangDetector.detect(req, res)
|
||||
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
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
*/
|
||||
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
@@ -27,9 +27,10 @@ export default function CloneProjectModalContent({
|
||||
)
|
||||
|
||||
// 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
|
||||
const handleSubmit = event => {
|
||||
|
||||
+2
-3
@@ -69,9 +69,8 @@ const EditorNavigationToolbarRoot = React.memo(
|
||||
pdfLayout,
|
||||
} = useLayoutContext(layoutContextPropTypes)
|
||||
|
||||
const { markMessagesAsRead, unreadMessageCount } = useChatContext(
|
||||
chatContextPropTypes
|
||||
)
|
||||
const { markMessagesAsRead, unreadMessageCount } =
|
||||
useChatContext(chatContextPropTypes)
|
||||
|
||||
const toggleChatOpen = useCallback(() => {
|
||||
if (!chatIsOpen) {
|
||||
|
||||
@@ -7,11 +7,8 @@ import { useFileTreeMainContext } from '../contexts/file-tree-main'
|
||||
import FileTreeItemMenuItems from './file-tree-item/file-tree-item-menu-items'
|
||||
|
||||
function FileTreeContextMenu() {
|
||||
const {
|
||||
hasWritePermissions,
|
||||
contextMenuCoords,
|
||||
setContextMenuCoords,
|
||||
} = useFileTreeMainContext()
|
||||
const { hasWritePermissions, contextMenuCoords, setContextMenuCoords } =
|
||||
useFileTreeMainContext()
|
||||
|
||||
if (!hasWritePermissions || !contextMenuCoords) return null
|
||||
|
||||
|
||||
+2
-4
@@ -16,10 +16,8 @@ import ErrorMessage from '../error-message'
|
||||
export default function FileTreeImportFromProject() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const {
|
||||
hasLinkedProjectFileFeature,
|
||||
hasLinkedProjectOutputFileFeature,
|
||||
} = window.ExposedSettings
|
||||
const { hasLinkedProjectFileFeature, hasLinkedProjectOutputFileFeature } =
|
||||
window.ExposedSettings
|
||||
const canSwitchOutputFilesMode =
|
||||
hasLinkedProjectFileFeature && hasLinkedProjectOutputFileFeature
|
||||
|
||||
|
||||
+2
-7
@@ -9,13 +9,8 @@ import { useFileTreeMainContext } from '../../contexts/file-tree-main'
|
||||
function FileTreeItemName({ name, isSelected, setIsDraggable }) {
|
||||
const { hasWritePermissions } = useFileTreeMainContext()
|
||||
|
||||
const {
|
||||
isRenaming,
|
||||
startRenaming,
|
||||
finishRenaming,
|
||||
error,
|
||||
cancel,
|
||||
} = useFileTreeActionable()
|
||||
const { isRenaming, startRenaming, finishRenaming, error, cancel } =
|
||||
useFileTreeActionable()
|
||||
|
||||
const isRenamingEntity = isRenaming && isSelected && !error
|
||||
|
||||
|
||||
@@ -63,12 +63,8 @@ function FileTreeToolbarLeft() {
|
||||
|
||||
function FileTreeToolbarRight() {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
canRename,
|
||||
canDelete,
|
||||
startRenaming,
|
||||
startDeleting,
|
||||
} = useFileTreeActionable()
|
||||
const { canRename, canDelete, startRenaming, startDeleting } =
|
||||
useFileTreeActionable()
|
||||
|
||||
if (!canRename && !canDelete) {
|
||||
return null
|
||||
|
||||
+2
-7
@@ -18,13 +18,8 @@ function FileTreeModalCreateFolder() {
|
||||
const [name, setName] = useState('')
|
||||
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
|
||||
|
||||
|
||||
@@ -5,116 +5,119 @@ import { cloneDeep } from 'lodash'
|
||||
import FileTreeRoot from '../components/file-tree-root'
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
$scope.$watch('connection.state', updateIsConnected)
|
||||
$scope.$watch('connection.reconnection_countdown', updateIsConnected)
|
||||
|
||||
$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.$on('project:joined', () => {
|
||||
$scope.rootFolder = $scope.project.rootFolder
|
||||
$scope.rootDocId = $scope.project.rootDoc_id
|
||||
$scope.$emit('file-tree:initialized')
|
||||
})
|
||||
}
|
||||
|
||||
$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,
|
||||
})
|
||||
$scope.$watch('permissions.write', hasWritePermissions => {
|
||||
$scope.hasWritePermissions = hasWritePermissions
|
||||
})
|
||||
|
||||
// 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.$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
|
||||
}
|
||||
|
||||
$scope.$watch('connection.state', updateIsConnected)
|
||||
$scope.$watch('connection.reconnection_countdown', updateIsConnected)
|
||||
|
||||
$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.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.userHasFeature = feature => ide.$scope.user.features[feature]
|
||||
|
||||
$scope.$watch('permissions.write', hasWritePermissions => {
|
||||
$scope.hasWritePermissions = hasWritePermissions
|
||||
})
|
||||
|
||||
$scope.refProviders = ide.$scope.user.refProviders || {}
|
||||
|
||||
ide.$scope.$watch(
|
||||
'user.refProviders',
|
||||
refProviders => {
|
||||
$scope.refProviders = cloneDeep(refProviders)
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
$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.userHasFeature = feature => ide.$scope.user.features[feature]
|
||||
|
||||
$scope.$watch('permissions.write', hasWritePermissions => {
|
||||
$scope.hasWritePermissions = hasWritePermissions
|
||||
})
|
||||
|
||||
$scope.refProviders = ide.$scope.user.refProviders || {}
|
||||
|
||||
ide.$scope.$watch(
|
||||
'user.refProviders',
|
||||
refProviders => {
|
||||
$scope.refProviders = cloneDeep(refProviders)
|
||||
},
|
||||
true
|
||||
)
|
||||
|
||||
$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', {})
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
App.component(
|
||||
'fileTreeRoot',
|
||||
|
||||
@@ -19,12 +19,8 @@ export function useFileTreeSocketListener() {
|
||||
dispatchCreateFile,
|
||||
fileTreeData,
|
||||
} = useFileTreeMutable()
|
||||
const {
|
||||
selectedEntityIds,
|
||||
selectedEntityParentIds,
|
||||
select,
|
||||
unselect,
|
||||
} = useFileTreeSelectable()
|
||||
const { selectedEntityIds, selectedEntityParentIds, select, unselect } =
|
||||
useFileTreeSelectable()
|
||||
const socket = window._ide && window._ide.socket
|
||||
|
||||
const selectEntityIfCreatedByUser = useCallback(
|
||||
|
||||
@@ -16,9 +16,10 @@ export function DetachCompileButton() {
|
||||
'detached'
|
||||
)
|
||||
|
||||
const handleStartCompile = useCallback(() => startOrTriggerCompile(), [
|
||||
startOrTriggerCompile,
|
||||
])
|
||||
const handleStartCompile = useCallback(
|
||||
() => startOrTriggerCompile(),
|
||||
[startOrTriggerCompile]
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -14,12 +14,8 @@ import getMeta from '../../../utils/meta'
|
||||
function PdfJsViewer({ url }) {
|
||||
const { _id: projectId } = useProjectContext()
|
||||
|
||||
const {
|
||||
setError,
|
||||
firstRenderDone,
|
||||
highlights,
|
||||
setPosition,
|
||||
} = useCompileContext()
|
||||
const { setError, firstRenderDone, highlights, setPosition } =
|
||||
useCompileContext()
|
||||
const [timePDFFetched, setTimePDFFetched] = useState()
|
||||
|
||||
// state values persisted in localStorage to restore on load
|
||||
|
||||
@@ -5,13 +5,8 @@ import { sendMBOnce } from '../../../infrastructure/event-tracking'
|
||||
import { useCompileContext } from '../../../shared/context/compile-context'
|
||||
|
||||
function PdfLogsButton() {
|
||||
const {
|
||||
codeCheckFailed,
|
||||
error,
|
||||
logEntries,
|
||||
showLogs,
|
||||
setShowLogs,
|
||||
} = useCompileContext()
|
||||
const { codeCheckFailed, error, logEntries, showLogs, setShowLogs } =
|
||||
useCompileContext()
|
||||
|
||||
const buttonStyle = useMemo(() => {
|
||||
if (showLogs) {
|
||||
|
||||
@@ -103,13 +103,8 @@ function PdfSynctexControls() {
|
||||
|
||||
const { detachRole } = useLayoutContext()
|
||||
|
||||
const {
|
||||
clsiServerId,
|
||||
pdfUrl,
|
||||
pdfViewer,
|
||||
position,
|
||||
setHighlights,
|
||||
} = useCompileContext()
|
||||
const { clsiServerId, pdfUrl, pdfViewer, position, setHighlights } =
|
||||
useCompileContext()
|
||||
|
||||
const [cursorPosition, setCursorPosition] = useState(() => {
|
||||
const position = localStorage.getItem(
|
||||
|
||||
@@ -10,12 +10,8 @@ const showPdfDetach = getMeta('ol-showPdfDetach')
|
||||
const debugPdfDetach = getMeta('ol-debugPdfDetach')
|
||||
|
||||
export default function useCompileTriggers() {
|
||||
const {
|
||||
startCompile,
|
||||
setChangedAt,
|
||||
cleanupCompileResult,
|
||||
setError,
|
||||
} = useCompileContext()
|
||||
const { startCompile, setChangedAt, cleanupCompileResult, setError } =
|
||||
useCompileContext()
|
||||
const { role: detachRole } = useDetachContext()
|
||||
|
||||
// recompile on key press
|
||||
|
||||
@@ -4,8 +4,9 @@ import * as eventTracking from '../../../infrastructure/event-tracking'
|
||||
|
||||
function getFormValues() {
|
||||
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 currency = modalEl.querySelector('#currency').value
|
||||
const usage = modalEl.querySelector('#usage').checked
|
||||
@@ -32,9 +33,8 @@ function updateGroupPlanView() {
|
||||
modalEl.querySelectorAll('[data-ol-group-plan-usage]').forEach(el => {
|
||||
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(
|
||||
'[data-ol-group-plan-price-per-user]'
|
||||
).innerText = `${currencySymbol}${perUserPrice} per user`
|
||||
|
||||
@@ -58,14 +58,10 @@ function PreviewLogEntryContent({
|
||||
formattedContent,
|
||||
extraInfoURL,
|
||||
}) {
|
||||
const {
|
||||
isExpanded,
|
||||
needsExpandCollapse,
|
||||
expandableProps,
|
||||
toggleProps,
|
||||
} = useExpandCollapse({
|
||||
collapsedSize: 150,
|
||||
})
|
||||
const { isExpanded, needsExpandCollapse, expandableProps, toggleProps } =
|
||||
useExpandCollapse({
|
||||
collapsedSize: 150,
|
||||
})
|
||||
|
||||
const buttonContainerClasses = classNames(
|
||||
'log-entry-content-button-container',
|
||||
|
||||
@@ -30,12 +30,10 @@ function PreviewPane({
|
||||
const [lastCompileTimestamp, setLastCompileTimestamp] = useState(
|
||||
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) {
|
||||
setLastCompileTimestamp(compilerState.lastCompileTimestamp)
|
||||
|
||||
@@ -18,10 +18,8 @@ import { useProjectContext } from '../../../shared/context/project-context'
|
||||
|
||||
export default function EditMember({ member }) {
|
||||
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
|
||||
useEffect(() => {
|
||||
|
||||
+6
-4
@@ -10,7 +10,8 @@ import Icon from '../../../shared/components/icon'
|
||||
// Unicode characters in these Unicode groups:
|
||||
// "General Punctuation — 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({
|
||||
loading,
|
||||
@@ -28,9 +29,10 @@ export default function SelectCollaborators({
|
||||
|
||||
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(
|
||||
() => options.filter(option => !selectedEmails.includes(option.email)),
|
||||
|
||||
+4
-3
@@ -124,9 +124,10 @@ const ShareProjectModal = React.memo(function ShareProjectModal({
|
||||
}, [])
|
||||
|
||||
// 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) {
|
||||
return null
|
||||
|
||||
@@ -426,7 +426,8 @@ Something went wrong connecting to your project. Please refresh if this continue
|
||||
this.joinProjectRetryInterval <
|
||||
this.JOIN_PROJECT_MAX_RETRY_INTERVAL
|
||||
) {
|
||||
this.joinProjectRetryInterval += this.JOIN_PROJECT_RETRY_INTERVAL
|
||||
this.joinProjectRetryInterval +=
|
||||
this.JOIN_PROJECT_RETRY_INTERVAL
|
||||
}
|
||||
return
|
||||
} else {
|
||||
|
||||
@@ -335,10 +335,8 @@ export default ShareJsDoc = (function () {
|
||||
// end-to-end check for edits -> acks, for this very ShareJsdoc
|
||||
// This will catch a broken connection and missing UX-blocker for the
|
||||
// user, allowing them to keep editing.
|
||||
this._detachEditorWatchdogManager = this.EditorWatchdogManager.attachToEditor(
|
||||
editorName,
|
||||
editor
|
||||
)
|
||||
this._detachEditorWatchdogManager =
|
||||
this.EditorWatchdogManager.attachToEditor(editorName, editor)
|
||||
}
|
||||
|
||||
_attachToEditor(editorName, editor, attachToShareJs) {
|
||||
|
||||
+4
-6
@@ -329,9 +329,8 @@ class AutoCompleteManager {
|
||||
)
|
||||
)
|
||||
// Delete back to command start, as appropriate
|
||||
const commandStartIndex = Helpers.getLastCommandFragmentIndex(
|
||||
lineUpToCursor
|
||||
)
|
||||
const commandStartIndex =
|
||||
Helpers.getLastCommandFragmentIndex(lineUpToCursor)
|
||||
if (commandStartIndex !== -1) {
|
||||
leftRange.start.column = commandStartIndex
|
||||
} else {
|
||||
@@ -351,9 +350,8 @@ class AutoCompleteManager {
|
||||
)
|
||||
|
||||
if (lineBeyondCursor) {
|
||||
const partialCommandMatch = lineBeyondCursor.match(
|
||||
/^([a-zA-Z0-9]+)\{/
|
||||
)
|
||||
const partialCommandMatch =
|
||||
lineBeyondCursor.match(/^([a-zA-Z0-9]+)\{/)
|
||||
if (partialCommandMatch) {
|
||||
// We've got a partial command after the cursor
|
||||
const commandTail = partialCommandMatch[1]
|
||||
|
||||
+4
-4
File diff suppressed because one or more lines are too long
+8
-16
@@ -89,10 +89,8 @@ class TrackChangesAdapter {
|
||||
if (this.changeIdToMarkerIdMap[change.id] == null) {
|
||||
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]
|
||||
const session = this.editor.getSession()
|
||||
session.removeMarker(background_marker_id)
|
||||
@@ -103,10 +101,8 @@ class TrackChangesAdapter {
|
||||
if (this.changeIdToMarkerIdMap[change.id] == null) {
|
||||
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]
|
||||
|
||||
const session = this.editor.getSession()
|
||||
@@ -166,10 +162,8 @@ class TrackChangesAdapter {
|
||||
onCommentRemoved(comment) {
|
||||
if (this.changeIdToMarkerIdMap[comment.id] != null) {
|
||||
// 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]
|
||||
const session = this.editor.getSession()
|
||||
session.removeMarker(background_marker_id)
|
||||
@@ -183,10 +177,8 @@ class TrackChangesAdapter {
|
||||
}
|
||||
const session = this.editor.getSession()
|
||||
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) {
|
||||
const background_marker = markers[background_marker_id]
|
||||
background_marker.range.start = start
|
||||
|
||||
+4
-8
@@ -496,10 +496,8 @@ class TrackChangesManager {
|
||||
for (const change of Array.from(this.rangesTracker.changes)) {
|
||||
if (this.adapter.changeIdToMarkerIdMap[change.id] != null) {
|
||||
;({ 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)
|
||||
if (op.i != null) {
|
||||
end = this.adapter.shareJsOffsetToRowColumn(op.p + op.i.length)
|
||||
@@ -521,10 +519,8 @@ class TrackChangesManager {
|
||||
|
||||
for (const comment of Array.from(this.rangesTracker.comments)) {
|
||||
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)
|
||||
end = this.adapter.shareJsOffsetToRowColumn(
|
||||
comment.op.p + comment.op.c.length
|
||||
|
||||
@@ -130,12 +130,8 @@ export default HistoryManager = (function () {
|
||||
reloadDiff() {
|
||||
let { diff } = this.$scope.history
|
||||
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) {
|
||||
return
|
||||
@@ -269,9 +265,8 @@ export default HistoryManager = (function () {
|
||||
if (updates == null) {
|
||||
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)) {
|
||||
update.pathnames = [] // Used for display
|
||||
|
||||
@@ -323,8 +323,8 @@ export default HistoryManager = (function () {
|
||||
|
||||
selectFile(file) {
|
||||
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
|
||||
if (this.$scope.history.viewMode === HistoryViewModes.POINT_IN_TIME) {
|
||||
this.loadFileAtPointInTime()
|
||||
@@ -659,13 +659,13 @@ export default HistoryManager = (function () {
|
||||
|
||||
_loadLabels(labels, lastUpdateToV) {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -902,9 +902,8 @@ export default HistoryManager = (function () {
|
||||
if (updates == null) {
|
||||
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 timestamp24hoursAgo = dateTimeNow.setDate(dateTimeNow.getDate() - 1)
|
||||
let cutOffIndex = null
|
||||
|
||||
@@ -500,7 +500,8 @@ const rules = [
|
||||
ruleId: 'hint_mismatched_environment2',
|
||||
types: ['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}',
|
||||
humanReadableHintComponent: (
|
||||
<>
|
||||
@@ -516,7 +517,8 @@ const rules = [
|
||||
ruleId: 'hint_mismatched_environment3',
|
||||
types: ['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}',
|
||||
humanReadableHintComponent: (
|
||||
<>
|
||||
@@ -532,7 +534,8 @@ const rules = [
|
||||
ruleId: 'hint_mismatched_environment4',
|
||||
types: ['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}',
|
||||
humanReadableHintComponent: (
|
||||
<>
|
||||
|
||||
@@ -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 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
|
||||
const BST_ERROR_REGEX = /^(.*?)\nwhile executing---line (\d+) of file (.*)/m
|
||||
|
||||
@@ -103,13 +106,8 @@ export default class BibLogParser {
|
||||
[
|
||||
MULTILINE_ERROR_REGEX,
|
||||
function (match) {
|
||||
const [
|
||||
fullMatch,
|
||||
firstMessage,
|
||||
lineNumber,
|
||||
fileName,
|
||||
secondMessage,
|
||||
] = match
|
||||
const [fullMatch, firstMessage, lineNumber, fileName, secondMessage] =
|
||||
match
|
||||
return {
|
||||
file: fileName,
|
||||
level: 'error',
|
||||
@@ -135,13 +133,8 @@ export default class BibLogParser {
|
||||
[
|
||||
MULTILINE_COMMAND_ERROR_REGEX,
|
||||
function (match) {
|
||||
const [
|
||||
fullMatch,
|
||||
firstMessage,
|
||||
lineNumber,
|
||||
fileName,
|
||||
secondMessage,
|
||||
] = match
|
||||
const [fullMatch, firstMessage, lineNumber, fileName, secondMessage] =
|
||||
match
|
||||
return {
|
||||
file: fileName,
|
||||
level: 'error',
|
||||
|
||||
@@ -620,8 +620,8 @@ export default App.directive('pdfViewer', ($q, $timeout, pdfSpinner) => ({
|
||||
if (selection.rangeCount === 0) {
|
||||
return false
|
||||
}
|
||||
const selectionAncestorNode = selection.getRangeAt(0)
|
||||
.commonAncestorContainer
|
||||
const selectionAncestorNode =
|
||||
selection.getRangeAt(0).commonAncestorContainer
|
||||
return (
|
||||
element.find(selectionAncestorNode).length > 0 ||
|
||||
element.is(selectionAncestorNode)
|
||||
|
||||
@@ -150,9 +150,8 @@ export default App.controller(
|
||||
$scope.$watch('project.members', function (members) {
|
||||
$scope.reviewPanel.formattedProjectMembers = {}
|
||||
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 (
|
||||
($scope.project != null ? $scope.project.members : undefined) != null
|
||||
@@ -170,9 +169,8 @@ export default App.controller(
|
||||
)
|
||||
}
|
||||
result.push(
|
||||
($scope.reviewPanel.formattedProjectMembers[
|
||||
member._id
|
||||
] = formatUser(member))
|
||||
($scope.reviewPanel.formattedProjectMembers[member._id] =
|
||||
formatUser(member))
|
||||
)
|
||||
} else {
|
||||
result.push(undefined)
|
||||
@@ -900,8 +898,8 @@ export default App.controller(
|
||||
|
||||
$scope.toggleFullTCStateCollapse = function () {
|
||||
if ($scope.project.features.trackChanges) {
|
||||
return ($scope.reviewPanel.fullTCStateCollapsed = !$scope.reviewPanel
|
||||
.fullTCStateCollapsed)
|
||||
return ($scope.reviewPanel.fullTCStateCollapsed =
|
||||
!$scope.reviewPanel.fullTCStateCollapsed)
|
||||
} else {
|
||||
_sendAnalytics()
|
||||
return $scope.openTrackChangesUpgradeModal()
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
||||
*/
|
||||
import App from '../../../base'
|
||||
export default App.filter('notEmpty', () => object =>
|
||||
!angular.equals({}, object)
|
||||
export default App.filter(
|
||||
'notEmpty',
|
||||
() => object => !angular.equals({}, object)
|
||||
)
|
||||
|
||||
@@ -1,52 +1,55 @@
|
||||
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
|
||||
})
|
||||
.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.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
|
||||
})
|
||||
.catch(() => ($scope.unsubscribing = true))
|
||||
}
|
||||
|
||||
$scope.upgradeIntegration = service =>
|
||||
eventTracking.send('subscription-funnel', 'settings-page', service)
|
||||
})
|
||||
$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)
|
||||
}
|
||||
)
|
||||
|
||||
App.controller(
|
||||
'DeleteAccountModalController',
|
||||
|
||||
+16
-14
@@ -41,7 +41,8 @@ export default App.controller(
|
||||
|
||||
const ONE_WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000
|
||||
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 match = userEmailInput
|
||||
@@ -84,9 +85,10 @@ export default App.controller(
|
||||
$scope.ui.isBlacklistedEmail = false
|
||||
$scope.ui.showManualUniversitySelectionUI = false
|
||||
if (userInputLocalAndDomain.domain) {
|
||||
$scope.ui.isBlacklistedEmail = UserAffiliationsDataService.isDomainBlacklisted(
|
||||
userInputLocalAndDomain.domain
|
||||
)
|
||||
$scope.ui.isBlacklistedEmail =
|
||||
UserAffiliationsDataService.isDomainBlacklisted(
|
||||
userInputLocalAndDomain.domain
|
||||
)
|
||||
return UserAffiliationsDataService.getUniversityDomainFromPartialDomainInput(
|
||||
userInputLocalAndDomain.domain
|
||||
)
|
||||
@@ -100,9 +102,8 @@ export default App.controller(
|
||||
) {
|
||||
$scope.newAffiliation.university = universityDomain.university
|
||||
$scope.newAffiliation.department = universityDomain.department
|
||||
$scope.newAffiliation.ssoAvailable = _ssoAvailableForDomain(
|
||||
universityDomain
|
||||
)
|
||||
$scope.newAffiliation.ssoAvailable =
|
||||
_ssoAvailableForDomain(universityDomain)
|
||||
} else {
|
||||
_resetAffiliationSuggestion()
|
||||
}
|
||||
@@ -175,13 +176,14 @@ export default App.controller(
|
||||
)
|
||||
} else {
|
||||
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 {
|
||||
addEmailPromise = UserAffiliationsDataService.addUserAffiliation(
|
||||
$scope.newAffiliation.email,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -87,9 +87,8 @@ export function CompileProvider({ children }) {
|
||||
const [pdfViewer] = useScopeValue('settings.pdfViewer')
|
||||
|
||||
// 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
|
||||
const [pdfUrl, setPdfUrl] = useScopeValueSetterOnly('pdf.url')
|
||||
|
||||
@@ -66,9 +66,8 @@ export function LayoutProvider({ children }) {
|
||||
const [chatIsOpen, setChatIsOpen] = useScopeValue('ui.chatOpen')
|
||||
|
||||
// whether the review pane is open
|
||||
const [reviewPanelOpen, setReviewPanelOpen] = useScopeValue(
|
||||
'ui.reviewPanelOpen'
|
||||
)
|
||||
const [reviewPanelOpen, setReviewPanelOpen] =
|
||||
useScopeValue('ui.reviewPanelOpen')
|
||||
|
||||
// whether the menu pane is open
|
||||
const [leftMenuShown, setLeftMenuShown] = useScopeValue('ui.leftMenuShown')
|
||||
|
||||
@@ -10,12 +10,8 @@ export default function useDetachAction(
|
||||
senderRole,
|
||||
targetRole
|
||||
) {
|
||||
const {
|
||||
role,
|
||||
broadcastEvent,
|
||||
addEventHandler,
|
||||
deleteEventHandler,
|
||||
} = useDetachContext()
|
||||
const { role, broadcastEvent, addEventHandler, deleteEventHandler } =
|
||||
useDetachContext()
|
||||
|
||||
const eventName = `action-${actionName}`
|
||||
|
||||
|
||||
@@ -11,13 +11,8 @@ const LINKING_TIMEOUT = 60000
|
||||
const RELINK_TIMEOUT = 10000
|
||||
|
||||
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)
|
||||
const [isLinking, setIsLinking] = useState(false)
|
||||
|
||||
@@ -12,12 +12,8 @@ export default function useDetachState(
|
||||
) {
|
||||
const [value, setValue] = useState(defaultValue)
|
||||
|
||||
const {
|
||||
role,
|
||||
broadcastEvent,
|
||||
addEventHandler,
|
||||
deleteEventHandler,
|
||||
} = useDetachContext()
|
||||
const { role, broadcastEvent, addEventHandler, deleteEventHandler } =
|
||||
useDetachContext()
|
||||
|
||||
const eventName = `state-${key}`
|
||||
|
||||
|
||||
@@ -98,23 +98,22 @@ export const mockCreateFileModalFetch = fetchMock =>
|
||||
return 204
|
||||
})
|
||||
|
||||
export const createFileModalDecorator = (
|
||||
contextProps = {},
|
||||
createMode = 'doc'
|
||||
export const createFileModalDecorator =
|
||||
(contextProps = {}, createMode = 'doc') =>
|
||||
// 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 }) {
|
||||
const { startCreatingFile } = useFileTreeActionable()
|
||||
|
||||
@@ -48,8 +48,7 @@ const indexes = [
|
||||
'thirdPartyIdentifiers.externalUserId': 1,
|
||||
'thirdPartyIdentifiers.providerId': 1,
|
||||
},
|
||||
name:
|
||||
'thirdPartyIdentifiers.externalUserId_1_thirdPartyIdentifiers.providerId_1',
|
||||
name: 'thirdPartyIdentifiers.externalUserId_1_thirdPartyIdentifiers.providerId_1',
|
||||
sparse: true,
|
||||
},
|
||||
{
|
||||
|
||||
@@ -33,12 +33,16 @@ describe('LaunchpadController', function () {
|
||||
requires: {
|
||||
'@overleaf/settings': (this.Settings = {}),
|
||||
'@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/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 = {}),
|
||||
},
|
||||
})
|
||||
|
||||
|
||||
@@ -21,8 +21,8 @@ describe('UserActivateController', function () {
|
||||
this.UserActivateController = SandboxedModule.require(MODULE_PATH, {
|
||||
requires: {
|
||||
'../../../../app/src/Features/User/UserGetter': this.UserGetter,
|
||||
'../../../../app/src/Features/Errors/ErrorController': this
|
||||
.ErrorController,
|
||||
'../../../../app/src/Features/Errors/ErrorController':
|
||||
this.ErrorController,
|
||||
},
|
||||
})
|
||||
this.req = {
|
||||
|
||||
Generated
+15
-3
@@ -7644,6 +7644,12 @@
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"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": {
|
||||
"version": "0.13.7",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||
@@ -10606,6 +10612,12 @@
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"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": {
|
||||
"version": "0.13.7",
|
||||
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz",
|
||||
@@ -31176,9 +31188,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"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
|
||||
},
|
||||
"pretty-error": {
|
||||
|
||||
@@ -270,7 +270,7 @@
|
||||
"optimize-css-assets-webpack-plugin": "^5.0.3",
|
||||
"pirates": "^4.0.1",
|
||||
"postcss-loader": "^3.0.0",
|
||||
"prettier": "^2.2.1",
|
||||
"prettier": "^2.5.1",
|
||||
"requirejs": "^2.3.6",
|
||||
"samlp": "^3.4.1",
|
||||
"sandboxed-module": "^2.0.4",
|
||||
|
||||
@@ -9,10 +9,11 @@ async function main() {
|
||||
|
||||
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(JSON.stringify(preview, null, 4))
|
||||
console.log('---------------')
|
||||
@@ -25,10 +26,11 @@ async function main() {
|
||||
console.log('Exit in the next 10s in case these numbers are off.')
|
||||
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(JSON.stringify(cleared, null, 4))
|
||||
console.log('---------------')
|
||||
|
||||
@@ -17,9 +17,8 @@ async function countFiles() {
|
||||
if (!project) {
|
||||
throw new Errors.NotFoundError('project not found')
|
||||
}
|
||||
const { files, docs } = ProjectEntityHandler.getAllEntitiesFromProject(
|
||||
project
|
||||
)
|
||||
const { files, docs } =
|
||||
ProjectEntityHandler.getAllEntitiesFromProject(project)
|
||||
console.error(
|
||||
projectId,
|
||||
files.length,
|
||||
|
||||
@@ -9,8 +9,8 @@ const {
|
||||
ObjectId,
|
||||
waitForDb,
|
||||
} = 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 commit = argv.commit !== undefined
|
||||
|
||||
@@ -30,7 +30,8 @@ const ID_WHEN_FULL_PROJECT_HISTORY_ENABLED = '5a8d8a370000000000000000'
|
||||
const OBJECT_ID_WHEN_FULL_PROJECT_HISTORY_ENABLED = new ObjectId(
|
||||
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) {
|
||||
await promiseMapWithLimit(WRITE_CONCURRENCY, projects, processProject)
|
||||
|
||||
+2
-1
@@ -32,7 +32,8 @@ const ID_WHEN_FULL_PROJECT_HISTORY_ENABLED = '5a8d8a370000000000000000'
|
||||
const OBJECT_ID_WHEN_FULL_PROJECT_HISTORY_ENABLED = new ObjectId(
|
||||
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
|
||||
// we still check against this cut off point later, even if
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
const fs = require('fs')
|
||||
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 commit = argv.commit !== undefined
|
||||
@@ -175,12 +175,8 @@ function loadCachedEntitlements(cachedEntitlementsFilename) {
|
||||
|
||||
for (const cachedEntitlementLine of cachedEntitlementsData) {
|
||||
// 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
|
||||
if (ignoreNulls) {
|
||||
hasEntitlementBoolean = hasEntitlement === 't'
|
||||
|
||||
@@ -563,10 +563,11 @@ describe('ProjectInviteTests', function () {
|
||||
throw err
|
||||
}
|
||||
this.secondInvite = invite
|
||||
this.secondLink = CollaboratorsEmailHandler._buildInviteUrl(
|
||||
this.fakeProject,
|
||||
invite
|
||||
)
|
||||
this.secondLink =
|
||||
CollaboratorsEmailHandler._buildInviteUrl(
|
||||
this.fakeProject,
|
||||
invite
|
||||
)
|
||||
cb()
|
||||
}
|
||||
)
|
||||
|
||||
@@ -31,12 +31,13 @@ const _createTag = (user, name, callback) => {
|
||||
const _createTags = (user, tagNames, callback) => {
|
||||
const tags = []
|
||||
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 => {
|
||||
callback(err, tags)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const { expect } = require('chai')
|
||||
const MockSubscription = require('./Subscription')
|
||||
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 {
|
||||
constructor(options = {}) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
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)
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const { db, ObjectId } = require('../../../../app/src/infrastructure/mongodb')
|
||||
const { expect } = require('chai')
|
||||
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 {
|
||||
constructor(options = {}) {
|
||||
|
||||
@@ -178,9 +178,8 @@ class UserHelper {
|
||||
|
||||
// hash password and delete plaintext if set
|
||||
if (attributes.password) {
|
||||
attributes.hashedPassword = await AuthenticationManager.promises.hashPassword(
|
||||
attributes.password
|
||||
)
|
||||
attributes.hashedPassword =
|
||||
await AuthenticationManager.promises.hashPassword(attributes.password)
|
||||
delete attributes.password
|
||||
}
|
||||
|
||||
|
||||
+8
-3
@@ -91,12 +91,17 @@ globalThis.requestAnimationFrame = global.requestAnimationFrame =
|
||||
globalThis.sessionStorage = global.sessionStorage = window.sessionStorage
|
||||
|
||||
// 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
|
||||
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
|
||||
const { addHook } = require('pirates')
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user