Merge branch 'master' into sk-account-sync

This commit is contained in:
Shane Kilkelly 2017-12-07 11:51:41 +00:00
commit a1f1c25294
89 changed files with 12691 additions and 7751 deletions

View file

@ -73,3 +73,4 @@ Gemfile.lock
app/views/external app/views/external
/modules/ /modules/
docker-shared.yml

View file

@ -196,6 +196,7 @@ module.exports = (grunt) ->
"mathjax": "/js/libs/mathjax/MathJax.js?config=TeX-AMS_HTML" "mathjax": "/js/libs/mathjax/MathJax.js?config=TeX-AMS_HTML"
"pdfjs-dist/build/pdf": "libs/#{PackageVersions.lib('pdfjs')}/pdf" "pdfjs-dist/build/pdf": "libs/#{PackageVersions.lib('pdfjs')}/pdf"
"ace": "#{PackageVersions.lib('ace')}" "ace": "#{PackageVersions.lib('ace')}"
"fineuploader": "libs/#{PackageVersions.lib('fineuploader')}"
shim: shim:
"pdfjs-dist/build/pdf": "pdfjs-dist/build/pdf":
deps: ["libs/#{PackageVersions.lib('pdfjs')}/compatibility"] deps: ["libs/#{PackageVersions.lib('pdfjs')}/compatibility"]

View file

@ -42,7 +42,7 @@ pipeline {
checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/tpr-webmodule'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/tpr-webmodule.git ']]]) checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/tpr-webmodule'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/tpr-webmodule.git ']]])
checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/learn-wiki'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@bitbucket.org:sharelatex/learn-wiki-web-module.git']]]) checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/learn-wiki'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@bitbucket.org:sharelatex/learn-wiki-web-module.git']]])
checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/templates'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/templates-webmodule.git']]]) checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/templates'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/templates-webmodule.git']]])
checkout([$class: 'GitSCM', branches: [[name: '*/sk-unlisted-projects']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/track-changes'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/track-changes-web-module.git']]]) checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/track-changes'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/track-changes-web-module.git']]])
checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/overleaf-integration'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/overleaf-integration-web-module.git']]]) checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/overleaf-integration'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/overleaf-integration-web-module.git']]])
checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/overleaf-account-merge'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/overleaf-account-merge.git']]]) checkout([$class: 'GitSCM', branches: [[name: '*/master']], extensions: [[$class: 'RelativeTargetDirectory', relativeTargetDir: 'modules/overleaf-account-merge'], [$class: 'CloneOption', shallow: true]], userRemoteConfigs: [[credentialsId: 'GIT_DEPLOY_KEY', url: 'git@github.com:sharelatex/overleaf-account-merge.git']]])
} }
@ -71,6 +71,19 @@ pipeline {
} }
} }
stage('Unit Tests') {
steps {
sh 'make clean install' // Removes js files, so do before compile
sh 'make test_unit MOCHA_ARGS="--reporter=tap"'
}
}
stage('Acceptance Tests') {
steps {
sh 'make test_acceptance MOCHA_ARGS="--reporter=tap"'
}
}
stage('Compile') { stage('Compile') {
agent { agent {
docker { docker {
@ -109,30 +122,6 @@ pipeline {
} }
} }
stage('Unit Test') {
agent {
docker {
image 'node:6.9.5'
reuseNode true
}
}
steps {
sh 'env NODE_ENV=development ./node_modules/.bin/grunt mochaTest:unit --reporter=tap'
}
}
stage('Acceptance Tests') {
steps {
// This tagged relase of the acceptance test runner is a temporary fix
// to get the acceptance tests working before we move to a
// docker-compose workflow. See:
// https://github.com/sharelatex/web-sharelatex-internal/pull/148
sh 'docker pull sharelatex/sl-acceptance-test-runner:node-6.9-mongo-3.4'
sh 'docker run --rm -v $(pwd):/app --env SHARELATEX_ALLOW_PUBLIC_ACCESS=true sharelatex/sl-acceptance-test-runner:node-6.9-mongo-3.4 || (cat forever/app.log && false)'
}
}
stage('Package') { stage('Package') {
steps { steps {
sh 'rm -rf ./node_modules/grunt*' sh 'rm -rf ./node_modules/grunt*'

77
services/web/Makefile Normal file
View file

@ -0,0 +1,77 @@
DOCKER_COMPOSE_FLAGS ?= -f docker-compose.yml
NPM := docker-compose ${DOCKER_COMPOSE_FLAGS} run --rm npm npm -q
BUILD_NUMBER ?= local
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)
PROJECT_NAME = web
all: install test
@echo "Run:"
@echo " make install to set up the project dependencies (in docker)"
@echo " make test to run all the tests for the project (in docker)"
add: docker-shared.yml
$(NPM) install --save ${P}
add_dev: docker-shared.yml
$(NPM) install --save-dev ${P}
install: docker-shared.yml
$(NPM) install
clean:
rm -f app.js
rm -rf app/js
rm -rf test/unit/js
rm -rf test/acceptance/js
for dir in modules/*; \
do \
rm -f $$dir/index.js; \
rm -rf $$dir/app/js; \
rm -rf $$dir/test/unit/js; \
rm -rf $$dir/test/acceptance/js; \
done
# Regenerate docker-shared.yml - not stictly a 'clean',
# but lets `make clean install` work nicely
bin/generate_volumes_file
# Deletes node_modules volume
docker-compose down --volumes
# Need regenerating if you change the web modules you have installed
docker-shared.yml:
bin/generate_volumes_file
test: test_unit test_acceptance
test_unit: docker-shared.yml
docker-compose ${DOCKER_COMPOSE_FLAGS} run --rm test_unit npm -q run test:unit -- ${MOCHA_ARGS}
test_acceptance: test_acceptance_app test_acceptance_modules
test_acceptance_app: test_acceptance_app_start_service test_acceptance_app_run test_acceptance_app_stop_service
test_acceptance_app_start_service: test_acceptance_app_stop_service docker-shared.yml
docker-compose ${DOCKER_COMPOSE_FLAGS} up -d test_acceptance
test_acceptance_app_stop_service: docker-shared.yml
docker-compose ${DOCKER_COMPOSE_FLAGS} stop test_acceptance redis mongo
test_acceptance_app_run: docker-shared.yml
docker-compose ${DOCKER_COMPOSE_FLAGS} exec -T test_acceptance npm -q run test:acceptance -- ${MOCHA_ARGS}
test_acceptance_modules: docker-shared.yml
# Break and error on any module failure
set -e; \
for dir in modules/*; \
do \
if [ -e $$dir/Makefile ]; then \
(make test_acceptance_module MODULE=$$dir) \
fi \
done
test_acceptance_module: docker-shared.yml
cd $(MODULE) && make test_acceptance
.PHONY:
all add install update test test_unit test_acceptance \
test_acceptance_start_service test_acceptance_stop_service \
test_acceptance_run

View file

@ -17,6 +17,69 @@ web-sharelatex uses [Grunt](http://gruntjs.com/) to build its front-end related
Image processing tasks are commented out in the gruntfile and the needed packages aren't presently in the project's `package.json`. If the images need to be processed again (minified and sprited), start by fetching the packages (`npm install grunt-contrib-imagemin grunt-sprity`), then *decomment* the tasks in `Gruntfile.coffee`. After this, the tasks can be called (explicitly, via `grunt imagemin` and `grunt sprity`). Image processing tasks are commented out in the gruntfile and the needed packages aren't presently in the project's `package.json`. If the images need to be processed again (minified and sprited), start by fetching the packages (`npm install grunt-contrib-imagemin grunt-sprity`), then *decomment* the tasks in `Gruntfile.coffee`. After this, the tasks can be called (explicitly, via `grunt imagemin` and `grunt sprity`).
New Docker-based build process
------------------------------
Note that the Grunt workflow from above should still work, but we are transitioning to a
Docker based testing workflow, which is documented below:
### Running the app
The app runs natively using npm and Node on the local system:
```
$ npm install
$ npm run start
```
*Ideally the app would run in Docker like the tests below, but with host networking not supported in OS X, we need to run it natively until all services are Dockerised.*
### Unit Tests
The test suites run in Docker.
Unit tests can be run in the `test_unit` container defined in `docker-compose.tests.yml`.
The makefile contains a short cut to run these:
```
make install # Only needs running once, or when npm packages are updated
make unit_test
```
During development it is often useful to only run a subset of tests, which can be configured with arguments to the mocha CLI:
```
make unit_test MOCHA_ARGS='--grep=AuthorizationManager'
```
### Acceptance Tests
Acceptance tests are run against a live service, which runs in the `acceptance_test` container defined in `docker-compose.tests.yml`.
To run the tests out-of-the-box, the makefile defines:
```
make install # Only needs running once, or when npm packages are updated
make acceptance_test
```
However, during development it is often useful to leave the service running for rapid iteration on the acceptance tests. This can be done with:
```
make acceptance_test_start_service
make acceptance_test_run # Run as many times as needed during development
make acceptance_test_stop_service
```
`make acceptance_test` just runs these three commands in sequence.
During development it is often useful to only run a subset of tests, which can be configured with arguments to the mocha CLI:
```
make acceptance_test_run MOCHA_ARGS='--grep=AuthorizationManager'
```
Unit test status Unit test status
---------------- ----------------

View file

@ -204,11 +204,11 @@ module.exports = DocumentUpdaterHandler =
logger.error {project_id, doc_id, thread_id}, "doc updater returned a non-success status code: #{res.statusCode}" logger.error {project_id, doc_id, thread_id}, "doc updater returned a non-success status code: #{res.statusCode}"
callback new Error("doc updater returned a non-success status code: #{res.statusCode}") callback new Error("doc updater returned a non-success status code: #{res.statusCode}")
updateProjectStructure : (project_id, userId, oldDocs, newDocs, oldFiles, newFiles, callback = (error) ->)-> updateProjectStructure : (project_id, userId, changes, callback = (error) ->)->
return callback() if !settings.apis.project_history?.enabled return callback() if !settings.apis.project_history?.enabled
docUpdates = DocumentUpdaterHandler._getRenameUpdates('doc', oldDocs, newDocs) docUpdates = DocumentUpdaterHandler._getRenameUpdates('doc', changes.oldDocs, changes.newDocs)
fileUpdates = DocumentUpdaterHandler._getRenameUpdates('file', oldFiles, newFiles) fileUpdates = DocumentUpdaterHandler._getRenameUpdates('file', changes.oldFiles, changes.newFiles)
timer = new metrics.Timer("set-document") timer = new metrics.Timer("set-document")
url = "#{settings.apis.documentupdater.url}/project/#{project_id}" url = "#{settings.apis.documentupdater.url}/project/#{project_id}"
@ -231,14 +231,25 @@ module.exports = DocumentUpdaterHandler =
callback new Error("doc updater returned a non-success status code: #{res.statusCode}") callback new Error("doc updater returned a non-success status code: #{res.statusCode}")
_getRenameUpdates: (entityType, oldEntities, newEntities) -> _getRenameUpdates: (entityType, oldEntities, newEntities) ->
oldEntities ||= []
newEntities ||= []
updates = [] updates = []
for oldEntity in oldEntities oldEntitiesHash = _.indexBy oldEntities, (entity) -> entity[entityType]._id.toString()
id = oldEntity[entityType]._id newEntitiesHash = _.indexBy newEntities, (entity) -> entity[entityType]._id.toString()
newEntity = _.find newEntities, (newEntity) ->
newEntity[entityType]._id.toString() == id.toString()
if newEntity.path != oldEntity.path for id, newEntity of newEntitiesHash
oldEntity = oldEntitiesHash[id]
if !oldEntity?
# entity added
updates.push
id: id
pathname: newEntity.path
docLines: newEntity.docLines
url: newEntity.url
else if newEntity.path != oldEntity.path
# entity renamed
updates.push updates.push
id: id id: id
pathname: oldEntity.path pathname: oldEntity.path

View file

@ -19,48 +19,48 @@ module.exports = EditorController =
DocumentUpdaterHandler.flushDocToMongo project_id, doc_id, callback DocumentUpdaterHandler.flushDocToMongo project_id, doc_id, callback
addDoc: (project_id, folder_id, docName, docLines, source, callback = (error, doc)->)-> addDoc: (project_id, folder_id, docName, docLines, source, user_id, callback = (error, doc)->)->
LockManager.getLock project_id, (err)-> LockManager.getLock project_id, (err)->
if err? if err?
logger.err err:err, project_id:project_id, source:source, "could not get lock to addDoc" logger.err err:err, project_id:project_id, source:source, "could not get lock to addDoc"
return callback(err) return callback(err)
EditorController.addDocWithoutLock project_id, folder_id, docName, docLines, source, (error, doc)-> EditorController.addDocWithoutLock project_id, folder_id, docName, docLines, source, user_id, (error, doc)->
LockManager.releaseLock project_id, -> LockManager.releaseLock project_id, ->
callback(error, doc) callback(error, doc)
addDocWithoutLock: (project_id, folder_id, docName, docLines, source, callback = (error, doc)->)-> addDocWithoutLock: (project_id, folder_id, docName, docLines, source, user_id, callback = (error, doc)->)->
docName = docName.trim() docName = docName.trim()
logger.log {project_id, folder_id, docName, source}, "sending new doc to project" logger.log {project_id, folder_id, docName, source}, "sending new doc to project"
Metrics.inc "editor.add-doc" Metrics.inc "editor.add-doc"
ProjectEntityHandler.addDoc project_id, folder_id, docName, docLines, (err, doc, folder_id)=> ProjectEntityHandler.addDoc project_id, folder_id, docName, docLines, user_id, (err, doc, folder_id)=>
if err? if err?
logger.err err:err, project_id:project_id, docName:docName, "error adding doc without lock" logger.err err:err, project_id:project_id, docName:docName, "error adding doc without lock"
return callback(err) return callback(err)
EditorRealTimeController.emitToRoom(project_id, 'reciveNewDoc', folder_id, doc, source) EditorRealTimeController.emitToRoom(project_id, 'reciveNewDoc', folder_id, doc, source)
callback(err, doc) callback(err, doc)
addFile: (project_id, folder_id, fileName, path, source, callback = (error, file)->)-> addFile: (project_id, folder_id, fileName, path, source, user_id, callback = (error, file)->)->
LockManager.getLock project_id, (err)-> LockManager.getLock project_id, (err)->
if err? if err?
logger.err err:err, project_id:project_id, source:source, "could not get lock to addFile" logger.err err:err, project_id:project_id, source:source, "could not get lock to addFile"
return callback(err) return callback(err)
EditorController.addFileWithoutLock project_id, folder_id, fileName, path, source, (error, file)-> EditorController.addFileWithoutLock project_id, folder_id, fileName, path, source, user_id, (error, file)->
LockManager.releaseLock project_id, -> LockManager.releaseLock project_id, ->
callback(error, file) callback(error, file)
addFileWithoutLock: (project_id, folder_id, fileName, path, source, callback = (error, file)->)-> addFileWithoutLock: (project_id, folder_id, fileName, path, source, user_id, callback = (error, file)->)->
fileName = fileName.trim() fileName = fileName.trim()
logger.log {project_id, folder_id, fileName, path}, "sending new file to project" logger.log {project_id, folder_id, fileName, path}, "sending new file to project"
Metrics.inc "editor.add-file" Metrics.inc "editor.add-file"
ProjectEntityHandler.addFile project_id, folder_id, fileName, path, (err, fileRef, folder_id)=> ProjectEntityHandler.addFile project_id, folder_id, fileName, path, user_id, (err, fileRef, folder_id)=>
if err? if err?
logger.err err:err, project_id:project_id, folder_id:folder_id, fileName:fileName, "error adding file without lock" logger.err err:err, project_id:project_id, folder_id:folder_id, fileName:fileName, "error adding file without lock"
return callback(err) return callback(err)
EditorRealTimeController.emitToRoom(project_id, 'reciveNewFile', folder_id, fileRef, source) EditorRealTimeController.emitToRoom(project_id, 'reciveNewFile', folder_id, fileRef, source)
callback(err, fileRef) callback(err, fileRef)
replaceFile: (project_id, file_id, fsPath, source, callback = (error) ->)-> replaceFile: (project_id, file_id, fsPath, source, user_id, callback = (error) ->)->
ProjectEntityHandler.replaceFile project_id, file_id, fsPath, callback ProjectEntityHandler.replaceFile project_id, file_id, fsPath, user_id, callback
addFolder : (project_id, folder_id, folderName, source, callback = (error, folder)->)-> addFolder : (project_id, folder_id, folderName, source, callback = (error, folder)->)->
LockManager.getLock project_id, (err)-> LockManager.getLock project_id, (err)->

View file

@ -81,10 +81,11 @@ module.exports = EditorHttpController =
project_id = req.params.Project_id project_id = req.params.Project_id
name = req.body.name name = req.body.name
parent_folder_id = req.body.parent_folder_id parent_folder_id = req.body.parent_folder_id
user_id = AuthenticationController.getLoggedInUserId(req)
logger.log project_id:project_id, name:name, parent_folder_id:parent_folder_id, "getting request to add doc to project" logger.log project_id:project_id, name:name, parent_folder_id:parent_folder_id, "getting request to add doc to project"
if !EditorHttpController._nameIsAcceptableLength(name) if !EditorHttpController._nameIsAcceptableLength(name)
return res.sendStatus 400 return res.sendStatus 400
EditorController.addDoc project_id, parent_folder_id, name, [], "editor", (error, doc) -> EditorController.addDoc project_id, parent_folder_id, name, [], "editor", user_id, (error, doc) ->
if error == "project_has_to_many_files" if error == "project_has_to_many_files"
res.status(400).json(req.i18n.translate("project_has_to_many_files")) res.status(400).json(req.i18n.translate("project_has_to_many_files"))
else if error? else if error?
@ -113,9 +114,9 @@ module.exports = EditorHttpController =
entity_id = req.params.entity_id entity_id = req.params.entity_id
entity_type = req.params.entity_type entity_type = req.params.entity_type
name = req.body.name name = req.body.name
user_id = AuthenticationController.getLoggedInUserId(req)
if !EditorHttpController._nameIsAcceptableLength(name) if !EditorHttpController._nameIsAcceptableLength(name)
return res.sendStatus 400 return res.sendStatus 400
user_id = AuthenticationController.getLoggedInUserId(req)
EditorController.renameEntity project_id, entity_id, entity_type, name, user_id, (error) -> EditorController.renameEntity project_id, entity_id, entity_type, name, user_id, (error) ->
return next(error) if error? return next(error) if error?
res.sendStatus 204 res.sendStatus 204

View file

@ -21,9 +21,9 @@ module.exports = FileStoreHandler =
return callback(new Error("can not upload symlink")) return callback(new Error("can not upload symlink"))
_cb = callback _cb = callback
callback = (err) -> callback = (err, url) ->
callback = -> # avoid double callbacks callback = -> # avoid double callbacks
_cb(err) _cb(err, url)
logger.log project_id:project_id, file_id:file_id, fsPath:fsPath, "uploading file from disk" logger.log project_id:project_id, file_id:file_id, fsPath:fsPath, "uploading file from disk"
readStream = fs.createReadStream(fsPath) readStream = fs.createReadStream(fsPath)
@ -31,9 +31,10 @@ module.exports = FileStoreHandler =
logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the read stream of uploadFileFromDisk" logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the read stream of uploadFileFromDisk"
callback err callback err
readStream.on "open", () -> readStream.on "open", () ->
url = FileStoreHandler._buildUrl(project_id, file_id)
opts = opts =
method: "post" method: "post"
uri: FileStoreHandler._buildUrl(project_id, file_id) uri: url
timeout:fiveMinsInMs timeout:fiveMinsInMs
writeStream = request(opts) writeStream = request(opts)
writeStream.on "error", (err)-> writeStream.on "error", (err)->
@ -45,7 +46,7 @@ module.exports = FileStoreHandler =
logger.err {err, statusCode: response.statusCode}, "error uploading to filestore" logger.err {err, statusCode: response.statusCode}, "error uploading to filestore"
callback(err) callback(err)
else else
callback(null) callback(null, url)
readStream.pipe writeStream readStream.pipe writeStream
getFileStream: (project_id, file_id, query, callback)-> getFileStream: (project_id, file_id, query, callback)->
@ -91,7 +92,7 @@ module.exports = FileStoreHandler =
request opts, (err)-> request opts, (err)->
if err? if err?
logger.err err:err, oldProject_id:oldProject_id, oldFile_id:oldFile_id, newProject_id:newProject_id, newFile_id:newFile_id, "something went wrong telling filestore api to copy file" logger.err err:err, oldProject_id:oldProject_id, oldFile_id:oldFile_id, newProject_id:newProject_id, newFile_id:newFile_id, "something went wrong telling filestore api to copy file"
callback(err) callback(err, opts.uri)
_buildUrl: (project_id, file_id)-> _buildUrl: (project_id, file_id)->
return "#{settings.apis.filestore.url}/project/#{project_id}/file/#{file_id}" return "#{settings.apis.filestore.url}/project/#{project_id}/file/#{file_id}"

View file

@ -170,6 +170,8 @@ module.exports = ProjectController =
return notification return notification
projects = ProjectController._buildProjectList results.projects, results.v1Projects?.projects projects = ProjectController._buildProjectList results.projects, results.v1Projects?.projects
user = results.user user = results.user
warnings = ProjectController._buildWarningsList results.v1Projects
ProjectController._injectProjectOwners projects, (error, projects) -> ProjectController._injectProjectOwners projects, (error, projects) ->
return next(error) if error? return next(error) if error?
viewModel = { viewModel = {
@ -181,7 +183,7 @@ module.exports = ProjectController =
user: user user: user
hasSubscription: results.hasSubscription[0] hasSubscription: results.hasSubscription[0]
isShowingV1Projects: results.v1Projects? isShowingV1Projects: results.v1Projects?
noV1Connection: results.v1Projects?.noConnection warnings: warnings
} }
if Settings?.algolia?.app_id? and Settings?.algolia?.read_only_api_key? if Settings?.algolia?.app_id? and Settings?.algolia?.read_only_api_key?
@ -251,7 +253,7 @@ module.exports = ProjectController =
# Extract data from user's ObjectId # Extract data from user's ObjectId
timestamp = parseInt(user_id.toString().substring(0, 8), 16) timestamp = parseInt(user_id.toString().substring(0, 8), 16)
rolloutPercentage = 10 # Percentage of users to roll out to rolloutPercentage = 40 # Percentage of users to roll out to
if !ProjectController._isInPercentageRollout('autocompile', user_id, rolloutPercentage) if !ProjectController._isInPercentageRollout('autocompile', user_id, rolloutPercentage)
# Don't show if user is not part of roll out # Don't show if user is not part of roll out
return cb(null, { enabled: false, showOnboarding: false }) return cb(null, { enabled: false, showOnboarding: false })
@ -427,6 +429,14 @@ module.exports = ProjectController =
project.owner = users[project.owner_ref.toString()] project.owner = users[project.owner_ref.toString()]
callback null, projects callback null, projects
_buildWarningsList: (v1ProjectData = {}) ->
warnings = []
if v1ProjectData.noConnection
warnings.push 'No V1 Connection'
if v1ProjectData.hasHiddenV1Projects
warnings.push "Looks like you've got a lot of V1 projects! Some of them may be hidden on V2. To view them all, use the V1 dashboard."
return warnings
defaultSettingsForAnonymousUser = (user_id)-> defaultSettingsForAnonymousUser = (user_id)->
id : user_id id : user_id
ace: ace:

View file

@ -52,7 +52,7 @@ module.exports = ProjectCreationHandler =
return callback(error) if error? return callback(error) if error?
self._buildTemplate "mainbasic.tex", owner_id, projectName, (error, docLines)-> self._buildTemplate "mainbasic.tex", owner_id, projectName, (error, docLines)->
return callback(error) if error? return callback(error) if error?
ProjectEntityHandler.addDoc project._id, project.rootFolder[0]._id, "main.tex", docLines, (error, doc)-> ProjectEntityHandler.addDoc project._id, project.rootFolder[0]._id, "main.tex", docLines, owner_id, (error, doc)->
if error? if error?
logger.err err:error, "error adding doc when creating basic project" logger.err err:error, "error adding doc when creating basic project"
return callback(error) return callback(error)
@ -67,17 +67,17 @@ module.exports = ProjectCreationHandler =
(callback) -> (callback) ->
self._buildTemplate "main.tex", owner_id, projectName, (error, docLines)-> self._buildTemplate "main.tex", owner_id, projectName, (error, docLines)->
return callback(error) if error? return callback(error) if error?
ProjectEntityHandler.addDoc project._id, project.rootFolder[0]._id, "main.tex", docLines, (error, doc)-> ProjectEntityHandler.addDoc project._id, project.rootFolder[0]._id, "main.tex", docLines, owner_id, (error, doc)->
return callback(error) if error? return callback(error) if error?
ProjectEntityHandler.setRootDoc project._id, doc._id, callback ProjectEntityHandler.setRootDoc project._id, doc._id, callback
(callback) -> (callback) ->
self._buildTemplate "references.bib", owner_id, projectName, (error, docLines)-> self._buildTemplate "references.bib", owner_id, projectName, (error, docLines)->
return callback(error) if error? return callback(error) if error?
ProjectEntityHandler.addDoc project._id, project.rootFolder[0]._id, "references.bib", docLines, (error, doc)-> ProjectEntityHandler.addDoc project._id, project.rootFolder[0]._id, "references.bib", docLines, owner_id, (error, doc)->
callback(error) callback(error)
(callback) -> (callback) ->
universePath = Path.resolve(__dirname + "/../../../templates/project_files/universe.jpg") universePath = Path.resolve(__dirname + "/../../../templates/project_files/universe.jpg")
ProjectEntityHandler.addFile project._id, project.rootFolder[0]._id, "universe.jpg", universePath, callback ProjectEntityHandler.addFile project._id, project.rootFolder[0]._id, "universe.jpg", universePath, owner_id, callback
], (error) -> ], (error) ->
callback(error, project) callback(error, project)

View file

@ -12,7 +12,7 @@ logger = require("logger-sharelatex")
module.exports = ProjectDuplicator = module.exports = ProjectDuplicator =
_copyDocs: (newProject, originalRootDoc, originalFolder, desFolder, docContents, callback)-> _copyDocs: (owner_id, newProject, originalRootDoc, originalFolder, desFolder, docContents, callback)->
setRootDoc = _.once (doc_id)-> setRootDoc = _.once (doc_id)->
projectEntityHandler.setRootDoc newProject._id, doc_id projectEntityHandler.setRootDoc newProject._id, doc_id
docs = originalFolder.docs or [] docs = originalFolder.docs or []
@ -21,7 +21,7 @@ module.exports = ProjectDuplicator =
if !doc?._id? if !doc?._id?
return callback() return callback()
content = docContents[doc._id.toString()] content = docContents[doc._id.toString()]
projectEntityHandler.addDocWithProject newProject, desFolder._id, doc.name, content.lines, (err, newDoc)-> projectEntityHandler.addDocWithProject newProject, desFolder._id, doc.name, content.lines, owner_id, (err, newDoc)->
if err? if err?
logger.err err:err, "error copying doc" logger.err err:err, "error copying doc"
return callback(err) return callback(err)
@ -31,15 +31,15 @@ module.exports = ProjectDuplicator =
async.series jobs, callback async.series jobs, callback
_copyFiles: (newProject, originalProject_id, originalFolder, desFolder, callback)-> _copyFiles: (owner_id, newProject, originalProject_id, originalFolder, desFolder, callback)->
fileRefs = originalFolder.fileRefs or [] fileRefs = originalFolder.fileRefs or []
jobs = fileRefs.map (file)-> jobs = fileRefs.map (file)->
return (cb)-> return (cb)->
projectEntityHandler.copyFileFromExistingProjectWithProject newProject, desFolder._id, originalProject_id, file, cb projectEntityHandler.copyFileFromExistingProjectWithProject newProject, desFolder._id, originalProject_id, file, owner_id, cb
async.parallelLimit jobs, 5, callback async.parallelLimit jobs, 5, callback
_copyFolderRecursivly: (newProject_id, originalProject_id, originalRootDoc, originalFolder, desFolder, docContents, callback)-> _copyFolderRecursivly: (owner_id, newProject_id, originalProject_id, originalRootDoc, originalFolder, desFolder, docContents, callback)->
ProjectGetter.getProject newProject_id, {rootFolder:true, name:true}, (err, newProject)-> ProjectGetter.getProject newProject_id, {rootFolder:true, name:true}, (err, newProject)->
if err? if err?
logger.err project_id:newProject_id, "could not get project" logger.err project_id:newProject_id, "could not get project"
@ -53,12 +53,12 @@ module.exports = ProjectDuplicator =
return cb() return cb()
projectEntityHandler.addFolderWithProject newProject, desFolder?._id, childFolder.name, (err, newFolder)-> projectEntityHandler.addFolderWithProject newProject, desFolder?._id, childFolder.name, (err, newFolder)->
return cb(err) if err? return cb(err) if err?
ProjectDuplicator._copyFolderRecursivly newProject_id, originalProject_id, originalRootDoc, childFolder, newFolder, docContents, cb ProjectDuplicator._copyFolderRecursivly owner_id, newProject_id, originalProject_id, originalRootDoc, childFolder, newFolder, docContents, cb
jobs.push (cb)-> jobs.push (cb)->
ProjectDuplicator._copyFiles newProject, originalProject_id, originalFolder, desFolder, cb ProjectDuplicator._copyFiles owner_id, newProject, originalProject_id, originalFolder, desFolder, cb
jobs.push (cb)-> jobs.push (cb)->
ProjectDuplicator._copyDocs newProject, originalRootDoc, originalFolder, desFolder, docContents, cb ProjectDuplicator._copyDocs owner_id, newProject, originalRootDoc, originalFolder, desFolder, docContents, cb
async.series jobs, callback async.series jobs, callback
@ -90,7 +90,7 @@ module.exports = ProjectDuplicator =
projectOptionsHandler.setCompiler newProject._id, originalProject.compiler, -> projectOptionsHandler.setCompiler newProject._id, originalProject.compiler, ->
ProjectDuplicator._copyFolderRecursivly newProject._id, originalProject_id, originalRootDoc, originalProject.rootFolder[0], newProject.rootFolder[0], docContents, -> ProjectDuplicator._copyFolderRecursivly owner._id, newProject._id, originalProject_id, originalRootDoc, originalProject.rootFolder[0], newProject.rootFolder[0], docContents, ->
if err? if err?
logger.err err:err, originalProject_id:originalProject_id, newProjectName:newProjectName, "error cloning project" logger.err err:err, originalProject_id:originalProject_id, newProjectName:newProjectName, "error cloning project"
callback(err, newProject) callback(err, newProject)

View file

@ -16,7 +16,7 @@ projectUpdateHandler = require('./ProjectUpdateHandler')
DocstoreManager = require "../Docstore/DocstoreManager" DocstoreManager = require "../Docstore/DocstoreManager"
ProjectGetter = require "./ProjectGetter" ProjectGetter = require "./ProjectGetter"
CooldownManager = require '../Cooldown/CooldownManager' CooldownManager = require '../Cooldown/CooldownManager'
DocumentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler')
module.exports = ProjectEntityHandler = module.exports = ProjectEntityHandler =
getAllFolders: (project_id, callback) -> getAllFolders: (project_id, callback) ->
@ -106,8 +106,7 @@ module.exports = ProjectEntityHandler =
flushProjectToThirdPartyDataStore: (project_id, callback) -> flushProjectToThirdPartyDataStore: (project_id, callback) ->
self = @ self = @
logger.log project_id:project_id, "flushing project to tpds" logger.log project_id:project_id, "flushing project to tpds"
documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler') DocumentUpdaterHandler.flushProjectToMongo project_id, (error) ->
documentUpdaterHandler.flushProjectToMongo project_id, (error) ->
return callback(error) if error? return callback(error) if error?
ProjectGetter.getProject project_id, {name:true}, (error, project) -> ProjectGetter.getProject project_id, {name:true}, (error, project) ->
return callback(error) if error? return callback(error) if error?
@ -150,14 +149,14 @@ module.exports = ProjectEntityHandler =
else else
DocstoreManager.getDoc project_id, doc_id, options, callback DocstoreManager.getDoc project_id, doc_id, options, callback
addDoc: (project_id, folder_id, docName, docLines, callback = (error, doc, folder_id) ->)=> addDoc: (project_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=>
ProjectGetter.getProjectWithOnlyFolders project_id, (err, project) -> ProjectGetter.getProjectWithOnlyFolders project_id, (err, project) ->
if err? if err?
logger.err project_id:project_id, err:err, "error getting project for add doc" logger.err project_id:project_id, err:err, "error getting project for add doc"
return callback(err) return callback(err)
ProjectEntityHandler.addDocWithProject project, folder_id, docName, docLines, callback ProjectEntityHandler.addDocWithProject project, folder_id, docName, docLines, userId, callback
addDocWithProject: (project, folder_id, docName, docLines, callback = (error, doc, folder_id) ->)=> addDocWithProject: (project, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=>
project_id = project._id project_id = project._id
logger.log project_id: project_id, folder_id: folder_id, doc_name: docName, "adding doc to project with project" logger.log project_id: project_id, folder_id: folder_id, doc_name: docName, "adding doc to project with project"
confirmFolder project, folder_id, (folder_id)=> confirmFolder project, folder_id, (folder_id)=>
@ -177,7 +176,14 @@ module.exports = ProjectEntityHandler =
rev: 0 rev: 0
}, (err) -> }, (err) ->
return callback(err) if err? return callback(err) if err?
callback(null, doc, folder_id) newDocs = [
doc: doc
path: result?.path?.fileSystem
docLines: docLines.join('\n')
]
DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newDocs}, (error) ->
return callback(error) if error?
callback null, doc, folder_id
restoreDoc: (project_id, doc_id, name, callback = (error, doc, folder_id) ->) -> restoreDoc: (project_id, doc_id, name, callback = (error, doc, folder_id) ->) ->
# getDoc will return the deleted doc's lines, but we don't actually remove # getDoc will return the deleted doc's lines, but we don't actually remove
@ -186,20 +192,20 @@ module.exports = ProjectEntityHandler =
return callback(error) if error? return callback(error) if error?
ProjectEntityHandler.addDoc project_id, null, name, lines, callback ProjectEntityHandler.addDoc project_id, null, name, lines, callback
addFile: (project_id, folder_id, fileName, path, callback = (error, fileRef, folder_id) ->)-> addFile: (project_id, folder_id, fileName, path, userId, callback = (error, fileRef, folder_id) ->)->
ProjectGetter.getProjectWithOnlyFolders project_id, (err, project) -> ProjectGetter.getProjectWithOnlyFolders project_id, (err, project) ->
if err? if err?
logger.err project_id:project_id, err:err, "error getting project for add file" logger.err project_id:project_id, err:err, "error getting project for add file"
return callback(err) return callback(err)
ProjectEntityHandler.addFileWithProject project, folder_id, fileName, path, callback ProjectEntityHandler.addFileWithProject project, folder_id, fileName, path, userId, callback
addFileWithProject: (project, folder_id, fileName, path, callback = (error, fileRef, folder_id) ->)-> addFileWithProject: (project, folder_id, fileName, path, userId, callback = (error, fileRef, folder_id) ->)->
project_id = project._id project_id = project._id
logger.log project_id: project._id, folder_id: folder_id, file_name: fileName, path:path, "adding file" logger.log project_id: project._id, folder_id: folder_id, file_name: fileName, path:path, "adding file"
return callback(err) if err? return callback(err) if err?
confirmFolder project, folder_id, (folder_id)-> confirmFolder project, folder_id, (folder_id)->
fileRef = new File name : fileName fileRef = new File name : fileName
FileStoreHandler.uploadFileFromDisk project._id, fileRef._id, path, (err)-> FileStoreHandler.uploadFileFromDisk project._id, fileRef._id, path, (err, fileStoreUrl)->
if err? if err?
logger.err err:err, project_id: project._id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error uploading image to s3" 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) return callback(err)
@ -209,27 +215,31 @@ module.exports = ProjectEntityHandler =
return callback(err) 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) -> 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? return callback(err) if err?
callback(null, fileRef, folder_id) 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: (project_id, file_id, fsPath, callback)-> replaceFile: (project_id, file_id, fsPath, userId, callback)->
ProjectGetter.getProject project_id, {name:true}, (err, project) -> self = ProjectEntityHandler
FileStoreHandler.uploadFileFromDisk project_id, file_id, fsPath, (err, fileStoreUrl)->
return callback(err) if err? return callback(err) if err?
findOpts = ProjectGetter.getProject project_id, {rootFolder: true, name:true}, (err, project) ->
project_id:project._id
element_id:file_id
type:"file"
FileStoreHandler.uploadFileFromDisk project._id, file_id, fsPath, (err)->
return callback(err) if err? return callback(err) if err?
# Note there is a potential race condition here (and elsewhere) # Note there is a potential race condition here (and elsewhere)
# If the file tree changes between findElement and the Project.update # If the file tree changes between findElement and the Project.update
# then the path to the file element will be out of date. In practice # then the path to the file element will be out of date. In practice
# this is not a problem so long as we do not do anything longer running # this is not a problem so long as we do not do anything longer running
# between them (like waiting for the file to upload.) # between them (like waiting for the file to upload.)
projectLocator.findElement findOpts, (err, fileRef, path)=> projectLocator.findElement {project:project, element_id: file_id, type: 'file'}, (err, fileRef, path)=>
return callback(err) if err? return callback(err) if err?
tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:path.fileSystem, rev:fileRef.rev+1, project_name:project.name}, (error) -> tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:path.fileSystem, rev:fileRef.rev+1, project_name:project.name}, (err) ->
return callback(err) if err? return callback(err) if err?
conditons = _id:project._id conditions = _id:project._id
inc = {} inc = {}
inc["#{path.mongo}.rev"] = 1 inc["#{path.mongo}.rev"] = 1
set = {} set = {}
@ -237,39 +247,43 @@ module.exports = ProjectEntityHandler =
update = update =
"$inc": inc "$inc": inc
"$set": set "$set": set
Project.update conditons, update, {}, (err, second)-> Project.findOneAndUpdate conditions, update, { "new": true}, (err) ->
callback() return callback(err) if err?
newFiles = [
file: fileRef
path: path.fileSystem
url: fileStoreUrl
]
DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, callback
copyFileFromExistingProject: (project_id, folder_id, originalProject_id, origonalFileRef, callback = (error, fileRef, folder_id) ->)-> copyFileFromExistingProjectWithProject: (project, folder_id, originalProject_id, origonalFileRef, userId, callback = (error, fileRef, folder_id) ->)->
logger.log project_id:project_id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "copying file in s3"
ProjectGetter.getProject project_id, {name:true}, (err, project) ->
if err?
logger.err project_id:project_id, err:err, "error getting project for copy file from existing project"
return callback(err)
ProjectEntityHandler.copyFileFromExistingProjectWithProject project, folder_id, originalProject_id, origonalFileRef, callback
copyFileFromExistingProjectWithProject: (project, folder_id, originalProject_id, origonalFileRef, callback = (error, fileRef, folder_id) ->)->
project_id = project._id project_id = project._id
logger.log project_id:project_id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "copying file in s3 with project" logger.log { project_id, folder_id, originalProject_id, origonalFileRef }, "copying file in s3 with project"
return callback(err) if err? return callback(err) if err?
confirmFolder project, folder_id, (folder_id)=> confirmFolder project, folder_id, (folder_id)=>
if !origonalFileRef? if !origonalFileRef?
logger.err project_id:project._id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "file trying to copy is null" logger.err { project_id, folder_id, originalProject_id, origonalFileRef }, "file trying to copy is null"
return callback() return callback()
fileRef = new File name : origonalFileRef.name fileRef = new File name : origonalFileRef.name
FileStoreHandler.copyFile originalProject_id, origonalFileRef._id, project._id, fileRef._id, (err)-> FileStoreHandler.copyFile originalProject_id, origonalFileRef._id, project._id, fileRef._id, (err, fileStoreUrl)->
if err? if err?
logger.err err:err, project_id:project._id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "error coping file in s3" logger.err { err, project_id, folder_id, originalProject_id, origonalFileRef }, "error coping file in s3"
return callback(err) return callback(err)
ProjectEntityHandler._putElement project, folder_id, fileRef, "file", (err, result)=> ProjectEntityHandler._putElement project, folder_id, fileRef, "file", (err, result)=>
if err? if err?
logger.err err:err, project_id:project._id, folder_id:folder_id, "error putting element as part of copy" logger.err { err, project_id, folder_id }, "error putting element as part of copy"
return callback(err) return callback(err)
tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:result?.path?.fileSystem, rev:fileRef.rev, project_name:project.name}, (err) -> tpdsUpdateSender.addFile { project_id, file_id:fileRef._id, path:result?.path?.fileSystem, rev:fileRef.rev, project_name:project.name}, (err) ->
if err? if err?
logger.err err:err, project_id:project._id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "error sending file to tpds worker" logger.err { err, project_id, folder_id, originalProject_id, origonalFileRef }, "error sending file to tpds worker"
callback(null, fileRef, folder_id) 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
mkdirp: (project_id, path, callback = (err, newlyCreatedFolders, lastFolderInPath)->)-> mkdirp: (project_id, path, callback = (err, newlyCreatedFolders, lastFolderInPath)->)->
self = @ self = @
@ -381,11 +395,9 @@ module.exports = ProjectEntityHandler =
endPath: result.path.fileSystem, endPath: result.path.fileSystem,
rev: entity.rev rev: entity.rev
tpdsUpdateSender.moveEntity opts tpdsUpdateSender.moveEntity opts
self.getAllEntitiesFromProject newProject, (error, newDocs, newFiles self.getAllEntitiesFromProject newProject, (error, newDocs, newFiles) =>
) =>
return callback(error) if error? return callback(error) if error?
documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler') DocumentUpdaterHandler.updateProjectStructure project_id, userId, {oldDocs, newDocs, oldFiles, newFiles}, callback
documentUpdaterHandler.updateProjectStructure project_id, userId, oldDocs, newDocs, oldFiles, newFiles, callback
_checkValidMove: (project, entityType, entityPath, destFolderId, callback = (error) ->) -> _checkValidMove: (project, entityType, entityPath, destFolderId, callback = (error) ->) ->
return callback() if !entityType.match(/folder/) return callback() if !entityType.match(/folder/)
@ -442,8 +454,7 @@ module.exports = ProjectEntityHandler =
return callback(error) if error? return callback(error) if error?
ProjectEntityHandler.getAllEntitiesFromProject newProject, (error, newDocs, newFiles) => ProjectEntityHandler.getAllEntitiesFromProject newProject, (error, newDocs, newFiles) =>
return callback(error) if error? return callback(error) if error?
documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler') DocumentUpdaterHandler.updateProjectStructure project_id, userId, {oldDocs, newDocs, oldFiles, newFiles}, callback
documentUpdaterHandler.updateProjectStructure project_id, userId, oldDocs, newDocs, oldFiles, newFiles, callback
_cleanUpEntity: (project, entity, entityType, callback = (error) ->) -> _cleanUpEntity: (project, entity, entityType, callback = (error) ->) ->
if(entityType.indexOf("file") != -1) if(entityType.indexOf("file") != -1)
@ -466,7 +477,7 @@ module.exports = ProjectEntityHandler =
unsetRootDocIfRequired (error) -> unsetRootDocIfRequired (error) ->
return callback(error) if error? return callback(error) if error?
require('../../Features/DocumentUpdater/DocumentUpdaterHandler').deleteDoc project_id, doc_id, (error) -> DocumentUpdaterHandler.deleteDoc project_id, doc_id, (error) ->
return callback(error) if error? return callback(error) if error?
ProjectEntityHandler._insertDeletedDocReference project._id, doc, (error) -> ProjectEntityHandler._insertDeletedDocReference project._id, doc, (error) ->
return callback(error) if error? return callback(error) if error?

View file

@ -28,7 +28,7 @@ module.exports =
FileTypeManager.isBinary path, fsPath, (err, isFile)-> FileTypeManager.isBinary path, fsPath, (err, isFile)->
return callback(err) if err? return callback(err) if err?
if isFile if isFile
self.p.processFile project_id, elementId, fsPath, path, source, callback self.p.processFile project_id, elementId, fsPath, path, source, user_id, callback
else else
self.p.processDoc project_id, elementId, user_id, fsPath, path, source, callback self.p.processDoc project_id, elementId, user_id, fsPath, path, source, callback
@ -57,9 +57,9 @@ module.exports =
if err? if err?
logger.err err:err, project_id:project_id, doc_id:doc_id, path:path, "error processing file" logger.err err:err, project_id:project_id, doc_id:doc_id, path:path, "error processing file"
return callback(err) return callback(err)
editorController.addDoc project_id, folder._id, fileName, docLines, source, callback editorController.addDoc project_id, folder._id, fileName, docLines, source, user_id, callback
processFile: (project_id, file_id, fsPath, path, source, callback)-> processFile: (project_id, file_id, fsPath, path, source, user_id, callback)->
finish = (err)-> finish = (err)->
logger.log project_id:project_id, file_id:file_id, path:path, "completed processing file update from tpds" logger.log project_id:project_id, file_id:file_id, path:path, "completed processing file update from tpds"
callback(err) callback(err)
@ -69,9 +69,9 @@ module.exports =
logger.err err:err, project_id:project_id, file_id:file_id, path:path, "error processing file" logger.err err:err, project_id:project_id, file_id:file_id, path:path, "error processing file"
return callback(err) return callback(err)
else if file_id? else if file_id?
editorController.replaceFile project_id, file_id, fsPath, source, finish editorController.replaceFile project_id, file_id, fsPath, source, user_id, finish
else else
editorController.addFile project_id, folder?._id, fileName, fsPath, source, finish editorController.addFile project_id, folder?._id, fileName, fsPath, source, user_id, finish
writeStreamToDisk: (project_id, file_id, stream, callback = (err, fsPath)->)-> writeStreamToDisk: (project_id, file_id, stream, callback = (err, fsPath)->)->
if !file_id? if !file_id?

View file

@ -28,9 +28,9 @@ module.exports = FileSystemImportManager =
if existingDoc? if existingDoc?
EditorController.setDoc project_id, existingDoc._id, user_id, lines, "upload", callback EditorController.setDoc project_id, existingDoc._id, user_id, lines, "upload", callback
else else
EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", callback EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", user_id, callback
else else
EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", callback EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", user_id, callback
addFile: (user_id, project_id, folder_id, name, path, replace, callback = (error, file)-> )-> addFile: (user_id, project_id, folder_id, name, path, replace, callback = (error, file)-> )->
FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)-> FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)->
@ -39,7 +39,7 @@ module.exports = FileSystemImportManager =
return callback("path is symlink") return callback("path is symlink")
if !replace if !replace
EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", callback EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", user_id, callback
else else
ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) -> ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
return callback(error) if error? return callback(error) if error?
@ -50,9 +50,9 @@ module.exports = FileSystemImportManager =
existingFile = fileRef existingFile = fileRef
break break
if existingFile? if existingFile?
EditorController.replaceFile project_id, existingFile._id, path, "upload", callback EditorController.replaceFile project_id, existingFile._id, path, "upload", user_id, callback
else else
EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", callback EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", user_id, callback
addFolder: (user_id, project_id, folder_id, name, path, replace, callback = (error)-> ) -> addFolder: (user_id, project_id, folder_id, name, path, replace, callback = (error)-> ) ->
FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)-> FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)->

View file

@ -24,6 +24,7 @@ jsPath =
ace = PackageVersions.lib('ace') ace = PackageVersions.lib('ace')
pdfjs = PackageVersions.lib('pdfjs') pdfjs = PackageVersions.lib('pdfjs')
fineuploader = PackageVersions.lib('fineuploader')
getFileContent = (filePath)-> getFileContent = (filePath)->
filePath = Path.join __dirname, "../../../", "public#{filePath}" filePath = Path.join __dirname, "../../../", "public#{filePath}"
@ -37,6 +38,7 @@ getFileContent = (filePath)->
logger.log "Generating file fingerprints..." logger.log "Generating file fingerprints..."
pathList = [ pathList = [
["#{jsPath}libs/#{fineuploader}.js"]
["#{jsPath}libs/require.js"] ["#{jsPath}libs/require.js"]
["#{jsPath}ide.js"] ["#{jsPath}ide.js"]
["#{jsPath}main.js"] ["#{jsPath}main.js"]

View file

@ -2,6 +2,7 @@ version = {
"pdfjs": "1.7.225" "pdfjs": "1.7.225"
"moment": "2.9.0" "moment": "2.9.0"
"ace": "1.2.5" "ace": "1.2.5"
"fineuploader": "5.15.4"
} }
module.exports = { module.exports = {

View file

@ -132,7 +132,8 @@ html(itemscope, itemtype='http://schema.org/Product')
// minimal requirejs configuration (can be extended/overridden) // minimal requirejs configuration (can be extended/overridden)
window.requirejs = { window.requirejs = {
"paths" : { "paths" : {
"moment": "libs/#{lib('moment')}" "moment": "libs/#{lib('moment')}",
"fineuploader": "libs/#{lib('fineuploader')}"
}, },
"urlArgs": "fingerprint=#{fingerprint(jsPath + 'main.js')}-#{fingerprint(jsPath + 'libs.js')}", "urlArgs": "fingerprint=#{fingerprint(jsPath + 'main.js')}-#{fingerprint(jsPath + 'libs.js')}",
"config":{ "config":{

View file

@ -130,7 +130,8 @@ block requirejs
"moment": "libs/#{lib('moment')}", "moment": "libs/#{lib('moment')}",
"pdfjs-dist/build/pdf": "libs/#{lib('pdfjs')}/pdf", "pdfjs-dist/build/pdf": "libs/#{lib('pdfjs')}/pdf",
"pdfjs-dist/build/pdf.worker": "#{pdfWorkerPath}", "pdfjs-dist/build/pdf.worker": "#{pdfWorkerPath}",
"ace": "#{lib('ace')}" "ace": "#{lib('ace')}",
"fineuploader": "libs/#{lib('fineuploader')}"
}, },
"urlArgs" : "fingerprint=#{fingerprint(jsPath + 'ide.js')}-#{fingerprint(jsPath + 'libs.js')}", "urlArgs" : "fingerprint=#{fingerprint(jsPath + 'ide.js')}-#{fingerprint(jsPath + 'libs.js')}",
"waitSeconds": 0, "waitSeconds": 0,

View file

@ -118,7 +118,11 @@ div.full-size(
h3.popover-title #{translate("link_sharing")} h3.popover-title #{translate("link_sharing")}
.popover-content .popover-content
p #{translate("try_out_link_sharing")} p #{translate("try_out_link_sharing")}
img(src="/img/onboarding/linksharing/link-sharing.png" width="100%") img(
src="/img/onboarding/linksharing/link-sharing.png"
alt="Link sharing demo"
width="100%"
)
p #{translate("try_link_sharing_description")} p #{translate("try_link_sharing_description")}
button.btn.btn-default.btn-block(ng-click="dismiss()") button.btn.btn-default.btn-block(ng-click="dismiss()")
| #{translate("got_it")} | #{translate("got_it")}

View file

@ -1,5 +1,5 @@
aside#file-tree(ng-controller="FileTreeController", ng-class="{ 'multi-selected': multiSelectedCount > 0 }").full-size aside#file-tree(ng-controller="FileTreeController", ng-class="{ 'multi-selected': multiSelectedCount > 0 }").full-size
.toolbar.toolbar-small.toolbar-alt(ng-if="permissions.write") .toolbar.toolbar-filetree(ng-if="permissions.write")
a( a(
href, href,
ng-click="openNewDocModal()", ng-click="openNewDocModal()",

View file

@ -7,9 +7,9 @@ header.toolbar.toolbar-header.toolbar-with-labels(
href, href,
ng-click="ui.leftMenuShown = true;", ng-click="ui.leftMenuShown = true;",
) )
i.fa.fa-fw.fa-bars i.fa.fa-fw.fa-bars.editor-menu-icon
p.toolbar-label #{translate("menu")} p.toolbar-label #{translate("menu")}
a( a.toolbar-header-back-projects(
href="/project" href="/project"
) )
i.fa.fa-fw.fa-level-up i.fa.fa-fw.fa-level-up

View file

@ -1,6 +1,6 @@
div.full-size.pdf(ng-controller="PdfController") div.full-size.pdf(ng-controller="PdfController")
.toolbar.toolbar-tall .toolbar.toolbar-pdf
.btn-group#recompile( .btn-group.btn-recompile-group#recompile(
dropdown, dropdown,
tooltip-html="'"+translate('recompile_pdf')+" <span class=\"keyboard-shortcut\">({{modifierKey}} + Enter)</span>'" tooltip-html="'"+translate('recompile_pdf')+" <span class=\"keyboard-shortcut\">({{modifierKey}} + Enter)</span>'"
tooltip-class="keyboard-tooltip" tooltip-class="keyboard-tooltip"
@ -8,7 +8,7 @@ div.full-size.pdf(ng-controller="PdfController")
tooltip-append-to-body="true" tooltip-append-to-body="true"
tooltip-placement="bottom" tooltip-placement="bottom"
) )
a.btn.btn-info( a.btn.btn-recompile(
href, href,
ng-disabled="pdf.compiling", ng-disabled="pdf.compiling",
ng-click="recompile()" ng-click="recompile()"
@ -19,7 +19,7 @@ div.full-size.pdf(ng-controller="PdfController")
| &nbsp;&nbsp; | &nbsp;&nbsp;
span(ng-show="!pdf.compiling") #{translate("recompile")} span(ng-show="!pdf.compiling") #{translate("recompile")}
span(ng-show="pdf.compiling") #{translate("compiling")}... span(ng-show="pdf.compiling") #{translate("compiling")}...
a.btn.btn-info.dropdown-toggle( a.btn.btn-recompile.dropdown-toggle(
href, href,
ng-disabled="pdf.compiling", ng-disabled="pdf.compiling",
dropdown-toggle dropdown-toggle
@ -102,6 +102,7 @@ div.full-size.pdf(ng-controller="PdfController")
tooltip-placement="bottom" tooltip-placement="bottom"
tooltip-append-to-body="true" tooltip-append-to-body="true"
) )
i.fa.fa-expand
i.full-screen i.full-screen
a( a(
href, href,
@ -111,6 +112,7 @@ div.full-size.pdf(ng-controller="PdfController")
tooltip-placement="bottom" tooltip-placement="bottom"
tooltip-append-to-body="true" tooltip-append-to-body="true"
) )
i.fa.fa-compress
i.split-screen i.split-screen
i.split-screen i.split-screen
// end of toolbar // end of toolbar

View file

@ -313,3 +313,59 @@ script(type="text/ng-template", id="userProfileModalTemplate")
.modal-footer .modal-footer
button.btn.btn-info(ng-click="done()") #{translate("done")} button.btn.btn-info(ng-click="done()") #{translate("done")}
script(type="text/ng-template", id="v1ImportModalTemplate")
.modal-header
button.close(ng-click="dismiss()") &times;
h3 #{translate("import_project_to_v2")}
.modal-body.v1-import-wrapper
.v1-import-step-1(ng-show="step === 1")
img.v1-import-img(
src="/img/v1-import/v2-editor.png"
alt="The new V2 Editor."
)
h2.v1-import-title Try importing your project to V2!
p Some exciting copy about the new features:
ul
li Some stuff
li Some more stuff
li Yet more stuff
.v1-import-step-2(ng-show="step === 2")
div.v1-import-warning(aria-label="Warning symbol.")
i.fa.fa-exclamation-triangle
h2.v1-import-title #[strong Warning:] Overleaf V2 is in beta
p Once importing your project you will lose access to the some of the features of Overleaf V1. This includes the git bridge, journal integrations, WYSIWYG and linked files. Were working on bringing these features to V2!
p Once you have imported a project to V2 you #[strong cannot go back to V1].
p Are you sure you want to import to V2?
.modal-footer.v1-import-footer
div(ng-show="step === 1")
if settings.overleaf && settings.overleaf.host
a.btn.btn-primary.v1-import-btn(
ng-href=settings.overleaf.host + "/{{project.id}}"
) #{translate("open_in_v1")}
button.btn.btn-primary.v1-import-btn(
ng-click="moveToConfirmation()"
) #{translate("import_to_v2")}
div(ng-show="step === 2")
form(
async-form="v1Import",
name="v1ImportForm",
action="{{'/overleaf/project/'+ project.id + '/import'}}"
method="POST"
ng-cloak
)
input(name='_csrf', type='hidden', value=csrfToken)
form-messages(for="v1ImportForm")
if settings.overleaf && settings.overleaf.host
a.btn.btn-primary.v1-import-btn(
ng-href=settings.overleaf.host + "/{{project.id}}"
ng-class="{disabled: v1ImportForm.inflight || v1ImportForm.success}"
) #{translate("never_mind_open_in_v1")}
input.btn.btn-primary.v1-import-btn(
type="submit",
value=translate('yes_im_sure')
ng-disabled="v1ImportForm.inflight || v1ImportForm.success"
)

View file

@ -114,9 +114,9 @@
) #{translate("delete_forever")} ) #{translate("delete_forever")}
.row.row-spaced .row.row-spaced
if noV1Connection each warning in warnings
.col-xs-12 .col-xs-12
.alert.alert-warning No V1 Connection .alert.alert-warning(role="alert")= warning
.col-xs-12 .col-xs-12
.card.card-thin.project-list-card .card.card-thin.project-list-card

View file

@ -5,9 +5,8 @@
tooltip-append-to-body="true" tooltip-append-to-body="true"
) )
span span
if settings.overleaf && settings.overleaf.host button.btn.btn-link.v1ProjectName(
a.projectName( ng-click="openV1ImportModal(project)"
href=settings.overleaf.host + "/{{project.id}}"
stop-propagation="click" stop-propagation="click"
) {{project.name}} ) {{project.name}}

View file

@ -0,0 +1,4 @@
#!/bin/bash
set -e;
MOCHA="node_modules/.bin/mocha --recursive --reporter spec --timeout 15000"
$MOCHA "$@"

View file

@ -0,0 +1,16 @@
#!/bin/bash
set -e;
COFFEE=node_modules/.bin/coffee
echo Compiling test/acceptance/coffee;
$COFFEE -o test/acceptance/js -c test/acceptance/coffee;
for dir in modules/*;
do
if [ -d $dir/test/acceptance ]; then
echo Compiling $dir/test/acceptance/coffee;
$COFFEE -o $dir/test/acceptance/js -c $dir/test/acceptance/coffee;
fi
done

23
services/web/bin/compile_app Executable file
View file

@ -0,0 +1,23 @@
#!/bin/bash
set -e;
COFFEE=node_modules/.bin/coffee
echo Compiling app.coffee;
$COFFEE -c app.coffee;
echo Compiling app/coffee;
$COFFEE -o app/js -c app/coffee;
for dir in modules/*;
do
if [ -d $dir/app/coffee ]; then
echo Compiling $dir/app/coffee;
$COFFEE -o $dir/app/js -c $dir/app/coffee;
fi
if [ -e $dir/index.coffee ]; then
echo Compiling $dir/index.coffee;
$COFFEE -c $dir/index.coffee;
fi
done

View file

@ -0,0 +1,15 @@
#!/bin/bash
set -e;
COFFEE=node_modules/.bin/coffee
echo Compiling test/unit/coffee;
$COFFEE -o test/unit/js -c test/unit/coffee;
for dir in modules/*;
do
if [ -d $dir/test/unit ]; then
echo Compiling $dir/test/unit/coffee;
$COFFEE -o $dir/test/unit/js -c $dir/test/unit/coffee;
fi
done

View file

@ -0,0 +1,26 @@
#!/usr/bin/env python2
from os import listdir
from os.path import isfile, isdir, join
volumes = []
for module in listdir("modules/"):
if module[0] != '.':
if isfile(join("modules", module, 'index.coffee')):
volumes.append(join("modules", module, 'index.coffee'))
for directory in ['app/coffee', 'app/views', 'public/coffee', 'test/unit/coffee', 'test/acceptance/coffee', 'test/acceptance/config', 'test/acceptance/files']:
if isdir(join("modules", module, directory)):
volumes.append(join("modules", module, directory))
volumes_string = map(lambda vol: "- ./" + vol + ":/app/" + vol + ":ro", volumes)
volumes_string = "\n ".join(volumes_string)
with open("docker-shared.template.yml", "r") as f:
docker_shared_file = f.read()
docker_shared_file = docker_shared_file.replace("MODULE_VOLUMES", volumes_string)
with open("docker-shared.yml", "w") as f:
f.write(docker_shared_file)

14
services/web/bin/unit_test Executable file
View file

@ -0,0 +1,14 @@
#!/bin/bash
set -e;
MOCHA="node_modules/.bin/mocha --recursive --reporter spec"
$MOCHA "$@" test/unit/js
for dir in modules/*;
do
if [ -d $dir/test/unit/js ]; then
$MOCHA "$@" $dir/test/unit/js
fi
done

View file

@ -35,12 +35,12 @@ module.exports = settings =
# Databases # Databases
# --------- # ---------
mongo: mongo:
url : 'mongodb://127.0.0.1/sharelatex' url : process.env['MONGO_URL'] || "mongodb://127.0.0.1/sharelatex"
redis: redis:
web: web:
host: "localhost" host: process.env['REDIS_HOST'] || "localhost"
port: "6379" port: process.env['REDIS_PORT'] || "6379"
password: "" password: ""
# websessions: # websessions:
@ -74,8 +74,8 @@ module.exports = settings =
# ] # ]
api: api:
host: "localhost" host: process.env['REDIS_HOST'] || "localhost"
port: "6379" port: process.env['REDIS_PORT'] || "6379"
password: "" password: ""
# Service locations # Service locations
@ -87,6 +87,7 @@ module.exports = settings =
internal: internal:
web: web:
port: webPort = 3000 port: webPort = 3000
host: process.env['LISTEN_ADDRESS'] or 'localhost'
documentupdater: documentupdater:
port: docUpdaterPort = 3003 port: docUpdaterPort = 3003
@ -99,7 +100,7 @@ module.exports = settings =
user: httpAuthUser user: httpAuthUser
pass: httpAuthPass pass: httpAuthPass
documentupdater: documentupdater:
url : "http://localhost:#{docUpdaterPort}" url : "http://#{process.env['DOCUPDATER_HOST'] or 'localhost'}:#{docUpdaterPort}"
thirdPartyDataStore: thirdPartyDataStore:
url : "http://localhost:3002" url : "http://localhost:3002"
emptyProjectFlushDelayMiliseconds: 5 * seconds emptyProjectFlushDelayMiliseconds: 5 * seconds
@ -110,10 +111,10 @@ module.exports = settings =
trackchanges: trackchanges:
url : "http://localhost:3015" url : "http://localhost:3015"
project_history: project_history:
enabled: false enabled: process.env.PROJECT_HISTORY_ENABLED == 'true' or false
url : "http://localhost:3054" url : "http://localhost:3054"
docstore: docstore:
url : "http://localhost:3016" url : "http://#{process.env['DOCSTORE_HOST'] or 'localhost'}:3016"
pubUrl: "http://localhost:3016" pubUrl: "http://localhost:3016"
chat: chat:
url: "http://localhost:3010" url: "http://localhost:3010"

View file

@ -0,0 +1,37 @@
version: "2"
volumes:
node_modules:
services:
npm:
extends:
file: docker-shared.yml
service: app
command: npm install
test_unit:
extends:
file: docker-shared.yml
service: app
command: npm run test:unit
test_acceptance:
extends:
file: docker-shared.yml
service: app
environment:
REDIS_HOST: redis
MONGO_URL: "mongodb://mongo/sharelatex"
SHARELATEX_ALLOW_PUBLIC_ACCESS: 'true'
PROJECT_HISTORY_ENABLED: 'true'
depends_on:
- redis
- mongo
command: npm run start
redis:
image: redis
mongo:
image: mongo:3.4.6

View file

@ -0,0 +1,31 @@
version: "2"
# We mount all the directories explicitly so that we are only mounting
# the coffee directories, so that the compiled js is only written inside
# the container, and not back to the local filesystem, where it would be
# root owned, and conflict with working outside of the container.
services:
app:
image: node:6.9.5
volumes:
- ./package.json:/app/package.json
- ./npm-shrinkwrap.json:/app/npm-shrinkwrap.json
- node_modules:/app/node_modules
- ./bin:/app/bin
# Copying the whole public dir is fine for now, and needed for
# some unit tests to pass, but we will want to isolate the coffee
# and vendor js files, so that the compiled js files are not written
# back to the local filesystem.
- ./public:/app/public
- ./app.coffee:/app/app.coffee:ro
- ./app/coffee:/app/app/coffee:ro
- ./app/templates:/app/app/templates:ro
- ./app/views:/app/app/views:ro
- ./config:/app/config
- ./test/unit/coffee:/app/test/unit/coffee:ro
- ./test/acceptance/coffee:/app/test/acceptance/coffee:ro
- ./test/acceptance/files:/app/test/acceptance/files:ro
- ./test/smoke/coffee:/app/test/smoke/coffee:ro
MODULE_VOLUMES
working_dir: /app

View file

@ -1,12 +1,6 @@
*/app/js # Ignore all modules except for a whitelist
*/test/unit/js *
*/index.js !dropbox
ldap !github-sync
admin-panel !public-registration
groovehq !.gitignore
launchpad
learn-wiki
references-search
sharelatex-saml
templates
tpr-webmodule

View file

@ -9,6 +9,17 @@
"directories": { "directories": {
"public": "./public" "public": "./public"
}, },
"scripts": {
"test:acceptance:wait_for_app": "echo 'Waiting for app to be accessible' && while (! curl -s -o /dev/null localhost:3000/status) do sleep 1; done",
"test:acceptance:run": "bin/acceptance_test $@",
"test:acceptance:dir": "npm -q run compile:acceptance_tests && npm -q run test:acceptance:wait_for_app && npm -q run test:acceptance:run -- $@",
"test:acceptance": "npm -q run test:acceptance:dir -- $@ test/acceptance/js",
"test:unit": "npm -q run compile:app && npm -q run compile:unit_tests && bin/unit_test $@",
"compile:unit_tests": "bin/compile_unit_tests",
"compile:acceptance_tests": "bin/compile_acceptance_tests",
"compile:app": "bin/compile_app",
"start": "npm -q run compile:app && node app.js"
},
"dependencies": { "dependencies": {
"archiver": "0.9.0", "archiver": "0.9.0",
"async": "0.6.2", "async": "0.6.2",
@ -98,6 +109,7 @@
"grunt-postcss": "^0.8.0", "grunt-postcss": "^0.8.0",
"grunt-sed": "^0.1.1", "grunt-sed": "^0.1.1",
"grunt-shell": "^2.1.0", "grunt-shell": "^2.1.0",
"mkdirp": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"sandboxed-module": "0.2.0", "sandboxed-module": "0.2.0",
"sinon": "^1.17.0", "sinon": "^1.17.0",
"timekeeper": "", "timekeeper": "",

View file

@ -1,6 +1,6 @@
define [ define [
"base" "base"
"libs/fineuploader" "fineuploader"
], (App, qq) -> ], (App, qq) ->
App.directive 'fineUpload', ($timeout) -> App.directive 'fineUpload', ($timeout) ->
return { return {

View file

@ -15,6 +15,7 @@ define [
"ide/metadata/services/metadata" "ide/metadata/services/metadata"
"ide/graphics/services/graphics" "ide/graphics/services/graphics"
"ide/preamble/services/preamble" "ide/preamble/services/preamble"
"ide/files/services/files"
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager, LabelsManager) -> ], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager, LabelsManager) ->
EditSession = ace.require('ace/edit_session').EditSession EditSession = ace.require('ace/edit_session').EditSession
ModeList = ace.require('ace/ext/modelist') ModeList = ace.require('ace/ext/modelist')
@ -37,7 +38,7 @@ define [
url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}" url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}"
return url return url
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, metadata, graphics, preamble, $http, $q) -> App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, metadata, graphics, preamble, files, $http, $q) ->
monkeyPatchSearch($rootScope, $compile) monkeyPatchSearch($rootScope, $compile)
return { return {
@ -106,7 +107,7 @@ define [
trackChangesManager = new TrackChangesManager(scope, editor, element) trackChangesManager = new TrackChangesManager(scope, editor, element)
labelsManager = new LabelsManager(scope, editor, element, labels) labelsManager = new LabelsManager(scope, editor, element, labels)
metadataManager = new MetadataManager(scope, editor, element, metadata) metadataManager = new MetadataManager(scope, editor, element, metadata)
autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, labelsManager, graphics, preamble) autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, labelsManager, graphics, preamble, files)
# Prevert Ctrl|Cmd-S from triggering save dialog # Prevert Ctrl|Cmd-S from triggering save dialog

View file

@ -10,7 +10,7 @@ define [
aceSnippetManager = ace.require('ace/snippets').snippetManager aceSnippetManager = ace.require('ace/snippets').snippetManager
class AutoCompleteManager class AutoCompleteManager
constructor: (@$scope, @editor, @element, @metadataManager, @labelsManager, @graphics, @preamble) -> constructor: (@$scope, @editor, @element, @metadataManager, @labelsManager, @graphics, @preamble, @files) ->
@monkeyPatchAutocomplete() @monkeyPatchAutocomplete()
@ -41,6 +41,8 @@ define [
Graphics = @graphics Graphics = @graphics
Preamble = @preamble Preamble = @preamble
Files = @files
GraphicsCompleter = GraphicsCompleter =
getCompletions: (editor, session, pos, prefix, callback) -> getCompletions: (editor, session, pos, prefix, callback) ->
context = Helpers.getContext(editor, pos) context = Helpers.getContext(editor, pos)
@ -67,6 +69,27 @@ define [
callback null, result callback null, result
metadataManager = @metadataManager metadataManager = @metadataManager
FilesCompleter =
getCompletions: (editor, session, pos, prefix, callback) =>
context = Helpers.getContext(editor, pos)
{lineUpToCursor, commandFragment, lineBeyondCursor, needsClosingBrace} = context
if commandFragment
match = commandFragment.match(/^\\(input|include){(\w*)/)
if match
commandName = match[1]
currentArg = match[2]
result = []
for file in Files.getTeXFiles()
if file.id != @$scope.docId
path = file.path
result.push {
caption: "\\#{commandName}{#{path}#{if needsClosingBrace then '}' else ''}",
value: "\\#{commandName}{#{path}#{if needsClosingBrace then '}' else ''}",
meta: "file",
score: 50
}
callback null, result
LabelsCompleter = LabelsCompleter =
getCompletions: (editor, session, pos, prefix, callback) -> getCompletions: (editor, session, pos, prefix, callback) ->
context = Helpers.getContext(editor, pos) context = Helpers.getContext(editor, pos)
@ -136,6 +159,7 @@ define [
ReferencesCompleter ReferencesCompleter
LabelsCompleter LabelsCompleter
GraphicsCompleter GraphicsCompleter
FilesCompleter
] ]
disable: () -> disable: () ->

View file

@ -29,7 +29,7 @@ define () ->
packageSnippets.push { packageSnippets.push {
caption: "\\usepackage{}" caption: "\\usepackage{}"
snippet: "\\usepackage{}" snippet: "\\usepackage{$1}"
meta: "pkg" meta: "pkg"
score: 70 score: 70
} }

View file

@ -22,7 +22,7 @@ define [
end = change.end end = change.end
range = new Range(end.row, 0, end.row, end.column) range = new Range(end.row, 0, end.row, end.column)
lineUpToCursor = @editor.getSession().getTextRange range lineUpToCursor = @editor.getSession().getTextRange range
if lineUpToCursor.trim() == '%' or lineUpToCursor.startsWith '\\' if lineUpToCursor.trim() == '%' or lineUpToCursor.slice(0, 1) == '\\'
range = new Range(end.row, 0, end.row, end.column + 80) range = new Range(end.row, 0, end.row, end.column + 80)
lineUpToCursor = @editor.getSession().getTextRange range lineUpToCursor = @editor.getSession().getTextRange range
commandFragment = getLastCommandFragment lineUpToCursor commandFragment = getLastCommandFragment lineUpToCursor
@ -44,9 +44,9 @@ define [
linesContainLabel or linesContainLabel or
linesContainReqPackage linesContainReqPackage
lastCommandFragmentIsLabel = commandFragment?.startsWith '\\label{' lastCommandFragmentIsLabel = commandFragment?.slice(0, 7) == '\\label{'
lastCommandFragmentIsPackage = commandFragment?.startsWith '\\usepackage' lastCommandFragmentIsPackage = commandFragment?.slice(0, 11) == '\\usepackage'
lastCommandFragmentIsReqPack = commandFragment?.startsWith '\\RequirePackage' lastCommandFragmentIsReqPack = commandFragment?.slice(0, 15) == '\\RequirePackage'
lastCommandFragmentIsMeta = lastCommandFragmentIsMeta =
lastCommandFragmentIsPackage or lastCommandFragmentIsPackage or
lastCommandFragmentIsLabel or lastCommandFragmentIsLabel or

View file

@ -0,0 +1,17 @@
define [
"base"
], (App) ->
App.factory 'files', (ide) ->
Files =
getTeXFiles: () ->
texFiles = []
ide.fileTreeManager.forEachEntity (entity, folder, path) ->
if entity.type == 'doc' && entity?.name?.match?(/.*\.(tex|txt|md)/)
cloned = _.clone(entity)
cloned.path = path
texFiles.push cloned
return texFiles
return Files

View file

@ -1,12 +1,10 @@
define [ define [
"moment"
"libs/angular-autocomplete/angular-autocomplete" "libs/angular-autocomplete/angular-autocomplete"
"libs/ui-bootstrap" "libs/ui-bootstrap"
"libs/ng-context-menu-0.1.4" "libs/ng-context-menu-0.1.4"
"libs/underscore-1.3.3" "libs/underscore-1.3.3"
"libs/algolia-2.5.2" "libs/algolia-2.5.2"
"libs/jquery.storage" "libs/jquery.storage"
"libs/fineuploader"
"libs/angular-sanitize-1.2.17" "libs/angular-sanitize-1.2.17"
"libs/angular-cookie" "libs/angular-cookie"
"libs/passfield" "libs/passfield"

View file

@ -123,3 +123,13 @@ define [
$scope.onComplete = (error, name, response) -> $scope.onComplete = (error, name, response) ->
if response.project_id? if response.project_id?
window.location = '/project/' + response.project_id window.location = '/project/' + response.project_id
App.controller 'V1ImportModalController', ($scope, $modalInstance, project) ->
$scope.project = project
$scope.step = 1
$scope.dismiss = () ->
$modalInstance.dismiss('cancel')
$scope.moveToConfirmation = () ->
$scope.step = 2

View file

@ -443,6 +443,16 @@ define [
window.location = path window.location = path
$scope.openV1ImportModal = (project) ->
$modal.open(
templateUrl: 'v1ImportModalTemplate'
controller: 'V1ImportModalController'
size: 'lg'
windowClass: 'v1-import-modal'
resolve:
project: () -> project
)
if storedUIOpts?.filter? if storedUIOpts?.filter?
if storedUIOpts.filter == "tag" and storedUIOpts.selectedTagId? if storedUIOpts.filter == "tag" and storedUIOpts.selectedTagId?
markTagAsSelected(storedUIOpts.selectedTagId) markTagAsSelected(storedUIOpts.selectedTagId)

View file

@ -1,18 +1 @@
<?xml version="1.0" encoding="utf-8"?> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 136 157"><path d="M37.2 39.7C14.8 54 0 77.3 0 102.3 0 132.5 24.5 157 54.7 157c30.2 0 54.7-24.5 54.7-54.7 0-23.3-14.6-43.3-35.2-51.1-4-1.5-12.6-4.2-19.4-3.6-9.8 6.2-21.8 19-27.4 31.8 8.4-10.1 21.5-14.5 33.2-12.6 17.1 2.8 30.2 17.6 30.2 35.6 0 19.9-16.1 36-36 36-11 0-20.8-4.9-27.4-12.6-9.9-11.5-12.4-23.9-10.4-36 6.9-42.4 57.2-66.5 94.6-75.8C99.4 20.5 77.4 31.1 62 42.6c44.9 17.3 52.2-20.5 73.2-37.5-21.2-8.2-97.9-11.2-98 34.6z" fill="#9b9b9b"/></svg>
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 136 157" style="enable-background:new 0 0 136 157;" xml:space="preserve">
<style type="text/css">
.st0{fill:#9B9B9B;}
</style>
<g id="Page-1">
<g id="overleaf">
<g id="Group">
<path id="Fill-1" class="st0" d="M37.2,39.7C14.8,54,0,77.3,0,102.3C0,132.5,24.5,157,54.7,157c30.2,0,54.7-24.5,54.7-54.7
c0-23.3-14.6-43.3-35.2-51.1c-4-1.5-12.6-4.2-19.4-3.6c-9.8,6.2-21.8,19-27.4,31.8c8.4-10.1,21.5-14.5,33.2-12.6
c17.1,2.8,30.2,17.6,30.2,35.6c0,19.9-16.1,36-36,36c-11,0-20.8-4.9-27.4-12.6C17.5,114.3,15,101.9,17,89.8
c6.9-42.4,57.2-66.5,94.6-75.8C99.4,20.5,77.4,31.1,62,42.6c44.9,17.3,52.2-20.5,73.2-37.5C114-3.1,37.3-6.1,37.2,39.7z"/>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 947 B

After

Width:  |  Height:  |  Size: 503 B

View file

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 136 157"><path d="M37.2 39.7C14.8 54 0 77.3 0 102.3 0 132.5 24.5 157 54.7 157s54.7-24.5 54.7-54.7c0-23.3-14.6-43.3-35.2-51.1-4-1.5-12.6-4.2-19.4-3.6-9.8 6.2-21.8 19-27.4 31.8 8.4-10.1 21.5-14.5 33.2-12.6 17.1 2.8 30.2 17.6 30.2 35.6 0 19.9-16.1 36-36 36-11 0-20.8-4.9-27.4-12.6-9.9-11.5-12.4-23.9-10.4-36 6.9-42.4 57.2-66.5 94.6-75.8C99.4 20.5 77.4 31.1 62 42.6c44.9 17.3 52.2-20.5 73.2-37.5-21.2-8.2-97.9-11.2-98 34.6z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 493 B

View file

@ -1,10 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg width="136" height="157" xmlns="http://www.w3.org/2000/svg"><path d="M37.205 39.652C14.822 53.982 0 77.339 0 102.326 0 132.522 24.48 157 54.681 157c30.198 0 54.674-24.478 54.674-54.674 0-23.34-14.626-43.276-35.204-51.111-3.958-1.505-12.556-4.213-19.421-3.635-9.806 6.234-21.751 19.044-27.411 31.809 8.416-10.093 21.537-14.488 33.17-12.619 17.126 2.777 30.208 17.638 30.208 35.551 0 19.896-16.126 36.021-36.016 36.021-10.962 0-20.785-4.896-27.388-12.619C17.516 114.299 15 101.91 16.975 89.809c6.927-42.375 57.233-66.53 94.636-75.799-12.207 6.458-34.227 17.074-49.626 28.63 44.924 17.341 52.184-20.517 73.217-37.459C114.038-3.07 37.33-6.117 37.205 39.652z" fill="#4F9C45" fill-rule="evenodd"/></svg>
<svg width="136px" height="157px" viewBox="0 0 136 157" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="overleaf" fill="#4F9C45">
<g id="Group">
<path d="M37.205,39.652 C14.822,53.982 0,77.339 0,102.326 C0,132.522 24.48,157 54.681,157 C84.879,157 109.355,132.522 109.355,102.326 C109.355,78.986 94.729,59.05 74.151,51.215 C70.193,49.71 61.595,47.002 54.73,47.58 C44.924,53.814 32.979,66.624 27.319,79.389 C35.735,69.296 48.856,64.901 60.489,66.77 C77.615,69.547 90.697,84.408 90.697,102.321 C90.697,122.217 74.571,138.342 54.681,138.342 C43.719,138.342 33.896,133.446 27.293,125.723 C17.516,114.299 15,101.91 16.975,89.809 C23.902,47.434 74.208,23.279 111.611,14.01 C99.404,20.468 77.384,31.084 61.985,42.64 C106.909,59.981 114.169,22.123 135.202,5.181 C114.038,-3.07 37.33,-6.117 37.205,39.652 Z" id="Fill-1"></path>
</g>
</g>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 1 KiB

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 205 KiB

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,7 +2,7 @@
padding: (@line-height-computed / 4) (@line-height-computed / 2); padding: (@line-height-computed / 4) (@line-height-computed / 2);
background-color: @state-warning-bg; background-color: @state-warning-bg;
color: #333; color: #333;
border-bottom: 1px solid @toolbar-border-color; border-bottom: 1px solid @common-border-color;
} }
.clickable { .clickable {

View file

@ -26,6 +26,20 @@
} }
} }
.editor-menu-icon when (@is-overleaf = true) {
&.fa {
width: 1em;
background: url(/img/ol-brand/overleaf-o-white.svg) center / contain no-repeat;
&::before {
// Disable the font-awesome icon when in Overleaf by replacing it with a
// non-breakable space instead (otherwise the browser would render a
// zero-width element).
content: "\00a0";
}
}
}
.full-size { .full-size {
position: absolute; position: absolute;
top: 0; top: 0;
@ -250,8 +264,8 @@
.ui-layout-resizer { .ui-layout-resizer {
width: 6px; width: 6px;
background-color: #f4f4f4; background-color: #f4f4f4;
border-left: 1px solid @toolbar-border-color; border-left: 1px solid @editor-border-color;
border-right: 1px solid @toolbar-border-color; border-right: 1px solid @editor-border-color;
.ui-layout-toggler { .ui-layout-toggler {
color: #999; color: #999;
font-family: FontAwesome; font-family: FontAwesome;

View file

@ -126,12 +126,12 @@
height: @new-message-height; height: @new-message-height;
background-color: @gray-lightest; background-color: @gray-lightest;
padding: @line-height-computed / 4; padding: @line-height-computed / 4;
border-top: 1px solid @toolbar-border-color; border-top: 1px solid @editor-border-color;
textarea { textarea {
overflow: auto; overflow: auto;
resize: none; resize: none;
border-radius: @border-radius-base; border-radius: @border-radius-base;
border: 1px solid @toolbar-border-color; border: 1px solid @editor-border-color;
height: 100%; height: 100%;
width: 100%; width: 100%;
color: @gray-dark; color: @gray-dark;

View file

@ -1,5 +1,20 @@
.fake-full-width-bg(@bg-color) {
&::before {
content: '\00a0';
position: absolute;
width: 100%;
right: 100%;
background-color: @bg-color;
}
}
#file-tree {
.toolbar.toolbar-filetree {
.toolbar-small-mixin;
.toolbar-alt-mixin;
padding: 0 5px;
}
aside#file-tree {
.file-tree-inner { .file-tree-inner {
position: absolute; position: absolute;
top: 32px; top: 32px;
@ -7,12 +22,14 @@ aside#file-tree {
left: 0; left: 0;
right: 0; right: 0;
overflow-y: auto; overflow-y: auto;
background-color: @file-tree-bg;
&.no-toolbar { &.no-toolbar {
top: 0; top: 0;
} }
} }
// TODO; Consolidate with "Project files" in Overleaf
h3 { h3 {
font-size: 1rem; font-size: 1rem;
border-bottom: 1px solid @gray; border-bottom: 1px solid @gray;
@ -20,10 +37,13 @@ aside#file-tree {
margin: (@line-height-computed / 2); margin: (@line-height-computed / 2);
} }
ul.file-tree-list { ul.file-tree-list when (@is-overleaf = false) {
font-size: 0.8rem; font-size: 0.8rem;
margin: 0;
padding: (@line-height-computed / 4) 0; padding: (@line-height-computed / 4) 0;
}
ul.file-tree-list {
margin: 0;
overflow-x: hidden; overflow-x: hidden;
height: 100%; height: 100%;
@ -32,7 +52,7 @@ aside#file-tree {
} }
li { li {
line-height: 2.6; line-height: @file-tree-line-height;
position: relative; position: relative;
.entity { .entity {
@ -40,29 +60,42 @@ aside#file-tree {
} }
.entity-name { .entity-name {
color: @gray-darker; color: @file-tree-item-color;
cursor: pointer; cursor: pointer;
white-space: nowrap; white-space: nowrap;
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
&:hover { &:hover {
background-color: @gray-lightest; background-color: @file-tree-item-hover-bg;
}
&:hover when (@is-overleaf = true) {
// When the entity is a subfolder, the DOM element is "indented" via margin-left. This makes the
// element not fill the entire file-tree width (as it's spaced from the left-hand side via margin)
// and, in consequence, the background gets clipped. The ::before pseudo-selector is used to fill
// the empty space.
.fake-full-width-bg(@file-tree-item-hover-bg);
} }
input { input {
line-height: 1.6; line-height: 1.6;
} }
&.droppable-hover { &.droppable-hover when (@is-overleaf = false) {
background-color: fade(@file-tree-droppable-background-color, 60%); background-color: fade(@file-tree-droppable-bg-color, 60%);
} }
&.droppable-hover when (@is-overleaf = true) {
background-color: @file-tree-droppable-bg-color;
.fake-full-width-bg(@file-tree-droppable-bg-color);
}
} }
i.fa { i.fa {
color: @gray-light; color: @file-tree-item-icon-color;
font-size: 14px; font-size: 14px;
} }
i.fa-folder-open, i.fa-folder { i.fa-folder-open, i.fa-folder {
color: lighten(desaturate(@link-color, 10%), 5%); color: @file-tree-item-folder-color;
font-size: 14px; font-size: 14px;
} }
@ -70,14 +103,31 @@ aside#file-tree {
width: 24px; width: 24px;
padding: 6px; padding: 6px;
font-size: 0.7rem; font-size: 0.7rem;
color: @gray color: @file-tree-item-toggle-color;
} }
&.multi-selected { &.multi-selected {
> .entity > .entity-name { > .entity > .entity-name when (@is-overleaf = false) {
background-color: lighten(@brand-info, 40%); background-color: @file-tree-multiselect-bg;
&:hover { &:hover {
background-color: lighten(@brand-info, 30%); background-color: @file-tree-multiselect-hover-bg;
}
}
> .entity when (@is-overleaf = true) {
> .entity-name {
> div > i.fa,
> i.fa,
.entity-menu-toggle i.fa {
color: #FFF;
}
color: #FFF;
font-weight: bold;
background-color: @file-tree-multiselect-bg;
.fake-full-width-bg(@file-tree-multiselect-bg);
&:hover {
background-color: @file-tree-multiselect-hover-bg;
.fake-full-width-bg(@file-tree-multiselect-hover-bg);
}
} }
} }
} }
@ -96,6 +146,7 @@ aside#file-tree {
top: 1px; top: 1px;
left: 44px; left: 44px;
right: 32px; right: 32px;
color: @file-tree-item-input-color;
input { input {
width: 100%; width: 100%;
} }
@ -111,7 +162,7 @@ aside#file-tree {
&:not(.multi-selected) { &:not(.multi-selected) {
ul.file-tree-list li.selected { ul.file-tree-list li.selected {
> .entity > .entity-name { > .entity > .entity-name when (@is-overleaf = false) {
color: @link-color; color: @link-color;
border-right: 4px solid @link-color; border-right: 4px solid @link-color;
font-weight: bold; font-weight: bold;
@ -123,15 +174,38 @@ aside#file-tree {
display: inline; display: inline;
} }
} }
> .entity when (@is-overleaf = true) {
> .entity-name {
> div > i.fa,
> i.fa,
.entity-menu-toggle i.fa {
color: #FFF;
}
background-color: @file-tree-item-selected-bg;
font-weight: bold;
padding-right: 32px;
.fake-full-width-bg(@file-tree-item-selected-bg);
.entity-menu-toggle {
display: inline;
}
}
}
} }
} }
ul.droppable-hover { ul.droppable-hover.file-tree-list when (@is-overleaf = false) {
background-color: fade(@file-tree-droppable-background-color, 60%); background-color: fade(@file-tree-droppable-bg-color, 60%);
}
ul.droppable-hover.file-tree-list when (@is-overleaf = true) {
background-color: @file-tree-droppable-bg-color;
.fake-full-width-bg(@file-tree-droppable-bg-color);
} }
} }
.editor-dark { // TODO check if the OL Beta theme is OK with darker themes.
.editor-dark when (@is-overleaf = false) {
aside#file-tree { aside#file-tree {
// background-color: lighten(@editor-dark-background-color, 10%); // background-color: lighten(@editor-dark-background-color, 10%);
@ -159,6 +233,4 @@ aside#file-tree {
} }
} }
} }
} }

View file

@ -70,7 +70,7 @@
} }
aside.change-list { aside.change-list {
border-left: 1px solid @toolbar-border-color; border-left: 1px solid @editor-border-color;
height: 100%; height: 100%;
width: @changesListWidth; width: @changesListWidth;
position: absolute; position: absolute;
@ -91,7 +91,7 @@
.day { .day {
background-color: #fafafa; background-color: #fafafa;
border-bottom: 1px solid @toolbar-border-color; border-bottom: 1px solid @editor-border-color;
padding: 4px; padding: 4px;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
@ -132,7 +132,7 @@
padding: (@line-height-computed / 4); padding: (@line-height-computed / 4);
padding-left: 38px; padding-left: 38px;
min-height: 38px; min-height: 38px;
border-bottom: 1px solid @toolbar-border-color; border-bottom: 1px solid @editor-border-color;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
background-color: @gray-lightest; background-color: @gray-lightest;

View file

@ -1,3 +1,15 @@
.pdf .toolbar.toolbar-pdf when (@is-overleaf = true) {
.toolbar-small-mixin;
.toolbar-alt-mixin;
border-bottom: 0;
padding-right: 5px;
}
.pdf .toolbar.toolbar-pdf when (@is-overleaf = false) {
.toolbar-tall-mixin;
padding: 0 (@line-height-computed / 2);
}
.pdf-viewer, .pdf-logs, .pdf-errors, .pdf-uncompiled { .pdf-viewer, .pdf-logs, .pdf-errors, .pdf-uncompiled {
.full-size; .full-size;
top: 58px; top: 58px;
@ -13,6 +25,42 @@
} }
} }
.btn-recompile-group when (@is-overleaf = true) {
align-self: stretch;
margin-right: 5px;
}
.btn-recompile-group when (@is-overleaf = false) {
margin-right: (@line-height-computed / 2);
}
.btn-recompile when (@is-overleaf = true) {
height: 100%;
.btn-primary;
padding-top: 3px;
padding-bottom: 3px;
&:first-child {
border-top-left-radius: 0;
border-bottom-left-radius: 0;
}
}
.btn-recompile when (@is-overleaf = false) {
.btn-info;
}
.btn-split-screen when (@is-overleaf = false) {
.fa {
display: none;
}
}
.btn-split-screen when (@is-overleaf = true) {
.fa {
display: none;
}
}
.pdf-viewer { .pdf-viewer {
iframe { iframe {
width: 100%; width: 100%;
@ -82,31 +130,43 @@
.pdf .toolbar { .pdf .toolbar {
.toolbar-right { .toolbar-right {
margin-right: @line-height-computed / 2;
a { a {
&:hover { &:hover {
i { i when (@is-overleaf = false) {
box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25); box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.25);
border-color: @gray-dark; border-color: @gray-dark;
} }
} }
i { i when (@is-overleaf = false) {
display: inline-block; display: inline-block;
width: 16px; width: 16px;
height: 16px; height: 16px;
border: 1px solid @gray-light; border: 1px solid @gray-light;
margin-top: 5px; margin-top: 5px;
} }
i.full-screen { i.full-screen {
border-top-width: 3px; border-top-width: 3px;
border-radius: 2px; border-radius: 2px;
} }
i.full-screen when (@is-overleaf = true) {
display: none;
}
i.split-screen { i.split-screen {
width: 7px; width: 7px;
border-top-width: 3px; border-top-width: 3px;
border-radius: 2px; border-radius: 2px;
margin-left: 2px; margin-left: 2px;
} }
i.split-screen when (@is-overleaf = true) {
display: none;
}
i.fa when (@is-overleaf = false) {
display: none;
}
} }
} }
} }

View file

@ -902,6 +902,13 @@
} }
} }
.review-icon when (@is-overleaf) {
background-position-y: -60px;
.toolbar .btn-full-height:hover & {
background-position-y: -60px;
}
}
.resolved-comments-toggle { .resolved-comments-toggle {
font-size: 14px; font-size: 14px;
color: lighten(@rp-type-blue, 25%); color: lighten(@rp-type-blue, 25%);

View file

@ -1,6 +1,6 @@
.ace_search { .ace_search {
background-color: @gray-lightest; background-color: @gray-lightest;
border: 1px solid @toolbar-border-color; border: 1px solid @editor-border-color;
border-top: 0 none; border-top: 0 none;
width: 350px; width: 350px;
overflow: hidden; overflow: hidden;

View file

@ -1,6 +1,8 @@
.toolbar { .toolbar {
display: flex;
align-items: center;
height: 40px; height: 40px;
border-bottom: 1px solid @toolbar-border-color; border-bottom: @toolbar-border-bottom;
> a, .toolbar-right > a { > a, .toolbar-right > a {
position: relative; position: relative;
@ -22,13 +24,16 @@
.toolbar-left > a:not(.btn), .toolbar-left > a:not(.btn),
.toolbar-right > a:not(.btn) { .toolbar-right > a:not(.btn) {
display: inline-block; display: inline-block;
color: @gray-light; color: @toolbar-icon-btn-color;
padding: 4px 10px 5px; padding: 0 5px;
margin: 1px 2px;
border-radius: @border-radius-small; border-radius: @border-radius-small;
&.toolbar-header-back-projects {
padding: 5px 10px 4px;
margin-bottom: 1px;
}
&:hover { &:hover {
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.25); text-shadow: @toolbar-icon-btn-hover-shadow;
color: @gray-dark; color: @toolbar-icon-btn-hover-color;
text-decoration: none; text-decoration: none;
} }
&.active, &:active { &.active, &:active {
@ -37,7 +42,7 @@
} }
color: white; color: white;
background-color: @link-color; background-color: @link-color;
.box-shadow(inset 0 3px 5px rgba(0, 0, 0, 0.225)); box-shadow: @toolbar-icon-btn-hover-boxshadow;
&:hover { &:hover {
color: white; color: white;
} }
@ -48,18 +53,18 @@
border: none; border: none;
border-radius: 0; border-radius: 0;
border-right: 1px solid @toolbar-border-color; border-right: 1px solid @toolbar-border-color;
color: @link-color; color: @toolbar-btn-color;
padding: 3px 10px 5px; padding: 3px 10px 5px;
font-size: 20px; font-size: 20px;
&:hover { &:hover {
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.15); text-shadow: @toolbar-btn-hover-text-shadow;
background-color: darken(white, 10%); background-color: @toolbar-btn-hover-bg-color;
color: @link-hover-color; color: @toolbar-btn-hover-color;
} }
&.active, &:active { &.active, &:active {
color: white; color: @toolbar-btn-active-color;
background-color: @link-color; background-color: @toolbar-btn-active-bg-color;
.box-shadow(inset 0 3px 5px rgba(0, 0, 0, 0.225)); box-shadow: @toolbar-btn-active-shadow;
} }
.label { .label {
top: 4px; top: 4px;
@ -72,12 +77,17 @@
} }
.toolbar-left { .toolbar-left {
display: flex;
float: left; float: left;
text-align: center; text-align: center;
align-items: center;
} }
.toolbar-right { .toolbar-right {
float: right; display: flex;
align-items: center;
flex-grow: 1;
justify-content: flex-end;
.btn-full-height { .btn-full-height {
border-right: 0; border-right: 0;
border-left: 1px solid @toolbar-border-color; border-left: 1px solid @toolbar-border-color;
@ -96,7 +106,8 @@
} }
&.toolbar-header { &.toolbar-header {
box-shadow: 0 0 2px #ccc; background-color: @toolbar-header-bg-color;
box-shadow: @toolbar-header-shadow;
position: absolute; position: absolute;
top: 0; top: 0;
left: 0; left: 0;
@ -105,43 +116,30 @@
} }
&.toolbar-small { &.toolbar-small {
height: 32px; .toolbar-small-mixin;
> a, .toolbar-right > a {
padding: 2px 4px 1px 4px;
margin: 0;
margin-top: 2px;
}
> a {
margin-left: 2px;
}
.toolbar-right > a {
margin-left: 0;
margin-right: 2px;
}
} }
&.toolbar-tall { &.toolbar-tall {
height: 58px; .toolbar-small-mixin;
padding-top: 10px;
> a, .toolbar-right > a {
padding: 4px 10px 5px;
}
> a.btn, .toolbar-right > a.btn {
margin: 0 (@line-height-computed / 2);
}
.btn-group {
margin: 0 (@line-height-computed / 2);
> .btn-group {
margin: 0;
}
}
} }
&.toolbar-alt { &.toolbar-alt {
background-color: #fafafa; .toolbar-alt-mixin;
} }
} }
.toolbar-small-mixin() {
height: 32px;
}
.toolbar-tall-mixin() {
height: 58px;
padding-top: 10px;
}
.toolbar-alt-mixin() {
background-color: @toolbar-alt-bg-color;
}
.toolbar-label { .toolbar-label {
display: none; display: none;
margin: 0 4px; margin: 0 4px;

View file

@ -0,0 +1,24 @@
.v1-import-title {
text-align: center;
}
.v1-import-img {
width: 100%;
}
.v1-import-warning {
text-align: center;
color: #fdce02;
font-size: 14em;
line-height: 1em;
}
.v1-import-footer {
display: flex;
justify-content: space-evenly;
text-align: left;
}
.v1-import-btn {
width: 20rem;
}

View file

@ -1,3 +1,5 @@
@import "./list/v1-import-modal.less";
@announcements-shadow: 0 2px 20px rgba(0, 0, 0, 0.5); @announcements-shadow: 0 2px 20px rgba(0, 0, 0, 0.5);
@keyframes pulse { @keyframes pulse {
@ -332,6 +334,10 @@ ul.project-list {
.projectName { .projectName {
margin-right: @line-height-computed / 4; margin-right: @line-height-computed / 4;
} }
.v1ProjectName {
margin-right: @line-height-computed / 4;
padding: 0;
}
.tag-label { .tag-label {
margin-left: @line-height-computed / 4; margin-left: @line-height-computed / 4;

View file

@ -3,7 +3,7 @@
padding: (@line-height-computed / 4) (@line-height-computed / 2); padding: (@line-height-computed / 4) (@line-height-computed / 2);
background-color: @state-warning-bg; background-color: @state-warning-bg;
color: #333; color: #333;
border-bottom: 1px solid @toolbar-border-color; border-bottom: 1px solid @common-border-color;
text-align:center; text-align:center;
img { img {

View file

@ -794,7 +794,9 @@
@left-menu-animation-duration: 0.35s; @left-menu-animation-duration: 0.35s;
@toolbar-border-color: @gray-lighter; @toolbar-border-color: @gray-lighter;
@file-tree-droppable-background-color: rgb(252, 231, 199); @common-border-color: @gray-lighter;
@editor-border-color: @gray-lighter;
@file-tree-droppable-bg-color: rgb(252, 231, 199);
@editor-dark-background-color: #333; @editor-dark-background-color: #333;
@editor-dark-toolbar-border-color: #222; @editor-dark-toolbar-border-color: #222;
@ -886,6 +888,35 @@
@footer-bg-color : transparent; @footer-bg-color : transparent;
@footer-padding : 2em; @footer-padding : 2em;
// Editor header
@toolbar-header-bg-color : transparent;
@toolbar-header-shadow : 0 0 2px #ccc;
@toolbar-btn-color : @link-color;
@toolbar-btn-hover-color : @link-hover-color;
@toolbar-btn-hover-bg-color : darken(white, 10%);
@toolbar-btn-hover-text-shadow : 0 1px 0 rgba(0, 0, 0, 0.15);
@toolbar-btn-active-color : white;
@toolbar-btn-active-bg-color : @link-color;
@toolbar-btn-active-shadow : inset 0 3px 5px rgba(0, 0, 0, 0.225);
@toolbar-alt-bg-color : #fafafa;
@toolbar-icon-btn-color : @gray-light;
@toolbar-icon-btn-hover-color : @gray-dark;
@toolbar-icon-btn-hover-shadow : 0 1px 0 rgba(0, 0, 0, 0.25);
@toolbar-icon-btn-hover-boxshadow : inset 0 3px 5px rgba(0, 0, 0, 0.225);
@toolbar-border-bottom : 1px solid @toolbar-border-color;
// Editor file-tree
@file-tree-bg : transparent;
@file-tree-line-height : 2.6;
@file-tree-item-color : @gray-darker;
@file-tree-item-toggle-color : @gray;
@file-tree-item-icon-color : @gray-light;
@file-tree-item-input-color : inherit;
@file-tree-item-folder-color : lighten(desaturate(@link-color, 10%), 5%);
@file-tree-item-hover-bg : @gray-lightest;
@file-tree-item-selected-bg : transparent;
@file-tree-multiselect-bg : lighten(@brand-info, 40%);
@file-tree-multiselect-hover-bg : lighten(@brand-info, 30%);
// Tags // Tags
@tag-border-radius : 0.25em; @tag-border-radius : 0.25em;
@tag-bg-color : @label-default-bg; @tag-bg-color : @label-default-bg;

View file

@ -8,7 +8,7 @@
@ol-blue-gray-1 : #E4E8EE; @ol-blue-gray-1 : #E4E8EE;
@ol-blue-gray-2 : #9DA7B7; @ol-blue-gray-2 : #9DA7B7;
@ol-blue-gray-3 : #5D6879; @ol-blue-gray-3 : #5D6879;
@ol-blue-gray-4 : #485973; @ol-blue-gray-4 : #455265;
@ol-blue-gray-5 : #2C3645; @ol-blue-gray-5 : #2C3645;
@ol-blue-gray-6 : #1E2530; @ol-blue-gray-6 : #1E2530;
@ -156,6 +156,36 @@
@footer-link-hover-color : @ol-dark-green; @footer-link-hover-color : @ol-dark-green;
@footer-padding : 2em 0; @footer-padding : 2em 0;
// Editor header
@toolbar-header-bg-color : @ol-blue-gray-6;
@toolbar-header-shadow : none;
@toolbar-btn-color : #FFF;
@toolbar-btn-hover-color : #FFF;
@toolbar-btn-hover-bg-color : @ol-blue-gray-5;
@toolbar-btn-hover-text-shadow : none;
@toolbar-btn-active-color : #FFF;
@toolbar-btn-active-bg-color : @ol-green;
@toolbar-btn-active-shadow : none;
@toolbar-border-color : @ol-blue-gray-5;
@toolbar-alt-bg-color : @ol-blue-gray-5;
@toolbar-icon-btn-color : #FFF;
@toolbar-icon-btn-hover-color : #FFF;
@toolbar-icon-btn-hover-shadow : none;
@toolbar-icon-btn-hover-boxshadow : none;
@toolbar-border-bottom : 1px solid @toolbar-border-color;
// Editor file-tree
@file-tree-bg : @ol-blue-gray-4;
@file-tree-line-height : 2.05;
@file-tree-item-color : #FFF;
@file-tree-item-input-color : @ol-blue-gray-5;
@file-tree-item-toggle-color : @ol-blue-gray-2;
@file-tree-item-icon-color : @ol-blue-gray-2;
@file-tree-item-folder-color : @ol-blue-gray-2;
@file-tree-item-hover-bg : @ol-blue-gray-5;
@file-tree-item-selected-bg : @ol-green;
@file-tree-multiselect-bg : @ol-blue;
@file-tree-multiselect-hover-bg : @ol-dark-blue;
@file-tree-droppable-bg-color : tint(@ol-green, 5%);
//== Colors //== Colors
// //
//## Gray and brand colors for use across Bootstrap. //## Gray and brand colors for use across Bootstrap.

View file

@ -0,0 +1,307 @@
async = require "async"
expect = require("chai").expect
mkdirp = require "mkdirp"
ObjectId = require("mongojs").ObjectId
Path = require "path"
fs = require "fs"
Settings = require "settings-sharelatex"
_ = require "underscore"
ProjectGetter = require "../../../app/js/Features/Project/ProjectGetter.js"
MockDocUpdaterApi = require './helpers/MockDocUpdaterApi'
MockFileStoreApi = require './helpers/MockFileStoreApi'
MockProjectHistoryApi = require './helpers/MockProjectHistoryApi'
request = require "./helpers/request"
User = require "./helpers/User"
describe "ProjectStructureChanges", ->
before (done) ->
@owner = new User()
@owner.login done
describe "creating a project from the example template", ->
before (done) ->
MockDocUpdaterApi.clearProjectStructureUpdates()
@owner.createProject "example-project", {template: "example"}, (error, project_id) =>
throw error if error?
@example_project_id = project_id
done()
it "should version creating a doc", ->
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates
expect(updates.length).to.equal(2)
_.each updates, (update) =>
expect(update.userId).to.equal(@owner._id)
expect(update.docLines).to.be.a('string')
expect(_.where(updates, pathname: "/main.tex").length).to.equal 1
expect(_.where(updates, pathname: "/references.bib").length).to.equal 1
it "should version creating a file", ->
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
expect(updates.length).to.equal(1)
update = updates[0]
expect(update.userId).to.equal(@owner._id)
expect(update.pathname).to.equal("/universe.jpg")
expect(update.url).to.be.a('string');
describe "duplicating a project", ->
before (done) ->
MockDocUpdaterApi.clearProjectStructureUpdates()
@owner.request.post {
uri: "/Project/#{@example_project_id}/clone",
json:
projectName: 'new.tex'
}, (error, res, body) =>
throw error if error?
if res.statusCode < 200 || res.statusCode >= 300
throw new Error("failed to add doc #{res.statusCode}")
@dup_project_id = body.project_id
done()
it "should version the dosc created", ->
updates = MockDocUpdaterApi.getProjectStructureUpdates(@dup_project_id).docUpdates
expect(updates.length).to.equal(2)
_.each updates, (update) =>
expect(update.userId).to.equal(@owner._id)
expect(update.docLines).to.be.a('string')
expect(_.where(updates, pathname: "/main.tex").length).to.equal(1)
expect(_.where(updates, pathname: "/references.bib").length).to.equal(1)
it "should version the files created", ->
updates = MockDocUpdaterApi.getProjectStructureUpdates(@dup_project_id).fileUpdates
expect(updates.length).to.equal(1)
update = updates[0]
expect(update.userId).to.equal(@owner._id)
expect(update.pathname).to.equal("/universe.jpg")
expect(update.url).to.be.a('string');
describe "adding a doc", ->
before (done) ->
MockDocUpdaterApi.clearProjectStructureUpdates()
ProjectGetter.getProject @example_project_id, (error, projects) =>
throw error if error?
@owner.request.post {
uri: "project/#{@example_project_id}/doc",
json:
name: 'new.tex'
parent_folder_id: projects[0].rootFolder[0]._id
}, (error, res, body) =>
throw error if error?
if res.statusCode < 200 || res.statusCode >= 300
throw new Error("failed to add doc #{res.statusCode}")
done()
it "should version the doc added", ->
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).docUpdates
expect(updates.length).to.equal(1)
update = updates[0]
expect(update.userId).to.equal(@owner._id)
expect(update.pathname).to.equal("/new.tex")
expect(update.docLines).to.be.a('string');
describe "uploading a project", ->
before (done) ->
MockDocUpdaterApi.clearProjectStructureUpdates()
zip_file = fs.createReadStream(Path.resolve(__dirname + '/../files/test_project.zip'))
req = @owner.request.post {
uri: "project/new/upload",
formData:
qqfile: zip_file
}, (error, res, body) =>
throw error if error?
if res.statusCode < 200 || res.statusCode >= 300
throw new Error("failed to upload project #{res.statusCode}")
@uploaded_project_id = JSON.parse(body).project_id
done()
it "should version the dosc created", ->
updates = MockDocUpdaterApi.getProjectStructureUpdates(@uploaded_project_id).docUpdates
expect(updates.length).to.equal(1)
update = updates[0]
expect(update.userId).to.equal(@owner._id)
expect(update.pathname).to.equal("/main.tex")
expect(update.docLines).to.equal("Test")
it "should version the files created", ->
updates = MockDocUpdaterApi.getProjectStructureUpdates(@uploaded_project_id).fileUpdates
expect(updates.length).to.equal(1)
update = updates[0]
expect(update.userId).to.equal(@owner._id)
expect(update.pathname).to.equal("/1pixel.png")
expect(update.url).to.be.a('string');
describe "uploading a file", ->
before (done) ->
ProjectGetter.getProject @example_project_id, (error, projects) =>
throw error if error?
@root_folder_id = projects[0].rootFolder[0]._id.toString()
done()
beforeEach () ->
MockDocUpdaterApi.clearProjectStructureUpdates()
it "should version a newly uploaded file", (done) ->
image_file = fs.createReadStream(Path.resolve(__dirname + '/../files/1pixel.png'))
req = @owner.request.post {
uri: "project/#{@example_project_id}/upload",
qs:
folder_id: @root_folder_id
formData:
qqfile:
value: image_file
options:
filename: '1pixel.png',
contentType: 'image/png'
}, (error, res, body) =>
throw error if error?
if res.statusCode < 200 || res.statusCode >= 300
throw new Error("failed to upload file #{res.statusCode}")
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
expect(updates.length).to.equal(1)
update = updates[0]
expect(update.userId).to.equal(@owner._id)
expect(update.pathname).to.equal("/1pixel.png")
expect(update.url).to.be.a('string');
@original_file_url = update.url
done()
it "should version a replacement file", (done) ->
image_file = fs.createReadStream(Path.resolve(__dirname + '/../files/2pixel.png'))
req = @owner.request.post {
uri: "project/#{@example_project_id}/upload",
qs:
folder_id: @root_folder_id
formData:
qqfile:
value: image_file
options:
filename: '1pixel.png',
contentType: 'image/png'
}, (error, res, body) =>
throw error if error?
if res.statusCode < 200 || res.statusCode >= 300
throw new Error("failed to upload file #{res.statusCode}")
updates = MockDocUpdaterApi.getProjectStructureUpdates(@example_project_id).fileUpdates
expect(updates.length).to.equal(1)
update = updates[0]
expect(update.userId).to.equal(@owner._id)
expect(update.pathname).to.equal("/1pixel.png")
expect(update.url).to.be.a('string');
done()
describe "tpds", ->
before (done) ->
@tpds_project_name = "tpds-project-#{new ObjectId().toString()}"
@owner.createProject @tpds_project_name, (error, project_id) =>
throw error if error?
@tpds_project_id = project_id
mkdirp Settings.path.dumpFolder, done
beforeEach () ->
MockDocUpdaterApi.clearProjectStructureUpdates()
it "should version adding a doc", (done) ->
tex_file = fs.createReadStream(Path.resolve(__dirname + '/../files/test.tex'))
req = @owner.request.post {
uri: "/user/#{@owner._id}/update/#{@tpds_project_name}/test.tex",
auth:
user: _.keys(Settings.httpAuthUsers)[0]
pass: _.values(Settings.httpAuthUsers)[0]
sendImmediately: true
}
tex_file.on "error", (err) ->
throw err
req.on "error", (err) ->
throw err
req.on "response", (res) =>
if res.statusCode < 200 || res.statusCode >= 300
throw new Error("failed to upload file #{res.statusCode}")
updates = MockDocUpdaterApi.getProjectStructureUpdates(@tpds_project_id).docUpdates
expect(updates.length).to.equal(1)
update = updates[0]
expect(update.userId).to.equal(@owner._id)
expect(update.pathname).to.equal("/test.tex")
expect(update.docLines).to.equal("Test")
done()
tex_file.pipe(req)
it "should version adding a new file", (done) ->
image_file = fs.createReadStream(Path.resolve(__dirname + '/../files/1pixel.png'))
req = @owner.request.post {
uri: "/user/#{@owner._id}/update/#{@tpds_project_name}/1pixel.png",
auth:
user: _.keys(Settings.httpAuthUsers)[0]
pass: _.values(Settings.httpAuthUsers)[0]
sendImmediately: true
}
image_file.on "error", (err) ->
throw err
req.on "error", (err) ->
throw err
req.on "response", (res) =>
if res.statusCode < 200 || res.statusCode >= 300
throw new Error("failed to upload file #{res.statusCode}")
updates = MockDocUpdaterApi.getProjectStructureUpdates(@tpds_project_id).fileUpdates
expect(updates.length).to.equal(1)
update = updates[0]
expect(update.userId).to.equal(@owner._id)
expect(update.pathname).to.equal("/1pixel.png")
expect(update.url).to.be.a('string');
done()
image_file.pipe(req)
it "should version replacing a file", (done) ->
image_file = fs.createReadStream(Path.resolve(__dirname + '/../files/2pixel.png'))
req = @owner.request.post {
uri: "/user/#{@owner._id}/update/#{@tpds_project_name}/1pixel.png",
auth:
user: _.keys(Settings.httpAuthUsers)[0]
pass: _.values(Settings.httpAuthUsers)[0]
sendImmediately: true
}
image_file.on "error", (err) ->
throw err
req.on "error", (err) ->
throw err
req.on "response", (res) =>
if res.statusCode < 200 || res.statusCode >= 300
throw new Error("failed to upload file #{res.statusCode}")
updates = MockDocUpdaterApi.getProjectStructureUpdates(@tpds_project_id).fileUpdates
expect(updates.length).to.equal(1)
update = updates[0]
expect(update.userId).to.equal(@owner._id)
expect(update.pathname).to.equal("/1pixel.png")
expect(update.url).to.be.a('string');
done()
image_file.pipe(req)

View file

@ -1,11 +1,38 @@
express = require("express") express = require("express")
app = express() app = express()
bodyParser = require "body-parser"
jsonParser = bodyParser.json()
module.exports = MockDocUpdaterApi = module.exports = MockDocUpdaterApi =
updates: {}
clearProjectStructureUpdates: () ->
@updates = {}
getProjectStructureUpdates: (project_id) ->
@updates[project_id] || { docUpdates: [], fileUpdates: [] }
addProjectStructureUpdates: (project_id, userId, docUpdates, fileUpdates) ->
@updates[project_id] ||= { docUpdates: [], fileUpdates: [] }
for update in docUpdates
update.userId = userId
@updates[project_id].docUpdates.push(update)
for update in fileUpdates
update.userId = userId
@updates[project_id].fileUpdates.push(update)
run: () -> run: () ->
app.post "/project/:project_id/flush", (req, res, next) => app.post "/project/:project_id/flush", (req, res, next) =>
res.sendStatus 200 res.sendStatus 200
app.post "/project/:project_id", jsonParser, (req, res, next) =>
project_id = req.params.project_id
{userId, docUpdates, fileUpdates} = req.body
@addProjectStructureUpdates(project_id, userId, docUpdates, fileUpdates)
res.sendStatus 200
app.listen 3003, (error) -> app.listen 3003, (error) ->
throw error if error? throw error if error?
.on "error", (error) -> .on "error", (error) ->

View file

@ -13,6 +13,7 @@ module.exports = MockDocStoreApi =
@docs[project_id][doc_id] = {lines, version, ranges} @docs[project_id][doc_id] = {lines, version, ranges}
@docs[project_id][doc_id].rev ?= 0 @docs[project_id][doc_id].rev ?= 0
@docs[project_id][doc_id].rev += 1 @docs[project_id][doc_id].rev += 1
@docs[project_id][doc_id]._id = doc_id
res.json { res.json {
modified: true modified: true
rev: @docs[project_id][doc_id].rev rev: @docs[project_id][doc_id].rev

View file

@ -0,0 +1,20 @@
express = require("express")
app = express()
module.exports = MockFileStoreApi =
files: {}
run: () ->
app.post "/project/:project_id/file/:file_id", (req, res, next) =>
req.on 'data', ->
req.on 'end', ->
res.send 200
app.listen 3009, (error) ->
throw error if error?
.on "error", (error) ->
console.error "error starting MockFileStoreApi:", error.message
process.exit(1)
MockFileStoreApi.run()

View file

@ -0,0 +1,18 @@
express = require("express")
app = express()
module.exports = MockProjectHistoryApi =
docs: {}
run: () ->
app.post "/project", (req, res, next) =>
res.json project: id: 1
app.listen 3054, (error) ->
throw error if error?
.on "error", (error) ->
console.error "error starting MockProjectHistoryApi:", error.message
process.exit(1)
MockProjectHistoryApi.run()

View file

@ -1,6 +1,9 @@
request = require("./request") request = require("./request")
_ = require("underscore")
settings = require("settings-sharelatex") settings = require("settings-sharelatex")
{db, ObjectId} = require("../../../../app/js/infrastructure/mongojs") {db, ObjectId} = require("../../../../app/js/infrastructure/mongojs")
UserModel = require("../../../../app/js/models/User").User
AuthenticationManager = require("../../../../app/js/Features/Authentication/AuthenticationManager")
count = 0 count = 0
@ -17,20 +20,20 @@ class User
login: (callback = (error) ->) -> login: (callback = (error) ->) ->
@getCsrfToken (error) => @getCsrfToken (error) =>
return callback(error) if error? return callback(error) if error?
@request.post { filter = {@email}
url: "/register" # Register will log in, but also ensure user exists options = {upsert: true, new: true, setDefaultsOnInsert: true}
json: UserModel.findOneAndUpdate filter, {}, options, (error, user) =>
email: @email
password: @password
}, (error, response, body) =>
return callback(error) if error? return callback(error) if error?
db.users.findOne {email: @email}, (error, user) => AuthenticationManager.setUserPassword user._id, @password, (error) =>
return callback(error) if error? return callback(error) if error?
@id = user?._id?.toString() @id = user?._id?.toString()
@_id = user?._id?.toString() @_id = user?._id?.toString()
@first_name = user?.first_name @first_name = user?.first_name
@referal_id = user?.referal_id @referal_id = user?.referal_id
callback() @request.post {
url: "/login"
json: { @email, @password }
}, callback
logout: (callback = (error) ->) -> logout: (callback = (error) ->) ->
@getCsrfToken (error) => @getCsrfToken (error) =>
@ -96,15 +99,20 @@ class User
getProject: (project_id, callback = (error, project)->) -> getProject: (project_id, callback = (error, project)->) ->
db.projects.findOne {_id: ObjectId(project_id.toString())}, callback db.projects.findOne {_id: ObjectId(project_id.toString())}, callback
createProject: (name, callback = (error, project_id) ->) -> createProject: (name, options, callback = (error, oroject_id) ->) ->
if typeof options == "function"
callback = options
options = {}
@request.post { @request.post {
url: "/project/new", url: "/project/new",
json: json: Object.assign({projectName: name}, options)
projectName: name
}, (error, response, body) -> }, (error, response, body) ->
return callback(error) if error? return callback(error) if error?
if !body?.project_id? if !body?.project_id?
console.error "SOMETHING WENT WRONG CREATING PROJECT", response.statusCode, response.headers["location"], body error = new Error("SOMETHING WENT WRONG CREATING PROJECT", response.statusCode, response.headers["location"], body)
callback error
else
callback(null, body.project_id) callback(null, body.project_id)
deleteProject: (project_id, callback=(error)) -> deleteProject: (project_id, callback=(error)) ->
@ -161,13 +169,10 @@ class User
getCsrfToken: (callback = (error) ->) -> getCsrfToken: (callback = (error) ->) ->
@request.get { @request.get {
url: "/register" url: "/dev/csrf"
}, (err, response, body) => }, (err, response, body) =>
return callback(err) if err? return callback(err) if err?
csrfMatches = body.match("window.csrfToken = \"(.*?)\";") @csrfToken = body
if !csrfMatches?
return callback(new Error("no csrf token found"))
@csrfToken = csrfMatches[1]
@request = @request.defaults({ @request = @request.defaults({
headers: headers:
"x-csrf-token": @csrfToken "x-csrf-token": @csrfToken

View file

@ -1,5 +1,4 @@
Settings = require('settings-sharelatex') Settings = require('settings-sharelatex')
redis = require('redis-sharelatex')
logger = require("logger-sharelatex") logger = require("logger-sharelatex")
Async = require('async') Async = require('async')

View file

@ -1,4 +1,4 @@
BASE_URL = "http://localhost:3000" BASE_URL = "http://#{process.env["HTTP_TEST_HOST"] or "localhost"}:3000"
module.exports = require("request").defaults({ module.exports = require("request").defaults({
baseUrl: BASE_URL, baseUrl: BASE_URL,
followRedirect: false followRedirect: false

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View file

@ -0,0 +1 @@
Test

Binary file not shown.

View file

@ -3,7 +3,7 @@
# If you're running on OS X, you probably need to rebuild # If you're running on OS X, you probably need to rebuild
# some dependencies in the docker container, before it will start. # some dependencies in the docker container, before it will start.
# #
# npm rebuild --update-binary #npm rebuild --update-binary
echo ">> Starting server..." echo ">> Starting server..."

View file

@ -390,26 +390,13 @@ describe 'DocumentUpdaterHandler', ->
describe "updateProjectStructure ", -> describe "updateProjectStructure ", ->
beforeEach -> beforeEach ->
@user_id = 1234 @user_id = 1234
@docIdA = new ObjectId()
@docIdB = new ObjectId()
@oldDocs = [
{ path: '/old_a', doc: _id: @docIdA }
{ path: '/old_b', doc: _id: @docIdB }
]
# create new instances of the same ObjectIds so that == doens't pass
@newDocs = [
{ path: '/old_a', doc: _id: new ObjectId(@docIdA.toString()) }
{ path: '/new_b', doc: _id: new ObjectId(@docIdB.toString()) }
]
@oldFiles = []
@newFiles = []
describe "with project history disabled", -> describe "with project history disabled", ->
beforeEach -> beforeEach ->
@settings.apis.project_history.enabled = false @settings.apis.project_history.enabled = false
@request.post = sinon.stub() @request.post = sinon.stub()
@handler.updateProjectStructure @project_id, @user_id, @oldDocs, @newDocs, @oldFiles, @newFiles, @callback @handler.updateProjectStructure @project_id, @user_id, {}, @callback
it 'does not make a web request', -> it 'does not make a web request', ->
@request.post.called.should.equal false @request.post.called.should.equal false
@ -420,20 +407,85 @@ describe 'DocumentUpdaterHandler', ->
describe "with project history enabled", -> describe "with project history enabled", ->
beforeEach -> beforeEach ->
@settings.apis.project_history.enabled = true @settings.apis.project_history.enabled = true
@url = "#{@settings.apis.documentupdater.url}/project/#{@project_id}"
@request.post = sinon.stub().callsArgWith(1, null, {statusCode: 204}, "") @request.post = sinon.stub().callsArgWith(1, null, {statusCode: 204}, "")
@handler.updateProjectStructure @project_id, @user_id, @oldDocs, @newDocs, @oldFiles, @newFiles, @callback
it 'should send the structure update to the document updater', -> describe "when an entity has changed name", ->
it 'should send the structure update to the document updater', (done) ->
@docIdA = new ObjectId()
@docIdB = new ObjectId()
@changes = {
oldDocs: [
{ path: '/old_a', doc: _id: @docIdA }
{ path: '/old_b', doc: _id: @docIdB }
]
# create new instances of the same ObjectIds so that == doesn't pass
newDocs: [
{ path: '/old_a', doc: _id: new ObjectId(@docIdA.toString()) }
{ path: '/new_b', doc: _id: new ObjectId(@docIdB.toString()) }
]
}
docUpdates = [ docUpdates = [
id: @docIdB, id: @docIdB.toString(),
pathname: "/old_b" pathname: "/old_b"
newPathname: "/new_b" newPathname: "/new_b"
] ]
url = "#{@settings.apis.documentupdater.url}/project/#{@project_id}" @handler.updateProjectStructure @project_id, @user_id, @changes, () =>
@request.post @request.post
.calledWith(url: url, json: {docUpdates, fileUpdates: [], userId: @user_id}) .calledWith(url: @url, json: {docUpdates, fileUpdates: [], userId: @user_id})
.should.equal true .should.equal true
done()
describe "when a doc has been added", ->
it 'should send the structure update to the document updater', (done) ->
@docId = new ObjectId()
@changes = newDocs: [
{ path: '/foo', docLines: 'a\nb', doc: _id: @docId }
]
docUpdates = [
id: @docId.toString(),
pathname: "/foo"
docLines: 'a\nb'
url: undefined
]
@handler.updateProjectStructure @project_id, @user_id, @changes, () =>
@request.post
.calledWith(url: @url, json: {docUpdates, fileUpdates: [], userId: @user_id})
.should.equal true
done()
describe "when a file has been added", ->
it 'should send the structure update to the document updater', (done) ->
@fileId = new ObjectId()
@changes = newFiles: [
{ path: '/bar', url: 'filestore.example.com/file', file: _id: @fileId }
]
fileUpdates = [
id: @fileId.toString(),
pathname: "/bar"
url: 'filestore.example.com/file'
docLines: undefined
]
@handler.updateProjectStructure @project_id, @user_id, @changes, () =>
@request.post
.calledWith(url: @url, json: {docUpdates: [], fileUpdates, userId: @user_id})
.should.equal true
done()
describe "when a doc has been deleted", ->
it 'should do nothing', (done) ->
@docId = new ObjectId()
@changes = oldDocs: [
{ path: '/foo', docLines: 'a\nb', doc: _id: @docId }
]
@handler.updateProjectStructure @project_id, @user_id, @changes, () =>
@request.post.called.should.equal false
done()
it "should call the callback with no error", ->
@callback.calledWith(null).should.equal true

View file

@ -131,24 +131,24 @@ describe "EditorController", ->
@docLines = ["1234","dskl"] @docLines = ["1234","dskl"]
it 'should add the doc using the project entity handler', (done)-> it 'should add the doc using the project entity handler', (done)->
mock = sinon.mock(@ProjectEntityHandler).expects("addDoc").withArgs(@project_id, @folder_id, @docName, @docLines).callsArg(4) mock = sinon.mock(@ProjectEntityHandler).expects("addDoc").withArgs(@project_id, @folder_id, @docName, @docLines).callsArg(5)
@EditorController.addDocWithoutLock @project_id, @folder_id, @docName, @docLines, @source, -> @EditorController.addDocWithoutLock @project_id, @folder_id, @docName, @docLines, @source, @user_id, ->
mock.verify() mock.verify()
done() done()
it 'should send the update out to the users in the project', (done)-> it 'should send the update out to the users in the project', (done)->
@ProjectEntityHandler.addDoc = sinon.stub().callsArgWith(4, null, @doc, @folder_id) @ProjectEntityHandler.addDoc = sinon.stub().callsArgWith(5, null, @doc, @folder_id)
@EditorController.addDocWithoutLock @project_id, @folder_id, @docName, @docLines, @source, => @EditorController.addDocWithoutLock @project_id, @folder_id, @docName, @docLines, @source, @user_id, =>
@EditorRealTimeController.emitToRoom @EditorRealTimeController.emitToRoom
.calledWith(@project_id, "reciveNewDoc", @folder_id, @doc, @source) .calledWith(@project_id, "reciveNewDoc", @folder_id, @doc, @source)
.should.equal true .should.equal true
done() done()
it 'should return the doc to the callback', (done) -> it 'should return the doc to the callback', (done) ->
@ProjectEntityHandler.addDoc = sinon.stub().callsArgWith(4, null, @doc, @folder_id) @ProjectEntityHandler.addDoc = sinon.stub().callsArgWith(5, null, @doc, @folder_id)
@EditorController.addDocWithoutLock @project_id, @folder_id, @docName, @docLines, @source, (error, doc) => @EditorController.addDocWithoutLock @project_id, @folder_id, @docName, @docLines, @source, @user_id, (error, doc) =>
doc.should.equal @doc doc.should.equal @doc
done() done()
@ -157,33 +157,30 @@ describe "EditorController", ->
beforeEach -> beforeEach ->
@LockManager.getLock.callsArgWith(1) @LockManager.getLock.callsArgWith(1)
@LockManager.releaseLock.callsArgWith(1) @LockManager.releaseLock.callsArgWith(1)
@EditorController.addDocWithoutLock = sinon.stub().callsArgWith(5) @EditorController.addDocWithoutLock = sinon.stub().callsArgWith(6)
it "should call addDocWithoutLock", (done)-> it "should call addDocWithoutLock", (done)->
@EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, => @EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, @user_id, =>
@EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @docName, @docLines, @source).should.equal true @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @docName, @docLines, @source, @user_id).should.equal true
done() done()
it "should take the lock", (done)-> it "should take the lock", (done)->
@EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, => @EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, @user_id, =>
@LockManager.getLock.calledWith(@project_id).should.equal true @LockManager.getLock.calledWith(@project_id).should.equal true
done() done()
it "should release the lock", (done)-> it "should release the lock", (done)->
@EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, => @EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, @user_id, =>
@LockManager.releaseLock.calledWith(@project_id).should.equal true @LockManager.releaseLock.calledWith(@project_id).should.equal true
done() done()
it "should error if it can't cat the lock", (done)-> it "should error if it can't cat the lock", (done)->
@LockManager.getLock = sinon.stub().callsArgWith(1, "timed out") @LockManager.getLock = sinon.stub().callsArgWith(1, "timed out")
@EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, (err)=> @EditorController.addDoc @project_id, @folder_id, @docName, @docLines, @source, @user_id, (err)=>
expect(err).to.exist expect(err).to.exist
err.should.equal "timed out" err.should.equal "timed out"
done() done()
describe 'addFileWithoutLock:', -> describe 'addFileWithoutLock:', ->
beforeEach -> beforeEach ->
@ProjectEntityHandler.addFile = -> @ProjectEntityHandler.addFile = ->
@ -196,23 +193,23 @@ describe "EditorController", ->
@stream = new ArrayBuffer() @stream = new ArrayBuffer()
it 'should add the folder using the project entity handler', (done)-> it 'should add the folder using the project entity handler', (done)->
@ProjectEntityHandler.addFile = sinon.stub().callsArgWith(4) @ProjectEntityHandler.addFile = sinon.stub().callsArgWith(5)
@EditorController.addFileWithoutLock @project_id, @folder_id, @fileName, @stream, @source, => @EditorController.addFileWithoutLock @project_id, @folder_id, @fileName, @stream, @source, @user_id, =>
@ProjectEntityHandler.addFile.calledWith(@project_id, @folder_id).should.equal true @ProjectEntityHandler.addFile.calledWith(@project_id, @folder_id, @fileName, @stream, @user_id).should.equal true
done() done()
it 'should send the update of a new folder out to the users in the project', (done)-> it 'should send the update of a new folder out to the users in the project', (done)->
@ProjectEntityHandler.addFile = sinon.stub().callsArgWith(4, null, @file, @folder_id) @ProjectEntityHandler.addFile = sinon.stub().callsArgWith(5, null, @file, @folder_id)
@EditorController.addFileWithoutLock @project_id, @folder_id, @fileName, @stream, @source, => @EditorController.addFileWithoutLock @project_id, @folder_id, @fileName, @stream, @source, @user_id, =>
@EditorRealTimeController.emitToRoom @EditorRealTimeController.emitToRoom
.calledWith(@project_id, "reciveNewFile", @folder_id, @file, @source) .calledWith(@project_id, "reciveNewFile", @folder_id, @file, @source)
.should.equal true .should.equal true
done() done()
it "should return the file in the callback", (done) -> it "should return the file in the callback", (done) ->
@ProjectEntityHandler.addFile = sinon.stub().callsArgWith(4, null, @file, @folder_id) @ProjectEntityHandler.addFile = sinon.stub().callsArgWith(5, null, @file, @folder_id)
@EditorController.addFileWithoutLock @project_id, @folder_id, @fileName, @stream, @source, (error, file) => @EditorController.addFileWithoutLock @project_id, @folder_id, @fileName, @stream, @source, @user_id, (error, file) =>
file.should.equal @file file.should.equal @file
done() done()
@ -222,28 +219,28 @@ describe "EditorController", ->
beforeEach -> beforeEach ->
@LockManager.getLock.callsArgWith(1) @LockManager.getLock.callsArgWith(1)
@LockManager.releaseLock.callsArgWith(1) @LockManager.releaseLock.callsArgWith(1)
@EditorController.addFileWithoutLock = sinon.stub().callsArgWith(5) @EditorController.addFileWithoutLock = sinon.stub().callsArgWith(6)
it "should call addFileWithoutLock", (done)-> it "should call addFileWithoutLock", (done)->
@EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, (error, file) => @EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, @user_id, (error, file) =>
@EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @fileName, @stream, @source).should.equal true @EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @fileName, @stream, @source, @user_id).should.equal true
done() done()
it "should take the lock", (done)-> it "should take the lock", (done)->
@EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, (error, file) => @EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, @user_id, (error, file) =>
@LockManager.getLock.calledWith(@project_id).should.equal true @LockManager.getLock.calledWith(@project_id).should.equal true
done() done()
it "should release the lock", (done)-> it "should release the lock", (done)->
@EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, (error, file) => @EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, @user_id, (error, file) =>
@LockManager.releaseLock.calledWith(@project_id).should.equal true @LockManager.releaseLock.calledWith(@project_id).should.equal true
done() done()
it "should error if it can't cat the lock", (done)-> it "should error if it can't cat the lock", (done)->
@LockManager.getLock = sinon.stub().callsArgWith(1, "timed out") @LockManager.getLock = sinon.stub().callsArgWith(1, "timed out")
@EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, (err, file) => @EditorController.addFile @project_id, @folder_id, @fileName, @stream, @source, @user_id, (error, file) =>
expect(err).to.exist expect(error).to.exist
err.should.equal "timed out" error.should.equal "timed out"
done() done()
@ -256,9 +253,9 @@ describe "EditorController", ->
@fsPath = "/folder/file.png" @fsPath = "/folder/file.png"
it 'should send the replace file message to the editor controller', (done)-> it 'should send the replace file message to the editor controller', (done)->
@ProjectEntityHandler.replaceFile = sinon.stub().callsArgWith(3) @ProjectEntityHandler.replaceFile = sinon.stub().callsArgWith(4)
@EditorController.replaceFile @project_id, @file_id, @fsPath, @source, => @EditorController.replaceFile @project_id, @file_id, @fsPath, @source, @user_id, =>
@ProjectEntityHandler.replaceFile.calledWith(@project_id, @file_id, @fsPath).should.equal true @ProjectEntityHandler.replaceFile.calledWith(@project_id, @file_id, @fsPath, @user_id).should.equal true
done() done()
describe 'addFolderWithoutLock :', -> describe 'addFolderWithoutLock :', ->

View file

@ -201,7 +201,7 @@ describe "EditorHttpController", ->
@req.body = @req.body =
name: @name = "doc-name" name: @name = "doc-name"
parent_folder_id: @parent_folder_id parent_folder_id: @parent_folder_id
@EditorController.addDoc = sinon.stub().callsArgWith(5, null, @doc) @EditorController.addDoc = sinon.stub().callsArgWith(6, null, @doc)
describe "successfully", -> describe "successfully", ->
beforeEach -> beforeEach ->
@ -209,7 +209,7 @@ describe "EditorHttpController", ->
it "should call EditorController.addDoc", -> it "should call EditorController.addDoc", ->
@EditorController.addDoc @EditorController.addDoc
.calledWith(@project_id, @parent_folder_id, @name, [], "editor") .calledWith(@project_id, @parent_folder_id, @name, [], "editor", @userId)
.should.equal true .should.equal true
it "should send the doc back as JSON", -> it "should send the doc back as JSON", ->

View file

@ -79,14 +79,15 @@ describe "FileStoreHandler", ->
@handler._buildUrl.calledWith(@project_id, @file_id).should.equal true @handler._buildUrl.calledWith(@project_id, @file_id).should.equal true
done() done()
it 'should callback with null', (done) -> it 'should callback with the url', (done) ->
@fs.createReadStream.returns @fs.createReadStream.returns
pipe:-> pipe:->
on: (type, cb)-> on: (type, cb)->
if type == "open" if type == "open"
cb() cb()
@handler.uploadFileFromDisk @project_id, @file_id, @fsPath, (err) => @handler.uploadFileFromDisk @project_id, @file_id, @fsPath, (err, url) =>
expect(err).to.not.exist expect(err).to.not.exist
expect(url).to.equal(@handler._buildUrl())
done() done()
describe "symlink", -> describe "symlink", ->
@ -218,6 +219,11 @@ describe "FileStoreHandler", ->
@handler._buildUrl.calledWith(@newProject_id, @newFile_id).should.equal true @handler._buildUrl.calledWith(@newProject_id, @newFile_id).should.equal true
done() done()
it "returns the url", (done)->
@request.callsArgWith(1, null)
@handler.copyFile @project_id, @file_id, @newProject_id, @newFile_id, (err, url) =>
url.should.equal "http://filestore.stubbedBuilder.com"
done()
it "should return the err", (done)-> it "should return the err", (done)->
error = "errrror" error = "errrror"

View file

@ -33,8 +33,8 @@ describe 'ProjectCreationHandler', ->
constructor:(options)-> constructor:(options)->
{@name} = options {@name} = options
@ProjectEntityHandler = @ProjectEntityHandler =
addDoc: sinon.stub().callsArgWith(4, null, {_id: docId}) addDoc: sinon.stub().callsArgWith(5, null, {_id: docId})
addFile: sinon.stub().callsArg(4) addFile: sinon.stub().callsArg(5)
setRootDoc: sinon.stub().callsArg(2) setRootDoc: sinon.stub().callsArg(2)
@ProjectDetailsHandler = @ProjectDetailsHandler =
validateProjectName: sinon.stub().yields() validateProjectName: sinon.stub().yields()
@ -149,7 +149,7 @@ describe 'ProjectCreationHandler', ->
.should.equal true .should.equal true
it 'should insert main.tex', -> it 'should insert main.tex', ->
@ProjectEntityHandler.addDoc.calledWith(project_id, rootFolderId, "main.tex", ["mainbasic.tex", "lines"]) @ProjectEntityHandler.addDoc.calledWith(project_id, rootFolderId, "main.tex", ["mainbasic.tex", "lines"], ownerId)
.should.equal true .should.equal true
it 'should set the main doc id', -> it 'should set the main doc id', ->
@ -180,19 +180,20 @@ describe 'ProjectCreationHandler', ->
it 'should insert main.tex', -> it 'should insert main.tex', ->
@ProjectEntityHandler.addDoc @ProjectEntityHandler.addDoc
.calledWith(project_id, rootFolderId, "main.tex", ["main.tex", "lines"]) .calledWith(project_id, rootFolderId, "main.tex", ["main.tex", "lines"], ownerId)
.should.equal true .should.equal true
it 'should insert references.bib', -> it 'should insert references.bib', ->
@ProjectEntityHandler.addDoc @ProjectEntityHandler.addDoc
.calledWith(project_id, rootFolderId, "references.bib", ["references.bib", "lines"]) .calledWith(project_id, rootFolderId, "references.bib", ["references.bib", "lines"], ownerId)
.should.equal true .should.equal true
it 'should insert universe.jpg', -> it 'should insert universe.jpg', ->
@ProjectEntityHandler.addFile @ProjectEntityHandler.addFile
.calledWith( .calledWith(
project_id, rootFolderId, "universe.jpg", project_id, rootFolderId, "universe.jpg",
Path.resolve(__dirname + "/../../../../app/templates/project_files/universe.jpg") Path.resolve(__dirname + "/../../../../app/templates/project_files/universe.jpg"),
ownerId
) )
.should.equal true .should.equal true

View file

@ -64,8 +64,8 @@ describe 'ProjectDuplicator', ->
@projectOptionsHandler = @projectOptionsHandler =
setCompiler : sinon.stub() setCompiler : sinon.stub()
@entityHandler = @entityHandler =
addDocWithProject: sinon.stub().callsArgWith(4, null, {name:"somDoc"}) addDocWithProject: sinon.stub().callsArgWith(5, null, {name:"somDoc"})
copyFileFromExistingProjectWithProject: sinon.stub().callsArgWith(4) copyFileFromExistingProjectWithProject: sinon.stub().callsArgWith(5)
setRootDoc: sinon.stub() setRootDoc: sinon.stub()
addFolderWithProject: sinon.stub().callsArgWith(3, null, @newFolder) addFolderWithProject: sinon.stub().callsArgWith(3, null, @newFolder)
@ -112,13 +112,13 @@ describe 'ProjectDuplicator', ->
done() done()
it 'should use the same compiler', (done)-> it 'should use the same compiler', (done)->
@entityHandler.addDocWithProject.callsArgWith(4, null, @rootFolder.docs[0]) @entityHandler.addDocWithProject.callsArgWith(5, null, @rootFolder.docs[0], @owner._id)
@duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=> @duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=>
@projectOptionsHandler.setCompiler.calledWith(@stubbedNewProject._id, @project.compiler).should.equal true @projectOptionsHandler.setCompiler.calledWith(@stubbedNewProject._id, @project.compiler).should.equal true
done() done()
it 'should use the same root doc', (done)-> it 'should use the same root doc', (done)->
@entityHandler.addDocWithProject.callsArgWith(4, null, @rootFolder.docs[0]) @entityHandler.addDocWithProject.callsArgWith(5, null, @rootFolder.docs[0], @owner._id)
@duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=> @duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=>
@entityHandler.setRootDoc.calledWith(@stubbedNewProject._id, @rootFolder.docs[0]._id).should.equal true @entityHandler.setRootDoc.calledWith(@stubbedNewProject._id, @rootFolder.docs[0]._id).should.equal true
done() done()
@ -139,14 +139,26 @@ describe 'ProjectDuplicator', ->
it 'should copy all the docs', (done)-> it 'should copy all the docs', (done)->
@duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=> @duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=>
@DocstoreManager.getAllDocs.calledWith(@old_project_id).should.equal true @DocstoreManager.getAllDocs.calledWith(@old_project_id).should.equal true
@entityHandler.addDocWithProject.calledWith(@stubbedNewProject, @stubbedNewProject.rootFolder[0]._id, @doc0.name, @doc0_lines).should.equal true @entityHandler.addDocWithProject
@entityHandler.addDocWithProject.calledWith(@stubbedNewProject, @newFolder._id, @doc1.name, @doc1_lines).should.equal true .calledWith(@stubbedNewProject, @stubbedNewProject.rootFolder[0]._id, @doc0.name, @doc0_lines, @owner._id)
@entityHandler.addDocWithProject.calledWith(@stubbedNewProject, @newFolder._id, @doc2.name, @doc2_lines).should.equal true .should.equal true
@entityHandler.addDocWithProject
.calledWith(@stubbedNewProject, @newFolder._id, @doc1.name, @doc1_lines, @owner._id)
.should.equal true
@entityHandler.addDocWithProject
.calledWith(@stubbedNewProject, @newFolder._id, @doc2.name, @doc2_lines, @owner._id)
.should.equal true
done() done()
it 'should copy all the files', (done)-> it 'should copy all the files', (done)->
@duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=> @duplicator.duplicate @owner, @old_project_id, "", (err, newProject)=>
@entityHandler.copyFileFromExistingProjectWithProject.calledWith(@stubbedNewProject, @stubbedNewProject.rootFolder[0]._id, @project._id, @rootFolder.fileRefs[0]).should.equal true @entityHandler.copyFileFromExistingProjectWithProject
@entityHandler.copyFileFromExistingProjectWithProject.calledWith(@stubbedNewProject, @newFolder._id, @project._id, @level1folder.fileRefs[0]).should.equal true .calledWith(@stubbedNewProject, @stubbedNewProject.rootFolder[0]._id, @project._id, @rootFolder.fileRefs[0], @owner._id)
@entityHandler.copyFileFromExistingProjectWithProject.calledWith(@stubbedNewProject, @newFolder._id, @project._id, @level2folder.fileRefs[0]).should.equal true .should.equal true
@entityHandler.copyFileFromExistingProjectWithProject
.calledWith(@stubbedNewProject, @newFolder._id, @project._id, @level1folder.fileRefs[0], @owner._id)
.should.equal true
@entityHandler.copyFileFromExistingProjectWithProject
.calledWith(@stubbedNewProject, @newFolder._id, @project._id, @level2folder.fileRefs[0], @owner._id)
.should.equal true
done() done()

View file

@ -17,9 +17,10 @@ describe 'ProjectEntityHandler', ->
userId = 1234 userId = 1234
beforeEach -> beforeEach ->
@fileUrl = 'filestore.example.com/file'
@FileStoreHandler = @FileStoreHandler =
uploadFileFromDisk:(project_id, fileRef, localImagePath, callback)->callback() uploadFileFromDisk: sinon.stub().callsArgWith(3, null, @fileUrl)
copyFile: sinon.stub().callsArgWith(4, null) copyFile: sinon.stub().callsArgWith(4, null, @fileUrl)
@tpdsUpdateSender = @tpdsUpdateSender =
addDoc:sinon.stub().callsArg(1) addDoc:sinon.stub().callsArg(1)
addFile:sinon.stub().callsArg(1) addFile:sinon.stub().callsArg(1)
@ -67,6 +68,9 @@ describe 'ProjectEntityHandler', ->
findElement : sinon.stub() findElement : sinon.stub()
@settings = @settings =
maxEntitiesPerProject:200 maxEntitiesPerProject:200
@documentUpdaterHandler =
updateProjectStructure: sinon.stub().yields()
deleteDoc: sinon.stub().callsArg(2)
@ProjectEntityHandler = SandboxedModule.require modulePath, requires: @ProjectEntityHandler = SandboxedModule.require modulePath, requires:
'../../models/Project': Project:@ProjectModel '../../models/Project': Project:@ProjectModel
'../../models/Doc': Doc:@DocModel '../../models/Doc': Doc:@DocModel
@ -75,7 +79,7 @@ describe 'ProjectEntityHandler', ->
'../FileStore/FileStoreHandler':@FileStoreHandler '../FileStore/FileStoreHandler':@FileStoreHandler
'../ThirdPartyDataStore/TpdsUpdateSender':@tpdsUpdateSender '../ThirdPartyDataStore/TpdsUpdateSender':@tpdsUpdateSender
'./ProjectLocator': @projectLocator './ProjectLocator': @projectLocator
'../../Features/DocumentUpdater/DocumentUpdaterHandler':@documentUpdaterHandler = {} '../../Features/DocumentUpdater/DocumentUpdaterHandler':@documentUpdaterHandler
'../Docstore/DocstoreManager': @DocstoreManager = {} '../Docstore/DocstoreManager': @DocstoreManager = {}
'logger-sharelatex': @logger = {log:sinon.stub(), error: sinon.stub(), err:->} 'logger-sharelatex': @logger = {log:sinon.stub(), error: sinon.stub(), err:->}
'./ProjectUpdateHandler': @projectUpdater './ProjectUpdateHandler': @projectUpdater
@ -184,7 +188,6 @@ describe 'ProjectEntityHandler', ->
describe "_cleanUpEntity", -> describe "_cleanUpEntity", ->
beforeEach -> beforeEach ->
@entity_id = "4eecaffcbffa66588e000009" @entity_id = "4eecaffcbffa66588e000009"
@documentUpdaterHandler.deleteDoc = sinon.stub().callsArg(2)
@FileStoreHandler.deleteFile = sinon.stub().callsArg(2) @FileStoreHandler.deleteFile = sinon.stub().callsArg(2)
@ProjectEntityHandler.unsetRootDoc = sinon.stub().callsArg(1) @ProjectEntityHandler.unsetRootDoc = sinon.stub().callsArg(1)
@ -240,7 +243,6 @@ describe 'ProjectEntityHandler', ->
@ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, path: @pathAfterMove) @ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, path: @pathAfterMove)
@ProjectGetter.getProject.callsArgWith(2, null, @project) @ProjectGetter.getProject.callsArgWith(2, null, @project)
@tpdsUpdateSender.moveEntity = sinon.stub() @tpdsUpdateSender.moveEntity = sinon.stub()
@documentUpdaterHandler.updateProjectStructure = sinon.stub().callsArg(6)
@ProjectEntityHandler.getAllEntitiesFromProject = sinon.stub() @ProjectEntityHandler.getAllEntitiesFromProject = sinon.stub()
@ProjectEntityHandler.getAllEntitiesFromProject @ProjectEntityHandler.getAllEntitiesFromProject
.onFirstCall() .onFirstCall()
@ -272,7 +274,7 @@ describe 'ProjectEntityHandler', ->
it "should should send the update to the doc updater", -> it "should should send the update to the doc updater", ->
@documentUpdaterHandler.updateProjectStructure @documentUpdaterHandler.updateProjectStructure
.calledWith(project_id, userId, @oldDocs, @newDocs, @oldFiles, @newFiles) .calledWith(project_id, userId, {@oldDocs, @newDocs, @oldFiles, @newFiles})
.should.equal true .should.equal true
it 'should remove the element from its current position', -> it 'should remove the element from its current position', ->
@ -324,7 +326,7 @@ describe 'ProjectEntityHandler', ->
it "should should send the update to the doc updater", -> it "should should send the update to the doc updater", ->
@documentUpdaterHandler.updateProjectStructure @documentUpdaterHandler.updateProjectStructure
.calledWith(project_id, userId, @oldDocs, @newDocs, @oldFiles, @newFiles) .calledWith(project_id, userId, {@oldDocs, @newDocs, @oldFiles, @newFiles})
.should.equal true .should.equal true
it 'should remove the element from its current position', -> it 'should remove the element from its current position', ->
@ -455,7 +457,7 @@ describe 'ProjectEntityHandler', ->
@tpdsUpdateSender.addDoc = sinon.stub().callsArg(1) @tpdsUpdateSender.addDoc = sinon.stub().callsArg(1)
@DocstoreManager.updateDoc = sinon.stub().yields(null, true, 0) @DocstoreManager.updateDoc = sinon.stub().yields(null, true, 0)
@ProjectEntityHandler.addDoc project_id, folder_id, @name, @lines, @callback @ProjectEntityHandler.addDoc project_id, folder_id, @name, @lines, userId, @callback
# Created doc # Created doc
@doc = @ProjectEntityHandler._putElement.args[0][2] @doc = @ProjectEntityHandler._putElement.args[0][2]
@ -484,6 +486,16 @@ describe 'ProjectEntityHandler', ->
.calledWith(project_id, @doc._id.toString(), @lines) .calledWith(project_id, @doc._id.toString(), @lines)
.should.equal true .should.equal true
it "should should send the change in project structure to the doc updater", () ->
newDocs = [
doc: @doc
path: @path
docLines: @lines.join('\n')
]
@documentUpdaterHandler.updateProjectStructure
.calledWith(project_id, userId, {newDocs})
.should.equal true
describe "restoreDoc", -> describe "restoreDoc", ->
beforeEach -> beforeEach ->
@name = "doc-name" @name = "doc-name"
@ -512,7 +524,10 @@ describe 'ProjectEntityHandler', ->
describe 'addFile', -> describe 'addFile', ->
fileName = "something.jpg" fileName = "something.jpg"
beforeEach -> beforeEach ->
@fileSystemPath = "somehintg"
@ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem: @fileSystemPath}})
@filePath = "somewhere" @filePath = "somewhere"
it 'should upload it via the FileStoreHandler', (done)-> it 'should upload it via the FileStoreHandler', (done)->
@FileStoreHandler.uploadFileFromDisk = (passedProject_id, file_id, filePath, callback)=> @FileStoreHandler.uploadFileFromDisk = (passedProject_id, file_id, filePath, callback)=>
file_id.should.equal "file_id" file_id.should.equal "file_id"
@ -520,7 +535,7 @@ describe 'ProjectEntityHandler', ->
filePath.should.equal @filePath filePath.should.equal @filePath
done() done()
@ProjectEntityHandler.addFile project_id, folder_id, fileName, @filePath, (err, fileRef, parentFolder)-> @ProjectEntityHandler.addFile project_id, folder_id, fileName, @filePath, userId, (err, fileRef, parentFolder)->
it 'should put file into folder by calling put element', (done)-> it 'should put file into folder by calling put element', (done)->
@ProjectEntityHandler._putElement = (passedProject, passedFolder_id, passedFileRef, passedType, callback)-> @ProjectEntityHandler._putElement = (passedProject, passedFolder_id, passedFileRef, passedType, callback)->
@ -530,11 +545,10 @@ describe 'ProjectEntityHandler', ->
passedType.should.equal 'file' passedType.should.equal 'file'
done() done()
@ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, (err, fileRef, parentFolder)-> @ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, userId, (err, fileRef, parentFolder)->
it 'should return doc and parent folder', (done)-> it 'should return doc and parent folder', (done)->
@ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem:"somehintg"}}) @ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, userId, (err, fileRef, parentFolder)->
@ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, (err, fileRef, parentFolder)->
parentFolder.should.equal folder_id parentFolder.should.equal folder_id
fileRef.name.should.equal fileName fileRef.name.should.equal fileName
done() done()
@ -554,33 +568,45 @@ describe 'ProjectEntityHandler', ->
options.rev.should.equal 0 options.rev.should.equal 0
done() done()
@ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, (err, fileRef, parentFolder)-> @ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, userId, (err, fileRef, parentFolder)->
describe 'replacing a file', -> it "should should send the change in project structure to the doc updater", (done) ->
@documentUpdaterHandler.updateProjectStructure = (passed_project_id, passed_user_id, changes) =>
passed_project_id.should.equal project_id
passed_user_id.should.equal userId
{ newFiles } = changes
newFiles.length.should.equal 1
newFile = newFiles[0]
newFile.file.name.should.equal fileName
newFile.path.should.equal @fileSystemPath
newFile.url.should.equal @fileUrl
done()
@ProjectEntityHandler.addFile project_id, folder_id, fileName, {}, userId, () ->
describe 'replaceFile', ->
beforeEach -> beforeEach ->
@projectLocator @projectLocator
@file_id = "file_id_here" @file_id = "file_id_here"
@fsPath = "fs_path_here.png" @fsPath = "fs_path_here.png"
@fileRef = {rev:3, _id:@file_id} @fileRef = {rev:3, _id: @file_id, name: @fileName = "fileName"}
@filePaths = {fileSystem:"/folder1/file.png", mongo:"folder.1.files.somewhere"} @filePaths = {fileSystem: @fileSystemPath="/folder1/file.png", mongo:"folder.1.files.somewhere"}
@projectLocator.findElement = sinon.stub().callsArgWith(1, null, @fileRef, @filePaths) @projectLocator.findElement = sinon.stub().callsArgWith(1, null, @fileRef, @filePaths)
@ProjectModel.update = (_, __, ___, cb)-> cb() @ProjectModel.findOneAndUpdate = sinon.stub().callsArgWith(3)
@ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, @project) @ProjectGetter.getProject = sinon.stub().callsArgWith(2, null, @project)
it 'should find the file', (done)-> it 'should find the file', (done)->
@ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, userId, =>
@ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, => @projectLocator.findElement
@projectLocator.findElement.calledWith({element_id:@file_id, type:"file", project_id:project_id}).should.equal true .calledWith({element_id:@file_id, type:"file", project: @project})
.should.equal true
done() done()
it 'should tell the file store handler to upload the file from disk', (done)-> it 'should tell the file store handler to upload the file from disk', (done)->
@FileStoreHandler.uploadFileFromDisk = sinon.stub().callsArgWith(3) @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, userId, =>
@ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, =>
@FileStoreHandler.uploadFileFromDisk.calledWith(project_id, @file_id, @fsPath).should.equal true @FileStoreHandler.uploadFileFromDisk.calledWith(project_id, @file_id, @fsPath).should.equal true
done() done()
it 'should send the file to the tpds with an incremented rev', (done)-> it 'should send the file to the tpds with an incremented rev', (done)->
@tpdsUpdateSender.addFile = (options)=> @tpdsUpdateSender.addFile = (options)=>
options.project_id.should.equal project_id options.project_id.should.equal project_id
@ -590,26 +616,39 @@ describe 'ProjectEntityHandler', ->
options.rev.should.equal @fileRef.rev + 1 options.rev.should.equal @fileRef.rev + 1
done() done()
@ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, => @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, userId, =>
it 'should inc the rev id', (done)-> it 'should inc the rev id', (done)->
@ProjectModel.update = (conditions, update, options, callback)=> @ProjectModel.findOneAndUpdate = (conditions, update, options, callback)=>
conditions._id.should.equal project_id conditions._id.should.equal project_id
update.$inc["#{@filePaths.mongo}.rev"].should.equal 1 update.$inc["#{@filePaths.mongo}.rev"].should.equal 1
done() done()
@ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, => @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, userId, =>
it 'should update the created at date', (done)-> it 'should update the created at date', (done)->
d = new Date() d = new Date()
@ProjectModel.update = (conditions, update, options, callback)=> @ProjectModel.findOneAndUpdate = (conditions, update, options, callback)=>
conditions._id.should.equal project_id conditions._id.should.equal project_id
differenceInMs = update.$set["#{@filePaths.mongo}.created"].getTime() - d.getTime() differenceInMs = update.$set["#{@filePaths.mongo}.created"].getTime() - d.getTime()
differenceInMs.should.be.below(20) differenceInMs.should.be.below(20)
done() done()
@ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, => @ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, userId, =>
it "should should send the old and new project structure to the doc updater", (done) ->
@documentUpdaterHandler.updateProjectStructure = (passed_project_id, passed_user_id, changes) =>
passed_project_id.should.equal project_id
passed_user_id.should.equal userId
{ newFiles } = changes
newFiles.length.should.equal 1
newFile = newFiles[0]
newFile.file.name.should.equal @fileName
newFile.path.should.equal @fileSystemPath
newFile.url.should.equal @fileUrl
done()
@ProjectEntityHandler.replaceFile project_id, @file_id, @fsPath, userId, =>
describe 'addFolder', -> describe 'addFolder', ->
folderName = "folder1234" folderName = "folder1234"
@ -943,19 +982,19 @@ describe 'ProjectEntityHandler', ->
@ProjectModel.update.calledWith({_id : @project_id}, {$unset : {rootDoc_id: true}}) @ProjectModel.update.calledWith({_id : @project_id}, {$unset : {rootDoc_id: true}})
.should.equal true .should.equal true
describe 'copyFileFromExistingProject', -> describe 'copyFileFromExistingProjectWithProject', ->
fileName = "something.jpg" fileName = "something.jpg"
filePath = "dumpFolder/somewhere/image.jpeg" filePath = "dumpFolder/somewhere/image.jpeg"
oldProject_id = "123kljadas" oldProject_id = "123kljadas"
oldFileRef = {name:fileName, _id:"oldFileRef"} oldFileRef = {name:fileName, _id:"oldFileRef"}
beforeEach ->
@ProjectGetter.getProject = (project_id, fields, callback)=> callback(null, {name:@project.name, _id:@project._id})
@ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem:"somehintg"}})
beforeEach ->
@fileSystemPath = "somehintg"
@ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem: @fileSystemPath}})
it 'should copy the file in FileStoreHandler', (done)-> it 'should copy the file in FileStoreHandler', (done)->
@ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem:"somehintg"}}) @ProjectEntityHandler._putElement = sinon.stub().callsArgWith(4, null, {path:{fileSystem:"somehintg"}})
@ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)=> @ProjectEntityHandler.copyFileFromExistingProjectWithProject @project, folder_id, oldProject_id, oldFileRef, userId, (err, fileRef, parentFolder)=>
@FileStoreHandler.copyFile.calledWith(oldProject_id, oldFileRef._id, project_id, fileRef._id).should.equal true @FileStoreHandler.copyFile.calledWith(oldProject_id, oldFileRef._id, project_id, fileRef._id).should.equal true
done() done()
@ -967,10 +1006,10 @@ describe 'ProjectEntityHandler', ->
passedType.should.equal 'file' passedType.should.equal 'file'
done() done()
@ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)-> @ProjectEntityHandler.copyFileFromExistingProjectWithProject @project, folder_id, oldProject_id, oldFileRef, userId, (err, fileRef, parentFolder)->
it 'should return doc and parent folder', (done)-> it 'should return doc and parent folder', (done)->
@ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)-> @ProjectEntityHandler.copyFileFromExistingProjectWithProject @project, folder_id, oldProject_id, oldFileRef, userId, (err, fileRef, parentFolder)->
parentFolder.should.equal folder_id parentFolder.should.equal folder_id
fileRef.name.should.equal fileName fileRef.name.should.equal fileName
done() done()
@ -990,8 +1029,21 @@ describe 'ProjectEntityHandler', ->
options.rev.should.equal 0 options.rev.should.equal 0
done() done()
@ProjectEntityHandler.copyFileFromExistingProject project_id, folder_id, oldProject_id, oldFileRef, (err, fileRef, parentFolder)-> @ProjectEntityHandler.copyFileFromExistingProjectWithProject @project, folder_id, oldProject_id, oldFileRef, userId, (err, fileRef, parentFolder)->
it "should should send the change in project structure to the doc updater", (done) ->
@documentUpdaterHandler.updateProjectStructure = (passed_project_id, passed_user_id, changes) =>
passed_project_id.should.equal project_id
passed_user_id.should.equal userId
{ newFiles } = changes
newFiles.length.should.equal 1
newFile = newFiles[0]
newFile.file.name.should.equal fileName
newFile.path.should.equal @fileSystemPath
newFile.url.should.equal @fileUrl
done()
@ProjectEntityHandler.copyFileFromExistingProjectWithProject @project, folder_id, oldProject_id, oldFileRef, userId, (err, fileRef, parentFolder)->
describe "renameEntity", -> describe "renameEntity", ->
beforeEach -> beforeEach ->
@ -1012,12 +1064,12 @@ describe 'ProjectEntityHandler', ->
@projectLocator.findElement = sinon.stub().callsArgWith(1, null, @entity = { _id: @entity_id, name:"old.tex", rev:4 }, @path) @projectLocator.findElement = sinon.stub().callsArgWith(1, null, @entity = { _id: @entity_id, name:"old.tex", rev:4 }, @path)
@tpdsUpdateSender.moveEntity = sinon.stub() @tpdsUpdateSender.moveEntity = sinon.stub()
@ProjectModel.findOneAndUpdate = sinon.stub().callsArgWith(3, null, @project) @ProjectModel.findOneAndUpdate = sinon.stub().callsArgWith(3, null, @project)
@documentUpdaterHandler.updateProjectStructure = sinon.stub().callsArg(6) @documentUpdaterHandler.updateProjectStructure = sinon.stub().yields()
it "should should send the old and new project structure to the doc updater", (done) -> it "should should send the old and new project structure to the doc updater", (done) ->
@ProjectEntityHandler.renameEntity project_id, @entity_id, @entityType, @newName, userId, => @ProjectEntityHandler.renameEntity project_id, @entity_id, @entityType, @newName, userId, =>
@documentUpdaterHandler.updateProjectStructure @documentUpdaterHandler.updateProjectStructure
.calledWith(project_id, userId, @oldDocs, @newDocs, @oldFiles, @newFiles) .calledWith(project_id, userId, {@oldDocs, @newDocs, @oldFiles, @newFiles})
.should.equal true .should.equal true
done() done()

View file

@ -63,11 +63,11 @@ describe 'UpdateMerger :', ->
file_id = "1231" file_id = "1231"
@projectLocator.findElementByPath = (_, __, cb)->cb(null, {_id:file_id}) @projectLocator.findElementByPath = (_, __, cb)->cb(null, {_id:file_id})
@FileTypeManager.isBinary.callsArgWith(2, null, true) @FileTypeManager.isBinary.callsArgWith(2, null, true)
@updateMerger.p.processFile = sinon.stub().callsArgWith(5) @updateMerger.p.processFile = sinon.stub().callsArgWith(6)
filePath = "/folder/file1.png" filePath = "/folder/file1.png"
@updateMerger.mergeUpdate @user_id, @project_id, filePath, @update, @source, => @updateMerger.mergeUpdate @user_id, @project_id, filePath, @update, @source, =>
@updateMerger.p.processFile.calledWith(@project_id, file_id, @fsPath, filePath, @source).should.equal true @updateMerger.p.processFile.calledWith(@project_id, file_id, @fsPath, filePath, @source, @user_id).should.equal true
@FileTypeManager.isBinary.calledWith(filePath, @fsPath).should.equal true @FileTypeManager.isBinary.calledWith(filePath, @fsPath).should.equal true
@fs.unlink.calledWith(@fsPath).should.equal true @fs.unlink.calledWith(@fsPath).should.equal true
done() done()
@ -97,7 +97,7 @@ describe 'UpdateMerger :', ->
path = "folder1/folder2/#{docName}" path = "folder1/folder2/#{docName}"
@editorController.mkdirp = sinon.stub().withArgs(@project_id).callsArgWith(2, null, [folder], folder) @editorController.mkdirp = sinon.stub().withArgs(@project_id).callsArgWith(2, null, [folder], folder)
@editorController.addDoc = -> @editorController.addDoc = ->
mock = sinon.mock(@editorController).expects("addDoc").withArgs(@project_id, folder._id, docName, @splitDocLines, @source).callsArg(5) mock = sinon.mock(@editorController).expects("addDoc").withArgs(@project_id, folder._id, docName, @splitDocLines, @source, @user_id).callsArg(6)
@update.write(@docLines) @update.write(@docLines)
@update.end() @update.end()
@ -114,22 +114,22 @@ describe 'UpdateMerger :', ->
@folder = _id: @folder_id @folder = _id: @folder_id
@fileName = "file.png" @fileName = "file.png"
@fsPath = "fs/path.tex" @fsPath = "fs/path.tex"
@editorController.addFile = sinon.stub().callsArg(5) @editorController.addFile = sinon.stub().callsArg(6)
@editorController.replaceFile = sinon.stub().callsArg(4) @editorController.replaceFile = sinon.stub().callsArg(5)
@editorController.deleteEntity = sinon.stub() @editorController.deleteEntity = sinon.stub()
@editorController.mkdirp = sinon.stub().withArgs(@project_id).callsArgWith(2, null, [@folder], @folder) @editorController.mkdirp = sinon.stub().withArgs(@project_id).callsArgWith(2, null, [@folder], @folder)
@updateMerger.p.writeStreamToDisk = sinon.stub().withArgs(@project_id, @file_id, @update).callsArgWith(3, null, @fsPath) @updateMerger.p.writeStreamToDisk = sinon.stub().withArgs(@project_id, @file_id, @update).callsArgWith(3, null, @fsPath)
it 'should replace file if the file already exists', (done)-> it 'should replace file if the file already exists', (done)->
@updateMerger.p.processFile @project_id, @file_id, @fsPath, @path, @source, => @updateMerger.p.processFile @project_id, @file_id, @fsPath, @path, @source, @user_id, =>
@editorController.addFile.called.should.equal false @editorController.addFile.called.should.equal false
@editorController.replaceFile.calledWith(@project_id, @file_id, @fsPath, @source).should.equal true @editorController.replaceFile.calledWith(@project_id, @file_id, @fsPath, @source, @user_id).should.equal true
done() done()
it 'should call add file if the file does not exist', (done)-> it 'should call add file if the file does not exist', (done)->
@updateMerger.p.processFile @project_id, undefined, @fsPath, @path, @source, => @updateMerger.p.processFile @project_id, undefined, @fsPath, @path, @source, @user_id, =>
@editorController.mkdirp.calledWith(@project_id, "folder/").should.equal true @editorController.mkdirp.calledWith(@project_id, "folder/").should.equal true
@editorController.addFile.calledWith(@project_id, @folder_id, @fileName, @fsPath, @source).should.equal true @editorController.addFile.calledWith(@project_id, @folder_id, @fileName, @fsPath, @source, @user_id).should.equal true
@editorController.replaceFile.called.should.equal false @editorController.replaceFile.called.should.equal false
done() done()

View file

@ -44,14 +44,14 @@ describe "FileSystemImportManager", ->
describe "with replace set to false", -> describe "with replace set to false", ->
beforeEach -> beforeEach ->
@EditorController.addDocWithoutLock = sinon.stub().callsArg(5) @EditorController.addDocWithoutLock = sinon.stub().callsArg(6)
@FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback @FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback
it "should read the file from disk", -> it "should read the file from disk", ->
@fs.readFile.calledWith(@path_on_disk, "utf8").should.equal true @fs.readFile.calledWith(@path_on_disk, "utf8").should.equal true
it "should insert the doc", -> it "should insert the doc", ->
@EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload") @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload", @user_id)
.should.equal true .should.equal true
describe "with windows line ending", -> describe "with windows line ending", ->
@ -59,11 +59,11 @@ describe "FileSystemImportManager", ->
@docContent = "one\r\ntwo\r\nthree" @docContent = "one\r\ntwo\r\nthree"
@docLines = ["one", "two", "three"] @docLines = ["one", "two", "three"]
@fs.readFile = sinon.stub().callsArgWith(2, null, @docContent) @fs.readFile = sinon.stub().callsArgWith(2, null, @docContent)
@EditorController.addDocWithoutLock = sinon.stub().callsArg(5) @EditorController.addDocWithoutLock = sinon.stub().callsArg(6)
@FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback @FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback
it "should strip the \\r characters before adding", -> it "should strip the \\r characters before adding", ->
@EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload") @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload", @user_id)
.should.equal true .should.equal true
describe "with replace set to true", -> describe "with replace set to true", ->
@ -76,7 +76,7 @@ describe "FileSystemImportManager", ->
}] }]
} }
@ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder) @ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder)
@EditorController.addDocWithoutLock = sinon.stub().callsArg(5) @EditorController.addDocWithoutLock = sinon.stub().callsArg(6)
@FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback @FileSystemImportManager.addDoc @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback
it "should look up the folder", -> it "should look up the folder", ->
@ -85,7 +85,7 @@ describe "FileSystemImportManager", ->
.should.equal true .should.equal true
it "should insert the doc", -> it "should insert the doc", ->
@EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload") @EditorController.addDocWithoutLock.calledWith(@project_id, @folder_id, @name, @docLines, "upload", @user_id)
.should.equal true .should.equal true
describe "when the doc does exist", -> describe "when the doc does exist", ->
@ -114,12 +114,12 @@ describe "FileSystemImportManager", ->
describe "addFile with replace set to false", -> describe "addFile with replace set to false", ->
beforeEach -> beforeEach ->
@EditorController.addFileWithoutLock = sinon.stub().callsArg(5) @EditorController.addFileWithoutLock = sinon.stub().callsArg(6)
@FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true) @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true)
@FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback @FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, false, @callback
it "should add the file", -> it "should add the file", ->
@EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @name, @path_on_disk, "upload") @EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @name, @path_on_disk, "upload", @user_id)
.should.equal true .should.equal true
describe "addFile with symlink", -> describe "addFile with symlink", ->
@ -144,7 +144,7 @@ describe "FileSystemImportManager", ->
} }
@FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true) @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true)
@ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder) @ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder)
@EditorController.addFileWithoutLock = sinon.stub().callsArg(5) @EditorController.addFileWithoutLock = sinon.stub().callsArg(6)
@FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback @FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback
it "should look up the folder", -> it "should look up the folder", ->
@ -153,7 +153,7 @@ describe "FileSystemImportManager", ->
.should.equal true .should.equal true
it "should add the file", -> it "should add the file", ->
@EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @name, @path_on_disk, "upload") @EditorController.addFileWithoutLock.calledWith(@project_id, @folder_id, @name, @path_on_disk, "upload", @user_id)
.should.equal true .should.equal true
describe "when the file does exist", -> describe "when the file does exist", ->
@ -169,7 +169,7 @@ describe "FileSystemImportManager", ->
} }
@FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true) @FileSystemImportManager._isSafeOnFileSystem = sinon.stub().callsArgWith(1, null, true)
@ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder) @ProjectLocator.findElement = sinon.stub().callsArgWith(1, null, @folder)
@EditorController.replaceFile = sinon.stub().callsArg(4) @EditorController.replaceFile = sinon.stub().callsArg(5)
@FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback @FileSystemImportManager.addFile @user_id, @project_id, @folder_id, @name, @path_on_disk, true, @callback
it "should look up the folder", -> it "should look up the folder", ->
@ -178,7 +178,7 @@ describe "FileSystemImportManager", ->
.should.equal true .should.equal true
it "should replace the file", -> it "should replace the file", ->
@EditorController.replaceFile.calledWith(@project_id, @file_id, @path_on_disk, "upload") @EditorController.replaceFile.calledWith(@project_id, @file_id, @path_on_disk, "upload", @user_id)
.should.equal true .should.equal true
describe "addFolder", -> describe "addFolder", ->