mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-09 11:35:47 +00:00
Merge branch 'master' into ja-restore-files
This commit is contained in:
commit
9e525702c1
41 changed files with 603 additions and 4730 deletions
|
@ -174,6 +174,23 @@ module.exports = DocumentUpdaterHandler =
|
|||
oldEntitiesHash = _.indexBy oldEntities, (entity) -> entity[entityType]._id.toString()
|
||||
newEntitiesHash = _.indexBy newEntities, (entity) -> entity[entityType]._id.toString()
|
||||
|
||||
# Send deletes before adds (and renames) to keep a 1:1 mapping between
|
||||
# paths and ids
|
||||
#
|
||||
# When a file is replaced, we first delete the old file and then add the
|
||||
# new file. If the 'add' operation is sent to project history before the
|
||||
# 'delete' then we would have two files with the same path at that point
|
||||
# in time.
|
||||
for id, oldEntity of oldEntitiesHash
|
||||
newEntity = newEntitiesHash[id]
|
||||
|
||||
if !newEntity?
|
||||
# entity deleted
|
||||
updates.push
|
||||
id: id
|
||||
pathname: oldEntity.path
|
||||
newPathname: ''
|
||||
|
||||
for id, newEntity of newEntitiesHash
|
||||
oldEntity = oldEntitiesHash[id]
|
||||
|
||||
|
@ -191,16 +208,6 @@ module.exports = DocumentUpdaterHandler =
|
|||
pathname: oldEntity.path
|
||||
newPathname: newEntity.path
|
||||
|
||||
for id, oldEntity of oldEntitiesHash
|
||||
newEntity = newEntitiesHash[id]
|
||||
|
||||
if !newEntity?
|
||||
# entity deleted
|
||||
updates.push
|
||||
id: id
|
||||
pathname: oldEntity.path
|
||||
newPathname: ''
|
||||
|
||||
updates
|
||||
|
||||
PENDINGUPDATESKEY = "PendingUpdates"
|
||||
|
|
|
@ -41,11 +41,13 @@ module.exports = EditorController =
|
|||
callback err, doc
|
||||
|
||||
upsertFile: (project_id, folder_id, fileName, fsPath, linkedFileData, source, user_id, callback = (err, file) ->) ->
|
||||
ProjectEntityUpdateHandler.upsertFile project_id, folder_id, fileName, fsPath, linkedFileData, user_id, (err, file, didAddFile) ->
|
||||
ProjectEntityUpdateHandler.upsertFile project_id, folder_id, fileName, fsPath, linkedFileData, user_id, (err, newFile, didAddFile, existingFile) ->
|
||||
return callback(err) if err?
|
||||
if didAddFile
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveNewFile', folder_id, file, source, linkedFileData
|
||||
callback null, file
|
||||
if not didAddFile # replacement, so remove the existing file from the client
|
||||
EditorRealTimeController.emitToRoom project_id, 'removeEntity', existingFile._id, source
|
||||
# now add the new file on the client
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveNewFile', folder_id, newFile, source, linkedFileData
|
||||
callback null, newFile
|
||||
|
||||
upsertDocWithPath: (project_id, elementPath, docLines, source, user_id, callback) ->
|
||||
ProjectEntityUpdateHandler.upsertDocWithPath project_id, elementPath, docLines, source, user_id, (err, doc, didAddNewDoc, newFolders, lastFolder) ->
|
||||
|
@ -57,12 +59,14 @@ module.exports = EditorController =
|
|||
callback()
|
||||
|
||||
upsertFileWithPath: (project_id, elementPath, fsPath, linkedFileData, source, user_id, callback) ->
|
||||
ProjectEntityUpdateHandler.upsertFileWithPath project_id, elementPath, fsPath, linkedFileData, user_id, (err, file, didAddFile, newFolders, lastFolder) ->
|
||||
ProjectEntityUpdateHandler.upsertFileWithPath project_id, elementPath, fsPath, linkedFileData, user_id, (err, newFile, didAddFile, existingFile, newFolders, lastFolder) ->
|
||||
return callback(err) if err?
|
||||
EditorController._notifyProjectUsersOfNewFolders project_id, newFolders, (err) ->
|
||||
return callback(err) if err?
|
||||
if didAddFile
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveNewFile', lastFolder._id, file, source, linkedFileData
|
||||
if not didAddFile # replacement, so remove the existing file from the client
|
||||
EditorRealTimeController.emitToRoom project_id, 'removeEntity', existingFile._id, source
|
||||
# now add the new file on the client
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveNewFile', lastFolder._id, newFile, source, linkedFileData
|
||||
callback()
|
||||
|
||||
addFolder : (project_id, folder_id, folderName, source, callback = (error, folder)->)->
|
||||
|
|
|
@ -301,6 +301,7 @@ module.exports = ProjectController =
|
|||
themes: THEME_LIST
|
||||
maxDocLength: Settings.max_doc_length
|
||||
useV2History: !!project.overleaf?.history?.display
|
||||
showRichText: req.query?.rt == 'true'
|
||||
timer.done()
|
||||
|
||||
_buildProjectList: (allProjects, v1Projects = [])->
|
||||
|
|
|
@ -48,28 +48,28 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
|
|||
self._confirmFolder project, folder_id, (folder_id)->
|
||||
self._putElement project, folder_id, fileRef, "file", callback
|
||||
|
||||
replaceFile: wrapWithLock (project_id, file_id, linkedFileData, callback) ->
|
||||
replaceFileWithNew: wrapWithLock (project_id, file_id, newFileRef, callback) ->
|
||||
ProjectGetter.getProjectWithoutLock project_id, {rootFolder: true, name:true}, (err, project) ->
|
||||
return callback(err) if err?
|
||||
ProjectLocator.findElement {project:project, element_id: file_id, type: 'file'}, (err, fileRef, path)=>
|
||||
return callback(err) if err?
|
||||
conditions = _id:project._id
|
||||
inc = {}
|
||||
inc["#{path.mongo}.rev"] = 1
|
||||
# currently we do not need to increment the project version number for changes that are replacements
|
||||
# but when we make switch to having immutable files the replace operation will add a new file, and
|
||||
# this will require a version increase. We will start incrementing the project version now as it does
|
||||
# no harm and will help to test it.
|
||||
inc['version'] = 1
|
||||
set = {}
|
||||
set["#{path.mongo}.created"] = new Date()
|
||||
set["#{path.mongo}.linkedFileData"] = linkedFileData
|
||||
update =
|
||||
"$inc": inc
|
||||
"$set": set
|
||||
Project.update conditions, update, {}, (err) ->
|
||||
ProjectEntityMongoUpdateHandler._insertDeletedFileReference project_id, fileRef, (err) ->
|
||||
return callback(err) if err?
|
||||
callback null, fileRef, project, path
|
||||
conditions = _id:project._id
|
||||
inc = {}
|
||||
# increment the project structure version as we are adding a new file here
|
||||
inc['version'] = 1
|
||||
set = {}
|
||||
set["#{path.mongo}._id"] = newFileRef._id
|
||||
set["#{path.mongo}.created"] = new Date()
|
||||
set["#{path.mongo}.linkedFileData"] = newFileRef.linkedFileData
|
||||
set["#{path.mongo}.rev"] = 1
|
||||
update =
|
||||
"$inc": inc
|
||||
"$set": set
|
||||
Project.update conditions, update, {}, (err) ->
|
||||
return callback(err) if err?
|
||||
callback null, fileRef, project, path
|
||||
|
||||
mkdirp: wrapWithLock (project_id, path, callback) ->
|
||||
folders = path.split('/')
|
||||
|
@ -300,3 +300,29 @@ module.exports = ProjectEntityMongoUpdateHandler = self =
|
|||
if isNestedFolder
|
||||
return callback(new Errors.InvalidNameError("destination folder is a child folder of me"))
|
||||
callback()
|
||||
|
||||
_insertDeletedDocReference: (project_id, doc, callback = (error) ->) ->
|
||||
Project.update {
|
||||
_id: project_id
|
||||
}, {
|
||||
$push: {
|
||||
deletedDocs: {
|
||||
_id: doc._id
|
||||
name: doc.name
|
||||
}
|
||||
}
|
||||
}, {}, callback
|
||||
|
||||
_insertDeletedFileReference: (project_id, fileRef, callback = (error) ->) ->
|
||||
Project.update {
|
||||
_id: project_id
|
||||
}, {
|
||||
$push: {
|
||||
deletedFiles: {
|
||||
_id: fileRef._id
|
||||
name: fileRef.name
|
||||
linkedFileData: fileRef.linkedFileData
|
||||
deletedAt: new Date()
|
||||
}
|
||||
}
|
||||
}, {}, callback
|
|
@ -24,12 +24,25 @@ wrapWithLock = (methodWithoutLock) ->
|
|||
# This lock is used to make sure that the project structure updates are made
|
||||
# sequentially. In particular the updates must be made in mongo and sent to
|
||||
# the doc-updater in the same order.
|
||||
methodWithLock = (project_id, args..., callback) ->
|
||||
LockManager.runWithLock LOCK_NAMESPACE, project_id,
|
||||
(cb) -> methodWithoutLock project_id, args..., cb
|
||||
callback
|
||||
methodWithLock.withoutLock = methodWithoutLock
|
||||
methodWithLock
|
||||
if typeof methodWithoutLock is 'function'
|
||||
methodWithLock = (project_id, args..., callback) ->
|
||||
LockManager.runWithLock LOCK_NAMESPACE, project_id,
|
||||
(cb) -> methodWithoutLock project_id, args..., cb
|
||||
callback
|
||||
methodWithLock.withoutLock = methodWithoutLock
|
||||
methodWithLock
|
||||
else
|
||||
# handle case with separate setup and locked stages
|
||||
wrapWithSetup = methodWithoutLock.beforeLock # a function to set things up before the lock
|
||||
mainTask = methodWithoutLock.withLock # function to execute inside the lock
|
||||
methodWithLock = wrapWithSetup (project_id, args..., callback) ->
|
||||
LockManager.runWithLock(LOCK_NAMESPACE, project_id, (cb) ->
|
||||
mainTask(project_id, args..., cb)
|
||||
callback)
|
||||
methodWithLock.withoutLock = wrapWithSetup mainTask
|
||||
methodWithLock.beforeLock = methodWithoutLock.beforeLock
|
||||
methodWithLock.mainTask = methodWithoutLock.withLock
|
||||
methodWithLock
|
||||
|
||||
module.exports = ProjectEntityUpdateHandler = self =
|
||||
# this doesn't need any locking because it's only called by ProjectDuplicator
|
||||
|
@ -120,31 +133,72 @@ module.exports = ProjectEntityUpdateHandler = self =
|
|||
return callback(error) if error?
|
||||
callback null, doc, folder_id
|
||||
|
||||
addFile: wrapWithLock (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (error, fileRef, folder_id) ->)->
|
||||
self.addFileWithoutUpdatingHistory.withoutLock project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, folder_id, path, fileStoreUrl) ->
|
||||
return callback(error) if error?
|
||||
newFiles = [
|
||||
file: fileRef
|
||||
path: path
|
||||
url: fileStoreUrl
|
||||
]
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, (error) ->
|
||||
return callback(error) if error?
|
||||
callback null, fileRef, folder_id
|
||||
_uploadFile: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (error, fileRef, fileStoreUrl) ->)->
|
||||
if not SafePath.isCleanFilename fileName
|
||||
return callback new Errors.InvalidNameError("invalid element name")
|
||||
fileRef = new File(
|
||||
name: fileName
|
||||
linkedFileData: linkedFileData
|
||||
)
|
||||
FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
|
||||
if err?
|
||||
logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error uploading image to s3"
|
||||
return callback(err)
|
||||
callback(null, fileRef, fileStoreUrl)
|
||||
|
||||
replaceFile: wrapWithLock (project_id, file_id, fsPath, linkedFileData, userId, callback)->
|
||||
FileStoreHandler.uploadFileFromDisk project_id, file_id, fsPath, (err, fileStoreUrl)->
|
||||
return callback(err) if err?
|
||||
ProjectEntityMongoUpdateHandler.replaceFile project_id, file_id, linkedFileData, (err, fileRef, project, path) ->
|
||||
_addFileAndSendToTpds: (project_id, folder_id, fileName, fileRef, callback = (error) ->)->
|
||||
ProjectEntityMongoUpdateHandler.addFile project_id, folder_id, fileRef, (err, result, project) ->
|
||||
if err?
|
||||
logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error adding file with project"
|
||||
return callback(err)
|
||||
TpdsUpdateSender.addFile {project_id:project_id, file_id:fileRef._id, path:result?.path?.fileSystem, project_name:project.name, rev:fileRef.rev}, (err) ->
|
||||
return callback(err) if err?
|
||||
callback(null, result, project)
|
||||
|
||||
addFile: wrapWithLock
|
||||
beforeLock: (next) ->
|
||||
(project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback) ->
|
||||
ProjectEntityUpdateHandler._uploadFile project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, fileStoreUrl) ->
|
||||
return callback(error) if error?
|
||||
next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback)
|
||||
withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback = (error, fileRef, folder_id) ->)->
|
||||
ProjectEntityUpdateHandler._addFileAndSendToTpds project_id, folder_id, fileName, fileRef, (err, result, project) ->
|
||||
return callback(err) if err?
|
||||
newFiles = [
|
||||
file: fileRef
|
||||
path: result?.path?.fileSystem
|
||||
url: fileStoreUrl
|
||||
]
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, (error) ->
|
||||
return callback(error) if error?
|
||||
callback(null, fileRef, folder_id)
|
||||
|
||||
replaceFile: wrapWithLock
|
||||
beforeLock: (next) ->
|
||||
(project_id, file_id, fsPath, linkedFileData, userId, callback)->
|
||||
# create a new file
|
||||
fileRef = new File(
|
||||
name: "dummy-upload-filename"
|
||||
linkedFileData: linkedFileData
|
||||
)
|
||||
FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
|
||||
return callback(err) if err?
|
||||
next project_id, file_id, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback
|
||||
withLock: (project_id, file_id, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, callback)->
|
||||
ProjectEntityMongoUpdateHandler.replaceFileWithNew project_id, file_id, newFileRef, (err, oldFileRef, project, path) ->
|
||||
return callback(err) if err?
|
||||
oldFiles = [
|
||||
file: oldFileRef
|
||||
path: path.fileSystem
|
||||
]
|
||||
newFiles = [
|
||||
file: newFileRef
|
||||
path: path.fileSystem
|
||||
url: fileStoreUrl
|
||||
]
|
||||
TpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:path.fileSystem, rev:fileRef.rev+1, project_name:project.name}, (err) ->
|
||||
TpdsUpdateSender.addFile {project_id:project._id, file_id:newFileRef._id, path:path.fileSystem, rev:newFileRef.rev+1, project_name:project.name}, (err) ->
|
||||
return callback(err) if err?
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, callback
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, {oldFiles, newFiles}, callback
|
||||
|
||||
addDocWithoutUpdatingHistory: wrapWithLock (project_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=>
|
||||
# This method should never be called directly, except when importing a project
|
||||
|
@ -171,29 +225,19 @@ module.exports = ProjectEntityUpdateHandler = self =
|
|||
return callback(err) if err?
|
||||
callback(null, doc, folder_id, result?.path?.fileSystem)
|
||||
|
||||
addFileWithoutUpdatingHistory: wrapWithLock (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (error, fileRef, folder_id, path, fileStoreUrl) ->)->
|
||||
addFileWithoutUpdatingHistory: wrapWithLock
|
||||
# This method should never be called directly, except when importing a project
|
||||
# from Overleaf. It skips sending updates to the project history, which will break
|
||||
# the history unless you are making sure it is updated in some other way.
|
||||
|
||||
if not SafePath.isCleanFilename fileName
|
||||
return callback new Errors.InvalidNameError("invalid element name")
|
||||
|
||||
fileRef = new File(
|
||||
name: fileName
|
||||
linkedFileData: linkedFileData
|
||||
)
|
||||
FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
|
||||
if err?
|
||||
logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error uploading image to s3"
|
||||
return callback(err)
|
||||
ProjectEntityMongoUpdateHandler.addFile project_id, folder_id, fileRef, (err, result, project) ->
|
||||
if err?
|
||||
logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error adding file with project"
|
||||
return callback(err)
|
||||
TpdsUpdateSender.addFile {project_id:project_id, file_id:fileRef._id, path:result?.path?.fileSystem, project_name:project.name, rev:fileRef.rev}, (err) ->
|
||||
return callback(err) if err?
|
||||
callback(null, fileRef, folder_id, result?.path?.fileSystem, fileStoreUrl)
|
||||
beforeLock: (next) ->
|
||||
(project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback) ->
|
||||
ProjectEntityUpdateHandler._uploadFile project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, fileStoreUrl) ->
|
||||
return callback(error) if error?
|
||||
next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback)
|
||||
withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback = (error, fileRef, folder_id, path, fileStoreUrl) ->)->
|
||||
ProjectEntityUpdateHandler._addFileAndSendToTpds project_id, folder_id, fileName, fileRef, (err, result, project) ->
|
||||
return callback(err) if err?
|
||||
callback(null, fileRef, folder_id, result?.path?.fileSystem, fileStoreUrl)
|
||||
|
||||
upsertDoc: wrapWithLock (project_id, folder_id, docName, docLines, source, userId, callback = (err, doc, folder_id, isNewDoc)->)->
|
||||
ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
|
||||
|
@ -215,23 +259,36 @@ module.exports = ProjectEntityUpdateHandler = self =
|
|||
return callback(err) if err?
|
||||
callback null, doc, !existingDoc?
|
||||
|
||||
upsertFile: wrapWithLock (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (err, file, isNewFile)->)->
|
||||
ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
|
||||
return callback(error) if error?
|
||||
return callback(new Error("Couldn't find folder")) if !folder?
|
||||
existingFile = null
|
||||
for fileRef in folder.fileRefs
|
||||
if fileRef.name == fileName
|
||||
existingFile = fileRef
|
||||
break
|
||||
if existingFile?
|
||||
self.replaceFile.withoutLock project_id, existingFile._id, fsPath, linkedFileData, userId, (err) ->
|
||||
upsertFile: wrapWithLock
|
||||
beforeLock: (next) ->
|
||||
(project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback)->
|
||||
# create a new file
|
||||
fileRef = new File(
|
||||
name: fileName
|
||||
linkedFileData: linkedFileData
|
||||
)
|
||||
FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
|
||||
return callback(err) if err?
|
||||
callback null, existingFile, !existingFile?
|
||||
else
|
||||
self.addFile.withoutLock project_id, folder_id, fileName, fsPath, linkedFileData, userId, (err, file) ->
|
||||
return callback(err) if err?
|
||||
callback null, file, !existingFile?
|
||||
next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback)
|
||||
withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, callback = (err, file, isNewFile, existingFile)->)->
|
||||
ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
|
||||
return callback(error) if error?
|
||||
return callback(new Error("Couldn't find folder")) if !folder?
|
||||
existingFile = null
|
||||
for fileRef in folder.fileRefs
|
||||
if fileRef.name == fileName
|
||||
existingFile = fileRef
|
||||
break
|
||||
if existingFile?
|
||||
# this calls directly into the replaceFile main task (without the beforeLock part)
|
||||
self.replaceFile.mainTask project_id, existingFile._id, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, (err) ->
|
||||
return callback(err) if err?
|
||||
callback null, newFileRef, !existingFile?, existingFile
|
||||
else
|
||||
# this calls directly into the addFile main task (without the beforeLock part)
|
||||
self.addFile.mainTask project_id, folder_id, fileName, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, (err) ->
|
||||
return callback(err) if err?
|
||||
callback null, newFileRef, !existingFile?, existingFile
|
||||
|
||||
upsertDocWithPath: wrapWithLock (project_id, elementPath, docLines, source, userId, callback) ->
|
||||
docName = path.basename(elementPath)
|
||||
|
@ -242,14 +299,26 @@ module.exports = ProjectEntityUpdateHandler = self =
|
|||
return callback(err) if err?
|
||||
callback null, doc, isNewDoc, newFolders, folder
|
||||
|
||||
upsertFileWithPath: wrapWithLock (project_id, elementPath, fsPath, linkedFileData, userId, callback) ->
|
||||
fileName = path.basename(elementPath)
|
||||
folderPath = path.dirname(elementPath)
|
||||
self.mkdirp.withoutLock project_id, folderPath, (err, newFolders, folder) ->
|
||||
return callback(err) if err?
|
||||
self.upsertFile.withoutLock project_id, folder._id, fileName, fsPath, linkedFileData, userId, (err, file, isNewFile) ->
|
||||
upsertFileWithPath: wrapWithLock
|
||||
beforeLock: (next) ->
|
||||
(project_id, elementPath, fsPath, linkedFileData, userId, callback)->
|
||||
fileName = path.basename(elementPath)
|
||||
folderPath = path.dirname(elementPath)
|
||||
# create a new file
|
||||
fileRef = new File(
|
||||
name: fileName
|
||||
linkedFileData: linkedFileData
|
||||
)
|
||||
FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)->
|
||||
return callback(err) if err?
|
||||
next project_id, folderPath, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback
|
||||
withLock: (project_id, folderPath, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback) ->
|
||||
self.mkdirp.withoutLock project_id, folderPath, (err, newFolders, folder) ->
|
||||
return callback(err) if err?
|
||||
callback null, file, isNewFile, newFolders, folder
|
||||
# this calls directly into the upsertFile main task (without the beforeLock part)
|
||||
self.upsertFile.mainTask project_id, folder._id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, (err, newFile, isNewFile, existingFile) ->
|
||||
return callback(err) if err?
|
||||
callback null, newFile, isNewFile, existingFile, newFolders, folder
|
||||
|
||||
deleteEntity: wrapWithLock (project_id, entity_id, entityType, userId, callback = (error) ->)->
|
||||
logger.log entity_id:entity_id, entityType:entityType, project_id:project_id, "deleting project entity"
|
||||
|
@ -326,7 +395,7 @@ module.exports = ProjectEntityUpdateHandler = self =
|
|||
path: file.path
|
||||
url: FileStoreHandler._buildUrl(project_id, file.file._id)
|
||||
|
||||
DocumentUpdaterHandler.resyncProjectHistory project_id, docs, files, callback
|
||||
DocumentUpdaterHandler.resyncProjectHistory project_id, docs, files, callback
|
||||
_cleanUpEntity: (project, entity, entityType, path, userId, callback = (error) ->) ->
|
||||
if(entityType.indexOf("file") != -1)
|
||||
self._cleanUpFile project, entity, path, userId, callback
|
||||
|
@ -348,7 +417,7 @@ module.exports = ProjectEntityUpdateHandler = self =
|
|||
|
||||
unsetRootDocIfRequired (error) ->
|
||||
return callback(error) if error?
|
||||
self._insertDeletedDocReference project._id, doc, (error) ->
|
||||
ProjectEntityMongoUpdateHandler._insertDeletedDocReference project._id, doc, (error) ->
|
||||
return callback(error) if error?
|
||||
DocumentUpdaterHandler.deleteDoc project_id, doc_id, (error) ->
|
||||
return callback(error) if error?
|
||||
|
@ -358,11 +427,12 @@ module.exports = ProjectEntityUpdateHandler = self =
|
|||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback
|
||||
|
||||
_cleanUpFile: (project, file, path, userId, callback = (error) ->) ->
|
||||
project_id = project._id.toString()
|
||||
file_id = file._id.toString()
|
||||
FileStoreHandler.deleteFile project_id, file_id, (error) ->
|
||||
ProjectEntityMongoUpdateHandler._insertDeletedFileReference project._id, file, (error) ->
|
||||
return callback(error) if error?
|
||||
project_id = project._id.toString()
|
||||
changes = oldFiles: [ {file, path} ]
|
||||
# we are now keeping a copy of every file versio so we no longer delete
|
||||
# the file from the filestore
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback
|
||||
|
||||
_cleanUpFolder: (project, folder, folderPath, userId, callback = (error) ->) ->
|
||||
|
@ -383,15 +453,3 @@ module.exports = ProjectEntityUpdateHandler = self =
|
|||
jobs.push (callback) -> self._cleanUpFolder project, childFolder, folderPath, userId, callback
|
||||
|
||||
async.series jobs, callback
|
||||
|
||||
_insertDeletedDocReference: (project_id, doc, callback = (error) ->) ->
|
||||
Project.update {
|
||||
_id: project_id
|
||||
}, {
|
||||
$push: {
|
||||
deletedDocs: {
|
||||
_id: doc._id
|
||||
name: doc.name
|
||||
}
|
||||
}
|
||||
}, {}, callback
|
||||
|
|
|
@ -15,6 +15,7 @@ htmlEncoder = new require("node-html-encoder").Encoder("numerical")
|
|||
hashedFiles = {}
|
||||
Path = require 'path'
|
||||
Features = require "./Features"
|
||||
Modules = require "./Modules"
|
||||
|
||||
jsPath =
|
||||
if Settings.useMinifiedJs
|
||||
|
@ -41,10 +42,9 @@ pathList = [
|
|||
"#{jsPath}ide.js"
|
||||
"#{jsPath}main.js"
|
||||
"#{jsPath}libraries.js"
|
||||
"#{jsPath}es/richText.js"
|
||||
"/stylesheets/style.css"
|
||||
"/stylesheets/ol-style.css"
|
||||
]
|
||||
].concat(Modules.moduleAssetFiles(jsPath))
|
||||
|
||||
if !Settings.useMinifiedJs
|
||||
logger.log "not using minified JS, not hashing static files"
|
||||
|
|
|
@ -14,8 +14,8 @@ module.exports = Features =
|
|||
return Settings.enableGithubSync
|
||||
when 'v1-return-message'
|
||||
return Settings.accountMerge? and Settings.overleaf?
|
||||
when 'rich-text'
|
||||
return Settings.showRichText
|
||||
when 'publish-modal'
|
||||
return Settings.showPublishModal
|
||||
when 'custom-togglers'
|
||||
return Settings.overleaf?
|
||||
else
|
||||
|
|
|
@ -43,6 +43,13 @@ module.exports = Modules =
|
|||
moduleIncludesAvailable: (view) ->
|
||||
return (Modules.viewIncludes[view] or []).length > 0
|
||||
|
||||
moduleAssetFiles: (pathPrefix) ->
|
||||
assetFiles = []
|
||||
for module in @modules
|
||||
for assetFile in module.assetFiles or []
|
||||
assetFiles.push "#{pathPrefix}#{assetFile}"
|
||||
return assetFiles
|
||||
|
||||
attachHooks: () ->
|
||||
for module in @modules
|
||||
if module.hooks?
|
||||
|
|
|
@ -12,6 +12,10 @@ ObjectId = Schema.ObjectId
|
|||
DeletedDocSchema = new Schema
|
||||
name: String
|
||||
|
||||
DeletedFileSchema = new Schema
|
||||
name: String
|
||||
deletedAt: {type: Date}
|
||||
|
||||
ProjectSchema = new Schema
|
||||
name : {type:String, default:'new project'}
|
||||
lastUpdated : {type:Date, default: () -> new Date()}
|
||||
|
@ -30,6 +34,7 @@ ProjectSchema = new Schema
|
|||
description : {type:String, default:''}
|
||||
archived : { type: Boolean }
|
||||
deletedDocs : [DeletedDocSchema]
|
||||
deletedFiles : [DeletedFileSchema]
|
||||
imageName : { type: String }
|
||||
track_changes : { type: Object }
|
||||
tokens :
|
||||
|
|
|
@ -55,6 +55,7 @@ block content
|
|||
include ./editor/header
|
||||
|
||||
include ./editor/share
|
||||
!= moduleIncludes("publish:body", locals)
|
||||
|
||||
#ide-body(
|
||||
ng-cloak,
|
||||
|
@ -137,6 +138,7 @@ block requirejs
|
|||
"ide": "#{buildJsPath('ide.js', {hashedPath:settings.useMinifiedJs, removeExtension:true})}",
|
||||
"libraries": "#{buildJsPath('libraries.js', {hashedPath:settings.useMinifiedJs, removeExtension:true})}",
|
||||
!{moduleIncludes("editor:script", locals)}
|
||||
!{moduleIncludes("publish:script", locals)}
|
||||
},
|
||||
"waitSeconds": 0,
|
||||
"shim": {
|
||||
|
|
|
@ -39,6 +39,7 @@ div.full-size(
|
|||
ace-editor="editor",
|
||||
ng-if="!editor.richText",
|
||||
ng-show="!!editor.sharejs_doc && !editor.opening",
|
||||
style=showRichText ? "top: 40px" : "",
|
||||
theme="settings.theme",
|
||||
keybindings="settings.mode",
|
||||
font-size="settings.fontSize",
|
||||
|
|
|
@ -107,6 +107,9 @@ header.toolbar.toolbar-header.toolbar-with-labels(
|
|||
)
|
||||
i.fa.fa-fw.fa-group
|
||||
p.toolbar-label #{translate("share")}
|
||||
|
||||
!= moduleIncludes('publish:button', locals)
|
||||
|
||||
a.btn.btn-full-height(
|
||||
href,
|
||||
ng-click="toggleHistory();",
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
div#history(ng-show="ui.view == 'history'")
|
||||
span(ng-controller="HistoryPremiumPopup")
|
||||
span
|
||||
.upgrade-prompt(ng-if="project.features.versioning === false && ui.view === 'history'")
|
||||
.message(ng-if="project.owner._id == user.id")
|
||||
p.text-center: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})}
|
||||
|
@ -33,7 +33,6 @@ div#history(ng-show="ui.view == 'history'")
|
|||
href
|
||||
ng-class="buttonClass"
|
||||
ng-click="startFreeTrial('history')"
|
||||
sixpack-convert="teaser-history"
|
||||
) #{translate("start_free_trial")}
|
||||
|
||||
.message(ng-show="project.owner._id != user.id")
|
||||
|
|
|
@ -402,7 +402,6 @@ div.full-size.pdf(ng-controller="PdfController")
|
|||
a.btn.btn-success.row-spaced-small(
|
||||
href
|
||||
ng-class="buttonClass"
|
||||
sixpack-convert="track_changes_feature_info"
|
||||
ng-click="startFreeTrial('compile-timeout')"
|
||||
) #{translate("start_free_trial")}
|
||||
|
||||
|
|
|
@ -173,7 +173,6 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
|
|||
a.btn.btn-success(
|
||||
href
|
||||
ng-class="buttonClass"
|
||||
sixpack-convert="track_changes_feature_info"
|
||||
ng-click="startFreeTrial('projectMembers')"
|
||||
) #{translate("start_free_trial")}
|
||||
|
||||
|
|
|
@ -10,19 +10,16 @@
|
|||
li
|
||||
a(
|
||||
href,
|
||||
sixpack-convert="first_sign_up",
|
||||
ng-click="openCreateProjectModal()"
|
||||
) #{translate("blank_project")}
|
||||
li
|
||||
a(
|
||||
href,
|
||||
sixpack-convert="first_sign_up",
|
||||
ng-click="openCreateProjectModal('example')"
|
||||
) #{translate("example_project")}
|
||||
li
|
||||
a(
|
||||
href,
|
||||
sixpack-convert="first_sign_up",
|
||||
ng-click="openUploadProjectModal()"
|
||||
) #{translate("upload_project")}
|
||||
!= moduleIncludes("newProjectMenu", locals)
|
||||
|
@ -31,7 +28,7 @@
|
|||
li.dropdown-header #{translate("templates")}
|
||||
each item in templates
|
||||
li
|
||||
a.menu-indent(href=item.url, sixpack-convert="first_sign_up") #{translate(item.name)}
|
||||
a.menu-indent(href=item.url) #{translate(item.name)}
|
||||
|
||||
.row-spaced(ng-if="projects.length > 0", ng-cloak)
|
||||
ul.list-unstyled.folders-menu(
|
||||
|
@ -133,7 +130,7 @@
|
|||
hr
|
||||
p.small #{translate("on_free_sl")}
|
||||
p
|
||||
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgraed-rotation").btn.btn-primary #{translate("upgrade")}
|
||||
a(href="/user/subscription/plans").btn.btn-primary #{translate("upgrade")}
|
||||
p.small.text-centered
|
||||
| #{translate("or_unlock_features_bonus")}
|
||||
a(href="/user/bonus") #{translate("sharing_sl")} .
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
extends ../layout
|
||||
|
||||
block scripts
|
||||
script(src="https://js.recurly.com/v3/recurly.js")
|
||||
|
||||
script(type='text/javascript').
|
||||
window.countryCode = '#{countryCode}'
|
||||
window.plan_code = '#{plan_code}'
|
||||
|
@ -41,19 +39,20 @@ block content
|
|||
span !{translate("first_few_days_free", {trialLen:'{{trialLength}}'})}
|
||||
span(ng-if="discountMonths && discountRate") - {{discountMonths}} #{translate("month")}s {{discountRate}}% Off
|
||||
div(ng-if="price")
|
||||
strong {{price.currency.symbol}}{{price.next.total}}
|
||||
strong {{plans[currencyCode]['symbol']}}{{price.next.total}}
|
||||
span(ng-if="monthlyBilling") #{translate("every")} #{translate("month")}
|
||||
span(ng-if="!monthlyBilling") #{translate("every")} #{translate("year")}
|
||||
div(ng-if="normalPrice")
|
||||
span.small Normally {{price.currency.symbol}}{{normalPrice}}
|
||||
span.small Normally {{plans[currencyCode]['symbol']}}{{normalPrice}}
|
||||
.row
|
||||
div()
|
||||
.col-md-12()
|
||||
form(
|
||||
ng-if="planName"
|
||||
name="simpleCCForm"
|
||||
novalidate
|
||||
)
|
||||
|
||||
|
||||
div.payment-method-toggle
|
||||
a.payment-method-toggle-switch(
|
||||
href
|
||||
|
@ -75,10 +74,10 @@ block content
|
|||
.alert.alert-warning.small(ng-show="genericError")
|
||||
strong {{genericError}}
|
||||
|
||||
div(ng-if="paymentMethod.value === 'credit_card'")
|
||||
div(ng-show="paymentMethod.value === 'credit_card'")
|
||||
.row
|
||||
.col-xs-6
|
||||
.form-group(ng-class="validation.errorFields.first_name || inputHasError(simpleCCForm.firstName) ? 'has-error' : ''")
|
||||
.form-group(ng-class="validation.errorFields.first_name || inputHasError(simpleCCForm.firstName) ? 'has-external-error' : ''")
|
||||
label(for="first-name") #{translate('first_name')}
|
||||
input#first-name.form-control(
|
||||
type="text"
|
||||
|
@ -90,7 +89,7 @@ block content
|
|||
)
|
||||
span.input-feedback-message {{ simpleCCForm.firstName.$error.required ? 'This field is required' : '' }}
|
||||
.col-xs-6
|
||||
.form-group(for="last-name",ng-class="validation.errorFields.last_name || inputHasError(simpleCCForm.lastName)? 'has-error' : ''")
|
||||
.form-group(for="last-name",ng-class="validation.errorFields.last_name || inputHasError(simpleCCForm.lastName)? 'has-external-error' : ''")
|
||||
label(for="last-name") #{translate('last_name')}
|
||||
input#last-name.form-control(
|
||||
type="text"
|
||||
|
@ -100,47 +99,41 @@ block content
|
|||
ng-model="data.last_name"
|
||||
required
|
||||
)
|
||||
span.input-feedback-message {{ simpleCCForm.lastName.$error.required ? 'This field is required' : '' }}
|
||||
|
||||
.form-group(ng-class="validation.correctCardNumber == false || validation.errorFields.number || inputHasError(simpleCCForm.ccNumber) ? 'has-error' : ''")
|
||||
.form-group(ng-class="validation.errorFields.number ? 'has-external-error' : ''")
|
||||
label(for="card-no") #{translate("credit_card_number")}
|
||||
input#card-no.form-control(
|
||||
div#card-no(
|
||||
type="text"
|
||||
ng-model="data.number"
|
||||
name="ccNumber"
|
||||
ng-focus="validation.correctCardNumber = true; validation.errorFields.number = false;"
|
||||
ng-blur="validateCardNumber();"
|
||||
required
|
||||
cc-format-card-number
|
||||
data-recurly='number'
|
||||
)
|
||||
span.input-feedback-message {{ simpleCCForm.ccNumber.$error.required ? 'This field is required' : 'Please re-check the card number' }}
|
||||
|
||||
.row
|
||||
.col-xs-6
|
||||
.form-group.has-feedback(ng-class="validation.correctExpiry == false || validation.errorFields.expiry || inputHasError(simpleCCForm.expiry) ? 'has-error' : ''")
|
||||
label #{translate("expiry")}
|
||||
input.form-control(
|
||||
type="text"
|
||||
ng-model="data.mmYY"
|
||||
name="expiry"
|
||||
placeholder="MM / YY"
|
||||
ng-focus="validation.correctExpiry = true; validation.errorFields.expiry = false;"
|
||||
ng-blur="updateExpiry(); validateExpiry()"
|
||||
required
|
||||
cc-format-expiry
|
||||
.col-xs-3
|
||||
.form-group.has-feedback(ng-class="validation.errorFields.month ? 'has-external-error' : ''")
|
||||
label(for="month") #{translate("month")}
|
||||
div(
|
||||
type="number"
|
||||
name="month"
|
||||
data-recurly="month"
|
||||
)
|
||||
span.input-feedback-message {{ simpleCCForm.expiry.$error.required ? 'This field is required' : 'Please re-check the expiry date' }}
|
||||
|
||||
.col-xs-3
|
||||
.form-group.has-feedback(ng-class="validation.errorFields.year ? 'has-external-error' : ''")
|
||||
label(for="year") #{translate("year")}
|
||||
div(
|
||||
type="number"
|
||||
name="year"
|
||||
data-recurly="year"
|
||||
)
|
||||
|
||||
.col-xs-6
|
||||
.form-group.has-feedback(ng-class="validation.correctCvv == false || validation.errorFields.cvv || inputHasError(simpleCCForm.cvv) ? 'has-error' : ''")
|
||||
.form-group.has-feedback(ng-class="validation.errorFields.cvv ? 'has-external-error' : ''")
|
||||
label #{translate("security_code")}
|
||||
input.form-control(
|
||||
type="text"
|
||||
div(
|
||||
type="number"
|
||||
ng-model="data.cvv"
|
||||
ng-focus="validation.correctCvv = true; validation.errorFields.cvv = false;"
|
||||
ng-blur="validateCvv()"
|
||||
data-recurly="cvv"
|
||||
name="cvv"
|
||||
required
|
||||
cc-format-sec-code
|
||||
)
|
||||
.form-control-feedback
|
||||
|
@ -151,11 +144,9 @@ block content
|
|||
tooltip-trigger="mouseenter"
|
||||
tooltip-append-to-body="true"
|
||||
) ?
|
||||
span.input-feedback-message {{ simpleCCForm.cvv.$error.required ? 'This field is required' : 'Please re-check the security code' }}
|
||||
|
||||
|
||||
div
|
||||
.form-group(ng-class="validation.errorFields.country || inputHasError(simpleCCForm.country) ? 'has-error' : ''")
|
||||
.form-group(ng-class="validation.errorFields.country || inputHasError(simpleCCForm.country) ? 'has-external-error' : ''")
|
||||
label(for="country") #{translate('country')}
|
||||
select#country.form-control(
|
||||
data-recurly="country"
|
||||
|
@ -189,8 +180,8 @@ block content
|
|||
div.price-breakdown(ng-if="price.next.tax !== '0.00'")
|
||||
hr.thin
|
||||
span Total:
|
||||
strong {{price.currency.symbol}}{{price.next.total}}
|
||||
span ({{price.currency.symbol}}{{price.next.subtotal}} + {{price.currency.symbol}}{{price.next.tax}} tax)
|
||||
strong {{plans[currencyCode]['symbol']}}{{price.next.total}}
|
||||
span ({{plans[currencyCode]['symbol']}}{{price.next.subtotal}} + {{plans[currencyCode]['symbol']}}{{price.next.tax}} tax)
|
||||
span(ng-if="monthlyBilling") #{translate("every")} #{translate("month")}
|
||||
span(ng-if="!monthlyBilling") #{translate("every")} #{translate("year")}
|
||||
hr.thin
|
||||
|
@ -198,7 +189,7 @@ block content
|
|||
div.payment-submit
|
||||
button.btn.btn-success.btn-block(
|
||||
ng-click="submit()"
|
||||
ng-disabled="processing || !isFormValid(simpleCCForm);"
|
||||
ng-disabled="processing || !isFormValid(simpleCCForm);"
|
||||
)
|
||||
span(ng-show="processing")
|
||||
i.fa.fa-spinner.fa-spin
|
||||
|
|
|
@ -76,7 +76,7 @@ block content
|
|||
br
|
||||
a.btn.btn-info(
|
||||
href="/register"
|
||||
style=(getLoggedInUserId() === undefined ? "" : "visibility: hidden")
|
||||
style=(getLoggedInUserId() === null ? "" : "visibility: hidden")
|
||||
) #{translate("sign_up_now")}
|
||||
.col-md-4
|
||||
.card.card-highlighted
|
||||
|
@ -90,10 +90,8 @@ block content
|
|||
| {{plans[currencyCode]['collaborator']['annual']}}
|
||||
span.small /yr
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:10})}
|
||||
strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:8})}
|
||||
strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:12})}
|
||||
li
|
||||
strong #{translate("collabs_per_proj", {collabcount:10})}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
|
@ -144,7 +142,7 @@ block content
|
|||
br
|
||||
a.btn.btn-info(
|
||||
href="/register"
|
||||
style=(getLoggedInUserId() === undefined ? "" : "visibility: hidden")
|
||||
style=(getLoggedInUserId() === null ? "" : "visibility: hidden")
|
||||
) #{translate("sign_up_now")}
|
||||
|
||||
.col-md-4
|
||||
|
@ -157,9 +155,7 @@ block content
|
|||
span.small /mo
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:6})}
|
||||
strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:4})}
|
||||
strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:8})}
|
||||
strong #{translate("collabs_per_proj", {collabcount:6})}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
|
@ -180,9 +176,7 @@ block content
|
|||
span.small /yr
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:6})}
|
||||
strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:4})}
|
||||
strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:8})}
|
||||
strong #{translate("collabs_per_proj", {collabcount:6})}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
|
|
134
services/web/npm-shrinkwrap.json
generated
134
services/web/npm-shrinkwrap.json
generated
|
@ -533,6 +533,12 @@
|
|||
"resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-helper-builder-react-jsx": {
|
||||
"version": "6.26.0",
|
||||
"from": "babel-helper-builder-react-jsx@>=6.24.1 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-helper-call-delegate": {
|
||||
"version": "6.24.1",
|
||||
"from": "babel-helper-call-delegate@>=6.24.1 <7.0.0",
|
||||
|
@ -629,6 +635,18 @@
|
|||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-syntax-flow": {
|
||||
"version": "6.18.0",
|
||||
"from": "babel-plugin-syntax-flow@>=6.18.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-syntax-jsx": {
|
||||
"version": "6.18.0",
|
||||
"from": "babel-plugin-syntax-jsx@>=6.3.13 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-syntax-trailing-function-commas": {
|
||||
"version": "6.22.0",
|
||||
"from": "babel-plugin-syntax-trailing-function-commas@>=6.22.0 <7.0.0",
|
||||
|
@ -779,6 +797,36 @@
|
|||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-flow-strip-types": {
|
||||
"version": "6.22.0",
|
||||
"from": "babel-plugin-transform-flow-strip-types@>=6.22.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-react-display-name": {
|
||||
"version": "6.25.0",
|
||||
"from": "babel-plugin-transform-react-display-name@>=6.23.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-react-jsx": {
|
||||
"version": "6.24.1",
|
||||
"from": "babel-plugin-transform-react-jsx@>=6.24.1 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-react-jsx-self": {
|
||||
"version": "6.22.0",
|
||||
"from": "babel-plugin-transform-react-jsx-self@>=6.22.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-react-jsx-source": {
|
||||
"version": "6.22.0",
|
||||
"from": "babel-plugin-transform-react-jsx-source@>=6.22.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-plugin-transform-regenerator": {
|
||||
"version": "6.26.0",
|
||||
"from": "babel-plugin-transform-regenerator@>=6.22.0 <7.0.0",
|
||||
|
@ -805,6 +853,18 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"babel-preset-flow": {
|
||||
"version": "6.23.0",
|
||||
"from": "babel-preset-flow@>=6.23.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-preset-react": {
|
||||
"version": "6.24.1",
|
||||
"from": "babel-preset-react@>=6.16.0 <7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"babel-register": {
|
||||
"version": "6.26.0",
|
||||
"from": "babel-register@>=6.26.0 <7.0.0",
|
||||
|
@ -2031,6 +2091,11 @@
|
|||
"resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"create-react-class": {
|
||||
"version": "15.6.3",
|
||||
"from": "create-react-class@>=15.6.0 <16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz"
|
||||
},
|
||||
"cross-spawn": {
|
||||
"version": "5.1.0",
|
||||
"from": "cross-spawn@>=5.0.1 <6.0.0",
|
||||
|
@ -3429,6 +3494,23 @@
|
|||
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"fbjs": {
|
||||
"version": "0.8.16",
|
||||
"from": "fbjs@>=0.8.9 <0.9.0",
|
||||
"resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz",
|
||||
"dependencies": {
|
||||
"core-js": {
|
||||
"version": "1.2.7",
|
||||
"from": "core-js@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz"
|
||||
},
|
||||
"promise": {
|
||||
"version": "7.3.1",
|
||||
"from": "promise@>=7.1.1 <8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz"
|
||||
}
|
||||
}
|
||||
},
|
||||
"fd-slicer": {
|
||||
"version": "1.0.1",
|
||||
"from": "fd-slicer@>=1.0.1 <1.1.0",
|
||||
|
@ -3744,6 +3826,11 @@
|
|||
"resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"fuse.js": {
|
||||
"version": "3.2.0",
|
||||
"from": "fuse.js@>=3.0.0 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.2.0.tgz"
|
||||
},
|
||||
"gauge": {
|
||||
"version": "2.7.4",
|
||||
"from": "gauge@>=2.7.3 <2.8.0",
|
||||
|
@ -5307,8 +5394,7 @@
|
|||
"is-stream": {
|
||||
"version": "1.1.0",
|
||||
"from": "is-stream@>=1.1.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
|
||||
"dev": true
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz"
|
||||
},
|
||||
"is-symbol": {
|
||||
"version": "1.0.1",
|
||||
|
@ -5355,6 +5441,11 @@
|
|||
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"isomorphic-fetch": {
|
||||
"version": "2.2.1",
|
||||
"from": "isomorphic-fetch@>=2.1.1 <3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz"
|
||||
},
|
||||
"isstream": {
|
||||
"version": "0.1.2",
|
||||
"from": "isstream@>=0.1.2 <0.2.0",
|
||||
|
@ -5391,8 +5482,7 @@
|
|||
"js-tokens": {
|
||||
"version": "3.0.2",
|
||||
"from": "js-tokens@>=3.0.2 <4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
|
||||
"dev": true
|
||||
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz"
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "2.0.5",
|
||||
|
@ -6858,8 +6948,7 @@
|
|||
"loose-envify": {
|
||||
"version": "1.3.1",
|
||||
"from": "loose-envify@>=1.0.0 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz",
|
||||
"dev": true
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz"
|
||||
},
|
||||
"loud-rejection": {
|
||||
"version": "1.6.0",
|
||||
|
@ -7620,6 +7709,11 @@
|
|||
"from": "nocache@2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/nocache/-/nocache-2.0.0.tgz"
|
||||
},
|
||||
"node-fetch": {
|
||||
"version": "1.7.3",
|
||||
"from": "node-fetch@>=1.0.1 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz"
|
||||
},
|
||||
"node-forge": {
|
||||
"version": "0.2.24",
|
||||
"from": "node-forge@0.2.24",
|
||||
|
@ -8663,6 +8757,11 @@
|
|||
"resolved": "https://registry.npmjs.org/prompt/-/prompt-0.2.14.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"prop-types": {
|
||||
"version": "15.6.1",
|
||||
"from": "prop-types@>=15.5.10 <16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz"
|
||||
},
|
||||
"proxy-addr": {
|
||||
"version": "1.0.10",
|
||||
"from": "proxy-addr@>=1.0.8 <1.1.0",
|
||||
|
@ -8967,6 +9066,16 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"react": {
|
||||
"version": "15.6.2",
|
||||
"from": "react@>=15.4.2 <16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz"
|
||||
},
|
||||
"react-dom": {
|
||||
"version": "15.6.2",
|
||||
"from": "react-dom@>=15.4.2 <16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz"
|
||||
},
|
||||
"read": {
|
||||
"version": "1.0.7",
|
||||
"from": "read@>=1.0.0 <1.1.0",
|
||||
|
@ -9676,8 +9785,7 @@
|
|||
"setimmediate": {
|
||||
"version": "1.0.5",
|
||||
"from": "setimmediate@>=1.0.4 <2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
|
||||
"dev": true
|
||||
"resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz"
|
||||
},
|
||||
"setprototypeof": {
|
||||
"version": "1.0.3",
|
||||
|
@ -10945,6 +11053,11 @@
|
|||
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"ua-parser-js": {
|
||||
"version": "0.7.17",
|
||||
"from": "ua-parser-js@>=0.7.9 <0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz"
|
||||
},
|
||||
"uglify-js": {
|
||||
"version": "2.4.24",
|
||||
"from": "uglify-js@>=2.4.0 <2.5.0",
|
||||
|
@ -12061,6 +12174,11 @@
|
|||
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz",
|
||||
"dev": true
|
||||
},
|
||||
"whatwg-fetch": {
|
||||
"version": "2.0.4",
|
||||
"from": "whatwg-fetch@>=0.10.0",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz"
|
||||
},
|
||||
"when": {
|
||||
"version": "3.7.8",
|
||||
"from": "when@>=3.7.7 <4.0.0",
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
"express-http-proxy": "^1.1.0",
|
||||
"express-session": "^1.14.2",
|
||||
"fs-extra": "^4.0.2",
|
||||
"fuse.js": "^3.0.0",
|
||||
"heapdump": "^0.3.7",
|
||||
"helmet": "^3.8.1",
|
||||
"http-proxy": "^1.8.1",
|
||||
|
@ -72,6 +73,8 @@
|
|||
"passport-oauth2-refresh": "^1.0.0",
|
||||
"passport-saml": "^0.15.0",
|
||||
"pug": "^2.0.0-beta6",
|
||||
"react": "^15.4.2",
|
||||
"react-dom": "^15.4.2",
|
||||
"redis-sharelatex": "git+https://github.com/sharelatex/redis-sharelatex.git#v1.0.4",
|
||||
"request": "^2.69.0",
|
||||
"requestretry": "^1.13.0",
|
||||
|
@ -95,6 +98,7 @@
|
|||
"babel-core": "^6.26.0",
|
||||
"babel-loader": "^7.1.2",
|
||||
"babel-preset-env": "^1.6.1",
|
||||
"babel-preset-react": "^6.16.0",
|
||||
"bunyan": "0.22.1",
|
||||
"chai": "3.5.0",
|
||||
"chai-spies": "",
|
||||
|
|
|
@ -119,6 +119,7 @@ define [
|
|||
customTogglerScope = scope.$new()
|
||||
|
||||
customTogglerScope.isOpen = true
|
||||
customTogglerScope.isVisible = true
|
||||
|
||||
if state[customTogglerPane]?.initClosed == true
|
||||
customTogglerScope.isOpen = false
|
||||
|
@ -132,6 +133,7 @@ define [
|
|||
repositionCustomToggler()
|
||||
customTogglerEl = $compile("
|
||||
<a href
|
||||
ng-show=\"isVisible\"
|
||||
class=\"custom-toggler #{ 'custom-toggler-' + customTogglerPane }\"
|
||||
ng-class=\"isOpen ? 'custom-toggler-open' : 'custom-toggler-closed'\"
|
||||
tooltip=\"{{ isOpen ? tooltipMsgWhenOpen : tooltipMsgWhenClosed }}\"
|
||||
|
@ -186,6 +188,11 @@ define [
|
|||
element.layout().hide("east")
|
||||
else
|
||||
element.layout().show("east")
|
||||
if hasCustomToggler
|
||||
customTogglerEl.scope().$applyAsync () ->
|
||||
customTogglerEl.scope().isOpen = !value
|
||||
customTogglerEl.scope().isVisible = !value
|
||||
|
||||
|
||||
post: (scope, element, attrs) ->
|
||||
name = attrs.layout
|
||||
|
|
|
@ -3,15 +3,6 @@ define [
|
|||
"ide/history/util/displayNameForUser"
|
||||
], (App, displayNameForUser) ->
|
||||
|
||||
App.controller "HistoryPremiumPopup", ($scope, ide, sixpack)->
|
||||
$scope.$watch "ui.view", ->
|
||||
if $scope.ui.view == "history"
|
||||
if $scope.project?.features?.versioning
|
||||
$scope.versioningPopupType = "default"
|
||||
else if $scope.ui.view == "history"
|
||||
sixpack.participate 'history-discount', ['default', 'discount'], (chosenVariation, rawResponse)->
|
||||
$scope.versioningPopupType = chosenVariation
|
||||
|
||||
App.controller "HistoryListController", ["$scope", "ide", ($scope, ide) ->
|
||||
$scope.hoveringOverListSelectors = false
|
||||
|
||||
|
|
|
@ -19,24 +19,8 @@ define [
|
|||
url = "#{url}&cc=#{couponCode}"
|
||||
$scope.startedFreeTrial = true
|
||||
|
||||
switch source
|
||||
when "dropbox"
|
||||
sixpack.participate 'teaser-dropbox-text', ['default', 'dropbox-focused'], (variant) ->
|
||||
event_tracking.sendMB "subscription-start-trial", { source, plan, variant }
|
||||
|
||||
when "history"
|
||||
sixpack.participate 'teaser-history', ['default', 'focused'], (variant) ->
|
||||
event_tracking.sendMB "subscription-start-trial", { source, plan, variant }
|
||||
|
||||
else
|
||||
event_tracking.sendMB "subscription-start-trial", { source, plan }
|
||||
event_tracking.sendMB "subscription-start-trial", { source, plan }
|
||||
|
||||
w.location = url
|
||||
|
||||
if $scope.shouldABTestPlans
|
||||
sixpack.participate 'plans-1610', ['default', 'heron', 'ibis'], (chosenVariation, rawResponse)->
|
||||
if chosenVariation in ['heron', 'ibis']
|
||||
plan = "collaborator_#{chosenVariation}"
|
||||
go()
|
||||
else
|
||||
go()
|
||||
go()
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
define [
|
||||
"base",
|
||||
"directives/creditCards"
|
||||
"libs/recurly-4.8.5"
|
||||
], (App)->
|
||||
|
||||
App.controller "NewSubscriptionController", ($scope, MultiCurrencyPricing, abTestManager, $http, sixpack, event_tracking, ccUtils)->
|
||||
|
@ -21,10 +22,6 @@ define [
|
|||
value: "credit_card"
|
||||
|
||||
$scope.data =
|
||||
number: ""
|
||||
month: ""
|
||||
year: ""
|
||||
cvv: ""
|
||||
first_name: ""
|
||||
last_name: ""
|
||||
postal_code: ""
|
||||
|
@ -34,16 +31,25 @@ define [
|
|||
city:""
|
||||
country:window.countryCode
|
||||
coupon: window.couponCode
|
||||
mmYY: ""
|
||||
|
||||
$scope.validation =
|
||||
correctCardNumber : true
|
||||
correctExpiry: true
|
||||
correctCvv: true
|
||||
|
||||
$scope.validation = {}
|
||||
|
||||
$scope.processing = false
|
||||
|
||||
recurly.configure window.recurlyApiKey
|
||||
recurly.configure
|
||||
publicKey: window.recurlyApiKey
|
||||
style:
|
||||
all:
|
||||
fontFamily: '"Open Sans", sans-serif',
|
||||
fontSize: '16px',
|
||||
fontColor: '#7a7a7a'
|
||||
month:
|
||||
placeholder: 'MM'
|
||||
year:
|
||||
placeholder: 'YY'
|
||||
cvv:
|
||||
placeholder: 'CVV'
|
||||
|
||||
|
||||
pricing = recurly.Pricing()
|
||||
window.pricing = pricing
|
||||
|
@ -73,6 +79,8 @@ define [
|
|||
$scope.normalPrice += (basePrice * pricing.price.taxes[0].rate)
|
||||
$scope.$apply()
|
||||
|
||||
|
||||
|
||||
$scope.applyCoupon = ->
|
||||
pricing.coupon($scope.data.coupon).done()
|
||||
|
||||
|
@ -83,26 +91,6 @@ define [
|
|||
$scope.currencyCode = newCurrency
|
||||
pricing.currency(newCurrency).done()
|
||||
|
||||
$scope.updateExpiry = () ->
|
||||
parsedDateObj = ccUtils.parseExpiry $scope.data.mmYY
|
||||
if parsedDateObj?
|
||||
$scope.data.month = parsedDateObj.month
|
||||
$scope.data.year = parsedDateObj.year
|
||||
|
||||
$scope.validateCardNumber = validateCardNumber = ->
|
||||
$scope.validation.errorFields = {}
|
||||
if $scope.data.number?.length != 0
|
||||
$scope.validation.correctCardNumber = recurly.validate.cardNumber($scope.data.number)
|
||||
|
||||
$scope.validateExpiry = validateExpiry = ->
|
||||
$scope.validation.errorFields = {}
|
||||
if $scope.data.month?.length != 0 and $scope.data.year?.length != 0
|
||||
$scope.validation.correctExpiry = recurly.validate.expiry($scope.data.month, $scope.data.year)
|
||||
|
||||
$scope.validateCvv = validateCvv = ->
|
||||
$scope.validation.errorFields = {}
|
||||
if $scope.data.cvv?.length != 0
|
||||
$scope.validation.correctCvv = recurly.validate.cvv($scope.data.cvv)
|
||||
|
||||
$scope.inputHasError = inputHasError = (formItem) ->
|
||||
if !formItem?
|
||||
|
@ -114,10 +102,7 @@ define [
|
|||
if $scope.paymentMethod.value == 'paypal'
|
||||
return $scope.data.country != ""
|
||||
else
|
||||
return (form.$valid and
|
||||
$scope.validation.correctCardNumber and
|
||||
$scope.validation.correctExpiry and
|
||||
$scope.validation.correctCvv)
|
||||
return form.$valid
|
||||
|
||||
$scope.updateCountry = ->
|
||||
pricing.address({country:$scope.data.country}).done()
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
define [
|
||||
"base"
|
||||
"libs/recurly-3.0.5"
|
||||
"libs/recurly-4.8.5"
|
||||
], (App, recurly) ->
|
||||
|
||||
|
||||
|
@ -11,164 +11,6 @@ define [
|
|||
return {
|
||||
currencyCode:currencyCode
|
||||
|
||||
heron:
|
||||
USD:
|
||||
student:
|
||||
monthly: "$6"
|
||||
annual: "$60"
|
||||
collaborator:
|
||||
monthly: "$12"
|
||||
annual: "$144"
|
||||
EUR:
|
||||
student:
|
||||
monthly: "€5"
|
||||
annual: "€50"
|
||||
collaborator:
|
||||
monthly: "€11"
|
||||
annual: "€132"
|
||||
GBP:
|
||||
student:
|
||||
monthly: "£5"
|
||||
annual: "£50"
|
||||
collaborator:
|
||||
monthly: "£10"
|
||||
annual: "£120"
|
||||
SEK:
|
||||
student:
|
||||
monthly: "45 kr"
|
||||
annual: "450 kr"
|
||||
collaborator:
|
||||
monthly: "90 kr"
|
||||
annual: "1080 kr"
|
||||
CAD:
|
||||
student:
|
||||
monthly: "$7"
|
||||
annual: "$70"
|
||||
collaborator:
|
||||
monthly: "$14"
|
||||
annual: "$168"
|
||||
NOK:
|
||||
student:
|
||||
monthly: "45 kr"
|
||||
annual: "450 kr"
|
||||
collaborator:
|
||||
monthly: "90 kr"
|
||||
annual: "1080 kr"
|
||||
DKK:
|
||||
student:
|
||||
monthly: "40 kr"
|
||||
annual: "400 kr"
|
||||
collaborator:
|
||||
monthly: "70 kr"
|
||||
annual: "840 kr"
|
||||
AUD:
|
||||
student:
|
||||
monthly: "$8"
|
||||
annual: "$80"
|
||||
collaborator:
|
||||
monthly: "$15"
|
||||
annual: "$180"
|
||||
NZD:
|
||||
student:
|
||||
monthly: "$8"
|
||||
annual: "$80"
|
||||
collaborator:
|
||||
monthly: "$15"
|
||||
annual: "$180"
|
||||
CHF:
|
||||
student:
|
||||
monthly: "Fr 6"
|
||||
annual: "Fr 60"
|
||||
collaborator:
|
||||
monthly: "Fr 12"
|
||||
annual: "Fr 144"
|
||||
SGD:
|
||||
student:
|
||||
monthly: "$8"
|
||||
annual: "$80"
|
||||
collaborator:
|
||||
monthly: "$16"
|
||||
annual: "$192"
|
||||
|
||||
ibis:
|
||||
USD:
|
||||
student:
|
||||
monthly: "$10"
|
||||
annual: "$100"
|
||||
collaborator:
|
||||
monthly: "$18"
|
||||
annual: "$216"
|
||||
EUR:
|
||||
student:
|
||||
monthly: "€9"
|
||||
annual: "€90"
|
||||
collaborator:
|
||||
monthly: "€17"
|
||||
annual: "€204"
|
||||
GBP:
|
||||
student:
|
||||
monthly: "£7"
|
||||
annual: "£70"
|
||||
collaborator:
|
||||
monthly: "£14"
|
||||
annual: "£168"
|
||||
SEK:
|
||||
student:
|
||||
monthly: "75 kr"
|
||||
annual: "750 kr"
|
||||
collaborator:
|
||||
monthly: "140 kr"
|
||||
annual: "1680 kr"
|
||||
CAD:
|
||||
student:
|
||||
monthly: "$12"
|
||||
annual: "$120"
|
||||
collaborator:
|
||||
monthly: "$22"
|
||||
annual: "$264"
|
||||
NOK:
|
||||
student:
|
||||
monthly: "75 kr"
|
||||
annual: "750 kr"
|
||||
collaborator:
|
||||
monthly: "140 kr"
|
||||
annual: "1680 kr"
|
||||
DKK:
|
||||
student:
|
||||
monthly: "68 kr"
|
||||
annual: "680 kr"
|
||||
collaborator:
|
||||
monthly: "110 kr"
|
||||
annual: "1320 kr"
|
||||
AUD:
|
||||
student:
|
||||
monthly: "$13"
|
||||
annual: "$130"
|
||||
collaborator:
|
||||
monthly: "$22"
|
||||
annual: "$264"
|
||||
NZD:
|
||||
student:
|
||||
monthly: "$14"
|
||||
annual: "$140"
|
||||
collaborator:
|
||||
monthly: "$22"
|
||||
annual: "$264"
|
||||
CHF:
|
||||
student:
|
||||
monthly: "Fr 10"
|
||||
annual: "Fr 100"
|
||||
collaborator:
|
||||
monthly: "Fr 18"
|
||||
annual: "Fr 216"
|
||||
SGD:
|
||||
student:
|
||||
monthly: "$14"
|
||||
annual: "$140"
|
||||
collaborator:
|
||||
monthly: "$25"
|
||||
annual: "$300"
|
||||
|
||||
plans:
|
||||
USD:
|
||||
symbol: "$"
|
||||
|
@ -312,14 +154,6 @@ define [
|
|||
$scope.shouldABTestPlans = window.shouldABTestPlans
|
||||
|
||||
if $scope.shouldABTestPlans
|
||||
sixpack.participate 'plans-1610', ['default', 'heron', 'ibis'], (chosenVariation, rawResponse)->
|
||||
$scope.plansVariant = chosenVariation
|
||||
event_tracking.sendMB 'plans-page', {plans_variant: chosenVariation}
|
||||
if chosenVariation in ['heron', 'ibis']
|
||||
# overwrite student plans with alternative
|
||||
for currency, _v of $scope.plans
|
||||
$scope.plans[currency]['student'] = MultiCurrencyPricing[chosenVariation][currency]['student']
|
||||
$scope.plans[currency]['collaborator'] = MultiCurrencyPricing[chosenVariation][currency]['collaborator']
|
||||
$scope.showPlans = true
|
||||
else
|
||||
$scope.showPlans = true
|
||||
|
|
|
@ -3,8 +3,6 @@ define [
|
|||
], (App)->
|
||||
|
||||
App.controller 'SuccessfulSubscriptionController', ($scope, sixpack) ->
|
||||
sixpack.convert 'plans-1610', () ->
|
||||
|
||||
|
||||
SUBSCRIPTION_URL = "/user/subscription/update"
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
6
services/web/public/js/libs/recurly-4.8.5.js
Normal file
6
services/web/public/js/libs/recurly-4.8.5.js
Normal file
File diff suppressed because one or more lines are too long
1
services/web/public/js/libs/recurly.min.js
vendored
1
services/web/public/js/libs/recurly.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -84,8 +84,4 @@
|
|||
@import "../js/libs/pdfListView/TextLayer.css";
|
||||
@import "../js/libs/pdfListView/AnnotationsLayer.css";
|
||||
@import "../js/libs/pdfListView/HighlightsLayer.css";
|
||||
|
||||
// CodeMirror
|
||||
& when (@show-rich-text) {
|
||||
@import "vendor/codemirror.css";
|
||||
}
|
||||
@import "vendor/codemirror.css";
|
||||
|
|
|
@ -12,9 +12,7 @@
|
|||
@import "./editor/online-users.less";
|
||||
@import "./editor/hotkeys.less";
|
||||
@import "./editor/review-panel.less";
|
||||
& when (@show-rich-text) {
|
||||
@import "./editor/rich-text.less";
|
||||
}
|
||||
@import "./editor/rich-text.less";
|
||||
|
||||
@ui-layout-toggler-def-height: 50px;
|
||||
@ui-resizer-size: 7px;
|
||||
|
@ -82,10 +80,7 @@
|
|||
.full-size;
|
||||
}
|
||||
|
||||
#editor when (@show-rich-text = true) {
|
||||
top: 40px; // TODO: replace with toolbar height var
|
||||
}
|
||||
#editor-rich-text when (@show-rich-text = true) {
|
||||
#editor-rich-text {
|
||||
top: 40px; // TODO: replace with toolbar height var
|
||||
}
|
||||
|
||||
|
@ -305,7 +300,6 @@
|
|||
}
|
||||
|
||||
.ui-layout-resizer when (@is-overleaf = true) {
|
||||
z-index: 5 !important;
|
||||
width: @ui-resizer-size !important;
|
||||
background-color: @editor-resizer-bg-color;
|
||||
&.ui-layout-resizer-closed {
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
.recurly-hosted-field {
|
||||
&:extend(.form-control);
|
||||
}
|
||||
|
||||
|
||||
.recurly {
|
||||
display: block;
|
||||
position: relative;
|
||||
|
|
|
@ -309,8 +309,9 @@ input[type="checkbox"],
|
|||
.has-warning {
|
||||
.form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg);
|
||||
}
|
||||
.has-error {
|
||||
.has-external-error {
|
||||
.form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg);
|
||||
color:@red;
|
||||
}
|
||||
|
||||
.form-control.ng-dirty.ng-invalid:not(:focus) {
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
async = require "async"
|
||||
expect = require("chai").expect
|
||||
_ = require 'underscore'
|
||||
mkdirp = require "mkdirp"
|
||||
|
||||
Settings = require "settings-sharelatex"
|
||||
MockFileStoreApi = require './helpers/MockFileStoreApi'
|
||||
request = require "./helpers/request"
|
||||
User = require "./helpers/User"
|
||||
|
@ -22,7 +24,8 @@ describe "LinkedFiles", ->
|
|||
LinkedUrlProxy.listen 6543, (error) =>
|
||||
return done(error) if error?
|
||||
@owner = new User()
|
||||
@owner.login done
|
||||
@owner.login ->
|
||||
mkdirp Settings.path.dumpFolder, done
|
||||
|
||||
describe "creating a URL based linked file", ->
|
||||
before (done) ->
|
||||
|
@ -181,4 +184,4 @@ describe "LinkedFiles", ->
|
|||
done()
|
||||
|
||||
# TODO: Add test for asking for host that return ENOTFOUND
|
||||
# (This will probably end up handled by the proxy)
|
||||
# (This will probably end up handled by the proxy)
|
||||
|
|
|
@ -186,10 +186,13 @@ describe "ProjectDuplicateNames", ->
|
|||
contentType: 'image/jpeg'
|
||||
, (err, res, body) =>
|
||||
@body = body
|
||||
# update the image id because we have replaced the file
|
||||
@imageFile._id = @body.entity_id
|
||||
done()
|
||||
|
||||
it "should succeed (overwriting the file)", ->
|
||||
expect(@body.success).to.equal true
|
||||
# at this point the @imageFile._id has changed
|
||||
|
||||
describe "for an existing folder", ->
|
||||
describe "trying to add a doc with the same name", ->
|
||||
|
|
|
@ -56,7 +56,6 @@ describe "ProjectStructureChanges", ->
|
|||
describe "duplicating a project", ->
|
||||
before (done) ->
|
||||
MockDocUpdaterApi.clearProjectStructureUpdates()
|
||||
console.log(example_project_id)
|
||||
@owner.request.post {
|
||||
uri: "/Project/#{example_project_id}/clone",
|
||||
json:
|
||||
|
@ -219,11 +218,17 @@ describe "ProjectStructureChanges", ->
|
|||
if res.statusCode < 200 || res.statusCode >= 300
|
||||
throw new Error("failed to upload file #{res.statusCode}")
|
||||
|
||||
example_file_id = JSON.parse(body).entity_id
|
||||
|
||||
{fileUpdates:updates, version} = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id)
|
||||
expect(updates.length).to.equal(1)
|
||||
expect(updates.length).to.equal(2)
|
||||
update = updates[0]
|
||||
expect(update.userId).to.equal(@owner._id)
|
||||
expect(update.pathname).to.equal("/1pixel.png")
|
||||
#expect(update.url).to.be.a('string');
|
||||
update = updates[1]
|
||||
expect(update.userId).to.equal(@owner._id)
|
||||
expect(update.pathname).to.equal("/1pixel.png")
|
||||
expect(update.url).to.be.a('string');
|
||||
expect(version).to.equal(@project_0.version + 1)
|
||||
|
||||
|
@ -591,10 +596,14 @@ describe "ProjectStructureChanges", ->
|
|||
throw new Error("failed to upload file #{res.statusCode}")
|
||||
|
||||
{fileUpdates:updates, version} = MockDocUpdaterApi.getProjectStructureUpdates(@tpds_project_id)
|
||||
expect(updates.length).to.equal(1)
|
||||
expect(updates.length).to.equal(2)
|
||||
update = updates[0]
|
||||
expect(update.userId).to.equal(@owner._id)
|
||||
expect(update.pathname).to.equal("/1pixel.png")
|
||||
#expect(update.url).to.be.a('string');
|
||||
update = updates[1]
|
||||
expect(update.userId).to.equal(@owner._id)
|
||||
expect(update.pathname).to.equal("/1pixel.png")
|
||||
expect(update.url).to.be.a('string');
|
||||
expect(version).to.equal(@project_0.version + 1)
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ describe "EditorController", ->
|
|||
@fsPath = "/folder/file.png"
|
||||
@linkedFileData = {provider: 'url'}
|
||||
|
||||
@newFile = _id: "new-file-id"
|
||||
|
||||
@folder_id = "123ksajdn"
|
||||
@folder = _id: @folder_id
|
||||
@folderName = "folder"
|
||||
|
@ -107,7 +109,7 @@ describe "EditorController", ->
|
|||
|
||||
describe 'upsertFile', ->
|
||||
beforeEach ->
|
||||
@ProjectEntityUpdateHandler.upsertFile = sinon.stub().yields(null, @file, false)
|
||||
@ProjectEntityUpdateHandler.upsertFile = sinon.stub().yields(null, @newFile, false, @file)
|
||||
@EditorController.upsertFile @project_id, @folder_id, @fileName, @fsPath, @linkedFileData, @source, @user_id, @callback
|
||||
|
||||
it 'upserts the file using the project entity handler', ->
|
||||
|
@ -116,7 +118,7 @@ describe "EditorController", ->
|
|||
.should.equal true
|
||||
|
||||
it 'returns the file', ->
|
||||
@callback.calledWith(null, @file).should.equal true
|
||||
@callback.calledWith(null, @newFile).should.equal true
|
||||
|
||||
describe 'file does not exist', ->
|
||||
beforeEach ->
|
||||
|
@ -171,7 +173,7 @@ describe "EditorController", ->
|
|||
beforeEach ->
|
||||
@filePath = '/folder/file'
|
||||
|
||||
@ProjectEntityUpdateHandler.upsertFileWithPath = sinon.stub().yields(null, @file, false, [], @folder)
|
||||
@ProjectEntityUpdateHandler.upsertFileWithPath = sinon.stub().yields(null, @newFile, false, @file, [], @folder)
|
||||
@EditorController.upsertFileWithPath @project_id, @filePath, @fsPath, @linkedFileData, @source, @user_id, @callback
|
||||
|
||||
it 'upserts the file using the project entity handler', ->
|
||||
|
@ -181,7 +183,7 @@ describe "EditorController", ->
|
|||
|
||||
describe 'file does not exist', ->
|
||||
beforeEach ->
|
||||
@ProjectEntityUpdateHandler.upsertFileWithPath = sinon.stub().yields(null, @file, true, [], @folder)
|
||||
@ProjectEntityUpdateHandler.upsertFileWithPath = sinon.stub().yields(null, @file, true, undefined, [], @folder)
|
||||
@EditorController.upsertFileWithPath @project_id, @filePath, @fsPath, @linkedFileData, @source, @user_id, @callback
|
||||
|
||||
it 'should send the update for the file out to users in the project', ->
|
||||
|
@ -195,7 +197,7 @@ describe "EditorController", ->
|
|||
@folderA = { _id: 2, parentFolder_id: 1}
|
||||
@folderB = { _id: 3, parentFolder_id: 2}
|
||||
]
|
||||
@ProjectEntityUpdateHandler.upsertFileWithPath = sinon.stub().yields(null, @file, true, folders, @folderB)
|
||||
@ProjectEntityUpdateHandler.upsertFileWithPath = sinon.stub().yields(null, @file, true, undefined, folders, @folderB)
|
||||
@EditorController.upsertFileWithPath @project_id, @filePath, @fsPath, @linkedFileData, @source, @user_id, @callback
|
||||
|
||||
it 'should send the update for each folder to users in the project', ->
|
||||
|
|
|
@ -93,33 +93,50 @@ describe 'ProjectEntityMongoUpdateHandler', ->
|
|||
.calledWith(@project, folder_id, @file, 'file', @callback)
|
||||
.should.equal true
|
||||
|
||||
describe 'replaceFile', ->
|
||||
describe 'replaceFileWithNew', ->
|
||||
beforeEach ->
|
||||
@file = _id: file_id
|
||||
@path = mongo: 'file.png'
|
||||
@linkedFileData = {provider: 'url'}
|
||||
@newFile = _id: 'new-file-id'
|
||||
@newFile.linkedFileData = @linkedFileData = {provider: 'url'}
|
||||
@ProjectLocator.findElement = sinon.stub().yields(null, @file, @path)
|
||||
@ProjectModel.update = sinon.stub().yields()
|
||||
|
||||
@subject.replaceFile project_id, file_id, @linkedFileData, @callback
|
||||
@subject.replaceFileWithNew project_id, file_id, @newFile, @callback
|
||||
|
||||
it 'gets the project', ->
|
||||
@ProjectGetter.getProjectWithoutLock
|
||||
.calledWith(project_id, {rootFolder:true, name: true})
|
||||
.should.equal true
|
||||
|
||||
it 'finds the element', ->
|
||||
it 'finds the existing element', ->
|
||||
@ProjectLocator.findElement
|
||||
.calledWith({ @project, element_id: file_id, type: 'file' })
|
||||
.should.equal true
|
||||
|
||||
it 'increments the rev and sets the created_at', ->
|
||||
it 'inserts a deletedFile reference for the old file', ->
|
||||
@ProjectModel.update
|
||||
.calledWith({ _id: project_id },
|
||||
{
|
||||
$push: {
|
||||
deletedFiles: {
|
||||
_id: file_id
|
||||
name: @file.name
|
||||
linkedFileData: @file.linkedFileData
|
||||
deletedAt: new Date()
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
.should.equal true
|
||||
|
||||
it 'increments the project version and sets the rev and created_at', ->
|
||||
@ProjectModel.update
|
||||
.calledWith(
|
||||
{ _id: project_id },
|
||||
{
|
||||
'$inc': { 'file.png.rev': 1, 'version': 1 }
|
||||
'$set': { 'file.png.created': new Date(), 'file.png.linkedFileData': @linkedFileData }
|
||||
'$inc': { 'version': 1 }
|
||||
'$set': { 'file.png._id': @newFile._id, 'file.png.created': new Date(), 'file.png.linkedFileData': @linkedFileData, 'file.png.rev': 1 }
|
||||
}
|
||||
{}
|
||||
)
|
||||
|
@ -596,3 +613,29 @@ describe 'ProjectEntityMongoUpdateHandler', ->
|
|||
folder = name: 'folder_name'
|
||||
@subject._checkValidMove @project, 'folder', folder, fileSystem: '/foo', @destFolder._id, (err) =>
|
||||
expect(err).to.deep.equal new Errors.InvalidNameError("destination folder is a child folder of me")
|
||||
|
||||
describe "_insertDeletedDocReference", ->
|
||||
beforeEach ->
|
||||
@doc =
|
||||
_id: ObjectId()
|
||||
name: "test.tex"
|
||||
@callback = sinon.stub()
|
||||
@ProjectModel.update = sinon.stub().yields()
|
||||
@subject._insertDeletedDocReference project_id, @doc, @callback
|
||||
|
||||
it "should insert the doc into deletedDocs", ->
|
||||
@ProjectModel.update
|
||||
.calledWith({
|
||||
_id: project_id
|
||||
}, {
|
||||
$push: {
|
||||
deletedDocs: {
|
||||
_id: @doc._id
|
||||
name: @doc.name
|
||||
}
|
||||
}
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
|
@ -14,6 +14,7 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
file_id = "4eecaffcbffa66588e000009"
|
||||
folder_id = "4eecaffcbffa66588e000008"
|
||||
rootFolderId = "4eecaffcbffa66588e000007"
|
||||
new_file_id = "4eecaffcbffa66588e000099"
|
||||
userId = 1234
|
||||
|
||||
beforeEach ->
|
||||
|
@ -31,7 +32,11 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
@FileModel = class File
|
||||
constructor:(options)->
|
||||
{@name} = options
|
||||
@._id = file_id
|
||||
# use a new id for replacement files
|
||||
if @name is 'dummy-upload-filename'
|
||||
@._id = new_file_id
|
||||
else
|
||||
@._id = file_id
|
||||
@rev = 0
|
||||
|
||||
@docName = "doc-name"
|
||||
|
@ -274,14 +279,33 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
beforeEach ->
|
||||
@path = "/path/to/file"
|
||||
|
||||
@newFile = _id: file_id
|
||||
@ProjectEntityUpdateHandler.addFileWithoutUpdatingHistory =
|
||||
withoutLock: sinon.stub().yields(null, @newFile, folder_id, @path, @fileUrl)
|
||||
@ProjectEntityUpdateHandler.addFile project_id, folder_id, @docName, @fileSystemPath, @linkedFileData, userId, @callback
|
||||
@newFile = {_id: file_id, rev: 0, name: @fileName}
|
||||
@TpdsUpdateSender.addFile = sinon.stub().yields()
|
||||
@ProjectEntityMongoUpdateHandler.addFile = sinon.stub().yields(null, {path: fileSystem: @path}, @project)
|
||||
@ProjectEntityUpdateHandler.addFile project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId, @callback
|
||||
|
||||
it "creates the doc without history", () ->
|
||||
@ProjectEntityUpdateHandler.addFileWithoutUpdatingHistory.withoutLock
|
||||
.calledWith(project_id, folder_id, @docName, @fileSystemPath, @linkedFileData, userId)
|
||||
it "updates the file in the filestore", () ->
|
||||
@FileStoreHandler.uploadFileFromDisk
|
||||
.calledWith(project_id, file_id, @fileSystemPath)
|
||||
.should.equal true
|
||||
|
||||
it "updates the file in mongo", () ->
|
||||
fileMatcher = sinon.match (file) =>
|
||||
file.name == @fileName
|
||||
|
||||
@ProjectEntityMongoUpdateHandler.addFile
|
||||
.calledWithMatch(project_id, folder_id, fileMatcher)
|
||||
.should.equal true
|
||||
|
||||
it "notifies the tpds", () ->
|
||||
@TpdsUpdateSender.addFile
|
||||
.calledWith({
|
||||
project_id: project_id
|
||||
project_name: @project.name
|
||||
file_id: file_id
|
||||
rev: 0
|
||||
path: @path
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "sends the change in project structure to the doc updater", () ->
|
||||
|
@ -296,21 +320,26 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
|
||||
describe 'replaceFile', ->
|
||||
beforeEach ->
|
||||
@newFile = _id: file_id, rev: 0
|
||||
# replacement file now creates a new file object
|
||||
@newFileUrl = "new-file-url"
|
||||
@FileStoreHandler.uploadFileFromDisk = sinon.stub().yields(null, @newFileUrl)
|
||||
|
||||
@newFile = _id: new_file_id, name: "dummy-upload-filename", rev: 0
|
||||
@oldFile = _id: file_id
|
||||
@path = "/path/to/file"
|
||||
@project = _id: project_id, name: 'some project'
|
||||
@ProjectEntityMongoUpdateHandler.replaceFile = sinon.stub().yields(null, @newFile, @project, fileSystem: @path)
|
||||
|
||||
@ProjectEntityMongoUpdateHandler._insertDeletedFileReference = sinon.stub().yields()
|
||||
@ProjectEntityMongoUpdateHandler.replaceFileWithNew = sinon.stub().yields(null, @oldFile, @project, fileSystem: @path)
|
||||
@ProjectEntityUpdateHandler.replaceFile project_id, file_id, @fileSystemPath, @linkedFileData, userId, @callback
|
||||
|
||||
it 'uploads a new version of the file', ->
|
||||
@FileStoreHandler.uploadFileFromDisk
|
||||
.calledWith(project_id, file_id, @fileSystemPath)
|
||||
.calledWith(project_id, new_file_id, @fileSystemPath)
|
||||
.should.equal true
|
||||
|
||||
it 'replaces the file in mongo', ->
|
||||
@ProjectEntityMongoUpdateHandler.replaceFile
|
||||
.calledWith(project_id, file_id, @linkedFileData)
|
||||
@ProjectEntityMongoUpdateHandler.replaceFileWithNew
|
||||
.calledWith(project_id, file_id, @newFile)
|
||||
.should.equal true
|
||||
|
||||
it 'notifies the tpds', ->
|
||||
|
@ -318,20 +347,24 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
.calledWith({
|
||||
project_id: project_id
|
||||
project_name: @project.name
|
||||
file_id: file_id
|
||||
file_id: new_file_id
|
||||
rev: @newFile.rev + 1
|
||||
path: @path
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it 'updates the project structure in the doc updater', ->
|
||||
oldFiles = [
|
||||
file: @oldFile
|
||||
path: @path
|
||||
]
|
||||
newFiles = [
|
||||
file: @newFile
|
||||
path: @path
|
||||
url: @fileUrl
|
||||
url: @newFileUrl
|
||||
]
|
||||
@DocumentUpdaterHandler.updateProjectStructure
|
||||
.calledWith(project_id, userId, {newFiles})
|
||||
.calledWith(project_id, userId, {oldFiles, newFiles})
|
||||
.should.equal true
|
||||
|
||||
describe 'addDocWithoutUpdatingHistory', ->
|
||||
|
@ -490,12 +523,12 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
@existingFile = _id: file_id, name: @fileName
|
||||
@folder = _id: folder_id, fileRefs: [@existingFile]
|
||||
@ProjectLocator.findElement = sinon.stub().yields(null, @folder)
|
||||
@ProjectEntityUpdateHandler.replaceFile = withoutLock: sinon.stub().yields(null, @newFile)
|
||||
@ProjectEntityUpdateHandler.replaceFile = mainTask: sinon.stub().yields(null, @newFile)
|
||||
|
||||
@ProjectEntityUpdateHandler.upsertFile project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId, @callback
|
||||
|
||||
it 'replaces the file', ->
|
||||
@ProjectEntityUpdateHandler.replaceFile.withoutLock
|
||||
@ProjectEntityUpdateHandler.replaceFile.mainTask
|
||||
.calledWith(project_id, file_id, @fileSystemPath, @linkedFileData, userId)
|
||||
.should.equal true
|
||||
|
||||
|
@ -507,7 +540,7 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
@folder = _id: folder_id, fileRefs: []
|
||||
@newFile = _id: file_id
|
||||
@ProjectLocator.findElement = sinon.stub().yields(null, @folder)
|
||||
@ProjectEntityUpdateHandler.addFile = withoutLock: sinon.stub().yields(null, @newFile)
|
||||
@ProjectEntityUpdateHandler.addFile = mainTask: sinon.stub().yields(null, @newFile)
|
||||
|
||||
@ProjectEntityUpdateHandler.upsertFile project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId, @callback
|
||||
|
||||
|
@ -517,7 +550,7 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
.should.equal true
|
||||
|
||||
it 'adds the file', ->
|
||||
@ProjectEntityUpdateHandler.addFile.withoutLock
|
||||
@ProjectEntityUpdateHandler.addFile.mainTask
|
||||
.calledWith(project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId)
|
||||
.should.equal true
|
||||
|
||||
|
@ -563,7 +596,7 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
@ProjectEntityUpdateHandler.mkdirp =
|
||||
withoutLock: sinon.stub().yields(null, @newFolders, @folder)
|
||||
@ProjectEntityUpdateHandler.upsertFile =
|
||||
withoutLock: sinon.stub().yields(null, @file, @isNewFile)
|
||||
mainTask: sinon.stub().yields(null, @file, @isNewFile)
|
||||
|
||||
@ProjectEntityUpdateHandler.upsertFileWithPath project_id, @path, @fileSystemPath, @linkedFileData, userId, @callback
|
||||
|
||||
|
@ -573,13 +606,13 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
.should.equal true
|
||||
|
||||
it 'upserts the file', ->
|
||||
@ProjectEntityUpdateHandler.upsertFile.withoutLock
|
||||
@ProjectEntityUpdateHandler.upsertFile.mainTask
|
||||
.calledWith(project_id, @folder._id, 'file.png', @fileSystemPath, @linkedFileData, userId)
|
||||
.should.equal true
|
||||
|
||||
it 'calls the callback', ->
|
||||
@callback
|
||||
.calledWith(null, @file, @isNewFile, @newFolders, @folder)
|
||||
.calledWith(null, @file, @isNewFile, undefined, @newFolders, @folder)
|
||||
.should.equal true
|
||||
|
||||
describe 'deleteEntity', ->
|
||||
|
@ -798,6 +831,7 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
@FileStoreHandler.deleteFile = sinon.stub().yields()
|
||||
@DocumentUpdaterHandler.deleteDoc = sinon.stub().yields()
|
||||
@ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
|
||||
@ProjectEntityMongoUpdateHandler._insertDeletedFileReference = sinon.stub().yields()
|
||||
|
||||
describe "a file", ->
|
||||
beforeEach (done) ->
|
||||
|
@ -805,8 +839,13 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
@entity = _id: @entity_id
|
||||
@ProjectEntityUpdateHandler._cleanUpEntity @project, @entity, 'file', @path, userId, done
|
||||
|
||||
it "should delete the file from FileStoreHandler", ->
|
||||
@FileStoreHandler.deleteFile.calledWith(project_id, @entity_id).should.equal true
|
||||
it "should insert the file into the deletedFiles array", ->
|
||||
@ProjectEntityMongoUpdateHandler._insertDeletedFileReference
|
||||
.calledWith(@project._id, @entity)
|
||||
.should.equal true
|
||||
|
||||
it "should not delete the file from FileStoreHandler", ->
|
||||
@FileStoreHandler.deleteFile.calledWith(project_id, @entity_id).should.equal false
|
||||
|
||||
it "should not attempt to delete from the document updater", ->
|
||||
@DocumentUpdaterHandler.deleteDoc.called.should.equal false
|
||||
|
@ -871,7 +910,7 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
name: "test.tex"
|
||||
@path = "/path/to/doc"
|
||||
@ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields()
|
||||
@ProjectEntityUpdateHandler._insertDeletedDocReference = sinon.stub().yields()
|
||||
@ProjectEntityMongoUpdateHandler._insertDeletedDocReference = sinon.stub().yields()
|
||||
@DocumentUpdaterHandler.deleteDoc = sinon.stub().yields()
|
||||
@DocstoreManager.deleteDoc = sinon.stub().yields()
|
||||
@callback = sinon.stub()
|
||||
|
@ -891,7 +930,7 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
.calledWith(project_id, @doc._id.toString())
|
||||
|
||||
it "should insert the doc into the deletedDocs array", ->
|
||||
@ProjectEntityUpdateHandler._insertDeletedDocReference
|
||||
@ProjectEntityMongoUpdateHandler._insertDeletedDocReference
|
||||
.calledWith(@project._id, @doc)
|
||||
.should.equal true
|
||||
|
||||
|
@ -920,28 +959,4 @@ describe 'ProjectEntityUpdateHandler', ->
|
|||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "_insertDeletedDocReference", ->
|
||||
beforeEach ->
|
||||
@doc =
|
||||
_id: ObjectId()
|
||||
name: "test.tex"
|
||||
@callback = sinon.stub()
|
||||
@ProjectModel.update = sinon.stub().yields()
|
||||
@ProjectEntityUpdateHandler._insertDeletedDocReference project_id, @doc, @callback
|
||||
|
||||
it "should insert the doc into deletedDocs", ->
|
||||
@ProjectModel.update
|
||||
.calledWith({
|
||||
_id: project_id
|
||||
}, {
|
||||
$push: {
|
||||
deletedDocs: {
|
||||
_id: @doc._id
|
||||
name: @doc.name
|
||||
}
|
||||
}
|
||||
})
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
|
|
|
@ -52,6 +52,7 @@ module.exports = {
|
|||
loader: 'babel-loader',
|
||||
options: {
|
||||
presets: [
|
||||
'react',
|
||||
['env', { modules: false }]
|
||||
],
|
||||
// Configure babel-loader to cache compiled output so that subsequent
|
||||
|
@ -64,4 +65,5 @@ module.exports = {
|
|||
|
||||
// TODO
|
||||
// plugins: {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,8 +5,7 @@ const merge = require('webpack-merge')
|
|||
const base = require('./webpack.config')
|
||||
|
||||
module.exports = merge(base, {
|
||||
// Enable a full source map.
|
||||
devtool: 'source-map',
|
||||
devtool: false,
|
||||
|
||||
output: {
|
||||
// Override output path to minjs dir
|
||||
|
|
Loading…
Add table
Reference in a new issue