Merge branch 'master' into ja-restore-files

This commit is contained in:
James Allen 2018-04-16 16:14:31 +01:00 committed by GitHub
commit 9e525702c1
41 changed files with 603 additions and 4730 deletions

View file

@ -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"

View file

@ -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)->)->

View file

@ -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 = [])->

View file

@ -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

View file

@ -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

View file

@ -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"

View file

@ -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

View file

@ -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?

View file

@ -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 :

View file

@ -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": {

View file

@ -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",

View file

@ -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();",

View file

@ -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")

View file

@ -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")}

View file

@ -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")}

View file

@ -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")} .

View file

@ -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

View file

@ -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")}

View file

@ -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",

View file

@ -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": "",

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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";

View file

@ -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 {

View file

@ -1,3 +1,8 @@
.recurly-hosted-field {
&:extend(.form-control);
}
.recurly {
display: block;
position: relative;

View file

@ -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) {

View file

@ -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)

View file

@ -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", ->

View file

@ -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)

View file

@ -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', ->

View file

@ -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

View file

@ -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

View file

@ -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: {}
}
}

View file

@ -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