mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge branch 'master' into node-4.2
This commit is contained in:
commit
f4cbcc22ba
182 changed files with 56804 additions and 3311 deletions
|
@ -95,10 +95,10 @@ module.exports = (grunt) ->
|
||||||
paths:
|
paths:
|
||||||
"moment": "libs/moment-2.9.0"
|
"moment": "libs/moment-2.9.0"
|
||||||
"mathjax": "/js/libs/mathjax/MathJax.js?config=TeX-AMS_HTML"
|
"mathjax": "/js/libs/mathjax/MathJax.js?config=TeX-AMS_HTML"
|
||||||
"libs/pdf": "libs/pdfjs-1.0.1040/pdf"
|
"libs/pdf": "libs/pdfjs-1.3.91/pdf"
|
||||||
shim:
|
shim:
|
||||||
"libs/pdf":
|
"libs/pdf":
|
||||||
deps: ["libs/pdfjs-1.0.1040/compatibility"]
|
deps: ["libs/pdfjs-1.3.91/compatibility"]
|
||||||
|
|
||||||
skipDirOptimize: true
|
skipDirOptimize: true
|
||||||
modules: [
|
modules: [
|
||||||
|
|
|
@ -3,8 +3,12 @@ logger = require 'logger-sharelatex'
|
||||||
logger.initialize("web-sharelatex")
|
logger.initialize("web-sharelatex")
|
||||||
logger.logger.serializers.user = require("./app/js/infrastructure/LoggerSerializers").user
|
logger.logger.serializers.user = require("./app/js/infrastructure/LoggerSerializers").user
|
||||||
logger.logger.serializers.project = require("./app/js/infrastructure/LoggerSerializers").project
|
logger.logger.serializers.project = require("./app/js/infrastructure/LoggerSerializers").project
|
||||||
|
if Settings.sentry?.dsn?
|
||||||
|
logger.initializeErrorReporting(Settings.sentry.dsn)
|
||||||
|
|
||||||
metrics = require("metrics-sharelatex")
|
metrics = require("metrics-sharelatex")
|
||||||
metrics.initialize("web")
|
metrics.initialize("web")
|
||||||
|
metrics.memory.monitor(logger)
|
||||||
Server = require("./app/js/infrastructure/Server")
|
Server = require("./app/js/infrastructure/Server")
|
||||||
Errors = require "./app/js/errors"
|
Errors = require "./app/js/errors"
|
||||||
|
|
||||||
|
@ -15,6 +19,10 @@ argv = require("optimist")
|
||||||
.argv
|
.argv
|
||||||
|
|
||||||
Server.app.use (error, req, res, next) ->
|
Server.app.use (error, req, res, next) ->
|
||||||
|
if error?.code is 'EBADCSRFTOKEN'
|
||||||
|
logger.log err: error,url:req.url, method:req.method, user:req?.sesson?.user, "invalid csrf"
|
||||||
|
res.sendStatus(403)
|
||||||
|
return
|
||||||
logger.error err: error, url:req.url, method:req.method, user:req?.sesson?.user, "error passed to top level next middlewear"
|
logger.error err: error, url:req.url, method:req.method, user:req?.sesson?.user, "error passed to top level next middlewear"
|
||||||
res.statusCode = error.status or 500
|
res.statusCode = error.status or 500
|
||||||
if res.statusCode == 500
|
if res.statusCode == 500
|
||||||
|
|
|
@ -8,13 +8,16 @@ querystring = require('querystring')
|
||||||
Url = require("url")
|
Url = require("url")
|
||||||
Settings = require "settings-sharelatex"
|
Settings = require "settings-sharelatex"
|
||||||
basicAuth = require('basic-auth-connect')
|
basicAuth = require('basic-auth-connect')
|
||||||
|
UserHandler = require("../User/UserHandler")
|
||||||
|
|
||||||
module.exports = AuthenticationController =
|
module.exports = AuthenticationController =
|
||||||
login: (req, res, next = (error) ->) ->
|
login: (req, res, next = (error) ->) ->
|
||||||
email = req.body?.email?.toLowerCase()
|
AuthenticationController.doLogin req.body, req, res, next
|
||||||
password = req.body?.password
|
|
||||||
redir = Url.parse(req.body?.redir or "/project").path
|
doLogin: (options, req, res, next) ->
|
||||||
|
email = options.email?.toLowerCase()
|
||||||
|
password = options.password
|
||||||
|
redir = Url.parse(options.redir or "/project").path
|
||||||
LoginRateLimiter.processLoginRequest email, (err, isAllowed)->
|
LoginRateLimiter.processLoginRequest email, (err, isAllowed)->
|
||||||
if !isAllowed
|
if !isAllowed
|
||||||
logger.log email:email, "too many login requests"
|
logger.log email:email, "too many login requests"
|
||||||
|
@ -26,17 +29,18 @@ module.exports = AuthenticationController =
|
||||||
AuthenticationManager.authenticate email: email, password, (error, user) ->
|
AuthenticationManager.authenticate email: email, password, (error, user) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
if user?
|
if user?
|
||||||
|
UserHandler.setupLoginData user, ->
|
||||||
LoginRateLimiter.recordSuccessfulLogin email
|
LoginRateLimiter.recordSuccessfulLogin email
|
||||||
AuthenticationController._recordSuccessfulLogin user._id
|
AuthenticationController._recordSuccessfulLogin user._id
|
||||||
AuthenticationController.establishUserSession req, user, (error) ->
|
AuthenticationController.establishUserSession req, user, (error) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
req.session.justLoggedIn = true
|
req.session.justLoggedIn = true
|
||||||
logger.log email: email, user_id: user._id.toString(), "successful log in"
|
logger.log email: email, user_id: user._id.toString(), "successful log in"
|
||||||
res.send redir: redir
|
res.json redir: redir
|
||||||
else
|
else
|
||||||
AuthenticationController._recordFailedLogin()
|
AuthenticationController._recordFailedLogin()
|
||||||
logger.log email: email, "failed log in"
|
logger.log email: email, "failed log in"
|
||||||
res.send message:
|
res.json message:
|
||||||
text: req.i18n.translate("email_or_password_wrong_try_again"),
|
text: req.i18n.translate("email_or_password_wrong_try_again"),
|
||||||
type: 'error'
|
type: 'error'
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
Settings = require 'settings-sharelatex'
|
|
||||||
User = require("../../models/User").User
|
User = require("../../models/User").User
|
||||||
{db, ObjectId} = require("../../infrastructure/mongojs")
|
{db, ObjectId} = require("../../infrastructure/mongojs")
|
||||||
crypto = require 'crypto'
|
crypto = require 'crypto'
|
||||||
|
|
|
@ -4,15 +4,13 @@ logger = require("logger-sharelatex")
|
||||||
_ = require("underscore")
|
_ = require("underscore")
|
||||||
ErrorController = require "../Errors/ErrorController"
|
ErrorController = require "../Errors/ErrorController"
|
||||||
|
|
||||||
extensionsToProxy = [".png", ".xml", ".jpeg", ".json", ".zip", ".eps"]
|
|
||||||
|
|
||||||
module.exports = BlogController =
|
module.exports = BlogController =
|
||||||
|
|
||||||
getPage: (req, res, next)->
|
getPage: (req, res, next)->
|
||||||
url = req.url?.toLowerCase()
|
url = req.url?.toLowerCase()
|
||||||
blogUrl = "#{settings.apis.blog.url}#{url}"
|
blogUrl = "#{settings.apis.blog.url}#{url}"
|
||||||
|
|
||||||
extensionsToProxy = [".png", ".xml", ".jpeg", ".json", ".zip", ".eps"]
|
extensionsToProxy = [".png", ".xml", ".jpeg", ".json", ".zip", ".eps", ".gif"]
|
||||||
|
|
||||||
shouldProxy = _.find extensionsToProxy, (extension)->
|
shouldProxy = _.find extensionsToProxy, (extension)->
|
||||||
url.indexOf(extension) != -1
|
url.indexOf(extension) != -1
|
||||||
|
|
|
@ -58,8 +58,8 @@ module.exports = ClsiManager =
|
||||||
return outputFiles
|
return outputFiles
|
||||||
|
|
||||||
VALID_COMPILERS: ["pdflatex", "latex", "xelatex", "lualatex"]
|
VALID_COMPILERS: ["pdflatex", "latex", "xelatex", "lualatex"]
|
||||||
_buildRequest: (project_id, settingsOverride={}, callback = (error, request) ->) ->
|
_buildRequest: (project_id, options={}, callback = (error, request) ->) ->
|
||||||
Project.findById project_id, {compiler: 1, rootDoc_id: 1}, (error, project) ->
|
Project.findById project_id, {compiler: 1, rootDoc_id: 1, imageName: 1}, (error, project) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
return callback(new Errors.NotFoundError("project does not exist: #{project_id}")) if !project?
|
return callback(new Errors.NotFoundError("project does not exist: #{project_id}")) if !project?
|
||||||
|
|
||||||
|
@ -82,7 +82,7 @@ module.exports = ClsiManager =
|
||||||
content: doc.lines.join("\n")
|
content: doc.lines.join("\n")
|
||||||
if project.rootDoc_id? and doc._id.toString() == project.rootDoc_id.toString()
|
if project.rootDoc_id? and doc._id.toString() == project.rootDoc_id.toString()
|
||||||
rootResourcePath = path
|
rootResourcePath = path
|
||||||
if settingsOverride.rootDoc_id? and doc._id.toString() == settingsOverride.rootDoc_id.toString()
|
if options.rootDoc_id? and doc._id.toString() == options.rootDoc_id.toString()
|
||||||
rootResourcePathOverride = path
|
rootResourcePathOverride = path
|
||||||
|
|
||||||
rootResourcePath = rootResourcePathOverride if rootResourcePathOverride?
|
rootResourcePath = rootResourcePathOverride if rootResourcePathOverride?
|
||||||
|
@ -101,7 +101,9 @@ module.exports = ClsiManager =
|
||||||
compile:
|
compile:
|
||||||
options:
|
options:
|
||||||
compiler: project.compiler
|
compiler: project.compiler
|
||||||
timeout: settingsOverride.timeout
|
timeout: options.timeout
|
||||||
|
imageName: project.imageName
|
||||||
|
draft: !!options.draft
|
||||||
rootResourcePath: rootResourcePath
|
rootResourcePath: rootResourcePath
|
||||||
resources: resources
|
resources: resources
|
||||||
}
|
}
|
||||||
|
@ -110,8 +112,11 @@ module.exports = ClsiManager =
|
||||||
ClsiManager._buildRequest project_id, options, (error, req) ->
|
ClsiManager._buildRequest project_id, options, (error, req) ->
|
||||||
compilerUrl = ClsiManager._getCompilerUrl(options?.compileGroup)
|
compilerUrl = ClsiManager._getCompilerUrl(options?.compileGroup)
|
||||||
filename = file || req?.compile?.rootResourcePath
|
filename = file || req?.compile?.rootResourcePath
|
||||||
|
wordcount_url = "#{compilerUrl}/project/#{project_id}/wordcount?file=#{encodeURIComponent(filename)}"
|
||||||
|
if req.compile.options.imageName?
|
||||||
|
wordcount_url += "&image=#{encodeURIComponent(req.compile.options.imageName)}"
|
||||||
request.get {
|
request.get {
|
||||||
url: "#{compilerUrl}/project/#{project_id}/wordcount?file=#{filename}"
|
url: wordcount_url
|
||||||
}, (error, response, body) ->
|
}, (error, response, body) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
if 200 <= response.statusCode < 300
|
if 200 <= response.statusCode < 300
|
||||||
|
|
|
@ -25,6 +25,8 @@ module.exports = CompileController =
|
||||||
options.rootDoc_id = req.body.settingsOverride.rootDoc_id
|
options.rootDoc_id = req.body.settingsOverride.rootDoc_id
|
||||||
if req.body?.compiler
|
if req.body?.compiler
|
||||||
options.compiler = req.body.compiler
|
options.compiler = req.body.compiler
|
||||||
|
if req.body?.draft
|
||||||
|
options.draft = req.body.draft
|
||||||
logger.log {options, project_id}, "got compile request"
|
logger.log {options, project_id}, "got compile request"
|
||||||
CompileManager.compile project_id, user_id, options, (error, status, outputFiles, output, limits) ->
|
CompileManager.compile project_id, user_id, options, (error, status, outputFiles, output, limits) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
request = require 'request'
|
request = require 'request'
|
||||||
request = request.defaults()
|
request = request.defaults()
|
||||||
async = require 'async'
|
|
||||||
settings = require 'settings-sharelatex'
|
settings = require 'settings-sharelatex'
|
||||||
_ = require 'underscore'
|
_ = require 'underscore'
|
||||||
async = require 'async'
|
async = require 'async'
|
||||||
|
@ -116,7 +115,7 @@ module.exports = DocumentUpdaterHandler =
|
||||||
logger.error project_id:project_id, doc_id:doc_id, url: url, "doc updater returned a non-success status code: #{res.statusCode}"
|
logger.error project_id:project_id, doc_id:doc_id, url: url, "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}")
|
||||||
|
|
||||||
setDocument : (project_id, doc_id, docLines, source, callback = (error) ->)->
|
setDocument : (project_id, doc_id, user_id, docLines, source, callback = (error) ->)->
|
||||||
timer = new metrics.Timer("set-document")
|
timer = new metrics.Timer("set-document")
|
||||||
url = "#{settings.apis.documentupdater.url}/project/#{project_id}/doc/#{doc_id}"
|
url = "#{settings.apis.documentupdater.url}/project/#{project_id}/doc/#{doc_id}"
|
||||||
body =
|
body =
|
||||||
|
@ -124,7 +123,8 @@ module.exports = DocumentUpdaterHandler =
|
||||||
json:
|
json:
|
||||||
lines: docLines
|
lines: docLines
|
||||||
source: source
|
source: source
|
||||||
logger.log project_id:project_id, doc_id: doc_id, source: source, "setting doc in document updater"
|
user_id: user_id
|
||||||
|
logger.log project_id:project_id, doc_id: doc_id, source: source, user_id: user_id, "setting doc in document updater"
|
||||||
request.post body, (error, res, body)->
|
request.post body, (error, res, body)->
|
||||||
timer.done()
|
timer.done()
|
||||||
if error?
|
if error?
|
||||||
|
|
|
@ -6,15 +6,20 @@ module.exports =
|
||||||
getDocument: (req, res, next = (error) ->) ->
|
getDocument: (req, res, next = (error) ->) ->
|
||||||
project_id = req.params.Project_id
|
project_id = req.params.Project_id
|
||||||
doc_id = req.params.doc_id
|
doc_id = req.params.doc_id
|
||||||
|
plain = req?.query?.plain == 'true'
|
||||||
logger.log doc_id:doc_id, project_id:project_id, "receiving get document request from api (docupdater)"
|
logger.log doc_id:doc_id, project_id:project_id, "receiving get document request from api (docupdater)"
|
||||||
ProjectEntityHandler.getDoc project_id, doc_id, (error, lines, rev) ->
|
ProjectEntityHandler.getDoc project_id, doc_id, (error, lines, rev) ->
|
||||||
if error?
|
if error?
|
||||||
logger.err err:error, doc_id:doc_id, project_id:project_id, "error finding element for getDocument"
|
logger.err err:error, doc_id:doc_id, project_id:project_id, "error finding element for getDocument"
|
||||||
return next(error)
|
return next(error)
|
||||||
res.type "json"
|
if plain
|
||||||
res.send JSON.stringify {
|
res.type "text/plain"
|
||||||
lines: lines
|
res.send lines.join('\n')
|
||||||
}
|
else
|
||||||
|
res.type "json"
|
||||||
|
res.send JSON.stringify {
|
||||||
|
lines: lines
|
||||||
|
}
|
||||||
|
|
||||||
setDocument: (req, res, next = (error) ->) ->
|
setDocument: (req, res, next = (error) ->) ->
|
||||||
project_id = req.params.Project_id
|
project_id = req.params.Project_id
|
||||||
|
|
|
@ -13,8 +13,8 @@ LockManager = require("../../infrastructure/LockManager")
|
||||||
_ = require('underscore')
|
_ = require('underscore')
|
||||||
|
|
||||||
module.exports = EditorController =
|
module.exports = EditorController =
|
||||||
setDoc: (project_id, doc_id, docLines, source, callback = (err)->)->
|
setDoc: (project_id, doc_id, user_id, docLines, source, callback = (err)->)->
|
||||||
DocumentUpdaterHandler.setDocument project_id, doc_id, docLines, source, (err)=>
|
DocumentUpdaterHandler.setDocument project_id, doc_id, user_id, docLines, source, (err)=>
|
||||||
logger.log project_id:project_id, doc_id:doc_id, "notifying users that the document has been updated"
|
logger.log project_id:project_id, doc_id:doc_id, "notifying users that the document has been updated"
|
||||||
DocumentUpdaterHandler.flushDocToMongo project_id, doc_id, callback
|
DocumentUpdaterHandler.flushDocToMongo project_id, doc_id, callback
|
||||||
|
|
||||||
|
@ -33,10 +33,12 @@ module.exports = EditorController =
|
||||||
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, (err, doc, folder_id)=>
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id:project_id, docName:docName, "error adding doc without lock"
|
||||||
|
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, callback = (error, file)->)->
|
||||||
LockManager.getLock project_id, (err)->
|
LockManager.getLock project_id, (err)->
|
||||||
if err?
|
if err?
|
||||||
|
@ -46,20 +48,20 @@ module.exports = EditorController =
|
||||||
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, 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, (err, fileRef, folder_id)=>
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id:project_id, folder_id:folder_id, fileName:fileName, "error adding file without lock"
|
||||||
|
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, callback = (error) ->)->
|
||||||
ProjectEntityHandler.replaceFile project_id, file_id, fsPath, callback
|
ProjectEntityHandler.replaceFile project_id, file_id, fsPath, 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)->
|
||||||
if err?
|
if err?
|
||||||
|
@ -74,6 +76,9 @@ module.exports = EditorController =
|
||||||
logger.log {project_id, folder_id, folderName, source}, "sending new folder to project"
|
logger.log {project_id, folder_id, folderName, source}, "sending new folder to project"
|
||||||
Metrics.inc "editor.add-folder"
|
Metrics.inc "editor.add-folder"
|
||||||
ProjectEntityHandler.addFolder project_id, folder_id, folderName, (err, folder, folder_id)=>
|
ProjectEntityHandler.addFolder project_id, folder_id, folderName, (err, folder, folder_id)=>
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id:project_id, folder_id:folder_id, folderName:folderName, "error adding folder without lock"
|
||||||
|
return callback(err)
|
||||||
@p.notifyProjectUsersOfNewFolder project_id, folder_id, folder, (error) ->
|
@p.notifyProjectUsersOfNewFolder project_id, folder_id, folder, (error) ->
|
||||||
callback error, folder
|
callback error, folder
|
||||||
|
|
||||||
|
@ -90,6 +95,9 @@ module.exports = EditorController =
|
||||||
mkdirpWithoutLock: (project_id, path, callback)->
|
mkdirpWithoutLock: (project_id, path, callback)->
|
||||||
logger.log project_id:project_id, path:path, "making directories if they don't exist"
|
logger.log project_id:project_id, path:path, "making directories if they don't exist"
|
||||||
ProjectEntityHandler.mkdirp project_id, path, (err, newFolders, lastFolder)=>
|
ProjectEntityHandler.mkdirp project_id, path, (err, newFolders, lastFolder)=>
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id:project_id, path:path, "error mkdirp without lock"
|
||||||
|
return callback(err)
|
||||||
self = @
|
self = @
|
||||||
jobs = _.map newFolders, (folder, index)->
|
jobs = _.map newFolders, (folder, index)->
|
||||||
return (cb)->
|
return (cb)->
|
||||||
|
@ -109,7 +117,10 @@ module.exports = EditorController =
|
||||||
deleteEntityWithoutLock: (project_id, entity_id, entityType, source, callback)->
|
deleteEntityWithoutLock: (project_id, entity_id, entityType, source, callback)->
|
||||||
logger.log {project_id, entity_id, entityType, source}, "start delete process of entity"
|
logger.log {project_id, entity_id, entityType, source}, "start delete process of entity"
|
||||||
Metrics.inc "editor.delete-entity"
|
Metrics.inc "editor.delete-entity"
|
||||||
ProjectEntityHandler.deleteEntity project_id, entity_id, entityType, =>
|
ProjectEntityHandler.deleteEntity project_id, entity_id, entityType, (err)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id:project_id, entity_id:entity_id, entityType:entityType, "error deleting entity"
|
||||||
|
return callback(err)
|
||||||
logger.log project_id:project_id, entity_id:entity_id, entityType:entityType, "telling users entity has been deleted"
|
logger.log project_id:project_id, entity_id:entity_id, entityType:entityType, "telling users entity has been deleted"
|
||||||
EditorRealTimeController.emitToRoom(project_id, 'removeEntity', entity_id, source)
|
EditorRealTimeController.emitToRoom(project_id, 'removeEntity', entity_id, source)
|
||||||
if callback?
|
if callback?
|
||||||
|
@ -143,19 +154,28 @@ module.exports = EditorController =
|
||||||
newName = sanitize.escape(newName)
|
newName = sanitize.escape(newName)
|
||||||
Metrics.inc "editor.rename-entity"
|
Metrics.inc "editor.rename-entity"
|
||||||
logger.log entity_id:entity_id, entity_id:entity_id, entity_id:entity_id, "reciving new name for entity for project"
|
logger.log entity_id:entity_id, entity_id:entity_id, entity_id:entity_id, "reciving new name for entity for project"
|
||||||
ProjectEntityHandler.renameEntity project_id, entity_id, entityType, newName, =>
|
ProjectEntityHandler.renameEntity project_id, entity_id, entityType, newName, ->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id:project_id, entity_id:entity_id, entityType:entityType, newName:newName, "error renaming entity"
|
||||||
|
return callback(err)
|
||||||
if newName.length > 0
|
if newName.length > 0
|
||||||
EditorRealTimeController.emitToRoom project_id, 'reciveEntityRename', entity_id, newName
|
EditorRealTimeController.emitToRoom project_id, 'reciveEntityRename', entity_id, newName
|
||||||
callback?()
|
callback?()
|
||||||
#
|
|
||||||
moveEntity: (project_id, entity_id, folder_id, entityType, callback)->
|
moveEntity: (project_id, entity_id, folder_id, entityType, callback)->
|
||||||
Metrics.inc "editor.move-entity"
|
Metrics.inc "editor.move-entity"
|
||||||
ProjectEntityHandler.moveEntity project_id, entity_id, folder_id, entityType, =>
|
ProjectEntityHandler.moveEntity project_id, entity_id, folder_id, entityType, =>
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id:project_id, entity_id:entity_id, folder_id:folder_id, "error moving entity"
|
||||||
|
return callback(err)
|
||||||
EditorRealTimeController.emitToRoom project_id, 'reciveEntityMove', entity_id, folder_id
|
EditorRealTimeController.emitToRoom project_id, 'reciveEntityMove', entity_id, folder_id
|
||||||
callback?()
|
callback?()
|
||||||
|
|
||||||
renameProject: (project_id, newName, callback = (err) ->) ->
|
renameProject: (project_id, newName, callback = (err) ->) ->
|
||||||
ProjectDetailsHandler.renameProject project_id, newName, =>
|
ProjectDetailsHandler.renameProject project_id, newName, ->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id:project_id, newName:newName, "error renaming project"
|
||||||
|
return callback(err)
|
||||||
EditorRealTimeController.emitToRoom project_id, 'projectNameUpdated', newName
|
EditorRealTimeController.emitToRoom project_id, 'projectNameUpdated', newName
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
|
|
|
@ -67,11 +67,16 @@ 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
|
||||||
|
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", (error, doc) ->
|
||||||
return next(error) if error?
|
if error == "project_has_to_many_files"
|
||||||
res.json doc
|
res.status(400).json(req.i18n.translate("project_has_to_many_files"))
|
||||||
|
else if error?
|
||||||
|
next(error)
|
||||||
|
else
|
||||||
|
res.json doc
|
||||||
|
|
||||||
addFolder: (req, res, next) ->
|
addFolder: (req, res, next) ->
|
||||||
project_id = req.params.Project_id
|
project_id = req.params.Project_id
|
||||||
|
@ -80,8 +85,12 @@ module.exports = EditorHttpController =
|
||||||
if !EditorHttpController._nameIsAcceptableLength(name)
|
if !EditorHttpController._nameIsAcceptableLength(name)
|
||||||
return res.sendStatus 400
|
return res.sendStatus 400
|
||||||
EditorController.addFolder project_id, parent_folder_id, name, "editor", (error, doc) ->
|
EditorController.addFolder project_id, parent_folder_id, name, "editor", (error, doc) ->
|
||||||
return next(error) if error?
|
if error == "project_has_to_many_files"
|
||||||
res.json doc
|
res.status(400).json(req.i18n.translate("project_has_to_many_files"))
|
||||||
|
else if error?
|
||||||
|
next(error)
|
||||||
|
else
|
||||||
|
res.json doc
|
||||||
|
|
||||||
renameEntity: (req, res, next) ->
|
renameEntity: (req, res, next) ->
|
||||||
project_id = req.params.Project_id
|
project_id = req.params.Project_id
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
_ = require('underscore')
|
_ = require('underscore')
|
||||||
|
|
||||||
PersonalEmailLayout = require("./Layouts/PersonalEmailLayout")
|
PersonalEmailLayout = require("./Layouts/PersonalEmailLayout")
|
||||||
NotificationEmailLayout = require("./Layouts/NotificationEmailLayout")
|
NotificationEmailLayout = require("./Layouts/NotificationEmailLayout")
|
||||||
settings = require("settings-sharelatex")
|
settings = require("settings-sharelatex")
|
||||||
|
@ -15,8 +14,6 @@ templates.registered =
|
||||||
|
|
||||||
<p><a href="<%= setNewPasswordUrl %>">Click here to set your password and log in.</a></p>
|
<p><a href="<%= setNewPasswordUrl %>">Click here to set your password and log in.</a></p>
|
||||||
|
|
||||||
<p>Once you have reset your password you can <a href="#{settings.siteUrl}/login">log in here</a>.</p>
|
|
||||||
|
|
||||||
<p>If you have any questions or problems, please contact <a href="mailto:#{settings.adminEmail}">#{settings.adminEmail}</a>.</p>
|
<p>If you have any questions or problems, please contact <a href="mailto:#{settings.adminEmail}">#{settings.adminEmail}</a>.</p>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
logger = require('logger-sharelatex')
|
logger = require('logger-sharelatex')
|
||||||
metrics = require('../../infrastructure/Metrics')
|
metrics = require('../../infrastructure/Metrics')
|
||||||
Settings = require('settings-sharelatex')
|
Settings = require('settings-sharelatex')
|
||||||
metrics = require("../../infrastructure/Metrics")
|
|
||||||
nodemailer = require("nodemailer")
|
nodemailer = require("nodemailer")
|
||||||
|
sesTransport = require('nodemailer-ses-transport')
|
||||||
|
_ = require("underscore")
|
||||||
|
|
||||||
if Settings.email? and Settings.email.fromAddress?
|
if Settings.email? and Settings.email.fromAddress?
|
||||||
defaultFromAddress = Settings.email.fromAddress
|
defaultFromAddress = Settings.email.fromAddress
|
||||||
|
@ -15,15 +16,24 @@ client =
|
||||||
logger.log options:options, "Would send email if enabled."
|
logger.log options:options, "Would send email if enabled."
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
if Settings.email?
|
if Settings?.email?.parameters?.AWSAccessKeyID?
|
||||||
if Settings.email.transport? and Settings.email.parameters?
|
logger.log "using aws ses for email"
|
||||||
nm_client = nodemailer.createTransport( Settings.email.transport, Settings.email.parameters )
|
nm_client = nodemailer.createTransport(sesTransport(Settings.email.parameters))
|
||||||
if nm_client
|
else if Settings?.email?.parameters?
|
||||||
client = nm_client
|
smtp = _.pick(Settings?.email?.parameters, "host", "port", "secure", "auth")
|
||||||
else
|
|
||||||
logger.warn "Failed to create email transport. Please check your settings. No email will be sent."
|
|
||||||
else
|
logger.log "using smtp for email"
|
||||||
logger.warn "Email transport and/or parameters not defined. No emails will be sent."
|
nm_client = nodemailer.createTransport(smtp)
|
||||||
|
else
|
||||||
|
nm_client = client
|
||||||
|
logger.warn "Email transport and/or parameters not defined. No emails will be sent."
|
||||||
|
|
||||||
|
if nm_client?
|
||||||
|
client = nm_client
|
||||||
|
else
|
||||||
|
logger.warn "Failed to create email transport. Please check your settings. No email will be sent."
|
||||||
|
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
sendEmail : (options, callback = (error) ->)->
|
sendEmail : (options, callback = (error) ->)->
|
||||||
|
@ -42,4 +52,3 @@ module.exports =
|
||||||
else
|
else
|
||||||
logger.log "Message sent to #{options.to}"
|
logger.log "Message sent to #{options.to}"
|
||||||
callback(err)
|
callback(err)
|
||||||
|
|
||||||
|
|
|
@ -6,24 +6,32 @@ settings = require("settings-sharelatex")
|
||||||
oneMinInMs = 60 * 1000
|
oneMinInMs = 60 * 1000
|
||||||
fiveMinsInMs = oneMinInMs * 5
|
fiveMinsInMs = oneMinInMs * 5
|
||||||
|
|
||||||
module.exports =
|
module.exports = FileStoreHandler =
|
||||||
|
|
||||||
uploadFileFromDisk: (project_id, file_id, fsPath, callback)->
|
uploadFileFromDisk: (project_id, file_id, fsPath, callback)->
|
||||||
logger.log project_id:project_id, file_id:file_id, fsPath:fsPath, "uploading file from disk"
|
fs.lstat fsPath, (err, stat)->
|
||||||
readStream = fs.createReadStream(fsPath)
|
if err?
|
||||||
opts =
|
logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "error stating file"
|
||||||
method: "post"
|
callback(err)
|
||||||
uri: @_buildUrl(project_id, file_id)
|
if !stat.isFile()
|
||||||
timeout:fiveMinsInMs
|
logger.log project_id:project_id, file_id:file_id, fsPath:fsPath, "tried to upload symlink, not contining"
|
||||||
writeStream = request(opts)
|
return callback(new Error("can not upload symlink"))
|
||||||
readStream.pipe writeStream
|
|
||||||
writeStream.on "end", callback
|
logger.log project_id:project_id, file_id:file_id, fsPath:fsPath, "uploading file from disk"
|
||||||
readStream.on "error", (err)->
|
readStream = fs.createReadStream(fsPath)
|
||||||
logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the read stream of uploadFileFromDisk"
|
opts =
|
||||||
callback err
|
method: "post"
|
||||||
writeStream.on "error", (err)->
|
uri: FileStoreHandler._buildUrl(project_id, file_id)
|
||||||
logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the write stream of uploadFileFromDisk"
|
timeout:fiveMinsInMs
|
||||||
callback err
|
writeStream = request(opts)
|
||||||
|
readStream.pipe writeStream
|
||||||
|
writeStream.on "end", callback
|
||||||
|
readStream.on "error", (err)->
|
||||||
|
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
|
||||||
|
writeStream.on "error", (err)->
|
||||||
|
logger.err err:err, project_id:project_id, file_id:file_id, fsPath:fsPath, "something went wrong on the write stream of uploadFileFromDisk"
|
||||||
|
callback err
|
||||||
|
|
||||||
getFileStream: (project_id, file_id, query, callback)->
|
getFileStream: (project_id, file_id, query, callback)->
|
||||||
logger.log project_id:project_id, file_id:file_id, query:query, "getting file stream from file store"
|
logger.log project_id:project_id, file_id:file_id, query:query, "getting file stream from file store"
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
|
||||||
|
NotificationsHandler = require("./NotificationsHandler")
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
|
||||||
|
groupPlan: (user, licence)->
|
||||||
|
key : "join-sub-#{licence.subscription_id}"
|
||||||
|
|
||||||
|
create: (callback = ->)->
|
||||||
|
messageOpts =
|
||||||
|
groupName: licence.name
|
||||||
|
subscription_id: licence.subscription_id
|
||||||
|
NotificationsHandler.createNotification user._id, @key, "notification_group_invite", messageOpts, callback
|
||||||
|
|
||||||
|
read: (callback = ->)->
|
||||||
|
NotificationsHandler.markAsReadWithKey user._id, @key, callback
|
|
@ -0,0 +1,19 @@
|
||||||
|
NotificationsHandler = require("./NotificationsHandler")
|
||||||
|
logger = require("logger-sharelatex")
|
||||||
|
_ = require("underscore")
|
||||||
|
|
||||||
|
module.exports =
|
||||||
|
|
||||||
|
getAllUnreadNotifications: (req, res)->
|
||||||
|
NotificationsHandler.getUserNotifications req.session.user._id, (err, unreadNotifications)->
|
||||||
|
unreadNotifications = _.map unreadNotifications, (notification)->
|
||||||
|
notification.html = req.i18n.translate(notification.templateKey, notification.messageOpts)
|
||||||
|
return notification
|
||||||
|
res.send(unreadNotifications)
|
||||||
|
|
||||||
|
markNotificationAsRead: (req, res)->
|
||||||
|
user_id = req.session.user._id
|
||||||
|
notification_id = req.params.notification_id
|
||||||
|
NotificationsHandler.markAsRead user_id, notification_id, ->
|
||||||
|
res.send()
|
||||||
|
logger.log user_id:user_id, notification_id:notification_id, "mark notification as read"
|
|
@ -0,0 +1,52 @@
|
||||||
|
settings = require("settings-sharelatex")
|
||||||
|
request = require("request")
|
||||||
|
logger = require("logger-sharelatex")
|
||||||
|
|
||||||
|
oneSecond = 1000
|
||||||
|
module.exports =
|
||||||
|
|
||||||
|
getUserNotifications: (user_id, callback)->
|
||||||
|
opts =
|
||||||
|
uri: "#{settings.apis.notifications.url}/user/#{user_id}"
|
||||||
|
json: true
|
||||||
|
timeout: oneSecond
|
||||||
|
request.get opts, (err, res, unreadNotifications)->
|
||||||
|
statusCode = if res? then res.statusCode else 500
|
||||||
|
if err? or statusCode != 200
|
||||||
|
e = new Error("something went wrong getting notifications, #{err}, #{statusCode}")
|
||||||
|
logger.err err:err, "something went wrong getting notifications"
|
||||||
|
callback(null, [])
|
||||||
|
else
|
||||||
|
if !unreadNotifications?
|
||||||
|
unreadNotifications = []
|
||||||
|
callback(null, unreadNotifications)
|
||||||
|
|
||||||
|
createNotification: (user_id, key, templateKey, messageOpts, callback)->
|
||||||
|
opts =
|
||||||
|
uri: "#{settings.apis.notifications.url}/user/#{user_id}"
|
||||||
|
timeout: oneSecond
|
||||||
|
json: {
|
||||||
|
key:key
|
||||||
|
messageOpts:messageOpts
|
||||||
|
templateKey:templateKey
|
||||||
|
}
|
||||||
|
logger.log opts:opts, "creating notification for user"
|
||||||
|
request.post opts, callback
|
||||||
|
|
||||||
|
markAsReadWithKey: (user_id, key, callback)->
|
||||||
|
opts =
|
||||||
|
uri: "#{settings.apis.notifications.url}/user/#{user_id}"
|
||||||
|
timeout: oneSecond
|
||||||
|
json: {
|
||||||
|
key:key
|
||||||
|
}
|
||||||
|
logger.log user_id:user_id, key:key, "sending mark notification as read with key to notifications api"
|
||||||
|
request.del opts, callback
|
||||||
|
|
||||||
|
|
||||||
|
markAsRead: (user_id, notification_id, callback)->
|
||||||
|
opts =
|
||||||
|
uri: "#{settings.apis.notifications.url}/user/#{user_id}/notification/#{notification_id}"
|
||||||
|
timeout:oneSecond
|
||||||
|
logger.log user_id:user_id, notification_id:notification_id, "sending mark notification as read to notifications api"
|
||||||
|
request.del opts, callback
|
|
@ -1,5 +1,7 @@
|
||||||
PasswordResetHandler = require("./PasswordResetHandler")
|
PasswordResetHandler = require("./PasswordResetHandler")
|
||||||
RateLimiter = require("../../infrastructure/RateLimiter")
|
RateLimiter = require("../../infrastructure/RateLimiter")
|
||||||
|
AuthenticationController = require("../Authentication/AuthenticationController")
|
||||||
|
UserGetter = require("../User/UserGetter")
|
||||||
logger = require "logger-sharelatex"
|
logger = require "logger-sharelatex"
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
|
@ -37,14 +39,19 @@ module.exports =
|
||||||
title:"set_password"
|
title:"set_password"
|
||||||
passwordResetToken: req.session.resetToken
|
passwordResetToken: req.session.resetToken
|
||||||
|
|
||||||
setNewUserPassword: (req, res)->
|
setNewUserPassword: (req, res, next)->
|
||||||
{passwordResetToken, password} = req.body
|
{passwordResetToken, password} = req.body
|
||||||
if !password? or password.length == 0 or !passwordResetToken? or passwordResetToken.length == 0
|
if !password? or password.length == 0 or !passwordResetToken? or passwordResetToken.length == 0
|
||||||
return res.sendStatus 400
|
return res.sendStatus 400
|
||||||
delete req.session.resetToken
|
delete req.session.resetToken
|
||||||
PasswordResetHandler.setNewUserPassword passwordResetToken?.trim(), password?.trim(), (err, found) ->
|
PasswordResetHandler.setNewUserPassword passwordResetToken?.trim(), password?.trim(), (err, found, user_id) ->
|
||||||
return next(err) if err?
|
return next(err) if err?
|
||||||
if found
|
if found
|
||||||
res.sendStatus 200
|
if req.body.login_after
|
||||||
|
UserGetter.getUser user_id, {email: 1}, (err, user) ->
|
||||||
|
return next(err) if err?
|
||||||
|
AuthenticationController.doLogin {email:user.email, password: password}, req, res, next
|
||||||
|
else
|
||||||
|
res.sendStatus 200
|
||||||
else
|
else
|
||||||
res.send 404, {message: req.i18n.translate("password_reset_token_expired")}
|
res.sendStatus 404
|
||||||
|
|
|
@ -23,11 +23,11 @@ module.exports =
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
callback null, true
|
callback null, true
|
||||||
|
|
||||||
setNewUserPassword: (token, password, callback = (error, found) ->)->
|
setNewUserPassword: (token, password, callback = (error, found, user_id) ->)->
|
||||||
OneTimeTokenHandler.getValueFromTokenAndExpire token, (err, user_id)->
|
OneTimeTokenHandler.getValueFromTokenAndExpire token, (err, user_id)->
|
||||||
if err then return callback(err)
|
if err then return callback(err)
|
||||||
if !user_id?
|
if !user_id?
|
||||||
return callback null, false
|
return callback null, false, null
|
||||||
AuthenticationManager.setUserPassword user_id, password, (err) ->
|
AuthenticationManager.setUserPassword user_id, password, (err) ->
|
||||||
if err then return callback(err)
|
if err then return callback(err)
|
||||||
callback null, true
|
callback null, true, user_id
|
|
@ -9,6 +9,7 @@ Project = require('../../models/Project').Project
|
||||||
User = require('../../models/User').User
|
User = require('../../models/User').User
|
||||||
TagsHandler = require("../Tags/TagsHandler")
|
TagsHandler = require("../Tags/TagsHandler")
|
||||||
SubscriptionLocator = require("../Subscription/SubscriptionLocator")
|
SubscriptionLocator = require("../Subscription/SubscriptionLocator")
|
||||||
|
NotificationsHandler = require("../Notifications/NotificationsHandler")
|
||||||
LimitationsManager = require("../Subscription/LimitationsManager")
|
LimitationsManager = require("../Subscription/LimitationsManager")
|
||||||
_ = require("underscore")
|
_ = require("underscore")
|
||||||
Settings = require("settings-sharelatex")
|
Settings = require("settings-sharelatex")
|
||||||
|
@ -51,7 +52,7 @@ module.exports = ProjectController =
|
||||||
deleteProject: (req, res) ->
|
deleteProject: (req, res) ->
|
||||||
project_id = req.params.Project_id
|
project_id = req.params.Project_id
|
||||||
forever = req.query?.forever?
|
forever = req.query?.forever?
|
||||||
logger.log project_id: project_id, forever: forever, "received request to delete project"
|
logger.log project_id: project_id, forever: forever, "received request to archive project"
|
||||||
|
|
||||||
if forever
|
if forever
|
||||||
doDelete = projectDeleter.deleteProject
|
doDelete = projectDeleter.deleteProject
|
||||||
|
@ -125,6 +126,8 @@ module.exports = ProjectController =
|
||||||
async.parallel {
|
async.parallel {
|
||||||
tags: (cb)->
|
tags: (cb)->
|
||||||
TagsHandler.getAllTags user_id, cb
|
TagsHandler.getAllTags user_id, cb
|
||||||
|
notifications: (cb)->
|
||||||
|
NotificationsHandler.getUserNotifications user_id, cb
|
||||||
projects: (cb)->
|
projects: (cb)->
|
||||||
Project.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref', cb
|
Project.findAllUsersProjects user_id, 'name lastUpdated publicAccesLevel archived owner_ref', cb
|
||||||
hasSubscription: (cb)->
|
hasSubscription: (cb)->
|
||||||
|
@ -137,6 +140,9 @@ module.exports = ProjectController =
|
||||||
return next(err)
|
return next(err)
|
||||||
logger.log results:results, user_id:user_id, "rendering project list"
|
logger.log results:results, user_id:user_id, "rendering project list"
|
||||||
tags = results.tags[0]
|
tags = results.tags[0]
|
||||||
|
notifications = require("underscore").map results.notifications, (notification)->
|
||||||
|
notification.html = req.i18n.translate(notification.templateKey, notification.messageOpts)
|
||||||
|
return notification
|
||||||
projects = ProjectController._buildProjectList results.projects[0], results.projects[1], results.projects[2]
|
projects = ProjectController._buildProjectList results.projects[0], results.projects[1], results.projects[2]
|
||||||
user = results.user
|
user = results.user
|
||||||
ProjectController._injectProjectOwners projects, (error, projects) ->
|
ProjectController._injectProjectOwners projects, (error, projects) ->
|
||||||
|
@ -147,6 +153,7 @@ module.exports = ProjectController =
|
||||||
priority_title: true
|
priority_title: true
|
||||||
projects: projects
|
projects: projects
|
||||||
tags: tags
|
tags: tags
|
||||||
|
notifications: notifications or []
|
||||||
user: user
|
user: user
|
||||||
hasSubscription: results.hasSubscription[0]
|
hasSubscription: results.hasSubscription[0]
|
||||||
}
|
}
|
||||||
|
@ -168,15 +175,15 @@ module.exports = ProjectController =
|
||||||
return res.render("general/closed", {title:"updating_site"})
|
return res.render("general/closed", {title:"updating_site"})
|
||||||
|
|
||||||
if req.session.user?
|
if req.session.user?
|
||||||
user_id = req.session.user._id
|
user_id = req.session.user._id
|
||||||
anonymous = false
|
anonymous = false
|
||||||
else
|
else
|
||||||
anonymous = true
|
anonymous = true
|
||||||
user_id = 'openUser'
|
user_id = 'openUser'
|
||||||
|
|
||||||
project_id = req.params.Project_id
|
project_id = req.params.Project_id
|
||||||
logger.log project_id:project_id, "loading editor"
|
logger.log project_id:project_id, "loading editor"
|
||||||
|
|
||||||
async.parallel {
|
async.parallel {
|
||||||
project: (cb)->
|
project: (cb)->
|
||||||
Project.findPopulatedById project_id, cb
|
Project.findPopulatedById project_id, cb
|
||||||
|
@ -193,7 +200,7 @@ module.exports = ProjectController =
|
||||||
SubscriptionLocator.getUsersSubscription user_id, cb
|
SubscriptionLocator.getUsersSubscription user_id, cb
|
||||||
activate: (cb)->
|
activate: (cb)->
|
||||||
InactiveProjectManager.reactivateProjectIfRequired project_id, cb
|
InactiveProjectManager.reactivateProjectIfRequired project_id, cb
|
||||||
markAsOpened: (cb)->
|
markAsOpened: (cb)->
|
||||||
#don't need to wait for this to complete
|
#don't need to wait for this to complete
|
||||||
ProjectUpdateHandler.markAsOpened project_id, ->
|
ProjectUpdateHandler.markAsOpened project_id, ->
|
||||||
cb()
|
cb()
|
||||||
|
@ -205,6 +212,7 @@ module.exports = ProjectController =
|
||||||
user = results.user
|
user = results.user
|
||||||
subscription = results.subscription
|
subscription = results.subscription
|
||||||
|
|
||||||
|
|
||||||
daysSinceLastUpdated = (new Date() - project.lastUpdated) /86400000
|
daysSinceLastUpdated = (new Date() - project.lastUpdated) /86400000
|
||||||
logger.log project_id:project_id, daysSinceLastUpdated:daysSinceLastUpdated, "got db results for loading editor"
|
logger.log project_id:project_id, daysSinceLastUpdated:daysSinceLastUpdated, "got db results for loading editor"
|
||||||
|
|
||||||
|
@ -214,6 +222,7 @@ module.exports = ProjectController =
|
||||||
|
|
||||||
if subscription? and subscription.freeTrial? and subscription.freeTrial.expiresAt?
|
if subscription? and subscription.freeTrial? and subscription.freeTrial.expiresAt?
|
||||||
allowedFreeTrial = !!subscription.freeTrial.allowed || true
|
allowedFreeTrial = !!subscription.freeTrial.allowed || true
|
||||||
|
|
||||||
logger.log project_id:project_id, "rendering editor page"
|
logger.log project_id:project_id, "rendering editor page"
|
||||||
res.render 'project/editor',
|
res.render 'project/editor',
|
||||||
title: project.name
|
title: project.name
|
||||||
|
@ -310,5 +319,4 @@ do generateThemeList = () ->
|
||||||
for file in files
|
for file in files
|
||||||
if file.slice(-2) == "js" and file.match(/^theme-/)
|
if file.slice(-2) == "js" and file.match(/^theme-/)
|
||||||
cleanName = file.slice(0,-3).slice(6)
|
cleanName = file.slice(0,-3).slice(6)
|
||||||
THEME_LIST.push cleanName
|
THEME_LIST.push cleanName
|
||||||
|
|
|
@ -19,6 +19,8 @@ module.exports =
|
||||||
project = new Project
|
project = new Project
|
||||||
owner_ref : new ObjectId(owner_id)
|
owner_ref : new ObjectId(owner_id)
|
||||||
name : projectName
|
name : projectName
|
||||||
|
if Settings.currentImageName?
|
||||||
|
project.imageName = Settings.currentImageName
|
||||||
project.rootFolder[0] = rootFolder
|
project.rootFolder[0] = rootFolder
|
||||||
User.findById owner_id, "ace.spellCheckLanguage", (err, user)->
|
User.findById owner_id, "ace.spellCheckLanguage", (err, user)->
|
||||||
project.spellCheckLanguage = user.ace.spellCheckLanguage
|
project.spellCheckLanguage = user.ace.spellCheckLanguage
|
||||||
|
@ -33,7 +35,9 @@ module.exports =
|
||||||
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, (error, doc)->
|
||||||
return callback(error) if error?
|
if error?
|
||||||
|
logger.err err:error, "error adding doc when creating basic project"
|
||||||
|
return callback(error)
|
||||||
ProjectEntityHandler.setRootDoc project._id, doc._id, (error) ->
|
ProjectEntityHandler.setRootDoc project._id, doc._id, (error) ->
|
||||||
callback(error, project)
|
callback(error, project)
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
Project = require('../../models/Project').Project
|
Project = require('../../models/Project').Project
|
||||||
|
ProjectGetter = require("./ProjectGetter")
|
||||||
logger = require('logger-sharelatex')
|
logger = require('logger-sharelatex')
|
||||||
documentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
|
documentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
|
||||||
tagsHandler = require("../Tags/TagsHandler")
|
tagsHandler = require("../Tags/TagsHandler")
|
||||||
|
@ -33,10 +34,10 @@ module.exports = ProjectDeleter =
|
||||||
Project.remove _id: project_id, callback
|
Project.remove _id: project_id, callback
|
||||||
|
|
||||||
archiveProject: (project_id, callback = (error) ->)->
|
archiveProject: (project_id, callback = (error) ->)->
|
||||||
logger.log project_id:project_id, "deleting project"
|
logger.log project_id:project_id, "archived project from user request"
|
||||||
Project.findById project_id, (err, project)=>
|
ProjectGetter.getProject project_id, {owner_ref:true, collaberator_refs:true, readOnly_refs:true}, (err, project)=>
|
||||||
if err? or !project?
|
if err? or !project?
|
||||||
logger.err err:err, project_id:project_id, "error getting project to delete it"
|
logger.err err:err, project_id:project_id, "error getting project to archived it"
|
||||||
callback(err)
|
callback(err)
|
||||||
else
|
else
|
||||||
async.series [
|
async.series [
|
||||||
|
@ -57,8 +58,10 @@ module.exports = ProjectDeleter =
|
||||||
Project.update {_id:project_id}, { $set: { archived: true }}, cb
|
Project.update {_id:project_id}, { $set: { archived: true }}, cb
|
||||||
], (err)->
|
], (err)->
|
||||||
if err?
|
if err?
|
||||||
logger.err err:err, "problem deleting project"
|
logger.err err:err, "problem archived project"
|
||||||
callback(err)
|
return callback(err)
|
||||||
|
logger.log project_id:project_id, "succesfully archived project from user request"
|
||||||
|
callback()
|
||||||
|
|
||||||
restoreProject: (project_id, callback = (error) ->) ->
|
restoreProject: (project_id, callback = (error) ->) ->
|
||||||
Project.update {_id:project_id}, { $unset: { archived: true }}, callback
|
Project.update {_id:project_id}, { $unset: { archived: true }}, callback
|
||||||
|
|
|
@ -8,7 +8,7 @@ _ = require("underscore")
|
||||||
module.exports =
|
module.exports =
|
||||||
|
|
||||||
getDetails: (project_id, callback)->
|
getDetails: (project_id, callback)->
|
||||||
ProjectGetter.getProjectWithoutDocLines project_id, (err, project)->
|
ProjectGetter.getProject project_id, {name:true, description:true, compiler:true, features:true, owner_ref:true}, (err, project)->
|
||||||
if err?
|
if err?
|
||||||
logger.err err:err, project_id:project_id, "error getting project"
|
logger.err err:err, project_id:project_id, "error getting project"
|
||||||
return callback(err)
|
return callback(err)
|
||||||
|
@ -37,7 +37,7 @@ module.exports =
|
||||||
|
|
||||||
renameProject: (project_id, newName, callback = ->)->
|
renameProject: (project_id, newName, callback = ->)->
|
||||||
logger.log project_id: project_id, newName:newName, "renaming project"
|
logger.log project_id: project_id, newName:newName, "renaming project"
|
||||||
ProjectGetter.getProject project_id, {"name":1}, (err, project)->
|
ProjectGetter.getProject project_id, {name:true}, (err, project)->
|
||||||
if err? or !project?
|
if err? or !project?
|
||||||
logger.err err:err, project_id:project_id, "error getting project or could not find it todo project rename"
|
logger.err err:err, project_id:project_id, "error getting project or could not find it todo project rename"
|
||||||
return callback(err)
|
return callback(err)
|
||||||
|
|
|
@ -4,61 +4,86 @@ projectLocator = require('./ProjectLocator')
|
||||||
projectOptionsHandler = require('./ProjectOptionsHandler')
|
projectOptionsHandler = require('./ProjectOptionsHandler')
|
||||||
DocumentUpdaterHandler = require("../DocumentUpdater/DocumentUpdaterHandler")
|
DocumentUpdaterHandler = require("../DocumentUpdater/DocumentUpdaterHandler")
|
||||||
DocstoreManager = require "../Docstore/DocstoreManager"
|
DocstoreManager = require "../Docstore/DocstoreManager"
|
||||||
Project = require("../../models/Project").Project
|
ProjectGetter = require("./ProjectGetter")
|
||||||
_ = require('underscore')
|
_ = require('underscore')
|
||||||
async = require('async')
|
async = require('async')
|
||||||
|
logger = require("logger-sharelatex")
|
||||||
|
|
||||||
module.exports =
|
|
||||||
duplicate: (owner, originalProjectId, newProjectName, callback)->
|
|
||||||
DocumentUpdaterHandler.flushProjectToMongo originalProjectId, (err) ->
|
|
||||||
return callback(err) if err?
|
|
||||||
Project.findById originalProjectId, (err, originalProject) ->
|
|
||||||
return callback(err) if err?
|
|
||||||
projectCreationHandler.createBlankProject owner._id, newProjectName, (err, newProject)->
|
|
||||||
return callback(err) if err?
|
|
||||||
projectLocator.findRootDoc {project:originalProject}, (err, originalRootDoc)->
|
|
||||||
return callback(err) if err?
|
|
||||||
DocstoreManager.getAllDocs originalProjectId, (err, docContentsArray) ->
|
|
||||||
return callback(err) if err?
|
|
||||||
|
|
||||||
docContents = {}
|
module.exports = ProjectDuplicator =
|
||||||
for docContent in docContentsArray
|
|
||||||
docContents[docContent._id] = docContent
|
|
||||||
|
|
||||||
projectOptionsHandler.setCompiler newProject._id, originalProject.compiler
|
_copyDocs: (newProject, originalRootDoc, originalFolder, desFolder, docContents, callback)->
|
||||||
|
setRootDoc = _.once (doc_id)->
|
||||||
|
projectEntityHandler.setRootDoc newProject._id, doc_id
|
||||||
|
|
||||||
setRootDoc = _.once (doc_id)->
|
jobs = originalFolder.docs.map (doc)->
|
||||||
projectEntityHandler.setRootDoc newProject, doc_id
|
return (cb)->
|
||||||
|
content = docContents[doc._id.toString()]
|
||||||
|
projectEntityHandler.addDocWithProject newProject, desFolder._id, doc.name, content.lines, (err, newDoc)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, "error copying doc"
|
||||||
|
return callback(err)
|
||||||
|
if originalRootDoc? and newDoc.name == originalRootDoc.name
|
||||||
|
setRootDoc newDoc._id
|
||||||
|
cb()
|
||||||
|
|
||||||
copyDocs = (originalFolder, newParentFolder, callback)->
|
async.series jobs, callback
|
||||||
jobs = originalFolder.docs.map (doc)->
|
|
||||||
return (callback)->
|
|
||||||
content = docContents[doc._id.toString()]
|
|
||||||
return callback(new Error("doc_id not found: #{doc._id}")) if !content?
|
|
||||||
projectEntityHandler.addDoc newProject, newParentFolder._id, doc.name, content.lines, (err, newDoc)->
|
|
||||||
if originalRootDoc? and newDoc.name == originalRootDoc.name
|
|
||||||
setRootDoc newDoc._id
|
|
||||||
callback()
|
|
||||||
async.series jobs, callback
|
|
||||||
|
|
||||||
copyFiles = (originalFolder, newParentFolder, callback)->
|
_copyFiles: (newProject, originalProject_id, originalFolder, desFolder, callback)->
|
||||||
jobs = originalFolder.fileRefs.map (file)->
|
jobs = originalFolder.fileRefs.map (file)->
|
||||||
return (callback)->
|
return (cb)->
|
||||||
projectEntityHandler.copyFileFromExistingProject newProject, newParentFolder._id, originalProject._id, file, callback
|
projectEntityHandler.copyFileFromExistingProjectWithProject newProject, desFolder._id, originalProject_id, file, cb
|
||||||
async.parallelLimit jobs, 5, callback
|
async.parallelLimit jobs, 5, callback
|
||||||
|
|
||||||
copyFolder = (folder, desFolder, callback)->
|
|
||||||
jobs = folder.folders.map (childFolder)->
|
|
||||||
return (callback)->
|
|
||||||
projectEntityHandler.addFolder newProject, desFolder._id, childFolder.name, (err, newFolder)->
|
|
||||||
copyFolder childFolder, newFolder, callback
|
|
||||||
jobs.push (cb)->
|
|
||||||
copyDocs folder, desFolder, cb
|
|
||||||
jobs.push (cb)->
|
|
||||||
copyFiles folder, desFolder, cb
|
|
||||||
|
|
||||||
async.series jobs, callback
|
_copyFolderRecursivly: (newProject_id, originalProject_id, originalRootDoc, originalFolder, desFolder, docContents, callback)->
|
||||||
|
ProjectGetter.getProject newProject_id, {rootFolder:true, name:true}, (err, newProject)->
|
||||||
|
if err?
|
||||||
|
logger.err project_id:newProject_id, "could not get project"
|
||||||
|
return cb(err)
|
||||||
|
|
||||||
copyFolder originalProject.rootFolder[0], newProject.rootFolder[0], ->
|
jobs = originalFolder.folders.map (childFolder)->
|
||||||
callback(err, newProject)
|
return (cb)->
|
||||||
|
projectEntityHandler.addFolderWithProject newProject, desFolder?._id, childFolder.name, (err, newFolder)->
|
||||||
|
return cb(err) if err?
|
||||||
|
ProjectDuplicator._copyFolderRecursivly newProject_id, originalProject_id, originalRootDoc, childFolder, newFolder, docContents, cb
|
||||||
|
|
||||||
|
jobs.push (cb)->
|
||||||
|
ProjectDuplicator._copyFiles newProject, originalProject_id, originalFolder, desFolder, cb
|
||||||
|
jobs.push (cb)->
|
||||||
|
ProjectDuplicator._copyDocs newProject, originalRootDoc, originalFolder, desFolder, docContents, cb
|
||||||
|
|
||||||
|
async.series jobs, callback
|
||||||
|
|
||||||
|
duplicate: (owner, originalProject_id, newProjectName, callback)->
|
||||||
|
|
||||||
|
jobs =
|
||||||
|
flush: (cb)->
|
||||||
|
DocumentUpdaterHandler.flushProjectToMongo originalProject_id, cb
|
||||||
|
originalProject: (cb)->
|
||||||
|
ProjectGetter.getProject originalProject_id, {compiler:true, rootFolder:true, rootDoc_id:true}, cb
|
||||||
|
newProject: (cb)->
|
||||||
|
projectCreationHandler.createBlankProject owner._id, newProjectName, cb
|
||||||
|
originalRootDoc: (cb)->
|
||||||
|
projectLocator.findRootDoc {project_id:originalProject_id}, cb
|
||||||
|
docContentsArray: (cb)->
|
||||||
|
DocstoreManager.getAllDocs originalProject_id, cb
|
||||||
|
|
||||||
|
async.series jobs, (err, results)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, originalProject_id:originalProject_id, "error duplicating project"
|
||||||
|
return callback(err)
|
||||||
|
{originalProject, newProject, originalRootDoc, docContentsArray} = results
|
||||||
|
|
||||||
|
originalRootDoc = originalRootDoc[0]
|
||||||
|
|
||||||
|
docContents = {}
|
||||||
|
for docContent in docContentsArray
|
||||||
|
docContents[docContent._id] = docContent
|
||||||
|
|
||||||
|
projectOptionsHandler.setCompiler newProject._id, originalProject.compiler, ->
|
||||||
|
|
||||||
|
ProjectDuplicator._copyFolderRecursivly newProject._id, originalProject_id, originalRootDoc, originalProject.rootFolder[0], newProject.rootFolder[0], docContents, ->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, originalProject_id:originalProject_id, newProjectName:newProjectName, "error cloning project"
|
||||||
|
callback(err, newProject)
|
||||||
|
|
|
@ -26,6 +26,8 @@ module.exports = ProjectEditorHandler =
|
||||||
dropbox:false
|
dropbox:false
|
||||||
compileTimeout: 60
|
compileTimeout: 60
|
||||||
compileGroup:"standard"
|
compileGroup:"standard"
|
||||||
|
templates: false
|
||||||
|
references: false
|
||||||
|
|
||||||
if project.owner_ref.features?
|
if project.owner_ref.features?
|
||||||
if project.owner_ref.features.collaborators?
|
if project.owner_ref.features.collaborators?
|
||||||
|
@ -37,7 +39,11 @@ module.exports = ProjectEditorHandler =
|
||||||
if project.owner_ref.features.compileTimeout?
|
if project.owner_ref.features.compileTimeout?
|
||||||
result.features.compileTimeout = project.owner_ref.features.compileTimeout
|
result.features.compileTimeout = project.owner_ref.features.compileTimeout
|
||||||
if project.owner_ref.features.compileGroup?
|
if project.owner_ref.features.compileGroup?
|
||||||
result.features.compileGroup = project.owner_ref.features.compileGroup
|
result.features.compileGroup = project.owner_ref.features.compileGroup
|
||||||
|
if project.owner_ref.features.templates?
|
||||||
|
result.features.templates = project.owner_ref.features.templates
|
||||||
|
if project.owner_ref.features.references?
|
||||||
|
result.features.references = project.owner_ref.features.references
|
||||||
|
|
||||||
|
|
||||||
result.owner = @buildUserModelView project.owner_ref, "owner"
|
result.owner = @buildUserModelView project.owner_ref, "owner"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
Project = require('../../models/Project').Project
|
Project = require('../../models/Project').Project
|
||||||
|
settings = require "settings-sharelatex"
|
||||||
Doc = require('../../models/Doc').Doc
|
Doc = require('../../models/Doc').Doc
|
||||||
Folder = require('../../models/Folder').Folder
|
Folder = require('../../models/Folder').Folder
|
||||||
File = require('../../models/File').File
|
File = require('../../models/File').File
|
||||||
|
@ -75,23 +76,21 @@ module.exports = ProjectEntityHandler =
|
||||||
documentUpdaterHandler = require('../../Features/DocumentUpdater/DocumentUpdaterHandler')
|
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?
|
||||||
Project.findById project_id, (error, project) ->
|
ProjectGetter.getProject project_id, {name:true}, (error, project) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
requests = []
|
requests = []
|
||||||
self.getAllDocs project_id, (error, docs) ->
|
self.getAllDocs project_id, (error, docs) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
for docPath, doc of docs
|
for docPath, doc of docs
|
||||||
do (docPath, doc) ->
|
do (docPath, doc) ->
|
||||||
requests.push (callback) ->
|
requests.push (cb) ->
|
||||||
tpdsUpdateSender.addDoc {project_id:project_id, doc_id:doc._id, path:docPath, project_name:project.name, rev:doc.rev||0},
|
tpdsUpdateSender.addDoc {project_id:project_id, doc_id:doc._id, path:docPath, project_name:project.name, rev:doc.rev||0}, cb
|
||||||
callback
|
|
||||||
self.getAllFiles project_id, (error, files) ->
|
self.getAllFiles project_id, (error, files) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
for filePath, file of files
|
for filePath, file of files
|
||||||
do (filePath, file) ->
|
do (filePath, file) ->
|
||||||
requests.push (callback) ->
|
requests.push (cb) ->
|
||||||
tpdsUpdateSender.addFile {project_id:project_id, file_id:file._id, path:filePath, project_name:project.name, rev:file.rev},
|
tpdsUpdateSender.addFile {project_id:project_id, file_id:file._id, path:filePath, project_name:project.name, rev:file.rev}, cb
|
||||||
callback
|
|
||||||
async.series requests, (err) ->
|
async.series requests, (err) ->
|
||||||
logger.log project_id:project_id, "finished flushing project to tpds"
|
logger.log project_id:project_id, "finished flushing project to tpds"
|
||||||
callback(err)
|
callback(err)
|
||||||
|
@ -110,27 +109,36 @@ module.exports = ProjectEntityHandler =
|
||||||
options = {}
|
options = {}
|
||||||
DocstoreManager.getDoc project_id, doc_id, options, callback
|
DocstoreManager.getDoc project_id, doc_id, options, callback
|
||||||
|
|
||||||
addDoc: (project_or_id, folder_id, docName, docLines, callback = (error, doc, folder_id) ->)=>
|
|
||||||
Project.getProject project_or_id, "", (err, project) ->
|
addDoc: (project_id, folder_id, docName, docLines, callback = (error, doc, folder_id) ->)=>
|
||||||
logger.log project: project._id, folder_id: folder_id, doc_name: docName, "adding doc"
|
ProjectGetter.getProjectWithOnlyFolders project_id, (err, project) ->
|
||||||
return callback(err) if err?
|
if err?
|
||||||
confirmFolder project, folder_id, (folder_id)=>
|
logger.err project_id:project_id, err:err, "error getting project for add doc"
|
||||||
doc = new Doc name: docName
|
return callback(err)
|
||||||
# Put doc in docstore first, so that if it errors, we don't have a doc_id in the project
|
ProjectEntityHandler.addDocWithProject project, folder_id, docName, docLines, callback
|
||||||
# which hasn't been created in docstore.
|
|
||||||
DocstoreManager.updateDoc project._id.toString(), doc._id.toString(), docLines, (err, modified, rev) ->
|
addDocWithProject: (project, folder_id, docName, docLines, callback = (error, doc, folder_id) ->)=>
|
||||||
return callback(err) if err?
|
project_id = project._id
|
||||||
Project.putElement project._id, folder_id, doc, "doc", (err, result)=>
|
logger.log project_id: project_id, folder_id: folder_id, doc_name: docName, "adding doc to project with project"
|
||||||
return callback(err) if err?
|
confirmFolder project, folder_id, (folder_id)=>
|
||||||
tpdsUpdateSender.addDoc {
|
doc = new Doc name: docName
|
||||||
project_id: project._id,
|
# Put doc in docstore first, so that if it errors, we don't have a doc_id in the project
|
||||||
doc_id: doc._id
|
# which hasn't been created in docstore.
|
||||||
path: result.path.fileSystem,
|
DocstoreManager.updateDoc project_id.toString(), doc._id.toString(), docLines, (err, modified, rev) ->
|
||||||
project_name: project.name,
|
return callback(err) if err?
|
||||||
rev: 0
|
|
||||||
}, (err) ->
|
ProjectEntityHandler._putElement project, folder_id, doc, "doc", (err, result)=>
|
||||||
return callback(err) if err?
|
return callback(err) if err?
|
||||||
callback(null, doc, folder_id)
|
tpdsUpdateSender.addDoc {
|
||||||
|
project_id: project_id,
|
||||||
|
doc_id: doc?._id
|
||||||
|
path: result?.path?.fileSystem,
|
||||||
|
project_name: project.name,
|
||||||
|
rev: 0
|
||||||
|
}, (err) ->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, "error adding doc to tpdsworker, contining anyway"
|
||||||
|
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
|
||||||
|
@ -139,22 +147,34 @@ 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_or_id, folder_id, fileName, path, callback = (error, fileRef, folder_id) ->)->
|
addFile: (project_id, folder_id, fileName, path, callback = (error, fileRef, folder_id) ->)->
|
||||||
ProjectGetter.getProjectWithOnlyFolders project_or_id, (err, project) ->
|
ProjectGetter.getProjectWithOnlyFolders project_id, (err, project) ->
|
||||||
logger.log project_id: project._id, folder_id: folder_id, file_name: fileName, path:path, "adding file"
|
if err?
|
||||||
return callback(err) if err?
|
logger.err project_id:project_id, err:err, "error getting project for add file"
|
||||||
confirmFolder project, folder_id, (folder_id)->
|
return callback(err)
|
||||||
fileRef = new File name : fileName
|
ProjectEntityHandler.addFileWithProject project, folder_id, fileName, path, callback
|
||||||
FileStoreHandler.uploadFileFromDisk project._id, fileRef._id, path, (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"
|
|
||||||
return callback(err)
|
|
||||||
Project.putElement project._id, folder_id, fileRef, "file", (err, result)=>
|
|
||||||
tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:result.path.fileSystem, project_name:project.name, rev:fileRef.rev}, ->
|
|
||||||
callback(err, fileRef, folder_id)
|
|
||||||
|
|
||||||
replaceFile: (project_or_id, file_id, fsPath, callback)->
|
addFileWithProject: (project, folder_id, fileName, path, callback = (error, fileRef, folder_id) ->)->
|
||||||
Project.getProject project_or_id, "", (err, project) ->
|
project_id = project._id
|
||||||
|
logger.log project_id: project._id, folder_id: folder_id, file_name: fileName, path:path, "adding file"
|
||||||
|
return callback(err) if err?
|
||||||
|
confirmFolder project, folder_id, (folder_id)->
|
||||||
|
fileRef = new File name : fileName
|
||||||
|
FileStoreHandler.uploadFileFromDisk project._id, fileRef._id, path, (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"
|
||||||
|
return callback(err)
|
||||||
|
ProjectEntityHandler._putElement project, folder_id, fileRef, "file", (err, result)=>
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id: project._id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error adding file with project"
|
||||||
|
return callback(err)
|
||||||
|
tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:result?.path?.fileSystem, project_name:project.name, rev:fileRef.rev}, (err)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id: project._id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error sending file to tpdsworker"
|
||||||
|
callback(null, fileRef, folder_id)
|
||||||
|
|
||||||
|
replaceFile: (project_id, file_id, fsPath, callback)->
|
||||||
|
ProjectGetter.getProject project_id, {name:true}, (err, project) ->
|
||||||
return callback(err) if err?
|
return callback(err) if err?
|
||||||
findOpts =
|
findOpts =
|
||||||
project_id:project._id
|
project_id:project._id
|
||||||
|
@ -182,21 +202,36 @@ module.exports = ProjectEntityHandler =
|
||||||
Project.update conditons, update, {}, (err, second)->
|
Project.update conditons, update, {}, (err, second)->
|
||||||
callback()
|
callback()
|
||||||
|
|
||||||
copyFileFromExistingProject: (project_or_id, folder_id, originalProject_id, origonalFileRef, callback = (error, fileRef, folder_id) ->)->
|
copyFileFromExistingProject: (project_id, folder_id, originalProject_id, origonalFileRef, callback = (error, fileRef, folder_id) ->)->
|
||||||
Project.getProject project_or_id, "", (err, project) ->
|
logger.log project_id:project_id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "copying file in s3"
|
||||||
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) ->
|
||||||
return callback(err) if err?
|
if err?
|
||||||
confirmFolder project, folder_id, (folder_id)=>
|
logger.err project_id:project_id, err:err, "error getting project for copy file from existing project"
|
||||||
if !origonalFileRef?
|
return callback(err)
|
||||||
logger.err project_id:project._id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "file trying to copy is null"
|
ProjectEntityHandler.copyFileFromExistingProjectWithProject project, folder_id, originalProject_id, origonalFileRef, callback
|
||||||
return callback()
|
|
||||||
fileRef = new File name : origonalFileRef.name
|
|
||||||
FileStoreHandler.copyFile originalProject_id, origonalFileRef._id, project._id, fileRef._id, (err)->
|
copyFileFromExistingProjectWithProject: (project, folder_id, originalProject_id, origonalFileRef, callback = (error, fileRef, folder_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"
|
||||||
|
return callback(err) if err?
|
||||||
|
confirmFolder project, folder_id, (folder_id)=>
|
||||||
|
if !origonalFileRef?
|
||||||
|
logger.err project_id:project._id, folder_id:folder_id, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "file trying to copy is null"
|
||||||
|
return callback()
|
||||||
|
fileRef = new File name : origonalFileRef.name
|
||||||
|
FileStoreHandler.copyFile originalProject_id, origonalFileRef._id, project._id, fileRef._id, (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"
|
||||||
|
return callback(err)
|
||||||
|
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, originalProject_id:originalProject_id, origonalFileRef:origonalFileRef, "error coping file in s3"
|
logger.err err:err, project_id:project._id, folder_id:folder_id, "error putting element as part of copy"
|
||||||
Project.putElement project._id, folder_id, fileRef, "file", (err, result)=>
|
return callback(err)
|
||||||
tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:result.path.fileSystem, rev:fileRef.rev, project_name:project.name}, (error) ->
|
tpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:result?.path?.fileSystem, rev:fileRef.rev, project_name:project.name}, (err) ->
|
||||||
callback(error, fileRef, folder_id)
|
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"
|
||||||
|
callback(null, fileRef, folder_id)
|
||||||
|
|
||||||
mkdirp: (project_id, path, callback = (err, newlyCreatedFolders, lastFolderInPath)->)->
|
mkdirp: (project_id, path, callback = (err, newlyCreatedFolders, lastFolderInPath)->)->
|
||||||
self = @
|
self = @
|
||||||
|
@ -204,7 +239,7 @@ module.exports = ProjectEntityHandler =
|
||||||
folders = _.select folders, (folder)->
|
folders = _.select folders, (folder)->
|
||||||
return folder.length != 0
|
return folder.length != 0
|
||||||
|
|
||||||
ProjectGetter.getProjectWithoutDocLines project_id, (err, project)=>
|
ProjectGetter.getProjectWithOnlyFolders project_id, (err, project)=>
|
||||||
if path == '/'
|
if path == '/'
|
||||||
logger.log project_id: project._id, "mkdir is only trying to make path of / so sending back root folder"
|
logger.log project_id: project._id, "mkdir is only trying to make path of / so sending back root folder"
|
||||||
return callback(null, [], project.rootFolder[0])
|
return callback(null, [], project.rootFolder[0])
|
||||||
|
@ -217,7 +252,7 @@ module.exports = ProjectEntityHandler =
|
||||||
if parentFolder?
|
if parentFolder?
|
||||||
parentFolder_id = parentFolder._id
|
parentFolder_id = parentFolder._id
|
||||||
builtUpPath = "#{builtUpPath}/#{folderName}"
|
builtUpPath = "#{builtUpPath}/#{folderName}"
|
||||||
projectLocator.findElementByPath project_id, builtUpPath, (err, foundFolder)=>
|
projectLocator.findElementByPath project, builtUpPath, (err, foundFolder)=>
|
||||||
if !foundFolder?
|
if !foundFolder?
|
||||||
logger.log path:path, project_id:project._id, folderName:folderName, "making folder from mkdirp"
|
logger.log path:path, project_id:project._id, folderName:folderName, "making folder from mkdirp"
|
||||||
@addFolder project_id, parentFolder_id, folderName, (err, newFolder, parentFolder_id)->
|
@addFolder project_id, parentFolder_id, folderName, (err, newFolder, parentFolder_id)->
|
||||||
|
@ -235,16 +270,23 @@ module.exports = ProjectEntityHandler =
|
||||||
folders = _.select folders, (folder)->
|
folders = _.select folders, (folder)->
|
||||||
!folder.filterOut
|
!folder.filterOut
|
||||||
callback(null, folders, lastFolder)
|
callback(null, folders, lastFolder)
|
||||||
|
|
||||||
addFolder: (project_or_id, parentFolder_id, folderName, callback) ->
|
addFolder: (project_id, parentFolder_id, folderName, callback) ->
|
||||||
folder = new Folder name: folderName
|
ProjectGetter.getProjectWithOnlyFolders project_id, (err, project)=>
|
||||||
Project.getProject project_or_id, "", (err, project) ->
|
if err?
|
||||||
return callback(err) if err?
|
logger.err project_id:project_id, err:err, "error getting project for add folder"
|
||||||
confirmFolder project, parentFolder_id, (parentFolder_id)=>
|
return callback(err)
|
||||||
logger.log project: project_or_id, parentFolder_id:parentFolder_id, folderName:folderName, "new folder added"
|
ProjectEntityHandler.addFolderWithProject project, parentFolder_id, folderName, callback
|
||||||
Project.putElement project._id, parentFolder_id, folder, "folder", (err, result)=>
|
|
||||||
if callback?
|
addFolderWithProject: (project, parentFolder_id, folderName, callback = (err, folder, parentFolder_id)->) ->
|
||||||
callback(err, folder, parentFolder_id)
|
confirmFolder project, parentFolder_id, (parentFolder_id)=>
|
||||||
|
folder = new Folder name: folderName
|
||||||
|
logger.log project: project._id, parentFolder_id:parentFolder_id, folderName:folderName, "adding new folder"
|
||||||
|
ProjectEntityHandler._putElement project, parentFolder_id, folder, "folder", (err, result)=>
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id:project._id, "error adding folder to project"
|
||||||
|
return callback(err)
|
||||||
|
callback(err, folder, parentFolder_id)
|
||||||
|
|
||||||
updateDocLines : (project_id, doc_id, lines, callback = (error) ->)->
|
updateDocLines : (project_id, doc_id, lines, callback = (error) ->)->
|
||||||
ProjectGetter.getProjectWithoutDocLines project_id, (err, project)->
|
ProjectGetter.getProjectWithoutDocLines project_id, (err, project)->
|
||||||
|
@ -281,7 +323,7 @@ module.exports = ProjectEntityHandler =
|
||||||
logger.err err: "No entityType set", project_id: project_id, entity_id: entity_id
|
logger.err err: "No entityType set", project_id: project_id, entity_id: entity_id
|
||||||
return callback("No entityType set")
|
return callback("No entityType set")
|
||||||
entityType = entityType.toLowerCase()
|
entityType = entityType.toLowerCase()
|
||||||
Project.findById project_id, (err, project)=>
|
ProjectGetter.getProject project_id, {rootFolder:true, name:true}, (err, project)=>
|
||||||
return callback(err) if err?
|
return callback(err) if err?
|
||||||
projectLocator.findElement {project:project, element_id:entity_id, type:entityType}, (err, entity, path)->
|
projectLocator.findElement {project:project, element_id:entity_id, type:entityType}, (err, entity, path)->
|
||||||
return callback(err) if err?
|
return callback(err) if err?
|
||||||
|
@ -302,7 +344,7 @@ module.exports = ProjectEntityHandler =
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
self._removeElementFromMongoArray Project, project_id, path.mongo, (err)->
|
self._removeElementFromMongoArray Project, project_id, path.mongo, (err)->
|
||||||
return callback(err) if err?
|
return callback(err) if err?
|
||||||
Project.putElement project_id, destinationFolder_id, entity, entityType, (err, result)->
|
ProjectEntityHandler._putElement project, destinationFolder_id, entity, entityType, (err, result)->
|
||||||
return callback(err) if err?
|
return callback(err) if err?
|
||||||
opts =
|
opts =
|
||||||
project_id:project_id
|
project_id:project_id
|
||||||
|
@ -319,7 +361,7 @@ module.exports = ProjectEntityHandler =
|
||||||
logger.err err: "No entityType set", project_id: project_id, entity_id: entity_id
|
logger.err err: "No entityType set", project_id: project_id, entity_id: entity_id
|
||||||
return callback("No entityType set")
|
return callback("No entityType set")
|
||||||
entityType = entityType.toLowerCase()
|
entityType = entityType.toLowerCase()
|
||||||
Project.findById project_id, (err, project)=>
|
ProjectGetter.getProject project_id, {name:true, rootFolder:true}, (err, project)=>
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
projectLocator.findElement {project: project, element_id: entity_id, type: entityType}, (error, entity, path)=>
|
projectLocator.findElement {project: project, element_id: entity_id, type: entityType}, (error, entity, path)=>
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
|
@ -338,7 +380,7 @@ module.exports = ProjectEntityHandler =
|
||||||
logger.err err: "No entityType set", project_id: project_id, entity_id: entity_id
|
logger.err err: "No entityType set", project_id: project_id, entity_id: entity_id
|
||||||
return callback("No entityType set")
|
return callback("No entityType set")
|
||||||
entityType = entityType.toLowerCase()
|
entityType = entityType.toLowerCase()
|
||||||
Project.findById project_id, (err, project)=>
|
ProjectGetter.getProject project_id, {rootFolder:true, name:true}, (err, project)=>
|
||||||
projectLocator.findElement {project:project, element_id:entity_id, type:entityType}, (err, entity, path, folder)=>
|
projectLocator.findElement {project:project, element_id:entity_id, type:entityType}, (err, entity, path, folder)=>
|
||||||
if err?
|
if err?
|
||||||
return callback err
|
return callback err
|
||||||
|
@ -426,6 +468,72 @@ module.exports = ProjectEntityHandler =
|
||||||
}
|
}
|
||||||
}, {}, callback
|
}, {}, callback
|
||||||
|
|
||||||
|
|
||||||
|
_countElements : (project, callback)->
|
||||||
|
|
||||||
|
countFolder = (folder, cb = (err, count)->)->
|
||||||
|
|
||||||
|
jobs = _.map folder?.folders, (folder)->
|
||||||
|
(asyncCb)-> countFolder folder, asyncCb
|
||||||
|
|
||||||
|
async.series jobs, (err, subfolderCounts)->
|
||||||
|
total = 0
|
||||||
|
|
||||||
|
if subfolderCounts?.length > 0
|
||||||
|
total = _.reduce subfolderCounts, (a, b)-> return a + b
|
||||||
|
if folder?.folders?.length?
|
||||||
|
total += folder?.folders?.length
|
||||||
|
if folder?.docs?.length?
|
||||||
|
total += folder?.docs?.length
|
||||||
|
if folder?.fileRefs?.length?
|
||||||
|
total += folder?.fileRefs?.length
|
||||||
|
cb(null, total)
|
||||||
|
|
||||||
|
countFolder project.rootFolder[0], callback
|
||||||
|
|
||||||
|
_putElement: (project, folder_id, element, type, callback = (err, path)->)->
|
||||||
|
|
||||||
|
sanitizeTypeOfElement = (elementType)->
|
||||||
|
lastChar = elementType.slice -1
|
||||||
|
if lastChar != "s"
|
||||||
|
elementType +="s"
|
||||||
|
if elementType == "files"
|
||||||
|
elementType = "fileRefs"
|
||||||
|
return elementType
|
||||||
|
|
||||||
|
if !element?
|
||||||
|
e = new Error("no element passed to be inserted")
|
||||||
|
logger.err project_id:project._id, folder_id:folder_id, element:element, type:type, "failed trying to insert element as it was null"
|
||||||
|
return callback(e)
|
||||||
|
type = sanitizeTypeOfElement type
|
||||||
|
|
||||||
|
if !folder_id?
|
||||||
|
folder_id = project.rootFolder[0]._id
|
||||||
|
ProjectEntityHandler._countElements project, (err, count)->
|
||||||
|
if count > settings.maxEntitiesPerProject
|
||||||
|
logger.warn project_id:project._id, "project too big, stopping insertions"
|
||||||
|
return callback("project_has_to_many_files")
|
||||||
|
projectLocator.findElement {project:project, element_id:folder_id, type:"folders"}, (err, folder, path)=>
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id:project._id, folder_id:folder_id, type:type, element:element, "error finding folder for _putElement"
|
||||||
|
return callback(err)
|
||||||
|
newPath =
|
||||||
|
fileSystem: "#{path.fileSystem}/#{element.name}"
|
||||||
|
mongo: path.mongo
|
||||||
|
logger.log project_id: project._id, element_id: element._id, fileType: type, folder_id: folder_id, "adding element to project"
|
||||||
|
id = element._id+''
|
||||||
|
element._id = require('mongoose').Types.ObjectId(id)
|
||||||
|
conditions = _id:project._id
|
||||||
|
mongopath = "#{path.mongo}.#{type}"
|
||||||
|
update = "$push":{}
|
||||||
|
update["$push"][mongopath] = element
|
||||||
|
Project.update conditions, update, {}, (err)->
|
||||||
|
if err?
|
||||||
|
logger.err err: err, project_id: project._id, 'error saving in putElement project'
|
||||||
|
return callback(err)
|
||||||
|
callback(err, {path:newPath})
|
||||||
|
|
||||||
|
|
||||||
confirmFolder = (project, folder_id, callback)->
|
confirmFolder = (project, folder_id, callback)->
|
||||||
logger.log folder_id:folder_id, project_id:project._id, "confirming folder in project"
|
logger.log folder_id:folder_id, project_id:project._id, "confirming folder in project"
|
||||||
if folder_id+'' == 'undefined'
|
if folder_id+'' == 'undefined'
|
||||||
|
|
|
@ -2,31 +2,54 @@ mongojs = require("../../infrastructure/mongojs")
|
||||||
db = mongojs.db
|
db = mongojs.db
|
||||||
ObjectId = mongojs.ObjectId
|
ObjectId = mongojs.ObjectId
|
||||||
async = require "async"
|
async = require "async"
|
||||||
|
Errors = require("../../errors")
|
||||||
|
logger = require("logger-sharelatex")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = ProjectGetter =
|
module.exports = ProjectGetter =
|
||||||
EXCLUDE_DEPTH: 8
|
EXCLUDE_DEPTH: 8
|
||||||
|
|
||||||
|
|
||||||
getProjectWithoutDocLines: (project_id, callback=(error, project) ->) ->
|
getProjectWithoutDocLines: (project_id, callback=(error, project) ->) ->
|
||||||
excludes = {}
|
excludes = {}
|
||||||
for i in [1..@EXCLUDE_DEPTH]
|
for i in [1..ProjectGetter.EXCLUDE_DEPTH]
|
||||||
excludes["rootFolder#{Array(i).join(".folder")}.docs.lines"] = 0
|
excludes["rootFolder#{Array(i).join(".folder")}.docs.lines"] = 0
|
||||||
db.projects.find _id: ObjectId(project_id.toString()), excludes, (error, projects = []) ->
|
db.projects.find _id: ObjectId(project_id.toString()), excludes, (error, projects = []) ->
|
||||||
callback error, projects[0]
|
callback error, projects[0]
|
||||||
|
|
||||||
getProjectWithOnlyFolders: (project_id, callback=(error, project) ->) ->
|
getProjectWithOnlyFolders: (project_id, callback=(error, project) ->) ->
|
||||||
excludes = {}
|
excludes = {}
|
||||||
for i in [1..@EXCLUDE_DEPTH]
|
for i in [1..ProjectGetter.EXCLUDE_DEPTH]
|
||||||
excludes["rootFolder#{Array(i).join(".folder")}.docs"] = 0
|
excludes["rootFolder#{Array(i).join(".folder")}.docs"] = 0
|
||||||
excludes["rootFolder#{Array(i).join(".folder")}.fileRefs"] = 0
|
excludes["rootFolder#{Array(i).join(".folder")}.fileRefs"] = 0
|
||||||
db.projects.find _id: ObjectId(project_id.toString()), excludes, (error, projects = []) ->
|
db.projects.find _id: ObjectId(project_id.toString()), excludes, (error, projects = []) ->
|
||||||
callback error, projects[0]
|
callback error, projects[0]
|
||||||
|
|
||||||
|
|
||||||
getProject: (query, projection, callback = (error, project) ->) ->
|
getProject: (query, projection, callback = (error, project) ->) ->
|
||||||
|
if !query?
|
||||||
|
return callback("no query provided")
|
||||||
|
|
||||||
|
if typeof(projection) == "function"
|
||||||
|
callback = projection
|
||||||
|
|
||||||
if typeof query == "string"
|
if typeof query == "string"
|
||||||
query = _id: ObjectId(query)
|
query = _id: ObjectId(query)
|
||||||
else if query instanceof ObjectId
|
else if query instanceof ObjectId
|
||||||
query = _id: query
|
query = _id: query
|
||||||
db.projects.findOne query, projection, callback
|
else if query?.toString().length == 24 # sometimes mongoose ids are hard to identify, this will catch them
|
||||||
|
query = _id: ObjectId(query.toString())
|
||||||
|
else
|
||||||
|
err = new Error("malformed get request")
|
||||||
|
logger.log query:query, err:err, type:typeof(query), "malformed get request"
|
||||||
|
return callback(err)
|
||||||
|
|
||||||
|
db.projects.find query, projection, (err, project)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, query:query, projection:projection, "error getting project"
|
||||||
|
return callback(err)
|
||||||
|
callback(null, project?[0])
|
||||||
|
|
||||||
populateProjectWithUsers: (project, callback=(error, project) ->) ->
|
populateProjectWithUsers: (project, callback=(error, project) ->) ->
|
||||||
# eventually this should be in a UserGetter.getUser module
|
# eventually this should be in a UserGetter.getUser module
|
||||||
|
|
|
@ -1,11 +1,16 @@
|
||||||
Project = require('../../models/Project').Project
|
Project = require('../../models/Project').Project
|
||||||
|
ProjectGetter = require("./ProjectGetter")
|
||||||
Errors = require "../../errors"
|
Errors = require "../../errors"
|
||||||
_ = require('underscore')
|
_ = require('underscore')
|
||||||
logger = require('logger-sharelatex')
|
logger = require('logger-sharelatex')
|
||||||
async = require('async')
|
async = require('async')
|
||||||
|
|
||||||
module.exports =
|
module.exports = ProjectLocator =
|
||||||
findElement: (options, callback = (err, element, path, parentFolder)->)->
|
findElement: (options, _callback = (err, element, path, parentFolder)->)->
|
||||||
|
callback = (args...) ->
|
||||||
|
_callback(args...)
|
||||||
|
_callback = () ->
|
||||||
|
|
||||||
{project, project_id, element_id, type} = options
|
{project, project_id, element_id, type} = options
|
||||||
elementType = sanitizeTypeOfElement type
|
elementType = sanitizeTypeOfElement type
|
||||||
|
|
||||||
|
@ -46,7 +51,7 @@ module.exports =
|
||||||
if project?
|
if project?
|
||||||
startSearch(project)
|
startSearch(project)
|
||||||
else
|
else
|
||||||
Project.findById project_id, (err, project)->
|
ProjectGetter.getProject project_id, {rootFolder:true, rootDoc_id:true}, (err, project)->
|
||||||
return callback(err) if err?
|
return callback(err) if err?
|
||||||
if !project?
|
if !project?
|
||||||
return callback(new Errors.NotFoundError("project not found"))
|
return callback(new Errors.NotFoundError("project not found"))
|
||||||
|
@ -62,8 +67,12 @@ module.exports =
|
||||||
if project?
|
if project?
|
||||||
getRootDoc project
|
getRootDoc project
|
||||||
else
|
else
|
||||||
Project.findById project_id, (err, project)->
|
ProjectGetter.getProject project_id, {rootFolder:true, rootDoc_id:true}, (err, project)->
|
||||||
getRootDoc project
|
if err?
|
||||||
|
logger.err err:err, "error getting project"
|
||||||
|
return callback(err)
|
||||||
|
else
|
||||||
|
getRootDoc project
|
||||||
|
|
||||||
findElementByPath: (project_or_id, needlePath, callback = (err, foundEntity)->)->
|
findElementByPath: (project_or_id, needlePath, callback = (err, foundEntity)->)->
|
||||||
|
|
||||||
|
@ -122,11 +131,11 @@ module.exports =
|
||||||
async.waterfall jobs, callback
|
async.waterfall jobs, callback
|
||||||
|
|
||||||
findUsersProjectByName: (user_id, projectName, callback)->
|
findUsersProjectByName: (user_id, projectName, callback)->
|
||||||
Project.findAllUsersProjects user_id, 'name', (err, projects, collabertions=[])->
|
Project.findAllUsersProjects user_id, 'name archived', (err, projects, collabertions=[])->
|
||||||
projects = projects.concat(collabertions)
|
projects = projects.concat(collabertions)
|
||||||
projectName = projectName.toLowerCase()
|
projectName = projectName.toLowerCase()
|
||||||
project = _.find projects, (project)->
|
project = _.find projects, (project)->
|
||||||
project.name.toLowerCase() == projectName
|
project.name.toLowerCase() == projectName and project.archived != true
|
||||||
logger.log user_id:user_id, projectName:projectName, totalProjects:projects.length, project:project, "looking for project by name"
|
logger.log user_id:user_id, projectName:projectName, totalProjects:projects.length, project:project, "looking for project by name"
|
||||||
callback(null, project)
|
callback(null, project)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
logger = require('logger-sharelatex')
|
||||||
|
ReferencesHandler = require('./ReferencesHandler')
|
||||||
|
settings = require('settings-sharelatex')
|
||||||
|
EditorRealTimeController = require("../Editor/EditorRealTimeController")
|
||||||
|
|
||||||
|
module.exports = ReferencesController =
|
||||||
|
|
||||||
|
|
||||||
|
index: (req, res) ->
|
||||||
|
projectId = req.params.Project_id
|
||||||
|
shouldBroadcast = req.body.shouldBroadcast
|
||||||
|
docIds = req.body.docIds
|
||||||
|
if (!docIds or !(docIds instanceof Array))
|
||||||
|
logger.err {projectId, docIds}, "docIds is not valid, should be either Array or String 'ALL'"
|
||||||
|
return res.sendStatus 400
|
||||||
|
logger.log {projectId, docIds: docIds}, "index references for project"
|
||||||
|
ReferencesHandler.index projectId, docIds, (err, data) ->
|
||||||
|
if err
|
||||||
|
logger.err {err, projectId}, "error indexing all references"
|
||||||
|
return res.sendStatus 500
|
||||||
|
ReferencesController._handleIndexResponse(req, res, projectId, shouldBroadcast, data)
|
||||||
|
|
||||||
|
indexAll: (req, res) ->
|
||||||
|
projectId = req.params.Project_id
|
||||||
|
shouldBroadcast = req.body.shouldBroadcast
|
||||||
|
logger.log {projectId}, "index all references for project"
|
||||||
|
ReferencesHandler.indexAll projectId, (err, data) ->
|
||||||
|
if err
|
||||||
|
logger.err {err, projectId}, "error indexing all references"
|
||||||
|
return res.sendStatus 500
|
||||||
|
ReferencesController._handleIndexResponse(req, res, projectId, shouldBroadcast, data)
|
||||||
|
|
||||||
|
_handleIndexResponse: (req, res, projectId, shouldBroadcast, data) ->
|
||||||
|
if shouldBroadcast
|
||||||
|
logger.log {projectId}, "emitting new references keys to connected clients"
|
||||||
|
EditorRealTimeController.emitToRoom projectId, 'references:keys:updated', data.keys
|
||||||
|
return res.json data
|
|
@ -0,0 +1,85 @@
|
||||||
|
logger = require("logger-sharelatex")
|
||||||
|
request = require("request")
|
||||||
|
settings = require("settings-sharelatex")
|
||||||
|
Project = require("../../models/Project").Project
|
||||||
|
DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
|
||||||
|
U = require('underscore')
|
||||||
|
Async = require('async')
|
||||||
|
|
||||||
|
oneMinInMs = 60 * 1000
|
||||||
|
fiveMinsInMs = oneMinInMs * 5
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = ReferencesHandler =
|
||||||
|
|
||||||
|
_buildDocUrl: (projectId, docId) ->
|
||||||
|
"#{settings.apis.docstore.url}/project/#{projectId}/doc/#{docId}/raw"
|
||||||
|
|
||||||
|
_findBibDocIds: (project) ->
|
||||||
|
ids = []
|
||||||
|
|
||||||
|
_process = (folder) ->
|
||||||
|
folder.docs.forEach (doc) ->
|
||||||
|
if doc?.name?.match(/^.*\.bib$/)
|
||||||
|
ids.push(doc._id)
|
||||||
|
folder.folders.forEach (folder) ->
|
||||||
|
_process(folder)
|
||||||
|
|
||||||
|
project.rootFolder.forEach (rootFolder) ->
|
||||||
|
_process(rootFolder)
|
||||||
|
|
||||||
|
return ids
|
||||||
|
|
||||||
|
_isFullIndex: (project, callback = (err, result) ->) ->
|
||||||
|
owner = project.owner_ref
|
||||||
|
callback(null, owner.features.references == true)
|
||||||
|
|
||||||
|
indexAll: (projectId, callback=(err, data)->) ->
|
||||||
|
Project.findPopulatedById projectId, (err, project) ->
|
||||||
|
if err
|
||||||
|
logger.err {err, projectId}, "error finding project"
|
||||||
|
return callback(err)
|
||||||
|
logger.log {projectId}, "indexing all bib files in project"
|
||||||
|
docIds = ReferencesHandler._findBibDocIds(project)
|
||||||
|
ReferencesHandler._doIndexOperation(projectId, project, docIds, callback)
|
||||||
|
|
||||||
|
index: (projectId, docIds, callback=(err, data)->) ->
|
||||||
|
Project.findPopulatedById projectId, (err, project) ->
|
||||||
|
if err
|
||||||
|
logger.err {err, projectId}, "error finding project"
|
||||||
|
return callback(err)
|
||||||
|
ReferencesHandler._doIndexOperation(projectId, project, docIds, callback)
|
||||||
|
|
||||||
|
_doIndexOperation: (projectId, project, docIds, callback) ->
|
||||||
|
ReferencesHandler._isFullIndex project, (err, isFullIndex) ->
|
||||||
|
if err
|
||||||
|
logger.err {err, projectId}, "error checking whether to do full index"
|
||||||
|
return callback(err)
|
||||||
|
logger.log {projectId, docIds}, 'flushing docs to mongo before calling references service'
|
||||||
|
Async.series(
|
||||||
|
docIds.map((docId) -> (cb) -> DocumentUpdaterHandler.flushDocToMongo(projectId, docId, cb)),
|
||||||
|
(err) ->
|
||||||
|
# continue
|
||||||
|
if err
|
||||||
|
logger.err {err, projectId, docIds}, "error flushing docs to mongo"
|
||||||
|
return callback(err)
|
||||||
|
bibDocUrls = docIds.map (docId) ->
|
||||||
|
ReferencesHandler._buildDocUrl projectId, docId
|
||||||
|
logger.log {projectId, isFullIndex, docIds, bibDocUrls}, "sending request to references service"
|
||||||
|
request.post {
|
||||||
|
url: "#{settings.apis.references.url}/project/#{projectId}/index"
|
||||||
|
json:
|
||||||
|
docUrls: bibDocUrls
|
||||||
|
fullIndex: isFullIndex
|
||||||
|
}, (err, res, data) ->
|
||||||
|
if err
|
||||||
|
logger.err {err, projectId}, "error communicating with references api"
|
||||||
|
return callback(err)
|
||||||
|
if 200 <= res.statusCode < 300
|
||||||
|
logger.log {projectId}, "got keys from references api"
|
||||||
|
return callback(null, data)
|
||||||
|
else
|
||||||
|
err = new Error("references api responded with non-success code: #{res.statusCode}")
|
||||||
|
logger.log {err, projectId}, "error updating references"
|
||||||
|
return callback(err)
|
||||||
|
)
|
|
@ -15,7 +15,7 @@ module.exports = RateLimiterMiddlewear =
|
||||||
###
|
###
|
||||||
rateLimit: (opts) ->
|
rateLimit: (opts) ->
|
||||||
return (req, res, next) ->
|
return (req, res, next) ->
|
||||||
if req.session.user?
|
if req.session?.user?
|
||||||
user_id = req.session.user._id
|
user_id = req.session.user._id
|
||||||
else
|
else
|
||||||
user_id = req.ip
|
user_id = req.ip
|
||||||
|
|
|
@ -12,5 +12,4 @@ module.exports = SpellingController =
|
||||||
request(url: Settings.apis.spelling.url + url, method: req.method, headers: req.headers, json: req.body, timeout:TEN_SECONDS)
|
request(url: Settings.apis.spelling.url + url, method: req.method, headers: req.headers, json: req.body, timeout:TEN_SECONDS)
|
||||||
.on "error", (error) ->
|
.on "error", (error) ->
|
||||||
logger.error err: error, "Spelling API error"
|
logger.error err: error, "Spelling API error"
|
||||||
res.sendStatus 500
|
|
||||||
.pipe(res)
|
.pipe(res)
|
||||||
|
|
|
@ -13,6 +13,7 @@ module.exports =
|
||||||
webRouter.get '/privacy_policy', HomeController.externalPage("privacy", "Privacy Policy")
|
webRouter.get '/privacy_policy', HomeController.externalPage("privacy", "Privacy Policy")
|
||||||
webRouter.get '/planned_maintenance', HomeController.externalPage("planned_maintenance", "Planned Maintenance")
|
webRouter.get '/planned_maintenance', HomeController.externalPage("planned_maintenance", "Planned Maintenance")
|
||||||
webRouter.get '/style', HomeController.externalPage("style_guide", "Style Guide")
|
webRouter.get '/style', HomeController.externalPage("style_guide", "Style Guide")
|
||||||
|
webRouter.get '/jobs', HomeController.externalPage("jobs", "Jobs")
|
||||||
|
|
||||||
webRouter.get '/dropbox', HomeController.externalPage("dropbox", "Dropbox and ShareLaTeX")
|
webRouter.get '/dropbox', HomeController.externalPage("dropbox", "Dropbox and ShareLaTeX")
|
||||||
|
|
||||||
|
|
|
@ -52,11 +52,17 @@ module.exports =
|
||||||
return callback(err) if err?
|
return callback(err) if err?
|
||||||
callback err, subscriptions.length > 0, subscriptions
|
callback err, subscriptions.length > 0, subscriptions
|
||||||
|
|
||||||
hasGroupMembersLimitReached: (user_id, callback)->
|
hasGroupMembersLimitReached: (user_id, callback = (err, limitReached, subscription)->)->
|
||||||
SubscriptionLocator.getUsersSubscription user_id, (err, subscription)->
|
SubscriptionLocator.getUsersSubscription user_id, (err, subscription)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, user_id:user_id, "error getting users subscription"
|
||||||
|
return callback(err)
|
||||||
|
if !subscription?
|
||||||
|
logger.err user_id:user_id, "no subscription found for user"
|
||||||
|
return callback("no subscription found")
|
||||||
limitReached = subscription.member_ids.length >= subscription.membersLimit
|
limitReached = subscription.member_ids.length >= subscription.membersLimit
|
||||||
logger.log user_id:user_id, limitReached:limitReached, currentTotal: subscription.member_ids.length, membersLimit: subscription.membersLimit, "checking if subscription members limit has been reached"
|
logger.log user_id:user_id, limitReached:limitReached, currentTotal: subscription.member_ids.length, membersLimit: subscription.membersLimit, "checking if subscription members limit has been reached"
|
||||||
callback(err, limitReached)
|
callback(err, limitReached, subscription)
|
||||||
|
|
||||||
getOwnerOfProject = (project_id, callback)->
|
getOwnerOfProject = (project_id, callback)->
|
||||||
Project.findById project_id, 'owner_ref', (error, project) ->
|
Project.findById project_id, 'owner_ref', (error, project) ->
|
||||||
|
|
|
@ -247,6 +247,18 @@ module.exports = RecurlyWrapper =
|
||||||
callback(error)
|
callback(error)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
extendTrial: (subscriptionId, daysUntilExpire = 7, callback)->
|
||||||
|
next_renewal_date = new Date()
|
||||||
|
next_renewal_date.setDate(next_renewal_date.getDate() + daysUntilExpire)
|
||||||
|
logger.log subscriptionId:subscriptionId, daysUntilExpire:daysUntilExpire, "Exending Free trial for user"
|
||||||
|
@apiRequest({
|
||||||
|
url : "/subscriptions/#{subscriptionId}/postpone?next_renewal_date=#{next_renewal_date}&bulk=false"
|
||||||
|
method : "put"
|
||||||
|
}, (error, response, responseBody) =>
|
||||||
|
if error?
|
||||||
|
logger.err err:error, subscriptionId:subscriptionId, daysUntilExpire:daysUntilExpire, "error exending trial"
|
||||||
|
callback(error)
|
||||||
|
)
|
||||||
|
|
||||||
_parseSubscriptionXml: (xml, callback) ->
|
_parseSubscriptionXml: (xml, callback) ->
|
||||||
@_parseXml xml, (error, data) ->
|
@_parseXml xml, (error, data) ->
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
SecurityManager = require '../../managers/SecurityManager'
|
SecurityManager = require '../../managers/SecurityManager'
|
||||||
SubscriptionHandler = require './SubscriptionHandler'
|
SubscriptionHandler = require './SubscriptionHandler'
|
||||||
PlansLocator = require("./PlansLocator")
|
PlansLocator = require("./PlansLocator")
|
||||||
SubscriptionFormatters = require("./SubscriptionFormatters")
|
|
||||||
SubscriptionViewModelBuilder = require('./SubscriptionViewModelBuilder')
|
SubscriptionViewModelBuilder = require('./SubscriptionViewModelBuilder')
|
||||||
LimitationsManager = require("./LimitationsManager")
|
LimitationsManager = require("./LimitationsManager")
|
||||||
RecurlyWrapper = require './RecurlyWrapper'
|
RecurlyWrapper = require './RecurlyWrapper'
|
||||||
|
@ -9,7 +8,6 @@ Settings = require 'settings-sharelatex'
|
||||||
logger = require('logger-sharelatex')
|
logger = require('logger-sharelatex')
|
||||||
GeoIpLookup = require("../../infrastructure/GeoIpLookup")
|
GeoIpLookup = require("../../infrastructure/GeoIpLookup")
|
||||||
SubscriptionDomainHandler = require("./SubscriptionDomainHandler")
|
SubscriptionDomainHandler = require("./SubscriptionDomainHandler")
|
||||||
require("../../infrastructure/Sixpack")
|
|
||||||
|
|
||||||
module.exports = SubscriptionController =
|
module.exports = SubscriptionController =
|
||||||
|
|
||||||
|
@ -98,14 +96,14 @@ module.exports = SubscriptionController =
|
||||||
else
|
else
|
||||||
SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel user, (error, subscription, groups) ->
|
SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel user, (error, subscription, groups) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
logger.log user: user, subscription:subscription, hasSubOrIsGroupMember:hasSubOrIsGroupMember, "showing subscription dashboard"
|
logger.log user: user, subscription:subscription, hasSubOrIsGroupMember:hasSubOrIsGroupMember, groups:groups, "showing subscription dashboard"
|
||||||
plans = SubscriptionViewModelBuilder.buildViewModel()
|
plans = SubscriptionViewModelBuilder.buildViewModel()
|
||||||
res.render "subscriptions/dashboard",
|
res.render "subscriptions/dashboard",
|
||||||
title: "your_subscription"
|
title: "your_subscription"
|
||||||
recomendedCurrency: subscription?.currency
|
recomendedCurrency: subscription?.currency
|
||||||
taxRate:subscription?.taxRate
|
taxRate:subscription?.taxRate
|
||||||
plans: plans
|
plans: plans
|
||||||
subscription: subscription
|
subscription: subscription || {}
|
||||||
groups: groups
|
groups: groups
|
||||||
subscriptionTabActive: true
|
subscriptionTabActive: true
|
||||||
|
|
||||||
|
@ -226,6 +224,14 @@ module.exports = SubscriptionController =
|
||||||
else
|
else
|
||||||
res.sendStatus 200
|
res.sendStatus 200
|
||||||
|
|
||||||
|
extendTrial: (req, res)->
|
||||||
|
SecurityManager.getCurrentUser req, (error, user) ->
|
||||||
|
LimitationsManager.userHasSubscription user, (err, hasSubscription, subscription)->
|
||||||
|
SubscriptionHandler.extendTrial subscription, 14, (err)->
|
||||||
|
if err?
|
||||||
|
res.send 500
|
||||||
|
else
|
||||||
|
res.send 200
|
||||||
|
|
||||||
recurlyNotificationParser: (req, res, next) ->
|
recurlyNotificationParser: (req, res, next) ->
|
||||||
xml = ""
|
xml = ""
|
||||||
|
|
|
@ -1,30 +1,17 @@
|
||||||
async = require("async")
|
async = require("async")
|
||||||
_ = require("underscore")
|
_ = require("underscore")
|
||||||
settings = require("settings-sharelatex")
|
settings = require("settings-sharelatex")
|
||||||
SubscriptionGroupHandler = require("./SubscriptionGroupHandler")
|
|
||||||
_s = require("underscore.string")
|
|
||||||
|
|
||||||
module.exports = SubscriptionDomainHandler =
|
module.exports = SubscriptionDomainHandler =
|
||||||
|
|
||||||
|
|
||||||
getLicenceUserCanJoin: (user, callback)->
|
getLicenceUserCanJoin: (user)->
|
||||||
licence = SubscriptionDomainHandler._findDomainLicence(user.email)
|
licence = SubscriptionDomainHandler._findDomainLicence(user.email)
|
||||||
if licence?
|
return licence
|
||||||
callback null, licence
|
|
||||||
else
|
|
||||||
callback()
|
|
||||||
|
|
||||||
attemptToJoinGroup: (user, callback)->
|
|
||||||
licence = SubscriptionDomainHandler._findDomainLicence(user.email)
|
|
||||||
if licence? and user.emailVerified
|
|
||||||
SubscriptionGroupHandler.addUserToGroup licence.adminUser_id, user.email, callback
|
|
||||||
else
|
|
||||||
callback "user not verified"
|
|
||||||
|
|
||||||
rejectInvitationToGroup: (user, subscription, callback)->
|
rejectInvitationToGroup: (user, subscription, callback)->
|
||||||
removeUserFromGroup(subscription.admin_id, user._id, callback)
|
removeUserFromGroup(subscription.admin_id, user._id, callback)
|
||||||
|
|
||||||
|
|
||||||
getDomainLicencePage: (user)->
|
getDomainLicencePage: (user)->
|
||||||
licence = SubscriptionDomainHandler._findDomainLicence(user.email)
|
licence = SubscriptionDomainHandler._findDomainLicence(user.email)
|
||||||
if licence?.verifyEmail
|
if licence?.verifyEmail
|
||||||
|
@ -32,20 +19,11 @@ module.exports = SubscriptionDomainHandler =
|
||||||
else
|
else
|
||||||
return undefined
|
return undefined
|
||||||
|
|
||||||
|
|
||||||
autoAllocate: (user, callback = ->)->
|
|
||||||
licence = SubscriptionDomainHandler._findDomainLicence(user.email)
|
|
||||||
#
|
|
||||||
if licence?
|
|
||||||
SubscriptionGroupHandler.addUserToGroup licence.adminUser_id, user.email, callback
|
|
||||||
else
|
|
||||||
callback()
|
|
||||||
|
|
||||||
|
|
||||||
_findDomainLicence: (email)->
|
_findDomainLicence: (email)->
|
||||||
licence = _.find settings.domainLicences, (licence)->
|
licence = _.find settings.domainLicences, (licence)->
|
||||||
_.find licence.domains, (domain)->
|
_.find licence.domains, (domain)->
|
||||||
_s.endsWith email, domain
|
regex = "[@\.]#{domain}"
|
||||||
|
return email.match(regex)
|
||||||
|
|
||||||
return licence
|
return licence
|
||||||
|
|
||||||
|
@ -53,4 +31,3 @@ module.exports = SubscriptionDomainHandler =
|
||||||
licence = _.find settings.domainLicences, (licence)->
|
licence = _.find settings.domainLicences, (licence)->
|
||||||
licence?.subscription_id == subscription_id
|
licence?.subscription_id == subscription_id
|
||||||
return licence
|
return licence
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
SubscriptionGroupHandler = require("./SubscriptionGroupHandler")
|
SubscriptionGroupHandler = require("./SubscriptionGroupHandler")
|
||||||
logger = require("logger-sharelatex")
|
logger = require("logger-sharelatex")
|
||||||
SubscriptionLocator = require("./SubscriptionLocator")
|
SubscriptionLocator = require("./SubscriptionLocator")
|
||||||
|
|
||||||
ErrorsController = require("../Errors/ErrorController")
|
ErrorsController = require("../Errors/ErrorController")
|
||||||
settings = require("settings-sharelatex")
|
|
||||||
|
|
||||||
SubscriptionDomainHandler = require("./SubscriptionDomainHandler")
|
SubscriptionDomainHandler = require("./SubscriptionDomainHandler")
|
||||||
_ = require("underscore")
|
_ = require("underscore")
|
||||||
|
|
||||||
|
@ -12,9 +9,12 @@ module.exports =
|
||||||
|
|
||||||
addUserToGroup: (req, res)->
|
addUserToGroup: (req, res)->
|
||||||
adminUserId = req.session.user._id
|
adminUserId = req.session.user._id
|
||||||
newEmail = req.body.email
|
newEmail = req.body?.email?.toLowerCase()?.trim()
|
||||||
logger.log adminUserId:adminUserId, newEmail:newEmail, "adding user to group subscription"
|
logger.log adminUserId:adminUserId, newEmail:newEmail, "adding user to group subscription"
|
||||||
SubscriptionGroupHandler.addUserToGroup adminUserId, newEmail, (err, user)->
|
SubscriptionGroupHandler.addUserToGroup adminUserId, newEmail, (err, user)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, newEmail:newEmail, adminUserId:adminUserId, "error adding user from group"
|
||||||
|
return res.sendStatus 500
|
||||||
result =
|
result =
|
||||||
user:user
|
user:user
|
||||||
if err and err.limitReached
|
if err and err.limitReached
|
||||||
|
@ -25,7 +25,20 @@ module.exports =
|
||||||
adminUserId = req.session.user._id
|
adminUserId = req.session.user._id
|
||||||
userToRemove_id = req.params.user_id
|
userToRemove_id = req.params.user_id
|
||||||
logger.log adminUserId:adminUserId, userToRemove_id:userToRemove_id, "removing user from group subscription"
|
logger.log adminUserId:adminUserId, userToRemove_id:userToRemove_id, "removing user from group subscription"
|
||||||
SubscriptionGroupHandler.removeUserFromGroup adminUserId, userToRemove_id, ->
|
SubscriptionGroupHandler.removeUserFromGroup adminUserId, userToRemove_id, (err)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, adminUserId:adminUserId, userToRemove_id:userToRemove_id, "error removing user from group"
|
||||||
|
return res.sendStatus 500
|
||||||
|
res.send()
|
||||||
|
|
||||||
|
removeSelfFromGroup: (req, res)->
|
||||||
|
adminUserId = req.query.admin_user_id
|
||||||
|
userToRemove_id = req.session.user._id
|
||||||
|
logger.log adminUserId:adminUserId, userToRemove_id:userToRemove_id, "removing user from group subscription after self request"
|
||||||
|
SubscriptionGroupHandler.removeUserFromGroup adminUserId, userToRemove_id, (err)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, userToRemove_id:userToRemove_id, adminUserId:adminUserId, "error removing self from group"
|
||||||
|
return res.sendStatus 500
|
||||||
res.send()
|
res.send()
|
||||||
|
|
||||||
renderSubscriptionGroupAdminPage: (req, res)->
|
renderSubscriptionGroupAdminPage: (req, res)->
|
||||||
|
@ -70,13 +83,16 @@ module.exports =
|
||||||
subscription_id = req.params.subscription_id
|
subscription_id = req.params.subscription_id
|
||||||
if !SubscriptionDomainHandler.findDomainLicenceBySubscriptionId(subscription_id)?
|
if !SubscriptionDomainHandler.findDomainLicenceBySubscriptionId(subscription_id)?
|
||||||
return ErrorsController.notFound(req, res)
|
return ErrorsController.notFound(req, res)
|
||||||
SubscriptionGroupHandler.processGroupVerification req.session.user.email, subscription_id, req.query.token, (err)->
|
email = req?.session?.user?.email
|
||||||
|
logger.log subscription_id:subscription_id, user_id:req?.session?.user?._id, email:email, "starting the completion of joining group"
|
||||||
|
SubscriptionGroupHandler.processGroupVerification email, subscription_id, req.query?.token, (err)->
|
||||||
if err? and err == "token_not_found"
|
if err? and err == "token_not_found"
|
||||||
res.redirect "/user/subscription/#{subscription_id}/group/invited?expired=true"
|
return res.redirect "/user/subscription/#{subscription_id}/group/invited?expired=true"
|
||||||
else if err?
|
else if err?
|
||||||
res.sendStatus 500
|
return res.sendStatus 500
|
||||||
else
|
else
|
||||||
res.redirect "/user/subscription/#{subscription_id}/group/successful-join"
|
logger.log subscription_id:subscription_id, email:email, "user successful completed join of group subscription"
|
||||||
|
return res.redirect "/user/subscription/#{subscription_id}/group/successful-join"
|
||||||
|
|
||||||
renderSuccessfulJoinPage: (req, res)->
|
renderSuccessfulJoinPage: (req, res)->
|
||||||
subscription_id = req.params.subscription_id
|
subscription_id = req.params.subscription_id
|
||||||
|
|
|
@ -9,15 +9,32 @@ logger = require("logger-sharelatex")
|
||||||
OneTimeTokenHandler = require("../Security/OneTimeTokenHandler")
|
OneTimeTokenHandler = require("../Security/OneTimeTokenHandler")
|
||||||
EmailHandler = require("../Email/EmailHandler")
|
EmailHandler = require("../Email/EmailHandler")
|
||||||
settings = require("settings-sharelatex")
|
settings = require("settings-sharelatex")
|
||||||
|
NotificationsBuilder = require("../Notifications/NotificationsBuilder")
|
||||||
|
|
||||||
module.exports = SubscriptionGroupHandler =
|
module.exports = SubscriptionGroupHandler =
|
||||||
|
|
||||||
addUserToGroup: (adminUser_id, newEmail, callback)->
|
addUserToGroup: (adminUserId, newEmail, callback)->
|
||||||
|
logger.log adminUserId:adminUserId, newEmail:newEmail, "adding user to group"
|
||||||
UserCreator.getUserOrCreateHoldingAccount newEmail, (err, user)->
|
UserCreator.getUserOrCreateHoldingAccount newEmail, (err, user)->
|
||||||
LimitationsManager.hasGroupMembersLimitReached adminUser_id, (err, limitReached)->
|
if err?
|
||||||
|
logger.err err:err, adminUserId:adminUserId, newEmail:newEmail, "error creating user for holding account"
|
||||||
|
return callback(err)
|
||||||
|
if !user?
|
||||||
|
msg = "no user returned whenc reating holidng account or getting user"
|
||||||
|
logger.err adminUserId:adminUserId, newEmail:newEmail, msg
|
||||||
|
return callback(msg)
|
||||||
|
LimitationsManager.hasGroupMembersLimitReached adminUserId, (err, limitReached, subscription)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, adminUserId:adminUserId, newEmail:newEmail, "error checking if limit reached for group plan"
|
||||||
|
return callback(err)
|
||||||
if limitReached
|
if limitReached
|
||||||
|
logger.err adminUserId:adminUserId, newEmail:newEmail, "group subscription limit reached not adding user to group"
|
||||||
return callback(limitReached:limitReached)
|
return callback(limitReached:limitReached)
|
||||||
SubscriptionUpdater.addUserToGroup adminUser_id, user._id, (err)->
|
SubscriptionUpdater.addUserToGroup adminUserId, user._id, (err)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, "error adding user to group"
|
||||||
|
return callback(err)
|
||||||
|
NotificationsBuilder.groupPlan(user, {subscription_id:subscription._id}).read()
|
||||||
userViewModel = buildUserViewModel(user)
|
userViewModel = buildUserViewModel(user)
|
||||||
callback(err, userViewModel)
|
callback(err, userViewModel)
|
||||||
|
|
||||||
|
@ -60,13 +77,20 @@ module.exports = SubscriptionGroupHandler =
|
||||||
EmailHandler.sendEmail "completeJoinGroupAccount", opts, callback
|
EmailHandler.sendEmail "completeJoinGroupAccount", opts, callback
|
||||||
|
|
||||||
processGroupVerification: (userEmail, subscription_id, token, callback)->
|
processGroupVerification: (userEmail, subscription_id, token, callback)->
|
||||||
|
logger.log userEmail:userEmail, subscription_id:subscription_id, "processing group verification for user"
|
||||||
OneTimeTokenHandler.getValueFromTokenAndExpire token, (err, token_subscription_id)->
|
OneTimeTokenHandler.getValueFromTokenAndExpire token, (err, token_subscription_id)->
|
||||||
|
|
||||||
if err? or subscription_id != token_subscription_id
|
if err? or subscription_id != token_subscription_id
|
||||||
logger.err userEmail:userEmail, token:token, "token value not found for processing group verification"
|
logger.err userEmail:userEmail, token:token, "token value not found for processing group verification"
|
||||||
return callback("token_not_found")
|
return callback("token_not_found")
|
||||||
SubscriptionLocator.getSubscription subscription_id, (err, subscription)->
|
SubscriptionLocator.getSubscription subscription_id, (err, subscription)->
|
||||||
SubscriptionGroupHandler.addUserToGroup subscription.admin_id, userEmail, callback
|
if err?
|
||||||
|
logger.err err:err, subscription:subscription, userEmail:userEmail, subscription_id:subscription_id, "error getting subscription"
|
||||||
|
return callback(err)
|
||||||
|
if !subscription?
|
||||||
|
logger.warn subscription_id:subscription_id, userEmail:userEmail, "no subscription found"
|
||||||
|
return callback()
|
||||||
|
SubscriptionGroupHandler.addUserToGroup subscription?.admin_id, userEmail, callback
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
buildUserViewModel = (user)->
|
buildUserViewModel = (user)->
|
||||||
|
|
|
@ -74,5 +74,5 @@ module.exports =
|
||||||
return callback("no user found")
|
return callback("no user found")
|
||||||
SubscriptionUpdater.syncSubscription recurlySubscription, user?._id, callback
|
SubscriptionUpdater.syncSubscription recurlySubscription, user?._id, callback
|
||||||
|
|
||||||
|
extendTrial: (subscription, daysToExend, callback)->
|
||||||
|
RecurlyWrapper.extendTrial subscription.recurlySubscription_id, daysToExend, callback
|
||||||
|
|
|
@ -22,4 +22,4 @@ module.exports =
|
||||||
Subscription.findOne _id:subscription_id, callback
|
Subscription.findOne _id:subscription_id, callback
|
||||||
|
|
||||||
getSubscriptionByMemberIdAndId: (user_id, subscription_id, callback)->
|
getSubscriptionByMemberIdAndId: (user_id, subscription_id, callback)->
|
||||||
Subscription.findOne member_ids: user_id, _id:subscription_id, callback
|
Subscription.findOne member_ids: user_id, _id:subscription_id, {_id:1}, callback
|
||||||
|
|
|
@ -24,6 +24,8 @@ module.exports =
|
||||||
webRouter.post '/subscription/group/user', AuthenticationController.requireLogin(), SubscriptionGroupController.addUserToGroup
|
webRouter.post '/subscription/group/user', AuthenticationController.requireLogin(), SubscriptionGroupController.addUserToGroup
|
||||||
webRouter.get '/subscription/group/export', AuthenticationController.requireLogin(), SubscriptionGroupController.exportGroupCsv
|
webRouter.get '/subscription/group/export', AuthenticationController.requireLogin(), SubscriptionGroupController.exportGroupCsv
|
||||||
webRouter.delete '/subscription/group/user/:user_id', AuthenticationController.requireLogin(), SubscriptionGroupController.removeUserFromGroup
|
webRouter.delete '/subscription/group/user/:user_id', AuthenticationController.requireLogin(), SubscriptionGroupController.removeUserFromGroup
|
||||||
|
webRouter.delete '/subscription/group/user', AuthenticationController.requireLogin(), SubscriptionGroupController.removeSelfFromGroup
|
||||||
|
|
||||||
|
|
||||||
webRouter.get '/user/subscription/:subscription_id/group/invited', AuthenticationController.requireLogin(), SubscriptionGroupController.renderGroupInvitePage
|
webRouter.get '/user/subscription/:subscription_id/group/invited', AuthenticationController.requireLogin(), SubscriptionGroupController.renderGroupInvitePage
|
||||||
webRouter.post '/user/subscription/:subscription_id/group/begin-join', AuthenticationController.requireLogin(), SubscriptionGroupController.beginJoinGroup
|
webRouter.post '/user/subscription/:subscription_id/group/begin-join', AuthenticationController.requireLogin(), SubscriptionGroupController.beginJoinGroup
|
||||||
|
@ -39,6 +41,7 @@ module.exports =
|
||||||
webRouter.post '/user/subscription/cancel', AuthenticationController.requireLogin(), SubscriptionController.cancelSubscription
|
webRouter.post '/user/subscription/cancel', AuthenticationController.requireLogin(), SubscriptionController.cancelSubscription
|
||||||
webRouter.post '/user/subscription/reactivate', AuthenticationController.requireLogin(), SubscriptionController.reactivateSubscription
|
webRouter.post '/user/subscription/reactivate', AuthenticationController.requireLogin(), SubscriptionController.reactivateSubscription
|
||||||
|
|
||||||
|
webRouter.put '/user/subscription/extend', AuthenticationController.requireLogin(), SubscriptionController.extendTrial
|
||||||
|
|
||||||
webRouter.get "/user/subscription/upgrade-annual", AuthenticationController.requireLogin(), SubscriptionController.renderUpgradeToAnnualPlanPage
|
webRouter.get "/user/subscription/upgrade-annual", AuthenticationController.requireLogin(), SubscriptionController.renderUpgradeToAnnualPlanPage
|
||||||
webRouter.post "/user/subscription/upgrade-annual", AuthenticationController.requireLogin(), SubscriptionController.processUpgradeToAnnualPlan
|
webRouter.post "/user/subscription/upgrade-annual", AuthenticationController.requireLogin(), SubscriptionController.processUpgradeToAnnualPlan
|
||||||
|
|
|
@ -32,6 +32,9 @@ module.exports =
|
||||||
insertOperation =
|
insertOperation =
|
||||||
"$addToSet": {member_ids:user_id}
|
"$addToSet": {member_ids:user_id}
|
||||||
Subscription.findAndModify searchOps, insertOperation, (err, subscription)->
|
Subscription.findAndModify searchOps, insertOperation, (err, subscription)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, searchOps:searchOps, insertOperation:insertOperation, "error findy and modify add user to group"
|
||||||
|
return callback(err)
|
||||||
UserFeaturesUpdater.updateFeatures user_id, subscription.planCode, callback
|
UserFeaturesUpdater.updateFeatures user_id, subscription.planCode, callback
|
||||||
|
|
||||||
removeUserFromGroup: (adminUser_id, user_id, callback)->
|
removeUserFromGroup: (adminUser_id, user_id, callback)->
|
||||||
|
@ -39,10 +42,12 @@ module.exports =
|
||||||
admin_id: adminUser_id
|
admin_id: adminUser_id
|
||||||
removeOperation =
|
removeOperation =
|
||||||
"$pull": {member_ids:user_id}
|
"$pull": {member_ids:user_id}
|
||||||
Subscription.update searchOps, removeOperation, ->
|
Subscription.update searchOps, removeOperation, (err)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, searchOps:searchOps, removeOperation:removeOperation, "error removing user from group"
|
||||||
|
return callback(err)
|
||||||
UserFeaturesUpdater.updateFeatures user_id, Settings.defaultPlanCode, callback
|
UserFeaturesUpdater.updateFeatures user_id, Settings.defaultPlanCode, callback
|
||||||
|
|
||||||
|
|
||||||
_createNewSubscription: (adminUser_id, callback)->
|
_createNewSubscription: (adminUser_id, callback)->
|
||||||
logger.log adminUser_id:adminUser_id, "creating new subscription"
|
logger.log adminUser_id:adminUser_id, "creating new subscription"
|
||||||
subscription = new Subscription(admin_id:adminUser_id)
|
subscription = new Subscription(admin_id:adminUser_id)
|
||||||
|
|
|
@ -27,6 +27,7 @@ module.exports =
|
||||||
currency:recurlySubscription?.currency
|
currency:recurlySubscription?.currency
|
||||||
taxRate:parseFloat(recurlySubscription?.tax_rate?._)
|
taxRate:parseFloat(recurlySubscription?.tax_rate?._)
|
||||||
groupPlan: subscription.groupPlan
|
groupPlan: subscription.groupPlan
|
||||||
|
trial_ends_at:recurlySubscription?.trial_ends_at
|
||||||
}, memberSubscriptions
|
}, memberSubscriptions
|
||||||
else
|
else
|
||||||
callback null, null, memberSubscriptions
|
callback null, null, memberSubscriptions
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
Settings = require "settings-sharelatex"
|
|
||||||
logger = require("logger-sharelatex")
|
logger = require("logger-sharelatex")
|
||||||
User = require('../../models/User').User
|
User = require('../../models/User').User
|
||||||
PlansLocator = require("./PlansLocator")
|
PlansLocator = require("./PlansLocator")
|
||||||
|
@ -9,7 +8,7 @@ module.exports =
|
||||||
conditions = _id:user_id
|
conditions = _id:user_id
|
||||||
update = {}
|
update = {}
|
||||||
plan = PlansLocator.findLocalPlanInSettings(plan_code)
|
plan = PlansLocator.findLocalPlanInSettings(plan_code)
|
||||||
logger.log user_id:user_id, plan:plan, plan_code:plan_code, "updating users features"
|
logger.log user_id:user_id, features:plan.features, plan_code:plan_code, "updating users features"
|
||||||
update["features.#{key}"] = value for key, value of plan.features
|
update["features.#{key}"] = value for key, value of plan.features
|
||||||
User.update conditions, update, (err)->
|
User.update conditions, update, (err)->
|
||||||
callback err, plan.features
|
callback err, plan.features
|
||||||
|
|
|
@ -2,20 +2,53 @@ TagsHandler = require("./TagsHandler")
|
||||||
logger = require("logger-sharelatex")
|
logger = require("logger-sharelatex")
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
|
getAllTags: (req, res, next)->
|
||||||
processTagsUpdate: (req, res)->
|
|
||||||
user_id = req.session.user._id
|
user_id = req.session.user._id
|
||||||
project_id = req.params.project_id
|
logger.log {user_id}, "getting tags"
|
||||||
if req.body.deletedTag?
|
TagsHandler.getAllTags user_id, (error, allTags)->
|
||||||
tag = req.body.deletedTag
|
return next(error) if error?
|
||||||
TagsHandler.deleteTag user_id, project_id, tag, ->
|
res.json(allTags)
|
||||||
res.send()
|
|
||||||
|
createTag: (req, res, next) ->
|
||||||
|
user_id = req.session.user._id
|
||||||
|
name = req.body.name
|
||||||
|
logger.log {user_id, name}, "creating tag"
|
||||||
|
TagsHandler.createTag user_id, name, (error, tag) ->
|
||||||
|
return next(error) if error?
|
||||||
|
res.json(tag)
|
||||||
|
|
||||||
|
addProjectToTag: (req, res, next) ->
|
||||||
|
user_id = req.session.user._id
|
||||||
|
{tag_id, project_id} = req.params
|
||||||
|
logger.log {user_id, tag_id, project_id}, "adding tag to project"
|
||||||
|
TagsHandler.addProjectToTag user_id, tag_id, project_id, (error) ->
|
||||||
|
return next(error) if error?
|
||||||
|
res.status(204).end()
|
||||||
|
|
||||||
|
removeProjectFromTag: (req, res, next) ->
|
||||||
|
user_id = req.session.user._id
|
||||||
|
{tag_id, project_id} = req.params
|
||||||
|
logger.log {user_id, tag_id, project_id}, "removing tag from project"
|
||||||
|
TagsHandler.removeProjectFromTag user_id, tag_id, project_id, (error) ->
|
||||||
|
return next(error) if error?
|
||||||
|
res.status(204).end()
|
||||||
|
|
||||||
|
deleteTag: (req, res, next) ->
|
||||||
|
user_id = req.session.user._id
|
||||||
|
tag_id = req.params.tag_id
|
||||||
|
logger.log {user_id, tag_id}, "deleting tag"
|
||||||
|
TagsHandler.deleteTag user_id, tag_id, (error) ->
|
||||||
|
return next(error) if error?
|
||||||
|
res.status(204).end()
|
||||||
|
|
||||||
|
renameTag: (req, res, next) ->
|
||||||
|
user_id = req.session.user._id
|
||||||
|
tag_id = req.params.tag_id
|
||||||
|
name = req.body?.name
|
||||||
|
if !name?
|
||||||
|
return res.status(400).end()
|
||||||
else
|
else
|
||||||
tag = req.body.tag
|
logger.log {user_id, tag_id, name}, "renaming tag"
|
||||||
TagsHandler.addTag user_id, project_id, tag, ->
|
TagsHandler.renameTag user_id, tag_id, name, (error) ->
|
||||||
res.send()
|
return next(error) if error?
|
||||||
logger.log user_id:user_id, project_id:project_id, body:req.body, "processing tag update"
|
res.status(204).end()
|
||||||
|
|
||||||
getAllTags: (req, res)->
|
|
||||||
TagsHandler.getAllTags req.session.user._id, (err, allTags)->
|
|
||||||
res.send(allTags)
|
|
||||||
|
|
|
@ -3,61 +3,84 @@ settings = require("settings-sharelatex")
|
||||||
request = require("request")
|
request = require("request")
|
||||||
logger = require("logger-sharelatex")
|
logger = require("logger-sharelatex")
|
||||||
|
|
||||||
oneSecond = 1000
|
TIMEOUT = 1000
|
||||||
module.exports =
|
module.exports = TagsHandler =
|
||||||
|
|
||||||
|
|
||||||
deleteTag: (user_id, project_id, tag, callback)->
|
|
||||||
uri = buildUri(user_id, project_id)
|
|
||||||
opts =
|
|
||||||
uri:uri
|
|
||||||
json:
|
|
||||||
name:tag
|
|
||||||
timeout:oneSecond
|
|
||||||
logger.log user_id:user_id, project_id:project_id, tag:tag, "send delete tag to tags api"
|
|
||||||
request.del opts, callback
|
|
||||||
|
|
||||||
addTag: (user_id, project_id, tag, callback)->
|
|
||||||
uri = buildUri(user_id, project_id)
|
|
||||||
opts =
|
|
||||||
uri:uri
|
|
||||||
json:
|
|
||||||
name:tag
|
|
||||||
timeout:oneSecond
|
|
||||||
logger.log user_id:user_id, project_id:project_id, tag:tag, "send add tag to tags api"
|
|
||||||
request.post opts, callback
|
|
||||||
|
|
||||||
requestTags: (user_id, callback)->
|
|
||||||
opts =
|
|
||||||
uri: "#{settings.apis.tags.url}/user/#{user_id}/tag"
|
|
||||||
json: true
|
|
||||||
timeout: 2000
|
|
||||||
request.get opts, (err, res, body)->
|
|
||||||
statusCode = if res? then res.statusCode else 500
|
|
||||||
if err? or statusCode != 200
|
|
||||||
e = new Error("something went wrong getting tags, #{err}, #{statusCode}")
|
|
||||||
logger.err err:err
|
|
||||||
callback(e, [])
|
|
||||||
else
|
|
||||||
callback(null, body)
|
|
||||||
|
|
||||||
getAllTags: (user_id, callback)->
|
getAllTags: (user_id, callback)->
|
||||||
@requestTags user_id, (err, allTags)=>
|
@_requestTags user_id, (err, allTags)=>
|
||||||
if !allTags?
|
if !allTags?
|
||||||
allTags = []
|
allTags = []
|
||||||
@groupTagsByProject allTags, (err, groupedByProject)->
|
@_groupTagsByProject allTags, (err, groupedByProject)->
|
||||||
logger.log allTags:allTags, user_id:user_id, groupedByProject:groupedByProject, "getting all tags from tags api"
|
logger.log allTags:allTags, user_id:user_id, groupedByProject:groupedByProject, "got all tags from tags api"
|
||||||
callback err, allTags, groupedByProject
|
callback err, allTags, groupedByProject
|
||||||
|
|
||||||
|
createTag: (user_id, name, callback = (error, tag) ->) ->
|
||||||
|
opts =
|
||||||
|
url: "#{settings.apis.tags.url}/user/#{user_id}/tag"
|
||||||
|
json:
|
||||||
|
name: name
|
||||||
|
timeout: TIMEOUT
|
||||||
|
request.post opts, (err, res, body)->
|
||||||
|
TagsHandler._handleResponse err, res, {user_id}, (error) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
callback(null, body or {})
|
||||||
|
|
||||||
|
renameTag: (user_id, tag_id, name, callback = (error) ->) ->
|
||||||
|
url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}/rename"
|
||||||
|
request.post {
|
||||||
|
url: url
|
||||||
|
json:
|
||||||
|
name: name
|
||||||
|
timeout: TIMEOUT
|
||||||
|
}, (err, res, body) ->
|
||||||
|
TagsHandler._handleResponse err, res, {url, user_id, tag_id, name}, callback
|
||||||
|
|
||||||
|
deleteTag: (user_id, tag_id, callback = (error) ->) ->
|
||||||
|
url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}"
|
||||||
|
request.del {url, timeout: TIMEOUT}, (err, res, body) ->
|
||||||
|
TagsHandler._handleResponse err, res, {url, user_id, tag_id}, callback
|
||||||
|
|
||||||
|
removeProjectFromTag: (user_id, tag_id, project_id, callback)->
|
||||||
|
url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}/project/#{project_id}"
|
||||||
|
request.del {url, timeout: TIMEOUT}, (err, res, body) ->
|
||||||
|
TagsHandler._handleResponse err, res, {url, user_id, tag_id, project_id}, callback
|
||||||
|
|
||||||
|
addProjectToTag: (user_id, tag_id, project_id, callback)->
|
||||||
|
url = "#{settings.apis.tags.url}/user/#{user_id}/tag/#{tag_id}/project/#{project_id}"
|
||||||
|
request.post {url, timeout: TIMEOUT}, (err, res, body) ->
|
||||||
|
TagsHandler._handleResponse err, res, {url, user_id, tag_id, project_id}, callback
|
||||||
|
|
||||||
removeProjectFromAllTags: (user_id, project_id, callback)->
|
removeProjectFromAllTags: (user_id, project_id, callback)->
|
||||||
uri = buildUri(user_id, project_id)
|
url = "#{settings.apis.tags.url}/user/#{user_id}/project/#{project_id}"
|
||||||
opts =
|
opts =
|
||||||
uri:"#{settings.apis.tags.url}/user/#{user_id}/project/#{project_id}"
|
url: url
|
||||||
timeout:oneSecond
|
timeout:TIMEOUT
|
||||||
logger.log user_id:user_id, project_id:project_id, "removing project_id from tags"
|
request.del opts, (err, res, body) ->
|
||||||
request.del opts, callback
|
TagsHandler._handleResponse err, res, {url, user_id, project_id}, callback
|
||||||
|
|
||||||
groupTagsByProject: (tags, callback)->
|
_handleResponse: (err, res, params, callback) ->
|
||||||
|
if err?
|
||||||
|
params.err = err
|
||||||
|
logger.err params, "error in tag api"
|
||||||
|
return callback(err)
|
||||||
|
else if res? and res.statusCode >= 200 and res.statusCode < 300
|
||||||
|
return callback(null)
|
||||||
|
else
|
||||||
|
err = new Error("tags api returned a failure status code: #{res?.statusCode}")
|
||||||
|
params.err = err
|
||||||
|
logger.err params, "tags api returned failure status code: #{res?.statusCode}"
|
||||||
|
return callback(err)
|
||||||
|
|
||||||
|
_requestTags: (user_id, callback)->
|
||||||
|
opts =
|
||||||
|
url: "#{settings.apis.tags.url}/user/#{user_id}/tag"
|
||||||
|
json: true
|
||||||
|
timeout: TIMEOUT
|
||||||
|
request.get opts, (err, res, body)->
|
||||||
|
TagsHandler._handleResponse err, res, {user_id}, (error) ->
|
||||||
|
return callback(error, []) if error?
|
||||||
|
callback(null, body or [])
|
||||||
|
|
||||||
|
_groupTagsByProject: (tags, callback)->
|
||||||
result = {}
|
result = {}
|
||||||
_.each tags, (tag)->
|
_.each tags, (tag)->
|
||||||
_.each tag.project_ids, (project_id)->
|
_.each tag.project_ids, (project_id)->
|
||||||
|
@ -69,7 +92,3 @@ module.exports =
|
||||||
delete clonedTag.project_ids
|
delete clonedTag.project_ids
|
||||||
result[project_id].push(clonedTag)
|
result[project_id].push(clonedTag)
|
||||||
callback null, result
|
callback null, result
|
||||||
|
|
||||||
|
|
||||||
buildUri = (user_id, project_id)->
|
|
||||||
uri = "#{settings.apis.tags.url}/user/#{user_id}/project/#{project_id}/tag"
|
|
||||||
|
|
|
@ -45,7 +45,7 @@ module.exports =
|
||||||
path = "/" + req.params[0] # UpdateMerger expects leading slash
|
path = "/" + req.params[0] # UpdateMerger expects leading slash
|
||||||
source = req.headers["x-sl-update-source"] or "unknown"
|
source = req.headers["x-sl-update-source"] or "unknown"
|
||||||
logger.log project_id: project_id, path: path, source: source, "received project contents update"
|
logger.log project_id: project_id, path: path, source: source, "received project contents update"
|
||||||
UpdateMerger.mergeUpdate project_id, path, req, source, (error) ->
|
UpdateMerger.mergeUpdate null, project_id, path, req, source, (error) ->
|
||||||
return next(error) if error?
|
return next(error) if error?
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ module.exports =
|
||||||
FileTypeManager.shouldIgnore path, (err, shouldIgnore)->
|
FileTypeManager.shouldIgnore path, (err, shouldIgnore)->
|
||||||
if shouldIgnore
|
if shouldIgnore
|
||||||
return callback()
|
return callback()
|
||||||
updateMerger.mergeUpdate project._id, path, updateRequest, source, callback
|
updateMerger.mergeUpdate user_id, project._id, path, updateRequest, source, callback
|
||||||
|
|
||||||
|
|
||||||
deleteUpdate: (user_id, projectName, path, source, callback)->
|
deleteUpdate: (user_id, projectName, path, source, callback)->
|
||||||
|
|
|
@ -42,6 +42,9 @@ module.exports = TpdsUpdateSender =
|
||||||
|
|
||||||
_addEntity: (options, callback = (err)->)->
|
_addEntity: (options, callback = (err)->)->
|
||||||
getProjectsUsersIds options.project_id, (err, user_id, allUserIds)->
|
getProjectsUsersIds options.project_id, (err, user_id, allUserIds)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, options:options, "error getting projects user ids"
|
||||||
|
return callback(err)
|
||||||
logger.log project_id: options.project_id, user_id:user_id, path: options.path, uri:options.uri, rev:options.rev, "sending file to third party data store"
|
logger.log project_id: options.project_id, user_id:user_id, path: options.path, uri:options.uri, rev:options.rev, "sending file to third party data store"
|
||||||
postOptions =
|
postOptions =
|
||||||
method : "post"
|
method : "post"
|
||||||
|
@ -53,6 +56,9 @@ module.exports = TpdsUpdateSender =
|
||||||
title: "addFile"
|
title: "addFile"
|
||||||
streamOrigin : options.streamOrigin
|
streamOrigin : options.streamOrigin
|
||||||
TpdsUpdateSender._enqueue options.project_id, "pipeStreamFrom", postOptions, (err)->
|
TpdsUpdateSender._enqueue options.project_id, "pipeStreamFrom", postOptions, (err)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, project_id: options.project_id, user_id:user_id, path: options.path, uri:options.uri, rev:options.rev, "error sending file to third party data store queued up for processing"
|
||||||
|
return callback(err)
|
||||||
logger.log project_id: options.project_id, user_id:user_id, path: options.path, uri:options.uri, rev:options.rev, "sending file to third party data store queued up for processing"
|
logger.log project_id: options.project_id, user_id:user_id, path: options.path, uri:options.uri, rev:options.rev, "sending file to third party data store queued up for processing"
|
||||||
callback(err)
|
callback(err)
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,7 @@ uuid = require('node-uuid')
|
||||||
fs = require('fs')
|
fs = require('fs')
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
mergeUpdate: (project_id, path, updateRequest, source, callback = (error) ->)->
|
mergeUpdate: (user_id, project_id, path, updateRequest, source, callback = (error) ->)->
|
||||||
self = @
|
self = @
|
||||||
logger.log project_id:project_id, path:path, "merging update from tpds"
|
logger.log project_id:project_id, path:path, "merging update from tpds"
|
||||||
projectLocator.findElementByPath project_id, path, (err, element)=>
|
projectLocator.findElementByPath project_id, path, (err, element)=>
|
||||||
|
@ -30,7 +30,7 @@ module.exports =
|
||||||
if isFile
|
if isFile
|
||||||
self.p.processFile project_id, elementId, fsPath, path, source, callback
|
self.p.processFile project_id, elementId, fsPath, path, source, callback
|
||||||
else
|
else
|
||||||
self.p.processDoc project_id, elementId, fsPath, path, source, callback
|
self.p.processDoc project_id, elementId, user_id, fsPath, path, source, callback
|
||||||
|
|
||||||
deleteUpdate: (project_id, path, source, callback)->
|
deleteUpdate: (project_id, path, source, callback)->
|
||||||
projectLocator.findElementByPath project_id, path, (err, element)->
|
projectLocator.findElementByPath project_id, path, (err, element)->
|
||||||
|
@ -49,14 +49,14 @@ module.exports =
|
||||||
|
|
||||||
p:
|
p:
|
||||||
|
|
||||||
processDoc: (project_id, doc_id, fsPath, path, source, callback)->
|
processDoc: (project_id, doc_id, user_id, fsPath, path, source, callback)->
|
||||||
readFileIntoTextArray fsPath, (err, docLines)->
|
readFileIntoTextArray fsPath, (err, docLines)->
|
||||||
if err?
|
if err?
|
||||||
logger.err project_id:project_id, doc_id:doc_id, fsPath:fsPath, "error reading file into text array for process doc update"
|
logger.err project_id:project_id, doc_id:doc_id, fsPath:fsPath, "error reading file into text array for process doc update"
|
||||||
return callback(err)
|
return callback(err)
|
||||||
logger.log docLines:docLines, doc_id:doc_id, project_id:project_id, "processing doc update from tpds"
|
logger.log docLines:docLines, doc_id:doc_id, project_id:project_id, "processing doc update from tpds"
|
||||||
if doc_id?
|
if doc_id?
|
||||||
editorController.setDoc project_id, doc_id, docLines, source, (err)->
|
editorController.setDoc project_id, doc_id, user_id, docLines, source, (err)->
|
||||||
callback()
|
callback()
|
||||||
else
|
else
|
||||||
setupNewEntity project_id, path, (err, folder, fileName)->
|
setupNewEntity project_id, path, (err, folder, fileName)->
|
||||||
|
|
|
@ -1,21 +1,23 @@
|
||||||
child = require "child_process"
|
child = require "child_process"
|
||||||
logger = require "logger-sharelatex"
|
logger = require "logger-sharelatex"
|
||||||
metrics = require "../../infrastructure/Metrics"
|
metrics = require "../../infrastructure/Metrics"
|
||||||
|
fs = require "fs"
|
||||||
|
Path = require "path"
|
||||||
|
_ = require("underscore")
|
||||||
|
|
||||||
|
ONE_MEG = 1024 * 1024
|
||||||
|
|
||||||
module.exports = ArchiveManager =
|
module.exports = ArchiveManager =
|
||||||
extractZipArchive: (source, destination, _callback = (err) ->) ->
|
|
||||||
callback = (args...) ->
|
|
||||||
_callback(args...)
|
|
||||||
_callback = () ->
|
|
||||||
|
|
||||||
timer = new metrics.Timer("unzipDirectory")
|
|
||||||
logger.log source: source, destination: destination, "unzipping file"
|
|
||||||
|
|
||||||
unzip = child.spawn("unzip", [source, "-d", destination])
|
_isZipTooLarge: (source, callback = (err, isTooLarge)->)->
|
||||||
|
callback = _.once callback
|
||||||
|
|
||||||
# don't remove this line, some zips need
|
unzip = child.spawn("unzip", ["-l", source])
|
||||||
# us to listen on this for some unknow reason
|
|
||||||
|
output = ""
|
||||||
unzip.stdout.on "data", (d)->
|
unzip.stdout.on "data", (d)->
|
||||||
|
output += d
|
||||||
|
|
||||||
error = null
|
error = null
|
||||||
unzip.stderr.on "data", (chunk) ->
|
unzip.stderr.on "data", (chunk) ->
|
||||||
|
@ -29,9 +31,80 @@ module.exports = ArchiveManager =
|
||||||
callback(err)
|
callback(err)
|
||||||
|
|
||||||
unzip.on "exit", () ->
|
unzip.on "exit", () ->
|
||||||
timer.done()
|
|
||||||
if error?
|
if error?
|
||||||
error = new Error(error)
|
error = new Error(error)
|
||||||
logger.error err:error, source: source, destination: destination, "error unzipping file"
|
logger.error err:error, source: source, destination: destination, "error checking zip size"
|
||||||
callback(error)
|
|
||||||
|
lines = output.split("\n")
|
||||||
|
lastLine = lines[lines.length - 2]?.trim()
|
||||||
|
totalSizeInBytes = lastLine?.split(" ")?[0]
|
||||||
|
|
||||||
|
totalSizeInBytes = parseInt(totalSizeInBytes)
|
||||||
|
|
||||||
|
if !totalSizeInBytes? or isNaN(totalSizeInBytes)
|
||||||
|
logger.err source:source, "error getting bytes of zip"
|
||||||
|
return callback(new Error("something went wrong"))
|
||||||
|
|
||||||
|
isTooLarge = totalSizeInBytes > (ONE_MEG * 300)
|
||||||
|
|
||||||
|
callback(error, isTooLarge)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
extractZipArchive: (source, destination, _callback = (err) ->) ->
|
||||||
|
callback = (args...) ->
|
||||||
|
_callback(args...)
|
||||||
|
_callback = () ->
|
||||||
|
|
||||||
|
ArchiveManager._isZipTooLarge source, (err, isTooLarge)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, "error checking size of zip file"
|
||||||
|
return callback(err)
|
||||||
|
|
||||||
|
if isTooLarge
|
||||||
|
return callback(new Error("zip_too_large"))
|
||||||
|
|
||||||
|
|
||||||
|
timer = new metrics.Timer("unzipDirectory")
|
||||||
|
logger.log source: source, destination: destination, "unzipping file"
|
||||||
|
|
||||||
|
unzip = child.spawn("unzip", [source, "-d", destination])
|
||||||
|
|
||||||
|
# don't remove this line, some zips need
|
||||||
|
# us to listen on this for some unknow reason
|
||||||
|
unzip.stdout.on "data", (d)->
|
||||||
|
|
||||||
|
error = null
|
||||||
|
unzip.stderr.on "data", (chunk) ->
|
||||||
|
error ||= ""
|
||||||
|
error += chunk
|
||||||
|
|
||||||
|
unzip.on "error", (err) ->
|
||||||
|
logger.error {err, source, destination}, "unzip failed"
|
||||||
|
if err.code == "ENOENT"
|
||||||
|
logger.error "unzip command not found. Please check the unzip command is installed"
|
||||||
|
callback(err)
|
||||||
|
|
||||||
|
unzip.on "exit", () ->
|
||||||
|
timer.done()
|
||||||
|
if error?
|
||||||
|
error = new Error(error)
|
||||||
|
logger.error err:error, source: source, destination: destination, "error unzipping file"
|
||||||
|
callback(error)
|
||||||
|
|
||||||
|
findTopLevelDirectory: (directory, callback = (error, topLevelDir) ->) ->
|
||||||
|
fs.readdir directory, (error, files) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
if files.length == 1
|
||||||
|
childPath = Path.join(directory, files[0])
|
||||||
|
fs.stat childPath, (error, stat) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
if stat.isDirectory()
|
||||||
|
return callback(null, childPath)
|
||||||
|
else
|
||||||
|
return callback(null, directory)
|
||||||
|
else
|
||||||
|
return callback(null, directory)
|
||||||
|
|
||||||
|
|
|
@ -4,62 +4,108 @@ _ = require "underscore"
|
||||||
FileTypeManager = require "./FileTypeManager"
|
FileTypeManager = require "./FileTypeManager"
|
||||||
EditorController = require "../Editor/EditorController"
|
EditorController = require "../Editor/EditorController"
|
||||||
ProjectLocator = require "../Project/ProjectLocator"
|
ProjectLocator = require "../Project/ProjectLocator"
|
||||||
|
logger = require("logger-sharelatex")
|
||||||
|
|
||||||
module.exports = FileSystemImportManager =
|
module.exports = FileSystemImportManager =
|
||||||
addDoc: (project_id, folder_id, name, path, replace, callback = (error, doc)-> )->
|
addDoc: (user_id, project_id, folder_id, name, path, replace, callback = (error, doc)-> )->
|
||||||
fs.readFile path, "utf8", (error, content = "") ->
|
FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)->
|
||||||
return callback(error) if error?
|
if !isSafe
|
||||||
content = content.replace(/\r/g, "")
|
logger.log user_id:user_id, project_id:project_id, folder_id:folder_id, name:name, path:path, "add doc is from symlink, stopping process"
|
||||||
lines = content.split("\n")
|
return callback("path is symlink")
|
||||||
EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", callback
|
fs.readFile path, "utf8", (error, content = "") ->
|
||||||
|
|
||||||
addFile: (project_id, folder_id, name, path, replace, callback = (error, file)-> )->
|
|
||||||
if replace
|
|
||||||
ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
|
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
return callback(new Error("Couldn't find folder")) if !folder?
|
content = content.replace(/\r/g, "")
|
||||||
existingFile = null
|
lines = content.split("\n")
|
||||||
for fileRef in folder.fileRefs
|
if replace
|
||||||
if fileRef.name == name
|
ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
|
||||||
existingFile = fileRef
|
|
||||||
break
|
|
||||||
if existingFile?
|
|
||||||
EditorController.replaceFile project_id, existingFile._id, path, "upload", callback
|
|
||||||
else
|
|
||||||
EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", callback
|
|
||||||
else
|
|
||||||
EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", callback
|
|
||||||
|
|
||||||
addFolder: (project_id, folder_id, name, path, replace, callback = (error)-> ) ->
|
|
||||||
EditorController.addFolderWithoutLock project_id, folder_id, name, "upload", (error, new_folder) =>
|
|
||||||
return callback(error) if error?
|
|
||||||
@addFolderContents project_id, new_folder._id, path, replace, (error) ->
|
|
||||||
return callback(error) if error?
|
|
||||||
callback null, new_folder
|
|
||||||
|
|
||||||
addFolderContents: (project_id, parent_folder_id, folderPath, replace, callback = (error)-> ) ->
|
|
||||||
fs.readdir folderPath, (error, entries = []) =>
|
|
||||||
return callback(error) if error?
|
|
||||||
jobs = _.map entries, (entry) =>
|
|
||||||
(callback) =>
|
|
||||||
FileTypeManager.shouldIgnore entry, (error, ignore) =>
|
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
if !ignore
|
return callback(new Error("Couldn't find folder")) if !folder?
|
||||||
@addEntity project_id, parent_folder_id, entry, "#{folderPath}/#{entry}", replace, callback
|
existingDoc = null
|
||||||
|
for doc in folder.docs
|
||||||
|
if doc.name == name
|
||||||
|
existingDoc = doc
|
||||||
|
break
|
||||||
|
if existingDoc?
|
||||||
|
EditorController.setDoc project_id, existingDoc._id, user_id, lines, "upload", callback
|
||||||
else
|
else
|
||||||
callback()
|
EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", callback
|
||||||
async.parallelLimit jobs, 5, callback
|
else
|
||||||
|
EditorController.addDocWithoutLock project_id, folder_id, name, lines, "upload", callback
|
||||||
|
|
||||||
addEntity: (project_id, folder_id, name, path, replace, callback = (error, entity)-> ) ->
|
addFile: (user_id, project_id, folder_id, name, path, replace, callback = (error, file)-> )->
|
||||||
FileTypeManager.isDirectory path, (error, isDirectory) =>
|
FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)->
|
||||||
return callback(error) if error?
|
if !isSafe
|
||||||
if isDirectory
|
logger.log user_id:user_id, project_id:project_id, folder_id:folder_id, name:name, path:path, "add file is from symlink, stopping insert"
|
||||||
@addFolder project_id, folder_id, name, path, replace, callback
|
return callback("path is symlink")
|
||||||
|
|
||||||
|
if !replace
|
||||||
|
EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", callback
|
||||||
else
|
else
|
||||||
FileTypeManager.isBinary name, path, (error, isBinary) =>
|
ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
if isBinary
|
return callback(new Error("Couldn't find folder")) if !folder?
|
||||||
@addFile project_id, folder_id, name, path, replace, callback
|
existingFile = null
|
||||||
|
for fileRef in folder.fileRefs
|
||||||
|
if fileRef.name == name
|
||||||
|
existingFile = fileRef
|
||||||
|
break
|
||||||
|
if existingFile?
|
||||||
|
EditorController.replaceFile project_id, existingFile._id, path, "upload", callback
|
||||||
else
|
else
|
||||||
@addDoc project_id, folder_id, name, path, replace, callback
|
EditorController.addFileWithoutLock project_id, folder_id, name, path, "upload", callback
|
||||||
|
|
||||||
|
addFolder: (user_id, project_id, folder_id, name, path, replace, callback = (error)-> ) ->
|
||||||
|
FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)->
|
||||||
|
if !isSafe
|
||||||
|
logger.log user_id:user_id, project_id:project_id, folder_id:folder_id, path:path, "add folder is from symlink, stopping insert"
|
||||||
|
return callback("path is symlink")
|
||||||
|
EditorController.addFolderWithoutLock project_id, folder_id, name, "upload", (error, new_folder) =>
|
||||||
|
return callback(error) if error?
|
||||||
|
FileSystemImportManager.addFolderContents user_id, project_id, new_folder._id, path, replace, (error) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
callback null, new_folder
|
||||||
|
|
||||||
|
addFolderContents: (user_id, project_id, parent_folder_id, folderPath, replace, callback = (error)-> ) ->
|
||||||
|
FileSystemImportManager._isSafeOnFileSystem folderPath, (err, isSafe)->
|
||||||
|
if !isSafe
|
||||||
|
logger.log user_id:user_id, project_id:project_id, parent_folder_id:parent_folder_id, folderPath:folderPath, "add folder contents is from symlink, stopping insert"
|
||||||
|
return callback("path is symlink")
|
||||||
|
fs.readdir folderPath, (error, entries = []) =>
|
||||||
|
return callback(error) if error?
|
||||||
|
jobs = _.map entries, (entry) =>
|
||||||
|
(callback) =>
|
||||||
|
FileTypeManager.shouldIgnore entry, (error, ignore) =>
|
||||||
|
return callback(error) if error?
|
||||||
|
if !ignore
|
||||||
|
FileSystemImportManager.addEntity user_id, project_id, parent_folder_id, entry, "#{folderPath}/#{entry}", replace, callback
|
||||||
|
else
|
||||||
|
callback()
|
||||||
|
async.parallelLimit jobs, 5, callback
|
||||||
|
|
||||||
|
addEntity: (user_id, project_id, folder_id, name, path, replace, callback = (error, entity)-> ) ->
|
||||||
|
FileSystemImportManager._isSafeOnFileSystem path, (err, isSafe)->
|
||||||
|
if !isSafe
|
||||||
|
logger.log user_id:user_id, project_id:project_id, folder_id:folder_id, path:path, "add entry is from symlink, stopping insert"
|
||||||
|
return callback("path is symlink")
|
||||||
|
|
||||||
|
FileTypeManager.isDirectory path, (error, isDirectory) =>
|
||||||
|
return callback(error) if error?
|
||||||
|
if isDirectory
|
||||||
|
FileSystemImportManager.addFolder user_id, project_id, folder_id, name, path, replace, callback
|
||||||
|
else
|
||||||
|
FileTypeManager.isBinary name, path, (error, isBinary) =>
|
||||||
|
return callback(error) if error?
|
||||||
|
if isBinary
|
||||||
|
FileSystemImportManager.addFile user_id, project_id, folder_id, name, path, replace, callback
|
||||||
|
else
|
||||||
|
FileSystemImportManager.addDoc user_id, project_id, folder_id, name, path, replace, callback
|
||||||
|
|
||||||
|
|
||||||
|
_isSafeOnFileSystem: (path, callback = (err, isSafe)->)->
|
||||||
|
fs.lstat path, (err, stat)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, "error with path symlink check"
|
||||||
|
return callback(err)
|
||||||
|
isSafe = stat.isFile() or stat.isDirectory()
|
||||||
|
callback(err, isSafe)
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,9 @@ module.exports = ProjectUploadController =
|
||||||
if !name? or name.length == 0 or name.length > 150
|
if !name? or name.length == 0 or name.length > 150
|
||||||
logger.err project_id:project_id, name:name, "bad name when trying to upload file"
|
logger.err project_id:project_id, name:name, "bad name when trying to upload file"
|
||||||
return res.send success: false
|
return res.send success: false
|
||||||
FileSystemImportManager.addEntity project_id, folder_id, name, path, true, (error, entity) ->
|
logger.log folder_id:folder_id, project_id:project_id, "getting upload file request"
|
||||||
|
user_id = req.session.user._id
|
||||||
|
FileSystemImportManager.addEntity user_id, project_id, folder_id, name, path, true, (error, entity) ->
|
||||||
fs.unlink path, ->
|
fs.unlink path, ->
|
||||||
timer.done()
|
timer.done()
|
||||||
if error?
|
if error?
|
||||||
|
|
|
@ -9,19 +9,21 @@ module.exports = ProjectUploadHandler =
|
||||||
createProjectFromZipArchive: (owner_id, name, zipPath, callback = (error, project) ->) ->
|
createProjectFromZipArchive: (owner_id, name, zipPath, callback = (error, project) ->) ->
|
||||||
ProjectCreationHandler.createBlankProject owner_id, name, (error, project) =>
|
ProjectCreationHandler.createBlankProject owner_id, name, (error, project) =>
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
@insertZipArchiveIntoFolder project._id, project.rootFolder[0]._id, zipPath, (error) ->
|
@insertZipArchiveIntoFolder owner_id, project._id, project.rootFolder[0]._id, zipPath, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
ProjectRootDocManager.setRootDocAutomatically project._id, (error) ->
|
ProjectRootDocManager.setRootDocAutomatically project._id, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
callback(error, project)
|
callback(error, project)
|
||||||
|
|
||||||
insertZipArchiveIntoFolder: (project_id, folder_id, path, callback = (error) ->) ->
|
insertZipArchiveIntoFolder: (owner_id, project_id, folder_id, path, callback = (error) ->) ->
|
||||||
destination = @_getDestinationDirectory path
|
destination = @_getDestinationDirectory path
|
||||||
ArchiveManager.extractZipArchive path, destination, (error) ->
|
ArchiveManager.extractZipArchive path, destination, (error) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
FileSystemImportManager.addFolderContents project_id, folder_id, destination, false, (error) ->
|
ArchiveManager.findTopLevelDirectory destination, (error, topLevelDestination) ->
|
||||||
return callback(error) if error?
|
return callback(error) if error?
|
||||||
rimraf(destination, callback)
|
FileSystemImportManager.addFolderContents owner_id, project_id, folder_id, topLevelDestination, false, (error) ->
|
||||||
|
return callback(error) if error?
|
||||||
|
rimraf(destination, callback)
|
||||||
|
|
||||||
_getDestinationDirectory: (source) ->
|
_getDestinationDirectory: (source) ->
|
||||||
return path.join(path.dirname(source), "#{path.basename(source, ".zip")}-#{Date.now()}")
|
return path.join(path.dirname(source), "#{path.basename(source, ".zip")}-#{Date.now()}")
|
||||||
|
|
|
@ -13,8 +13,8 @@ module.exports =
|
||||||
RateLimiterMiddlewear.rateLimit({
|
RateLimiterMiddlewear.rateLimit({
|
||||||
endpointName: "file-upload"
|
endpointName: "file-upload"
|
||||||
params: ["Project_id"]
|
params: ["Project_id"]
|
||||||
maxRequests: 100
|
maxRequests: 200
|
||||||
timeInterval: 60 * 20
|
timeInterval: 60 * 30
|
||||||
}),
|
}),
|
||||||
SecurityManager.requestCanModifyProject,
|
SecurityManager.requestCanModifyProject,
|
||||||
ProjectUploadController.uploadFile
|
ProjectUploadController.uploadFile
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
UserHandler = require("./UserHandler")
|
||||||
UserDeleter = require("./UserDeleter")
|
UserDeleter = require("./UserDeleter")
|
||||||
UserLocator = require("./UserLocator")
|
UserLocator = require("./UserLocator")
|
||||||
User = require("../../models/User").User
|
User = require("../../models/User").User
|
||||||
|
@ -8,12 +9,9 @@ metrics = require("../../infrastructure/Metrics")
|
||||||
Url = require("url")
|
Url = require("url")
|
||||||
AuthenticationManager = require("../Authentication/AuthenticationManager")
|
AuthenticationManager = require("../Authentication/AuthenticationManager")
|
||||||
UserUpdater = require("./UserUpdater")
|
UserUpdater = require("./UserUpdater")
|
||||||
EmailHandler = require("../Email/EmailHandler")
|
|
||||||
OneTimeTokenHandler = require "../Security/OneTimeTokenHandler"
|
|
||||||
settings = require "settings-sharelatex"
|
settings = require "settings-sharelatex"
|
||||||
crypto = require "crypto"
|
|
||||||
|
|
||||||
module.exports =
|
module.exports = UserController =
|
||||||
|
|
||||||
deleteUser: (req, res)->
|
deleteUser: (req, res)->
|
||||||
user_id = req.session.user._id
|
user_id = req.session.user._id
|
||||||
|
@ -66,11 +64,18 @@ module.exports =
|
||||||
if err?
|
if err?
|
||||||
logger.err err:err, user_id:user_id, newEmail:newEmail, "problem updaing users email address"
|
logger.err err:err, user_id:user_id, newEmail:newEmail, "problem updaing users email address"
|
||||||
if err.message == "alread_exists"
|
if err.message == "alread_exists"
|
||||||
message = req.i18n.translate("alread_exists")
|
message = req.i18n.translate("email_already_registered")
|
||||||
else
|
else
|
||||||
message = req.i18n.translate("problem_changing_email_address")
|
message = req.i18n.translate("problem_changing_email_address")
|
||||||
return res.send 500, {message:message}
|
return res.send 500, {message:message}
|
||||||
res.sendStatus(200)
|
User.findById user_id, (err, user)->
|
||||||
|
if err?
|
||||||
|
logger.err err:err, user_id:user_id, "error getting user for email update"
|
||||||
|
return res.send 500
|
||||||
|
UserHandler.populateGroupLicenceInvite user, (err)-> #need to refresh this in the background
|
||||||
|
if err?
|
||||||
|
logger.err err:err, "error populateGroupLicenceInvite"
|
||||||
|
res.sendStatus(200)
|
||||||
|
|
||||||
logout : (req, res)->
|
logout : (req, res)->
|
||||||
metrics.inc "user.logout"
|
metrics.inc "user.logout"
|
||||||
|
@ -85,32 +90,12 @@ module.exports =
|
||||||
if !email? or email == ""
|
if !email? or email == ""
|
||||||
res.sendStatus 422 # Unprocessable Entity
|
res.sendStatus 422 # Unprocessable Entity
|
||||||
return
|
return
|
||||||
logger.log {email}, "registering new user"
|
UserRegistrationHandler.registerNewUserAndSendActivationEmail email, (error, user, setNewPasswordUrl) ->
|
||||||
UserRegistrationHandler.registerNewUser {
|
return next(error) if error?
|
||||||
email: email
|
res.json {
|
||||||
password: crypto.randomBytes(32).toString("hex")
|
email: user.email
|
||||||
}, (err, user)->
|
setNewPasswordUrl: setNewPasswordUrl
|
||||||
if err? and err?.message != "EmailAlreadyRegistered"
|
}
|
||||||
return next(err)
|
|
||||||
|
|
||||||
if err?.message == "EmailAlreadyRegistered"
|
|
||||||
logger.log {email}, "user already exists, resending welcome email"
|
|
||||||
|
|
||||||
ONE_WEEK = 7 * 24 * 60 * 60 # seconds
|
|
||||||
OneTimeTokenHandler.getNewToken user._id, { expiresIn: ONE_WEEK }, (err, token)->
|
|
||||||
return next(err) if err?
|
|
||||||
|
|
||||||
setNewPasswordUrl = "#{settings.siteUrl}/user/password/set?passwordResetToken=#{token}&email=#{encodeURIComponent(email)}"
|
|
||||||
|
|
||||||
EmailHandler.sendEmail "registered", {
|
|
||||||
to: user.email
|
|
||||||
setNewPasswordUrl: setNewPasswordUrl
|
|
||||||
}, () ->
|
|
||||||
|
|
||||||
res.json {
|
|
||||||
email: user.email
|
|
||||||
setNewPasswordUrl: setNewPasswordUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
changePassword : (req, res, next = (error) ->)->
|
changePassword : (req, res, next = (error) ->)->
|
||||||
metrics.inc "user.password-change"
|
metrics.inc "user.password-change"
|
||||||
|
|
26
services/web/app/coffee/Features/User/UserHandler.coffee
Normal file
26
services/web/app/coffee/Features/User/UserHandler.coffee
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
SubscriptionDomainHandler = require("../Subscription/SubscriptionDomainHandler")
|
||||||
|
NotificationsBuilder = require("../Notifications/NotificationsBuilder")
|
||||||
|
SubscriptionGroupHandler = require("../Subscription/SubscriptionGroupHandler")
|
||||||
|
logger = require("logger-sharelatex")
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = UserHandler =
|
||||||
|
|
||||||
|
populateGroupLicenceInvite: (user, callback)->
|
||||||
|
logger.log user_id:user._id, "populating any potential group licence invites"
|
||||||
|
licence = SubscriptionDomainHandler.getLicenceUserCanJoin user
|
||||||
|
if !licence?
|
||||||
|
return callback()
|
||||||
|
|
||||||
|
SubscriptionGroupHandler.isUserPartOfGroup user._id, licence.subscription_id, (err, alreadyPartOfGroup)->
|
||||||
|
if err?
|
||||||
|
return callback(err)
|
||||||
|
else if alreadyPartOfGroup
|
||||||
|
logger.log user_id:user._id, "user already part of group, not creating notifcation for them"
|
||||||
|
return callback()
|
||||||
|
else
|
||||||
|
NotificationsBuilder.groupPlan(user, licence).create(callback)
|
||||||
|
|
||||||
|
setupLoginData: (user, callback = ->)->
|
||||||
|
@populateGroupLicenceInvite user, callback
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
UserLocator = require("./UserLocator")
|
UserLocator = require("./UserLocator")
|
||||||
|
UserGetter = require("./UserGetter")
|
||||||
|
ErrorController = require("../Errors/ErrorController")
|
||||||
logger = require("logger-sharelatex")
|
logger = require("logger-sharelatex")
|
||||||
Settings = require("settings-sharelatex")
|
Settings = require("settings-sharelatex")
|
||||||
fs = require('fs')
|
fs = require('fs')
|
||||||
|
@ -20,11 +22,33 @@ module.exports =
|
||||||
sharedProjectData: sharedProjectData
|
sharedProjectData: sharedProjectData
|
||||||
newTemplateData: newTemplateData
|
newTemplateData: newTemplateData
|
||||||
new_email:req.query.new_email || ""
|
new_email:req.query.new_email || ""
|
||||||
|
|
||||||
|
activateAccountPage: (req, res) ->
|
||||||
|
# An 'activation' is actually just a password reset on an account that
|
||||||
|
# was set with a random password originally.
|
||||||
|
if !req.query?.user_id? or !req.query?.token?
|
||||||
|
return ErrorController.notFound(req, res)
|
||||||
|
|
||||||
|
UserGetter.getUser req.query.user_id, {email: 1, loginCount: 1}, (error, user) ->
|
||||||
|
return next(error) if error?
|
||||||
|
if !user
|
||||||
|
return ErrorController.notFound(req, res)
|
||||||
|
if user.loginCount > 0
|
||||||
|
# Already seen this user, so account must be activate
|
||||||
|
# This lets users keep clicking the 'activate' link in their email
|
||||||
|
# as a way to log in which, if I know our users, they will.
|
||||||
|
res.redirect "/login?email=#{encodeURIComponent(user.email)}"
|
||||||
|
else
|
||||||
|
res.render 'user/activate',
|
||||||
|
title: 'activate_account'
|
||||||
|
email: user.email,
|
||||||
|
token: req.query.token
|
||||||
|
|
||||||
loginPage : (req, res)->
|
loginPage : (req, res)->
|
||||||
res.render 'user/login',
|
res.render 'user/login',
|
||||||
title: 'login',
|
title: 'login',
|
||||||
redir: req.query.redir
|
redir: req.query.redir,
|
||||||
|
email: req.query.email
|
||||||
|
|
||||||
settingsPage : (req, res, next)->
|
settingsPage : (req, res, next)->
|
||||||
logger.log user: req.session.user, "loading settings page"
|
logger.log user: req.session.user, "loading settings page"
|
||||||
|
|
|
@ -5,8 +5,12 @@ AuthenticationManager = require("../Authentication/AuthenticationManager")
|
||||||
NewsLetterManager = require("../Newsletter/NewsletterManager")
|
NewsLetterManager = require("../Newsletter/NewsletterManager")
|
||||||
async = require("async")
|
async = require("async")
|
||||||
logger = require("logger-sharelatex")
|
logger = require("logger-sharelatex")
|
||||||
|
crypto = require("crypto")
|
||||||
|
EmailHandler = require("../Email/EmailHandler")
|
||||||
|
OneTimeTokenHandler = require "../Security/OneTimeTokenHandler"
|
||||||
|
settings = require "settings-sharelatex"
|
||||||
|
|
||||||
module.exports =
|
module.exports = UserRegistrationHandler =
|
||||||
validateEmail : (email) ->
|
validateEmail : (email) ->
|
||||||
re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\ ".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA -Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\ ".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA -Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
|
||||||
return re.test(email)
|
return re.test(email)
|
||||||
|
@ -58,6 +62,31 @@ module.exports =
|
||||||
], (err)->
|
], (err)->
|
||||||
logger.log user: user, "registered"
|
logger.log user: user, "registered"
|
||||||
callback(err, user)
|
callback(err, user)
|
||||||
|
|
||||||
|
registerNewUserAndSendActivationEmail: (email, callback = (error, user, setNewPasswordUrl) ->) ->
|
||||||
|
logger.log {email}, "registering new user"
|
||||||
|
UserRegistrationHandler.registerNewUser {
|
||||||
|
email: email
|
||||||
|
password: crypto.randomBytes(32).toString("hex")
|
||||||
|
}, (err, user)->
|
||||||
|
if err? and err?.message != "EmailAlreadyRegistered"
|
||||||
|
return callback(err)
|
||||||
|
|
||||||
|
if err?.message == "EmailAlreadyRegistered"
|
||||||
|
logger.log {email}, "user already exists, resending welcome email"
|
||||||
|
|
||||||
|
ONE_WEEK = 7 * 24 * 60 * 60 # seconds
|
||||||
|
OneTimeTokenHandler.getNewToken user._id, { expiresIn: ONE_WEEK }, (err, token)->
|
||||||
|
return callback(err) if err?
|
||||||
|
|
||||||
|
setNewPasswordUrl = "#{settings.siteUrl}/user/activate?token=#{token}&user_id=#{user._id}"
|
||||||
|
|
||||||
|
EmailHandler.sendEmail "registered", {
|
||||||
|
to: user.email
|
||||||
|
setNewPasswordUrl: setNewPasswordUrl
|
||||||
|
}, () ->
|
||||||
|
|
||||||
|
callback null, user, setNewPasswordUrl
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,9 @@ for path in [
|
||||||
"#{jsPath}main.js",
|
"#{jsPath}main.js",
|
||||||
"#{jsPath}libs.js",
|
"#{jsPath}libs.js",
|
||||||
"#{jsPath}ace/ace.js",
|
"#{jsPath}ace/ace.js",
|
||||||
"#{jsPath}libs/pdfjs-1.0.1040/pdf.js",
|
"#{jsPath}libs/pdfjs-1.3.91/pdf.js",
|
||||||
"#{jsPath}libs/pdfjs-1.0.1040/pdf.worker.js",
|
"#{jsPath}libs/pdfjs-1.3.91/pdf.worker.js",
|
||||||
"#{jsPath}libs/pdfjs-1.0.1040/compatibility.js",
|
"#{jsPath}libs/pdfjs-1.3.91/compatibility.js",
|
||||||
"/stylesheets/style.css"
|
"/stylesheets/style.css"
|
||||||
]
|
]
|
||||||
filePath = Path.join __dirname, "../../../", "public#{path}"
|
filePath = Path.join __dirname, "../../../", "public#{path}"
|
||||||
|
|
|
@ -31,6 +31,7 @@ ProjectSchema = new Schema
|
||||||
description : {type:String, default:''}
|
description : {type:String, default:''}
|
||||||
archived : { type: Boolean }
|
archived : { type: Boolean }
|
||||||
deletedDocs : [DeletedDocSchema]
|
deletedDocs : [DeletedDocSchema]
|
||||||
|
imageName : { type: String }
|
||||||
|
|
||||||
ProjectSchema.statics.getProject = (project_or_id, fields, callback)->
|
ProjectSchema.statics.getProject = (project_or_id, fields, callback)->
|
||||||
if project_or_id._id?
|
if project_or_id._id?
|
||||||
|
@ -65,51 +66,7 @@ ProjectSchema.statics.findAllUsersProjects = (user_id, requiredFields, callback)
|
||||||
this.find {readOnly_refs:user_id}, requiredFields, (err, readOnlyProjects)=>
|
this.find {readOnly_refs:user_id}, requiredFields, (err, readOnlyProjects)=>
|
||||||
callback(err, projects, collabertions, readOnlyProjects)
|
callback(err, projects, collabertions, readOnlyProjects)
|
||||||
|
|
||||||
sanitizeTypeOfElement = (elementType)->
|
|
||||||
lastChar = elementType.slice -1
|
|
||||||
if lastChar != "s"
|
|
||||||
elementType +="s"
|
|
||||||
if elementType == "files"
|
|
||||||
elementType = "fileRefs"
|
|
||||||
return elementType
|
|
||||||
|
|
||||||
ProjectSchema.statics.putElement = (project_id, folder_id, element, type, callback)->
|
|
||||||
if !element?
|
|
||||||
e = new Error("no element passed to be inserted")
|
|
||||||
logger.err project_id:project_id, folder_id:folder_id, element:element, type:type, "failed trying to insert element as it was null"
|
|
||||||
return callback(e)
|
|
||||||
type = sanitizeTypeOfElement type
|
|
||||||
this.findById project_id, (err, project)=>
|
|
||||||
if err?
|
|
||||||
callback(err)
|
|
||||||
if !folder_id?
|
|
||||||
folder_id = project.rootFolder[0]._id
|
|
||||||
require('../Features/Project/ProjectLocator').findElement {project:project, element_id:folder_id, type:"folders"}, (err, folder, path)=>
|
|
||||||
newPath =
|
|
||||||
fileSystem: "#{path.fileSystem}/#{element.name}"
|
|
||||||
mongo: path.mongo # TODO: This is not correct
|
|
||||||
if err?
|
|
||||||
callback(err)
|
|
||||||
logger.log project_id: project_id, element_id: element._id, fileType: type, folder_id: folder_id, "adding element to project"
|
|
||||||
id = element._id+''
|
|
||||||
element._id = concreteObjectId(id)
|
|
||||||
conditions = _id:project_id
|
|
||||||
mongopath = "#{path.mongo}.#{type}"
|
|
||||||
update = "$push":{}
|
|
||||||
update["$push"][mongopath] = element
|
|
||||||
this.update conditions, update, {}, (err)->
|
|
||||||
if(err)
|
|
||||||
logger.err err: err, project: project, 'error saving in putElement project'
|
|
||||||
if callback?
|
|
||||||
callback(err, {path:newPath})
|
|
||||||
|
|
||||||
getIndexOf = (searchEntity, id)->
|
|
||||||
length = searchEntity.length
|
|
||||||
count = 0
|
|
||||||
while(count < length)
|
|
||||||
if searchEntity[count]._id+"" == id+""
|
|
||||||
return count
|
|
||||||
count++
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,8 @@ UserSchema = new Schema
|
||||||
github: { type:Boolean, default: Settings.defaultFeatures.github }
|
github: { type:Boolean, default: Settings.defaultFeatures.github }
|
||||||
compileTimeout: { type:Number, default: Settings.defaultFeatures.compileTimeout }
|
compileTimeout: { type:Number, default: Settings.defaultFeatures.compileTimeout }
|
||||||
compileGroup: { type:String, default: Settings.defaultFeatures.compileGroup }
|
compileGroup: { type:String, default: Settings.defaultFeatures.compileGroup }
|
||||||
|
templates: { type:Boolean, default: Settings.defaultFeatures.templates }
|
||||||
|
references: { type:Boolean, default: Settings.defaultFeatures.references }
|
||||||
}
|
}
|
||||||
featureSwitches : {
|
featureSwitches : {
|
||||||
pdfng: { type: Boolean }
|
pdfng: { type: Boolean }
|
||||||
|
|
|
@ -16,6 +16,7 @@ ReferalController = require('./Features/Referal/ReferalController')
|
||||||
ReferalMiddleware = require('./Features/Referal/ReferalMiddleware')
|
ReferalMiddleware = require('./Features/Referal/ReferalMiddleware')
|
||||||
AuthenticationController = require('./Features/Authentication/AuthenticationController')
|
AuthenticationController = require('./Features/Authentication/AuthenticationController')
|
||||||
TagsController = require("./Features/Tags/TagsController")
|
TagsController = require("./Features/Tags/TagsController")
|
||||||
|
NotificationsController = require("./Features/Notifications/NotificationsController")
|
||||||
CollaboratorsRouter = require('./Features/Collaborators/CollaboratorsRouter')
|
CollaboratorsRouter = require('./Features/Collaborators/CollaboratorsRouter')
|
||||||
UserInfoController = require('./Features/User/UserInfoController')
|
UserInfoController = require('./Features/User/UserInfoController')
|
||||||
UserController = require("./Features/User/UserController")
|
UserController = require("./Features/User/UserController")
|
||||||
|
@ -37,6 +38,7 @@ RateLimiterMiddlewear = require('./Features/Security/RateLimiterMiddlewear')
|
||||||
RealTimeProxyRouter = require('./Features/RealTimeProxy/RealTimeProxyRouter')
|
RealTimeProxyRouter = require('./Features/RealTimeProxy/RealTimeProxyRouter')
|
||||||
InactiveProjectController = require("./Features/InactiveData/InactiveProjectController")
|
InactiveProjectController = require("./Features/InactiveData/InactiveProjectController")
|
||||||
ContactRouter = require("./Features/Contacts/ContactRouter")
|
ContactRouter = require("./Features/Contacts/ContactRouter")
|
||||||
|
ReferencesController = require('./Features/References/ReferencesController')
|
||||||
|
|
||||||
logger = require("logger-sharelatex")
|
logger = require("logger-sharelatex")
|
||||||
_ = require("underscore")
|
_ = require("underscore")
|
||||||
|
@ -46,7 +48,7 @@ module.exports = class Router
|
||||||
if !Settings.allowPublicAccess
|
if !Settings.allowPublicAccess
|
||||||
webRouter.all '*', AuthenticationController.requireGlobalLogin
|
webRouter.all '*', AuthenticationController.requireGlobalLogin
|
||||||
|
|
||||||
|
|
||||||
webRouter.get '/login', UserPagesController.loginPage
|
webRouter.get '/login', UserPagesController.loginPage
|
||||||
AuthenticationController.addEndpointToLoginWhitelist '/login'
|
AuthenticationController.addEndpointToLoginWhitelist '/login'
|
||||||
|
|
||||||
|
@ -67,16 +69,18 @@ module.exports = class Router
|
||||||
StaticPagesRouter.apply(webRouter, apiRouter)
|
StaticPagesRouter.apply(webRouter, apiRouter)
|
||||||
RealTimeProxyRouter.apply(webRouter, apiRouter)
|
RealTimeProxyRouter.apply(webRouter, apiRouter)
|
||||||
ContactRouter.apply(webRouter, apiRouter)
|
ContactRouter.apply(webRouter, apiRouter)
|
||||||
|
|
||||||
Modules.applyRouter(webRouter, apiRouter)
|
Modules.applyRouter(webRouter, apiRouter)
|
||||||
|
|
||||||
|
|
||||||
if Settings.enableSubscriptions
|
if Settings.enableSubscriptions
|
||||||
webRouter.get '/user/bonus', AuthenticationController.requireLogin(), ReferalMiddleware.getUserReferalId, ReferalController.bonus
|
webRouter.get '/user/bonus', AuthenticationController.requireLogin(), ReferalMiddleware.getUserReferalId, ReferalController.bonus
|
||||||
|
|
||||||
webRouter.get '/blog', BlogController.getIndexPage
|
webRouter.get '/blog', BlogController.getIndexPage
|
||||||
webRouter.get '/blog/*', BlogController.getPage
|
webRouter.get '/blog/*', BlogController.getPage
|
||||||
|
|
||||||
|
webRouter.get '/user/activate', UserPagesController.activateAccountPage
|
||||||
|
|
||||||
webRouter.get '/user/settings', AuthenticationController.requireLogin(), UserPagesController.settingsPage
|
webRouter.get '/user/settings', AuthenticationController.requireLogin(), UserPagesController.settingsPage
|
||||||
webRouter.post '/user/settings', AuthenticationController.requireLogin(), UserController.updateUserSettings
|
webRouter.post '/user/settings', AuthenticationController.requireLogin(), UserController.updateUserSettings
|
||||||
webRouter.post '/user/password/update', AuthenticationController.requireLogin(), UserController.changePassword
|
webRouter.post '/user/password/update', AuthenticationController.requireLogin(), UserController.changePassword
|
||||||
|
@ -128,8 +132,15 @@ module.exports = class Router
|
||||||
webRouter.get '/Project/:Project_id/download/zip', SecurityManager.requestCanAccessProject, ProjectDownloadsController.downloadProject
|
webRouter.get '/Project/:Project_id/download/zip', SecurityManager.requestCanAccessProject, ProjectDownloadsController.downloadProject
|
||||||
webRouter.get '/project/download/zip', SecurityManager.requestCanAccessMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
|
webRouter.get '/project/download/zip', SecurityManager.requestCanAccessMultipleProjects, ProjectDownloadsController.downloadMultipleProjects
|
||||||
|
|
||||||
webRouter.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
|
webRouter.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
|
||||||
webRouter.post '/project/:project_id/tag', AuthenticationController.requireLogin(), TagsController.processTagsUpdate
|
webRouter.post '/tag', AuthenticationController.requireLogin(), TagsController.createTag
|
||||||
|
webRouter.post '/tag/:tag_id/rename', AuthenticationController.requireLogin(), TagsController.renameTag
|
||||||
|
webRouter.delete '/tag/:tag_id', AuthenticationController.requireLogin(), TagsController.deleteTag
|
||||||
|
webRouter.post '/tag/:tag_id/project/:project_id', AuthenticationController.requireLogin(), TagsController.addProjectToTag
|
||||||
|
webRouter.delete '/tag/:tag_id/project/:project_id', AuthenticationController.requireLogin(), TagsController.removeProjectFromTag
|
||||||
|
|
||||||
|
webRouter.get '/notifications', AuthenticationController.requireLogin(), NotificationsController.getAllUnreadNotifications
|
||||||
|
webRouter.delete '/notifications/:notification_id', AuthenticationController.requireLogin(), NotificationsController.markNotificationAsRead
|
||||||
|
|
||||||
# Deprecated in favour of /internal/project/:project_id but still used by versioning
|
# Deprecated in favour of /internal/project/:project_id but still used by versioning
|
||||||
apiRouter.get '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails
|
apiRouter.get '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails
|
||||||
|
@ -156,7 +167,7 @@ module.exports = class Router
|
||||||
|
|
||||||
apiRouter.post '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.mergeUpdate
|
apiRouter.post '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.mergeUpdate
|
||||||
apiRouter.delete '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.deleteUpdate
|
apiRouter.delete '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.deleteUpdate
|
||||||
|
|
||||||
apiRouter.post '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.updateProjectContents
|
apiRouter.post '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.updateProjectContents
|
||||||
apiRouter.delete '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.deleteProjectContents
|
apiRouter.delete '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.deleteProjectContents
|
||||||
|
|
||||||
|
@ -165,9 +176,12 @@ module.exports = class Router
|
||||||
|
|
||||||
webRouter.get "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.getMessages
|
webRouter.get "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.getMessages
|
||||||
webRouter.post "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.sendMessage
|
webRouter.post "/project/:Project_id/messages", SecurityManager.requestCanAccessProject, ChatController.sendMessage
|
||||||
|
|
||||||
webRouter.get /learn(\/.*)?/, WikiController.getPage
|
webRouter.get /learn(\/.*)?/, WikiController.getPage
|
||||||
|
|
||||||
|
webRouter.post "/project/:Project_id/references/index", SecurityManager.requestCanAccessProject, ReferencesController.index
|
||||||
|
webRouter.post "/project/:Project_id/references/indexAll", SecurityManager.requestCanAccessProject, ReferencesController.indexAll
|
||||||
|
|
||||||
#Admin Stuff
|
#Admin Stuff
|
||||||
webRouter.get '/admin', SecurityManager.requestIsAdmin, AdminController.index
|
webRouter.get '/admin', SecurityManager.requestIsAdmin, AdminController.index
|
||||||
webRouter.get '/admin/user', SecurityManager.requestIsAdmin, (req, res)-> res.redirect("/admin/register") #this gets removed by admin-panel addon
|
webRouter.get '/admin/user', SecurityManager.requestIsAdmin, (req, res)-> res.redirect("/admin/register") #this gets removed by admin-panel addon
|
||||||
|
@ -186,7 +200,7 @@ module.exports = class Router
|
||||||
|
|
||||||
apiRouter.get '/status', (req,res)->
|
apiRouter.get '/status', (req,res)->
|
||||||
res.send("websharelatex is up")
|
res.send("websharelatex is up")
|
||||||
|
|
||||||
|
|
||||||
webRouter.get '/health_check', HealthCheckController.check
|
webRouter.get '/health_check', HealthCheckController.check
|
||||||
webRouter.get '/health_check/redis', HealthCheckController.checkRedis
|
webRouter.get '/health_check/redis', HealthCheckController.checkRedis
|
||||||
|
@ -222,4 +236,4 @@ module.exports = class Router
|
||||||
logger.error err: req.body.error, meta: req.body.meta, "client side error"
|
logger.error err: req.body.error, meta: req.body.meta, "client side error"
|
||||||
res.sendStatus(204)
|
res.sendStatus(204)
|
||||||
|
|
||||||
webRouter.get '*', ErrorController.notFound
|
webRouter.get '*', ErrorController.notFound
|
|
@ -53,7 +53,13 @@ block content
|
||||||
|
|
||||||
include ./editor/share
|
include ./editor/share
|
||||||
|
|
||||||
#ide-body(ng-cloak, layout="main", ng-hide="state.loading", resize-on="layout:chat:resize")
|
#ide-body(
|
||||||
|
ng-cloak,
|
||||||
|
layout="main",
|
||||||
|
ng-hide="state.loading",
|
||||||
|
resize-on="layout:chat:resize",
|
||||||
|
minimum-restore-size-west="130"
|
||||||
|
)
|
||||||
.ui-layout-west
|
.ui-layout-west
|
||||||
include ./editor/file-tree
|
include ./editor/file-tree
|
||||||
|
|
||||||
|
@ -76,7 +82,7 @@ block content
|
||||||
ng-click="done()"
|
ng-click="done()"
|
||||||
) ×
|
) ×
|
||||||
h3 {{ title }}
|
h3 {{ title }}
|
||||||
.modal-body {{ message }}
|
.modal-body(ng-bind-html="message")
|
||||||
.modal-footer
|
.modal-footer
|
||||||
button.btn.btn-info(ng-click="done()") #{translate("ok")}
|
button.btn.btn-info(ng-click="done()") #{translate("ok")}
|
||||||
|
|
||||||
|
@ -95,13 +101,13 @@ block content
|
||||||
"paths" : {
|
"paths" : {
|
||||||
"mathjax": "/js/libs/mathjax/MathJax.js?config=TeX-AMS_HTML",
|
"mathjax": "/js/libs/mathjax/MathJax.js?config=TeX-AMS_HTML",
|
||||||
"moment": "libs/moment-2.7.0",
|
"moment": "libs/moment-2.7.0",
|
||||||
"libs/pdf": "libs/pdfjs-1.0.1040/pdf"
|
"libs/pdf": "libs/pdfjs-1.3.91/pdf"
|
||||||
},
|
},
|
||||||
"urlArgs" : "fingerprint=#{fingerprint(jsPath + 'ide.js')}-#{fingerprint(jsPath + 'libs.js')}",
|
"urlArgs" : "fingerprint=#{fingerprint(jsPath + 'ide.js')}-#{fingerprint(jsPath + 'libs.js')}",
|
||||||
"waitSeconds": 0,
|
"waitSeconds": 0,
|
||||||
"shim": {
|
"shim": {
|
||||||
"libs/pdf": {
|
"libs/pdf": {
|
||||||
deps: ["libs/pdfjs-1.0.1040/compatibility"]
|
deps: ["libs/pdfjs-1.3.91/compatibility"]
|
||||||
},
|
},
|
||||||
"ace/ext-searchbox": {
|
"ace/ext-searchbox": {
|
||||||
deps: ["ace/ace"]
|
deps: ["ace/ace"]
|
||||||
|
@ -120,7 +126,7 @@ block content
|
||||||
|
|
||||||
- locals.suppressDefaultJs = true
|
- locals.suppressDefaultJs = true
|
||||||
|
|
||||||
- var pdfPath = 'libs/pdfjs-1.0.1040/pdf.worker.js'
|
- var pdfPath = 'libs/pdfjs-1.3.91/pdf.worker.js'
|
||||||
- var fingerprintedPath = fingerprint(jsPath+pdfPath)
|
- var fingerprintedPath = fingerprint(jsPath+pdfPath)
|
||||||
- var pdfJsWorkerPath = jsPath+pdfPath+'?fingerprint='+fingerprintedPath
|
- var pdfJsWorkerPath = jsPath+pdfPath+'?fingerprint='+fingerprintedPath
|
||||||
script(type='text/javascript').
|
script(type='text/javascript').
|
||||||
|
|
|
@ -6,6 +6,7 @@ div.full-size(
|
||||||
resize-on="layout:main:resize"
|
resize-on="layout:main:resize"
|
||||||
resize-proportionally="true"
|
resize-proportionally="true"
|
||||||
initial-size-east="'50%'"
|
initial-size-east="'50%'"
|
||||||
|
minimum-restore-size-east="300"
|
||||||
)
|
)
|
||||||
.ui-layout-center
|
.ui-layout-center
|
||||||
.loading-panel(ng-show="!editor.sharejs_doc || editor.opening")
|
.loading-panel(ng-show="!editor.sharejs_doc || editor.opening")
|
||||||
|
@ -19,6 +20,7 @@ div.full-size(
|
||||||
keybindings="settings.mode",
|
keybindings="settings.mode",
|
||||||
font-size="settings.fontSize",
|
font-size="settings.fontSize",
|
||||||
auto-complete="settings.autoComplete",
|
auto-complete="settings.autoComplete",
|
||||||
|
spell-check="true",
|
||||||
spell-check-language="project.spellCheckLanguage",
|
spell-check-language="project.spellCheckLanguage",
|
||||||
highlights="onlineUserCursorHighlights[editor.open_doc_id]"
|
highlights="onlineUserCursorHighlights[editor.open_doc_id]"
|
||||||
show-print-margin="false",
|
show-print-margin="false",
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
aside#file-tree(ng-controller="FileTreeController").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-small.toolbar-alt(ng-if="permissions.write")
|
||||||
a(
|
a(
|
||||||
href,
|
href,
|
||||||
|
@ -27,7 +27,8 @@ aside#file-tree(ng-controller="FileTreeController").full-size
|
||||||
href,
|
href,
|
||||||
ng-click="startRenamingSelected()",
|
ng-click="startRenamingSelected()",
|
||||||
tooltip="#{translate('rename')}",
|
tooltip="#{translate('rename')}",
|
||||||
tooltip-placement="bottom"
|
tooltip-placement="bottom",
|
||||||
|
ng-show="multiSelectedCount == 0"
|
||||||
)
|
)
|
||||||
i.fa.fa-pencil
|
i.fa.fa-pencil
|
||||||
a(
|
a(
|
||||||
|
@ -45,26 +46,24 @@ aside#file-tree(ng-controller="FileTreeController").full-size
|
||||||
ng-controller="FileTreeRootFolderController",
|
ng-controller="FileTreeRootFolderController",
|
||||||
ng-class="{ 'no-toolbar': !permissions.write }"
|
ng-class="{ 'no-toolbar': !permissions.write }"
|
||||||
)
|
)
|
||||||
|
|
||||||
div(ng-show="ui.pdfLayout == 'flat' && (ui.view == 'editor' || ui.view == 'pdf' || ui.view == 'file')")
|
|
||||||
ul.list-unstyled.file-tree-list
|
|
||||||
li(
|
|
||||||
ng-class="{ 'selected': ui.view == 'pdf' }"
|
|
||||||
ng-controller="PdfViewToggleController"
|
|
||||||
)
|
|
||||||
.entity
|
|
||||||
.entity-name(
|
|
||||||
ng-click="togglePdfView()"
|
|
||||||
)
|
|
||||||
i.fa.fa-fw.toggle
|
|
||||||
i.fa.fa-fw.fa-file-pdf-o
|
|
||||||
| PDF
|
|
||||||
|
|
||||||
ul.list-unstyled.file-tree-list(
|
ul.list-unstyled.file-tree-list(
|
||||||
droppable="permissions.write"
|
droppable="permissions.write"
|
||||||
accept=".entity-name"
|
accept=".entity-name"
|
||||||
on-drop-callback="onDrop"
|
on-drop-callback="onDrop"
|
||||||
)
|
)
|
||||||
|
li(
|
||||||
|
ng-show="ui.pdfLayout == 'flat' && (ui.view == 'editor' || ui.view == 'pdf' || ui.view == 'file')"
|
||||||
|
ng-class="{ 'selected': ui.view == 'pdf' }"
|
||||||
|
ng-controller="PdfViewToggleController"
|
||||||
|
)
|
||||||
|
.entity
|
||||||
|
.entity-name(
|
||||||
|
ng-click="togglePdfView()"
|
||||||
|
)
|
||||||
|
i.fa.fa-fw.toggle
|
||||||
|
i.fa.fa-fw.fa-file-pdf-o
|
||||||
|
| PDF
|
||||||
|
|
||||||
file-entity(
|
file-entity(
|
||||||
entity="entity",
|
entity="entity",
|
||||||
permissions="permissions",
|
permissions="permissions",
|
||||||
|
@ -81,27 +80,25 @@ aside#file-tree(ng-controller="FileTreeController").full-size
|
||||||
)
|
)
|
||||||
.entity
|
.entity
|
||||||
.entity-name(
|
.entity-name(
|
||||||
ng-click="select()"
|
ng-click="select($event)"
|
||||||
)
|
)
|
||||||
//- Just a spacer to align with folders
|
//- Just a spacer to align with folders
|
||||||
i.fa.fa-fw.toggle
|
i.fa.fa-fw.toggle
|
||||||
i.fa.fa-fw.fa-file
|
i.fa.fa-fw.fa-file
|
||||||
|
|
||||||
span {{ entity.name }}
|
span {{ entity.name }}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
script(type='text/ng-template', id='entityListItemTemplate')
|
script(type='text/ng-template', id='entityListItemTemplate')
|
||||||
li(
|
li(
|
||||||
ng-class="{ 'selected': entity.selected }",
|
ng-class="{ 'selected': entity.selected, 'multi-selected': entity.multiSelected }",
|
||||||
ng-controller="FileTreeEntityController"
|
ng-controller="FileTreeEntityController"
|
||||||
)
|
)
|
||||||
.entity(ng-if="entity.type != 'folder'")
|
.entity(ng-if="entity.type != 'folder'")
|
||||||
.entity-name(
|
.entity-name(
|
||||||
ng-click="select()"
|
ng-click="select($event)"
|
||||||
ng-dblclick="permissions.write && startRenaming()"
|
ng-dblclick="permissions.write && startRenaming()"
|
||||||
draggable="permissions.write"
|
draggable="permissions.write"
|
||||||
|
draggable-helper="draggableHelper"
|
||||||
context-menu
|
context-menu
|
||||||
data-target="context-menu-{{ entity.id }}"
|
data-target="context-menu-{{ entity.id }}"
|
||||||
context-menu-container="body"
|
context-menu-container="body"
|
||||||
|
@ -112,7 +109,6 @@ script(type='text/ng-template', id='entityListItemTemplate')
|
||||||
|
|
||||||
i.fa.fa-fw.fa-file(ng-if="entity.type == 'doc'")
|
i.fa.fa-fw.fa-file(ng-if="entity.type == 'doc'")
|
||||||
i.fa.fa-fw.fa-image(ng-if="entity.type == 'file'")
|
i.fa.fa-fw.fa-image(ng-if="entity.type == 'file'")
|
||||||
|
|
||||||
span(
|
span(
|
||||||
ng-hide="entity.renaming"
|
ng-hide="entity.renaming"
|
||||||
) {{ entity.name }}
|
) {{ entity.name }}
|
||||||
|
@ -126,12 +122,11 @@ script(type='text/ng-template', id='entityListItemTemplate')
|
||||||
on-enter="finishRenaming()"
|
on-enter="finishRenaming()"
|
||||||
)
|
)
|
||||||
|
|
||||||
span.dropdown(
|
span.dropdown.entity-menu-toggle(
|
||||||
dropdown,
|
dropdown,
|
||||||
ng-show="entity.selected",
|
|
||||||
ng-if="permissions.write"
|
ng-if="permissions.write"
|
||||||
)
|
)
|
||||||
a.dropdown-toggle(href, dropdown-toggle)
|
a.dropdown-toggle(href, dropdown-toggle, stop-propagation="click")
|
||||||
i.fa.fa-chevron-down
|
i.fa.fa-chevron-down
|
||||||
|
|
||||||
ul.dropdown-menu.dropdown-menu-right
|
ul.dropdown-menu.dropdown-menu-right
|
||||||
|
@ -140,12 +135,14 @@ script(type='text/ng-template', id='entityListItemTemplate')
|
||||||
href
|
href
|
||||||
ng-click="startRenaming()"
|
ng-click="startRenaming()"
|
||||||
right-click="startRenaming()"
|
right-click="startRenaming()"
|
||||||
|
ng-show="!entity.multiSelected"
|
||||||
) #{translate("rename")}
|
) #{translate("rename")}
|
||||||
li
|
li
|
||||||
a(
|
a(
|
||||||
href
|
href
|
||||||
ng-click="openDeleteModal()"
|
ng-click="openDeleteModal()"
|
||||||
right-click="openDeleteModal()"
|
right-click="openDeleteModal()"
|
||||||
|
stop-propagation="click"
|
||||||
) #{translate("delete")}
|
) #{translate("delete")}
|
||||||
|
|
||||||
div.dropdown.context-menu(
|
div.dropdown.context-menu(
|
||||||
|
@ -158,20 +155,23 @@ script(type='text/ng-template', id='entityListItemTemplate')
|
||||||
href
|
href
|
||||||
ng-click="startRenaming()"
|
ng-click="startRenaming()"
|
||||||
right-click="startRenaming()"
|
right-click="startRenaming()"
|
||||||
|
ng-show="!entity.multiSelected"
|
||||||
) #{translate("rename")}
|
) #{translate("rename")}
|
||||||
li
|
li
|
||||||
a(
|
a(
|
||||||
href
|
href
|
||||||
ng-click="openDeleteModal()"
|
ng-click="openDeleteModal()"
|
||||||
right-click="openDeleteModal()"
|
right-click="openDeleteModal()"
|
||||||
|
stop-propagation="click"
|
||||||
) #{translate("delete")}
|
) #{translate("delete")}
|
||||||
|
|
||||||
|
|
||||||
.entity(ng-if="entity.type == 'folder'", ng-controller="FileTreeFolderController")
|
.entity(ng-if="entity.type == 'folder'", ng-controller="FileTreeFolderController")
|
||||||
.entity-name(
|
.entity-name(
|
||||||
ng-click="select()"
|
ng-click="select($event)"
|
||||||
ng-dblclick="permissions.write && startRenaming()"
|
ng-dblclick="permissions.write && startRenaming()"
|
||||||
draggable="permissions.write"
|
draggable="permissions.write"
|
||||||
|
draggable-helper="draggableHelper"
|
||||||
droppable="permissions.write"
|
droppable="permissions.write"
|
||||||
accept=".entity-name"
|
accept=".entity-name"
|
||||||
on-drop-callback="onDrop"
|
on-drop-callback="onDrop"
|
||||||
|
@ -194,7 +194,7 @@ script(type='text/ng-template', id='entityListItemTemplate')
|
||||||
'fa-folder': !expanded, \
|
'fa-folder': !expanded, \
|
||||||
'fa-folder-open': expanded \
|
'fa-folder-open': expanded \
|
||||||
}"
|
}"
|
||||||
ng-click="select()"
|
ng-click="select($event)"
|
||||||
)
|
)
|
||||||
|
|
||||||
span(
|
span(
|
||||||
|
@ -210,12 +210,11 @@ script(type='text/ng-template', id='entityListItemTemplate')
|
||||||
on-enter="finishRenaming()"
|
on-enter="finishRenaming()"
|
||||||
)
|
)
|
||||||
|
|
||||||
span.dropdown(
|
span.dropdown.entity-menu-toggle(
|
||||||
dropdown,
|
dropdown,
|
||||||
ng-if="permissions.write"
|
ng-if="permissions.write"
|
||||||
ng-show="entity.selected"
|
|
||||||
)
|
)
|
||||||
a.dropdown-toggle(href, dropdown-toggle)
|
a.dropdown-toggle(href, dropdown-toggle, stop-propagation="click")
|
||||||
i.fa.fa-chevron-down
|
i.fa.fa-chevron-down
|
||||||
|
|
||||||
ul.dropdown-menu.dropdown-menu-right
|
ul.dropdown-menu.dropdown-menu-right
|
||||||
|
@ -224,12 +223,14 @@ script(type='text/ng-template', id='entityListItemTemplate')
|
||||||
href
|
href
|
||||||
ng-click="startRenaming()"
|
ng-click="startRenaming()"
|
||||||
right-click="startRenaming()"
|
right-click="startRenaming()"
|
||||||
|
ng-show="!entity.multiSelected"
|
||||||
) #{translate("rename")}
|
) #{translate("rename")}
|
||||||
li
|
li
|
||||||
a(
|
a(
|
||||||
href
|
href
|
||||||
ng-click="openDeleteModal()"
|
ng-click="openDeleteModal()"
|
||||||
right-click="openDeleteModal()"
|
right-click="openDeleteModal()"
|
||||||
|
stop-propagation="click"
|
||||||
) #{translate("delete")}
|
) #{translate("delete")}
|
||||||
li.divider
|
li.divider
|
||||||
li
|
li
|
||||||
|
@ -261,12 +262,14 @@ script(type='text/ng-template', id='entityListItemTemplate')
|
||||||
href
|
href
|
||||||
ng-click="startRenaming()"
|
ng-click="startRenaming()"
|
||||||
right-click="startRenaming()"
|
right-click="startRenaming()"
|
||||||
|
ng-show="!entity.multiSelected"
|
||||||
) #{translate("rename")}
|
) #{translate("rename")}
|
||||||
li
|
li
|
||||||
a(
|
a(
|
||||||
href
|
href
|
||||||
ng-click="openDeleteModal()"
|
ng-click="openDeleteModal()"
|
||||||
right-click="openDeleteModal()"
|
right-click="openDeleteModal()"
|
||||||
|
stop-propagation="click"
|
||||||
) #{translate("delete")}
|
) #{translate("delete")}
|
||||||
li.divider
|
li.divider
|
||||||
li
|
li
|
||||||
|
@ -306,6 +309,7 @@ script(type='text/ng-template', id='newDocModalTemplate')
|
||||||
h3 #{translate("new_file")}
|
h3 #{translate("new_file")}
|
||||||
.modal-body
|
.modal-body
|
||||||
form(novalidate, name="newDocForm")
|
form(novalidate, name="newDocForm")
|
||||||
|
div.alert.alert-danger(ng-if="error") {{error}}
|
||||||
input.form-control(
|
input.form-control(
|
||||||
type="text",
|
type="text",
|
||||||
placeholder="File Name",
|
placeholder="File Name",
|
||||||
|
@ -330,6 +334,7 @@ script(type='text/ng-template', id='newFolderModalTemplate')
|
||||||
.modal-header
|
.modal-header
|
||||||
h3 #{translate("new_folder")}
|
h3 #{translate("new_folder")}
|
||||||
.modal-body
|
.modal-body
|
||||||
|
div.alert.alert-danger(ng-if="error") {{error}}
|
||||||
form(novalidate, name="newFolderForm")
|
form(novalidate, name="newFolderForm")
|
||||||
input.form-control(
|
input.form-control(
|
||||||
type="text",
|
type="text",
|
||||||
|
@ -354,9 +359,20 @@ script(type='text/ng-template', id='newFolderModalTemplate')
|
||||||
script(type="text/ng-template", id="uploadFileModalTemplate")
|
script(type="text/ng-template", id="uploadFileModalTemplate")
|
||||||
.modal-header
|
.modal-header
|
||||||
h3 #{translate("upload_files")}
|
h3 #{translate("upload_files")}
|
||||||
span
|
.alert.alert-warning.small.modal-alert(ng-if="tooManyFiles") #{translate("maximum_files_uploaded_together", {max:"{{max_files}}"})}
|
||||||
.alert.alert-warning.small(ng-if="tooManyFiles") #{translate("maximum_files_uploaded_together", {max:"{{max_files}}"})}
|
.alert.alert-warning.small.modal-alert(ng-if="rateLimitHit") #{translate("too_many_files_uploaded_throttled_short_period")}
|
||||||
.alert.alert-warning.small(ng-if="rateLimitHit") #{translate("too_many_files_uploaded_throttled_short_period")}
|
.alert.alert-warning.small.modal-alert(ng-if="notLoggedIn") #{translate("session_expired_redirecting_to_login", {seconds:"{{secondsToRedirect}}"})}
|
||||||
|
.alert.alert-warning.small.modal-alert(ng-if="conflicts.length > 0")
|
||||||
|
p.text-center
|
||||||
|
| The following files already exist in this project:
|
||||||
|
ul.text-center.list-unstyled.row-spaced-small
|
||||||
|
li(ng-repeat="conflict in conflicts"): strong {{ conflict }}
|
||||||
|
p.text-center.row-spaced-small
|
||||||
|
| Do you want to overwrite them?
|
||||||
|
p.text-center
|
||||||
|
a(href, ng-click="doUpload()").btn.btn-primary Overwrite
|
||||||
|
|
|
||||||
|
a(href, ng-click="cancel()").btn.btn-default Cancel
|
||||||
|
|
||||||
.modal-body(
|
.modal-body(
|
||||||
fine-upload
|
fine-upload
|
||||||
|
@ -367,10 +383,14 @@ script(type="text/ng-template", id="uploadFileModalTemplate")
|
||||||
drag-area-text="{{drag_files}}"
|
drag-area-text="{{drag_files}}"
|
||||||
hint-text="{{hint_press_and_hold_control_key}}"
|
hint-text="{{hint_press_and_hold_control_key}}"
|
||||||
multiple="true"
|
multiple="true"
|
||||||
|
auto-upload="false"
|
||||||
on-complete-callback="onComplete"
|
on-complete-callback="onComplete"
|
||||||
on-upload-callback="onUpload"
|
on-upload-callback="onUpload"
|
||||||
on-validate-batch="onValidateBatch"
|
on-validate-batch="onValidateBatch"
|
||||||
on-error-callback="onError"
|
on-error-callback="onError"
|
||||||
|
on-submit-callback="onSubmit"
|
||||||
|
on-cancel-callback="onCancel"
|
||||||
|
control="control"
|
||||||
params="{'folder_id': parent_folder_id}"
|
params="{'folder_id': parent_folder_id}"
|
||||||
)
|
)
|
||||||
span #{translate("upload_files")}
|
span #{translate("upload_files")}
|
||||||
|
@ -382,6 +402,8 @@ script(type='text/ng-template', id='deleteEntityModalTemplate')
|
||||||
h3 #{translate("delete")} {{ entity.name }}
|
h3 #{translate("delete")} {{ entity.name }}
|
||||||
.modal-body
|
.modal-body
|
||||||
p !{translate("sure_you_want_to_delete")}
|
p !{translate("sure_you_want_to_delete")}
|
||||||
|
ul
|
||||||
|
li(ng-repeat="entity in entities") {{entity.name}}
|
||||||
.modal-footer
|
.modal-footer
|
||||||
button.btn.btn-default(
|
button.btn.btn-default(
|
||||||
ng-disabled="state.inflight"
|
ng-disabled="state.inflight"
|
||||||
|
|
|
@ -50,16 +50,22 @@ script(type="text/ng-template", id="hotkeysModalTemplate")
|
||||||
.hotkey
|
.hotkey
|
||||||
span.combination {{ctrl}} + A
|
span.combination {{ctrl}} + A
|
||||||
span.description Select All
|
span.description Select All
|
||||||
.col-xs-6
|
|
||||||
.hotkey
|
.hotkey
|
||||||
span.combination Tab
|
span.combination Tab
|
||||||
span.description Indent Selection
|
span.description Indent Selection
|
||||||
|
.col-xs-6
|
||||||
.hotkey
|
.hotkey
|
||||||
span.combination {{ctrl}} + U
|
span.combination Ctrl + U
|
||||||
span.description To Uppercase
|
span.description To Uppercase
|
||||||
.hotkey
|
.hotkey
|
||||||
span.combination Ctrl + Shift + U
|
span.combination Ctrl + Shift + U
|
||||||
span.description To Lowercase
|
span.description To Lowercase
|
||||||
|
.hotkey
|
||||||
|
span.combination {{ctrl}} + B
|
||||||
|
span.description Bold text
|
||||||
|
.hotkey
|
||||||
|
span.combination {{ctrl}} + I
|
||||||
|
span.description Italic Text
|
||||||
.modal-footer
|
.modal-footer
|
||||||
button.btn.btn-default(
|
button.btn.btn-default(
|
||||||
ng-click="cancel()"
|
ng-click="cancel()"
|
||||||
|
|
|
@ -1,16 +1,34 @@
|
||||||
div.full-size.pdf(ng-controller="PdfController")
|
div.full-size.pdf(ng-controller="PdfController")
|
||||||
.toolbar.toolbar-tall
|
.toolbar.toolbar-tall
|
||||||
a.btn.btn-info(
|
.btn-group(dropdown)
|
||||||
href,
|
a.btn.btn-info(
|
||||||
ng-disabled="pdf.compiling",
|
href,
|
||||||
ng-click="recompile()"
|
ng-disabled="pdf.compiling",
|
||||||
)
|
ng-click="recompile()"
|
||||||
i.fa.fa-refresh(
|
|
||||||
ng-class="{'fa-spin': pdf.compiling }"
|
|
||||||
)
|
)
|
||||||
|
|
i.fa.fa-refresh(
|
||||||
span(ng-show="!pdf.compiling") #{translate("recompile")}
|
ng-class="{'fa-spin': pdf.compiling }"
|
||||||
span(ng-show="pdf.compiling") #{translate("compiling")}...
|
)
|
||||||
|
|
|
||||||
|
span(ng-show="!pdf.compiling") #{translate("recompile")}
|
||||||
|
span(ng-show="pdf.compiling") #{translate("compiling")}...
|
||||||
|
a.btn.btn-info.dropdown-toggle(
|
||||||
|
href,
|
||||||
|
ng-disabled="pdf.compiling",
|
||||||
|
dropdown-toggle
|
||||||
|
)
|
||||||
|
span.caret
|
||||||
|
ul.dropdown-menu.dropdown-menu-right
|
||||||
|
li.dropdown-header #{translate("compile_mode")}
|
||||||
|
li
|
||||||
|
a(href, ng-click="draft = false")
|
||||||
|
i.fa.fa-fw(ng-class="{'fa-check': !draft}")
|
||||||
|
| #{translate("normal")}
|
||||||
|
li
|
||||||
|
a(href, ng-click="draft = true")
|
||||||
|
i.fa.fa-fw(ng-class="{'fa-check': draft}")
|
||||||
|
| #{translate("fast")}
|
||||||
|
span.subdued [draft]
|
||||||
a.log-btn(
|
a.log-btn(
|
||||||
href
|
href
|
||||||
ng-click="toggleLogs()"
|
ng-click="toggleLogs()"
|
||||||
|
@ -94,41 +112,43 @@ div.full-size.pdf(ng-controller="PdfController")
|
||||||
| #{translate("learn_how_to_make_documents_compile_quickly")}
|
| #{translate("learn_how_to_make_documents_compile_quickly")}
|
||||||
|
|
||||||
.alert.alert-success(ng-show="pdf.timedout && !hasPremiumCompile")
|
.alert.alert-success(ng-show="pdf.timedout && !hasPremiumCompile")
|
||||||
p
|
p
|
||||||
strong #{translate("upgrade_for_faster_compiles")}
|
strong #{translate("upgrade_for_faster_compiles")}
|
||||||
h5 #{translate("free_accounts_have_timeout_upgrade_to_increase")}
|
p #{translate("free_accounts_have_timeout_upgrade_to_increase")}
|
||||||
h4 Plus:
|
p Plus:
|
||||||
h4
|
p
|
||||||
|
ul.list-unstyled
|
||||||
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
| #{translate("unlimited_projects")}
|
| #{translate("unlimited_projects")}
|
||||||
|
|
||||||
h4
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
||||||
|
|
||||||
h4
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
| #{translate("full_doc_history")}
|
| #{translate("full_doc_history")}
|
||||||
|
|
||||||
h4
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
| #{translate("sync_to_dropbox")}
|
| #{translate("sync_to_dropbox")}
|
||||||
|
|
||||||
h4
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
| #{translate("sync_to_github")}
|
| #{translate("sync_to_github")}
|
||||||
|
|
||||||
h4
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
|#{translate("compile_larger_projects")}
|
|#{translate("compile_larger_projects")}
|
||||||
|
|
||||||
p(ng-controller="FreeTrialModalController")
|
p(ng-controller="FreeTrialModalController")
|
||||||
a.btn.btn-success(
|
a.btn.btn-success.row-spaced-small(
|
||||||
href
|
href
|
||||||
ng-class="buttonClass"
|
ng-class="buttonClass"
|
||||||
sixpack-convert="track_changes_feature_info"
|
sixpack-convert="track_changes_feature_info"
|
||||||
ng-click="startFreeTrial('compile-timeout')"
|
ng-click="startFreeTrial('compile-timeout')"
|
||||||
) #{translate("start_free_trial")}
|
) #{translate("start_free_trial")}
|
||||||
|
|
||||||
.pdf-errors(ng-show="pdf.projectTooLarge")
|
.pdf-errors(ng-show="pdf.projectTooLarge")
|
||||||
.alert.alert-danger
|
.alert.alert-danger
|
||||||
|
|
|
@ -6,46 +6,84 @@ script(type="text/ng-template", id="publishProjectAsTemplateModalTemplate")
|
||||||
ng-click="cancel()"
|
ng-click="cancel()"
|
||||||
) ×
|
) ×
|
||||||
h3 #{translate("publish_as_template")}
|
h3 #{translate("publish_as_template")}
|
||||||
.modal-body.modal-body-share
|
div(ng-if="project.features.templates")
|
||||||
span(ng-hide="problemTalkingToTemplateApi")
|
.modal-body.modal-body-share
|
||||||
form()
|
span(ng-hide="problemTalkingToTemplateApi")
|
||||||
label(for='Description') #{translate("template_description")}
|
form()
|
||||||
.form-group
|
label(for='Description') #{translate("template_description")}
|
||||||
textarea.form-control(
|
.form-group
|
||||||
rows=5,
|
textarea.form-control(
|
||||||
name='Description',
|
rows=5,
|
||||||
ng-model="templateDetails.description",
|
name='Description',
|
||||||
value=""
|
ng-model="templateDetails.description",
|
||||||
)
|
value=""
|
||||||
div(ng-show="templateDetails.exists").text-center.templateDetails
|
)
|
||||||
| #{translate("project_last_published_at")}
|
div(ng-show="templateDetails.exists").text-center.templateDetails
|
||||||
strong {{templateDetails.publishedDate}}.
|
| #{translate("project_last_published_at")}
|
||||||
a(ng-href="{{templateDetails.canonicalUrl}}") #{translate("view_in_template_gallery")}.
|
strong {{templateDetails.publishedDate}}.
|
||||||
|
a(ng-href="{{templateDetails.canonicalUrl}}") #{translate("view_in_template_gallery")}.
|
||||||
|
|
||||||
span(ng-show="problemTalkingToTemplateApi") #{translate("problem_talking_to_publishing_service")}.
|
span(ng-show="problemTalkingToTemplateApi") #{translate("problem_talking_to_publishing_service")}.
|
||||||
|
|
||||||
|
.modal-footer(ng-hide="problemTalkingToTemplateApi")
|
||||||
|
button.btn.btn-default(
|
||||||
|
ng-click="cancel()",
|
||||||
|
ng-disabled="state.publishInflight || state.unpublishInflight"
|
||||||
|
)
|
||||||
|
span #{translate("cancel")}
|
||||||
|
|
||||||
|
button.btn.btn-info(
|
||||||
|
ng-click="unpublishTemplate()",
|
||||||
|
ng-disabled="state.publishInflight || state.unpublishInflight"
|
||||||
|
ng-show="templateDetails.exists"
|
||||||
|
)
|
||||||
|
span(ng-show="!state.unpublishInflight") #{translate("unpublish")}
|
||||||
|
span(ng-show="state.unpublishInflight") #{translate("unpublishing")}...
|
||||||
|
|
||||||
|
button.btn.btn-primary(
|
||||||
|
ng-click="publishTemplate()",
|
||||||
|
ng-disabled="state.publishInflight || state.unpublishInflight"
|
||||||
|
)
|
||||||
|
span(ng-show="!state.publishInflight && !templateDetails.exists") #{translate("publish")}
|
||||||
|
span(ng-show="!state.publishInflight && templateDetails.exists") #{translate("republish")}
|
||||||
|
span(ng-show="state.publishInflight") #{translate("publishing")}...
|
||||||
|
|
||||||
|
div(ng-hide="project.features.templates")
|
||||||
|
.modal-body.modal-body-share
|
||||||
|
p #{translate("upgrade_to_get_feature", {feature:"templates"})}
|
||||||
|
|
||||||
.modal-footer(ng-hide="problemTalkingToTemplateApi")
|
ul.list-unstyled
|
||||||
button.btn.btn-default(
|
li
|
||||||
ng-click="cancel()",
|
i.fa.fa-check
|
||||||
ng-disabled="state.publishInflight || state.unpublishInflight"
|
| #{translate("unlimited_projects")}
|
||||||
)
|
|
||||||
span #{translate("cancel")}
|
li
|
||||||
|
i.fa.fa-check
|
||||||
|
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
||||||
|
|
||||||
|
li
|
||||||
|
i.fa.fa-check
|
||||||
|
| #{translate("full_doc_history")}
|
||||||
|
|
||||||
|
li
|
||||||
|
i.fa.fa-check
|
||||||
|
| #{translate("sync_to_dropbox")}
|
||||||
|
|
||||||
button.btn.btn-info(
|
li
|
||||||
ng-click="unpublishTemplate()",
|
i.fa.fa-check
|
||||||
ng-disabled="state.publishInflight || state.unpublishInflight"
|
| #{translate("sync_to_github")}
|
||||||
ng-show="templateDetails.exists"
|
|
||||||
)
|
|
||||||
span(ng-show="!state.unpublishInflight") #{translate("unpublish")}
|
|
||||||
span(ng-show="state.unpublishInflight") #{translate("unpublishing")}...
|
|
||||||
|
|
||||||
button.btn.btn-primary(
|
li
|
||||||
ng-click="publishTemplate()",
|
i.fa.fa-check
|
||||||
ng-disabled="state.publishInflight || state.unpublishInflight"
|
|#{translate("compile_larger_projects")}
|
||||||
)
|
|
||||||
span(ng-show="!state.publishInflight && !templateDetails.exists") #{translate("publish")}
|
.modal-footer(ng-controller="FreeTrialModalController")
|
||||||
span(ng-show="!state.publishInflight && templateDetails.exists") #{translate("republish")}
|
.text-center
|
||||||
span(ng-show="state.publishInflight") #{translate("publishing")}...
|
a.btn.btn-success(
|
||||||
|
href
|
||||||
|
ng-class="buttonClass"
|
||||||
|
ng-click="startFreeTrial('templates')"
|
||||||
|
) #{translate("start_free_trial")}
|
||||||
|
|
||||||
|
.row-spaced-small.small(ng-show="startedFreeTrial")
|
||||||
|
| #{translate("refresh_page_after_starting_free_trial")}
|
||||||
|
|
|
@ -79,33 +79,36 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
|
||||||
type="submit"
|
type="submit"
|
||||||
ng-mousedown="addMembers()"
|
ng-mousedown="addMembers()"
|
||||||
) #{translate("share")}
|
) #{translate("share")}
|
||||||
div.text-center(ng-hide="canAddCollaborators")
|
div(ng-hide="canAddCollaborators")
|
||||||
p #{translate("need_to_upgrade_for_more_collabs")}.
|
p.text-center #{translate("need_to_upgrade_for_more_collabs")}. Also:
|
||||||
h4
|
.row
|
||||||
i.fa.fa-check
|
.col-md-8.col-md-offset-2
|
||||||
| #{translate("unlimited_projects")}
|
ul.list-unstyled
|
||||||
|
li
|
||||||
h4
|
i.fa.fa-check
|
||||||
i.fa.fa-check
|
| #{translate("unlimited_projects")}
|
||||||
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
|
||||||
|
li
|
||||||
h4
|
i.fa.fa-check
|
||||||
i.fa.fa-check
|
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
||||||
| #{translate("full_doc_history")}
|
|
||||||
|
li
|
||||||
h4
|
i.fa.fa-check
|
||||||
i.fa.fa-check
|
| #{translate("full_doc_history")}
|
||||||
| #{translate("sync_to_dropbox")}
|
|
||||||
|
li
|
||||||
|
i.fa.fa-check
|
||||||
|
| #{translate("sync_to_dropbox")}
|
||||||
|
|
||||||
h4
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
| #{translate("sync_to_github")}
|
| #{translate("sync_to_github")}
|
||||||
|
|
||||||
h4
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
|#{translate("compile_larger_projects")}
|
|#{translate("compile_larger_projects")}
|
||||||
|
|
||||||
p(ng-controller="FreeTrialModalController")
|
p.text-center.row-spaced-thin(ng-controller="FreeTrialModalController")
|
||||||
a.btn.btn-success(
|
a.btn.btn-success(
|
||||||
href
|
href
|
||||||
ng-class="buttonClass"
|
ng-class="buttonClass"
|
||||||
|
@ -122,7 +125,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
|
||||||
span.text-danger.error(ng-show="state.error") #{translate("generic_something_went_wrong")}
|
span.text-danger.error(ng-show="state.error") #{translate("generic_something_went_wrong")}
|
||||||
button.btn.btn-primary(
|
button.btn.btn-primary(
|
||||||
ng-click="done()"
|
ng-click="done()"
|
||||||
) #{translate("done")}
|
) #{translate("close")}
|
||||||
|
|
||||||
script(type="text/ng-template", id="makePublicModalTemplate")
|
script(type="text/ng-template", id="makePublicModalTemplate")
|
||||||
.modal-header
|
.modal-header
|
||||||
|
|
|
@ -1,83 +1,39 @@
|
||||||
div#trackChanges(ng-show="ui.view == 'track-changes'")
|
div#trackChanges(ng-show="ui.view == 'track-changes'")
|
||||||
span(ng-controller="TrackChangesPremiumPopup")
|
span(ng-controller="TrackChangesPremiumPopup")
|
||||||
span(ng-if="versioningPopupType == 'default'")
|
.upgrade-prompt(ng-show="!project.features.versioning")
|
||||||
.upgrade-prompt(ng-show="!project.features.versioning")
|
.message(ng-show="project.owner._id == user.id")
|
||||||
.message(ng-show="project.owner._id == user.id")
|
p.text-center: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})}
|
||||||
|
p.text-center.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")}
|
||||||
p #{translate("upgrade_to_get_feature", {feature:"Entire Doc History"})}
|
ul.list-unstyled
|
||||||
p.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")}
|
li
|
||||||
h4
|
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
| #{translate("unlimited_projects")}
|
| #{translate("unlimited_projects")}
|
||||||
|
|
||||||
h4
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
||||||
|
|
||||||
h4
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
| #{translate("full_doc_history")}
|
| #{translate("full_doc_history")}
|
||||||
|
|
||||||
h4
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
| #{translate("sync_to_dropbox")}
|
| #{translate("sync_to_dropbox")}
|
||||||
|
|
||||||
h4
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
| #{translate("sync_to_github")}
|
| #{translate("sync_to_github")}
|
||||||
|
|
||||||
h4
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
|#{translate("compile_larger_projects")}
|
|#{translate("compile_larger_projects")}
|
||||||
|
|
||||||
p(ng-controller="FreeTrialModalController")
|
p.text-center(ng-controller="FreeTrialModalController")
|
||||||
a.btn.btn-success(
|
|
||||||
href
|
|
||||||
ng-class="buttonClass"
|
|
||||||
ng-click="startFreeTrial('track-changes')"
|
|
||||||
) #{translate("start_free_trial")}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.message(ng-show="project.owner._id != user.id")
|
|
||||||
p #{translate("ask_proj_owner_to_upgrade_for_history")}
|
|
||||||
p
|
|
||||||
a.small(href, ng-click="toggleTrackChanges()") #{translate("cancel")}
|
|
||||||
|
|
||||||
.upgrade-prompt(ng-show="!project.features.versioning", ng-if="versioningPopupType == 'discount'")
|
|
||||||
.message(ng-show="project.owner._id == user.id")
|
|
||||||
|
|
||||||
p #{translate("upgrade_to_get_feature", {feature:"Entire Doc History"})}
|
|
||||||
h2(style="color:#a93529;") 20% Off First 6 Months!
|
|
||||||
p.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")}
|
|
||||||
h4
|
|
||||||
i.fa.fa-check
|
|
||||||
| #{translate("unlimited_projects")}
|
|
||||||
|
|
||||||
h4
|
|
||||||
i.fa.fa-check
|
|
||||||
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
|
||||||
|
|
||||||
h4
|
|
||||||
i.fa.fa-check
|
|
||||||
| #{translate("full_doc_history")}
|
|
||||||
|
|
||||||
h4
|
|
||||||
i.fa.fa-check
|
|
||||||
| #{translate("sync_to_dropbox")}
|
|
||||||
|
|
||||||
h4
|
|
||||||
i.fa.fa-check
|
|
||||||
| #{translate("sync_to_github")}
|
|
||||||
|
|
||||||
h4
|
|
||||||
i.fa.fa-check
|
|
||||||
|#{translate("compile_larger_projects")}
|
|
||||||
p(ng-controller="FreeTrialModalController")
|
|
||||||
a.btn.btn-success(
|
a.btn.btn-success(
|
||||||
href
|
href
|
||||||
ng-class="buttonClass"
|
ng-class="buttonClass"
|
||||||
ng-click="startFreeTrial('track-changes', 'cf3yutfzu7ztxz')"
|
ng-click="startFreeTrial('track-changes')"
|
||||||
) #{translate("start_free_trial")}
|
) #{translate("start_free_trial")}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@ block content
|
||||||
script(type="text/javascript").
|
script(type="text/javascript").
|
||||||
window.data = {
|
window.data = {
|
||||||
projects: !{JSON.stringify(projects).replace(/\//g, '\\/')},
|
projects: !{JSON.stringify(projects).replace(/\//g, '\\/')},
|
||||||
tags: !{JSON.stringify(tags).replace(/\//g, '\\/')}
|
tags: !{JSON.stringify(tags).replace(/\//g, '\\/')},
|
||||||
|
notifications: !{JSON.stringify(notifications).replace(/\//g, '\\/')}
|
||||||
};
|
};
|
||||||
window.algolia = {
|
window.algolia = {
|
||||||
institutions: {
|
institutions: {
|
||||||
|
@ -20,12 +21,14 @@ block content
|
||||||
|
|
||||||
.content.content-alt(ng-controller="ProjectPageController")
|
.content.content-alt(ng-controller="ProjectPageController")
|
||||||
.container
|
.container
|
||||||
|
|
||||||
.row(ng-cloak)
|
.row(ng-cloak)
|
||||||
span(ng-show="first_sign_up == 'default' || projects.length > 0")
|
span(ng-show="first_sign_up == 'default' || projects.length > 0")
|
||||||
aside.col-md-2.col-xs-3
|
aside.col-md-2.col-xs-3
|
||||||
include ./list/side-bar
|
include ./list/side-bar
|
||||||
|
|
||||||
.col-md-10.col-xs-9
|
.col-md-10.col-xs-9
|
||||||
|
include ./list/notifications
|
||||||
include ./list/project-list
|
include ./list/project-list
|
||||||
|
|
||||||
span(ng-if="first_sign_up == 'minimial' && projects.length == 0")
|
span(ng-if="first_sign_up == 'minimial' && projects.length == 0")
|
||||||
|
|
|
@ -18,6 +18,8 @@ script(type='text/ng-template', id='newTagModalTemplate')
|
||||||
stop-propagation="click"
|
stop-propagation="click"
|
||||||
)
|
)
|
||||||
.modal-footer
|
.modal-footer
|
||||||
|
.modal-footer-left
|
||||||
|
span.text-danger.error(ng-show="state.error") #{translate("generic_something_went_wrong")}
|
||||||
//- We stop propagation to stop the clicks from closing the
|
//- We stop propagation to stop the clicks from closing the
|
||||||
//- 'move to folder' menu.
|
//- 'move to folder' menu.
|
||||||
button.btn.btn-default(
|
button.btn.btn-default(
|
||||||
|
@ -25,10 +27,67 @@ script(type='text/ng-template', id='newTagModalTemplate')
|
||||||
stop-propagation="click"
|
stop-propagation="click"
|
||||||
) #{translate("cancel")}
|
) #{translate("cancel")}
|
||||||
button.btn.btn-primary(
|
button.btn.btn-primary(
|
||||||
ng-disabled="newTagForm.$invalid"
|
ng-disabled="newTagForm.$invalid || state.inflight"
|
||||||
ng-click="create()"
|
ng-click="create()"
|
||||||
stop-propagation="click"
|
stop-propagation="click"
|
||||||
) #{translate("create")}
|
)
|
||||||
|
span(ng-show="!state.inflight") #{translate("create")}
|
||||||
|
span(ng-show="state.inflight") #{translate("creating")}...
|
||||||
|
|
||||||
|
script(type='text/ng-template', id='deleteTagModalTemplate')
|
||||||
|
.modal-header
|
||||||
|
button.close(
|
||||||
|
type="button"
|
||||||
|
data-dismiss="modal"
|
||||||
|
ng-click="cancel()"
|
||||||
|
) ×
|
||||||
|
h3 #{translate("delete_folder")}
|
||||||
|
.modal-body
|
||||||
|
p #{translate("about_to_delete_folder")}
|
||||||
|
ul
|
||||||
|
li
|
||||||
|
strong {{tag.name}}
|
||||||
|
.modal-footer
|
||||||
|
.modal-footer-left
|
||||||
|
span.text-danger.error(ng-show="state.error") #{translate("generic_something_went_wrong")}
|
||||||
|
button.btn.btn-default(
|
||||||
|
ng-click="cancel()"
|
||||||
|
) #{translate("cancel")}
|
||||||
|
button.btn.btn-danger(
|
||||||
|
ng-click="delete()",
|
||||||
|
ng-disabled="state.inflight"
|
||||||
|
)
|
||||||
|
span(ng-show="state.inflight") #{translate("deleting")}...
|
||||||
|
span(ng-show="!state.inflight") #{translate("delete")}
|
||||||
|
|
||||||
|
script(type='text/ng-template', id='renameTagModalTemplate')
|
||||||
|
.modal-header
|
||||||
|
button.close(
|
||||||
|
type="button"
|
||||||
|
data-dismiss="modal"
|
||||||
|
ng-click="cancel()"
|
||||||
|
) ×
|
||||||
|
h3 #{translate("rename_folder")}
|
||||||
|
.modal-body
|
||||||
|
form(name="renameTagForm", novalidate)
|
||||||
|
input.form-control(
|
||||||
|
type="text",
|
||||||
|
placeholder="Tag Name",
|
||||||
|
ng-model="inputs.tagName",
|
||||||
|
required,
|
||||||
|
on-enter="rename()",
|
||||||
|
focus-on="open"
|
||||||
|
)
|
||||||
|
.modal-footer
|
||||||
|
.modal-footer-left
|
||||||
|
span.text-danger.error(ng-show="state.error") #{translate("generic_something_went_wrong")}
|
||||||
|
button.btn.btn-default(ng-click="cancel()") #{translate("cancel")}
|
||||||
|
button.btn.btn-primary(
|
||||||
|
ng-click="rename()",
|
||||||
|
ng-disabled="renameTagForm.$invalid || state.inflight"
|
||||||
|
)
|
||||||
|
span(ng-show="!state.inflight") #{translate("rename")}
|
||||||
|
span(ng-show="state.inflight") #{translate("renaming")}...
|
||||||
|
|
||||||
script(type='text/ng-template', id='renameProjectModalTemplate')
|
script(type='text/ng-template', id='renameProjectModalTemplate')
|
||||||
.modal-header
|
.modal-header
|
||||||
|
|
15
services/web/app/views/project/list/notifications.jade
Normal file
15
services/web/app/views/project/list/notifications.jade
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
span(ng-controller="NotificationsController").userNotifications
|
||||||
|
ul.list-unstyled.notifications-list(
|
||||||
|
ng-if="notifications.length > 0",
|
||||||
|
ng-cloak
|
||||||
|
)
|
||||||
|
li.notification_entry(
|
||||||
|
ng-repeat="unreadNotification in notifications",
|
||||||
|
)
|
||||||
|
.row(ng-hide="unreadNotification.hide")
|
||||||
|
.col-xs-12
|
||||||
|
.alert.alert-info
|
||||||
|
span(ng-bind-html="unreadNotification.html")
|
||||||
|
button(ng-click="dismiss(unreadNotification)").close.pull-right
|
||||||
|
span(aria-hidden="true") ×
|
||||||
|
span.sr-only #{translate("close")}
|
|
@ -58,7 +58,7 @@
|
||||||
)
|
)
|
||||||
li.dropdown-header #{translate("add_to_folder")}
|
li.dropdown-header #{translate("add_to_folder")}
|
||||||
li(
|
li(
|
||||||
ng-repeat="tag in tags | filter:nonEmpty | orderBy:'name'",
|
ng-repeat="tag in tags | orderBy:'name'",
|
||||||
ng-controller="TagDropdownItemController"
|
ng-controller="TagDropdownItemController"
|
||||||
)
|
)
|
||||||
a(href="#", ng-click="addOrRemoveProjectsFromTag()", stop-propagation="click")
|
a(href="#", ng-click="addOrRemoveProjectsFromTag()", stop-propagation="click")
|
||||||
|
@ -79,7 +79,7 @@
|
||||||
href='#',
|
href='#',
|
||||||
data-toggle="dropdown",
|
data-toggle="dropdown",
|
||||||
dropdown-toggle
|
dropdown-toggle
|
||||||
) #{translate("more")}
|
) #{translate("more")}
|
||||||
span.caret
|
span.caret
|
||||||
ul.dropdown-menu.dropdown-menu-right(role="menu")
|
ul.dropdown-menu.dropdown-menu-right(role="menu")
|
||||||
li(ng-show="getFirstSelectedProject().accessLevel == 'owner'")
|
li(ng-show="getFirstSelectedProject().accessLevel == 'owner'")
|
||||||
|
|
|
@ -37,22 +37,23 @@
|
||||||
ul.list-unstyled.folders-menu(
|
ul.list-unstyled.folders-menu(
|
||||||
ng-controller="TagListController"
|
ng-controller="TagListController"
|
||||||
)
|
)
|
||||||
li(ng-class="{active: (filter == 'all')}")
|
li(ng-class="{active: (filter == 'all')}", ng-click="filterProjects('all')")
|
||||||
a(href, ng-click="filterProjects('all')") #{translate("all_projects")}
|
a(href) #{translate("all_projects")}
|
||||||
li(ng-class="{active: (filter == 'owned')}")
|
li(ng-class="{active: (filter == 'owned')}", ng-click="filterProjects('owned')")
|
||||||
a(href, ng-click="filterProjects('owned')") #{translate("your_projects")}
|
a(href) #{translate("your_projects")}
|
||||||
li(ng-class="{active: (filter == 'shared')}")
|
li(ng-class="{active: (filter == 'shared')}", ng-click="filterProjects('shared')")
|
||||||
a(href, ng-click="filterProjects('shared')") #{translate("shared_with_you")}
|
a(href) #{translate("shared_with_you")}
|
||||||
li(ng-class="{active: (filter == 'archived')}")
|
li(ng-class="{active: (filter == 'archived')}", ng-click="filterProjects('archived')")
|
||||||
a(href, ng-click="filterProjects('archived')") #{translate("deleted_projects")}
|
a(href) #{translate("deleted_projects")}
|
||||||
li
|
li
|
||||||
h2 #{translate("folders")}
|
h2 #{translate("folders")}
|
||||||
li(
|
li.tag(
|
||||||
ng-repeat="tag in tags | filter:nonEmpty",
|
ng-repeat="tag in tags | orderBy:name",
|
||||||
ng-class="{active: tag.selected}",
|
ng-class="{active: tag.selected}",
|
||||||
ng-cloak
|
ng-cloak,
|
||||||
|
ng-click="selectTag(tag)"
|
||||||
)
|
)
|
||||||
a.tag(href, ng-click="selectTag(tag)")
|
a.tag-name(href)
|
||||||
i.icon.fa.fa-fw(
|
i.icon.fa.fa-fw(
|
||||||
ng-class="{\
|
ng-class="{\
|
||||||
'fa-folder-open-o': tag.selected,\
|
'fa-folder-open-o': tag.selected,\
|
||||||
|
@ -61,6 +62,23 @@
|
||||||
)
|
)
|
||||||
span.name {{tag.name}}
|
span.name {{tag.name}}
|
||||||
span.subdued ({{tag.project_ids.length}})
|
span.subdued ({{tag.project_ids.length}})
|
||||||
|
span.dropdown.tag-menu(dropdown)
|
||||||
|
a.dropdown-toggle(
|
||||||
|
href="#",
|
||||||
|
data-toggle="dropdown",
|
||||||
|
dropdown-toggle,
|
||||||
|
stop-propagation="click"
|
||||||
|
)
|
||||||
|
span.caret
|
||||||
|
ul.dropdown-menu.dropdown-menu-right(
|
||||||
|
role="menu"
|
||||||
|
)
|
||||||
|
li
|
||||||
|
a(href, ng-click="renameTag(tag)", stop-propagation="click")
|
||||||
|
| #{translate("rename")}
|
||||||
|
li
|
||||||
|
a(href, ng-click="deleteTag(tag)", stop-propagation="click")
|
||||||
|
| #{translate("delete")}
|
||||||
li(ng-cloak)
|
li(ng-cloak)
|
||||||
a.tag(href, ng-click="openNewTagModal()")
|
a.tag(href, ng-click="openNewTagModal()")
|
||||||
i.fa.fa-fw.fa-plus
|
i.fa.fa-fw.fa-plus
|
||||||
|
@ -102,41 +120,51 @@
|
||||||
) #{translate("complete")}
|
) #{translate("complete")}
|
||||||
|
|
||||||
|
|
||||||
.row-spaced(ng-if="hasProjects && userHasSubscription", ng-cloak, sixpack-switch="left-menu-upgrade-reason").text-centered
|
.row-spaced(ng-if="hasProjects && userHasSubscription", ng-cloak, sixpack-switch="left-menu-upgraed-rotation").text-centered
|
||||||
span(sixpack-default).text-centered
|
span(sixpack-default).text-centered
|
||||||
hr
|
hr
|
||||||
p.small #{translate("on_free_sl")}
|
p.small #{translate("on_free_sl")}
|
||||||
p
|
p
|
||||||
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgrade-reason").btn.btn-primary #{translate("upgrade")}
|
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgraed-rotation").btn.btn-primary #{translate("upgrade")}
|
||||||
p.small.text-centered
|
p.small.text-centered
|
||||||
| #{translate("or_unlock_features_bonus")}
|
| #{translate("or_unlock_features_bonus")}
|
||||||
a(href="/user/bonus") #{translate("sharing_sl")} .
|
a(href="/user/bonus") #{translate("sharing_sl")} .
|
||||||
|
|
||||||
span(sixpack-when="dropbox").text-centered
|
span(sixpack-when="random").text-centered
|
||||||
hr
|
span(ng-if="randomView == 'default'")
|
||||||
.card.card-thin
|
hr
|
||||||
|
p.small #{translate("on_free_sl")}
|
||||||
p
|
p
|
||||||
span Get Dropbox Sync
|
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgraed-rotation").btn.btn-primary #{translate("upgrade")}
|
||||||
p
|
|
||||||
img(src="/img/dropbox/simple_logo.png")
|
|
||||||
p
|
|
||||||
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgrade-reason").btn.btn-primary #{translate("upgrade")}
|
|
||||||
p.small.text-centered
|
p.small.text-centered
|
||||||
| #{translate("or_unlock_features_bonus")}
|
| #{translate("or_unlock_features_bonus")}
|
||||||
a(href="/user/bonus") #{translate("sharing_sl")} .
|
a(href="/user/bonus") #{translate("sharing_sl")} .
|
||||||
|
|
||||||
span(sixpack-when="github").text-centered
|
span(ng-if="randomView == 'dropbox'")
|
||||||
hr
|
hr
|
||||||
.card.card-thin
|
.card.card-thin
|
||||||
p
|
p
|
||||||
span Get Github Sync
|
span Get Dropbox Sync
|
||||||
p
|
p
|
||||||
img(src="/img/github/octocat.jpg")
|
img(src="/img/dropbox/simple_logo.png")
|
||||||
p
|
p
|
||||||
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgrade-reason").btn.btn-primary #{translate("upgrade")}
|
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgraed-rotation").btn.btn-primary #{translate("upgrade")}
|
||||||
p.small.text-centered
|
p.small.text-centered
|
||||||
| #{translate("or_unlock_features_bonus")}
|
| #{translate("or_unlock_features_bonus")}
|
||||||
a(href="/user/bonus") #{translate("sharing_sl")} .
|
a(href="/user/bonus") #{translate("sharing_sl")} .
|
||||||
|
|
||||||
|
span(ng-if="randomView == 'github'")
|
||||||
|
hr
|
||||||
|
.card.card-thin
|
||||||
|
p
|
||||||
|
span Get Github Sync
|
||||||
|
p
|
||||||
|
img(src="/img/github/octocat.jpg")
|
||||||
|
p
|
||||||
|
a(href="/user/subscription/plans", sixpack-convert="left-menu-upgraed-rotation").btn.btn-primary #{translate("upgrade")}
|
||||||
|
p.small.text-centered
|
||||||
|
| #{translate("or_unlock_features_bonus")}
|
||||||
|
a(href="/user/bonus") #{translate("sharing_sl")} .
|
||||||
script.
|
script.
|
||||||
window.userHasSubscription = #{settings.enableSubscriptions && !hasSubscription}
|
window.userHasSubscription = #{settings.enableSubscriptions && !hasSubscription}
|
||||||
|
|
||||||
|
|
|
@ -32,8 +32,8 @@
|
||||||
'EBCallBackMessageReceived',
|
'EBCallBackMessageReceived',
|
||||||
// See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx
|
// See http://toolbar.conduit.com/Developer/HtmlAndGadget/Methods/JSInjection.aspx
|
||||||
'conduitPage',
|
'conduitPage',
|
||||||
'NS_ERROR_NOT_CONNECTED:',
|
'/NS_ERROR_NOT_CONNECTED/i',
|
||||||
"TypeError: Cannot read property 'row' of undefined",
|
"/Cannot read property 'row' of undefined/i",
|
||||||
'TypeError: start is undefined'
|
'TypeError: start is undefined'
|
||||||
],
|
],
|
||||||
ignoreUrls: [
|
ignoreUrls: [
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
],
|
],
|
||||||
shouldSendCallback: function(data) {
|
shouldSendCallback: function(data) {
|
||||||
// only send a fraction of errors
|
// only send a fraction of errors
|
||||||
var sampleRate = 1.00;
|
var sampleRate = 0.01;
|
||||||
return (Math.random() <= sampleRate);
|
return (Math.random() <= sampleRate);
|
||||||
},
|
},
|
||||||
dataCallback: function(data) {
|
dataCallback: function(data) {
|
||||||
|
|
|
@ -8,7 +8,7 @@ block content
|
||||||
.card
|
.card
|
||||||
.page-header
|
.page-header
|
||||||
h1 #{translate("your_subscription")}
|
h1 #{translate("your_subscription")}
|
||||||
div To make changes to your subscription please contact team@sharelatex.com
|
div To make changes to your subscription please contact accounts@sharelatex.com
|
||||||
div
|
div
|
||||||
div
|
div
|
||||||
-if(subscription.groupPlan)
|
-if(subscription.groupPlan)
|
||||||
|
|
|
@ -6,7 +6,7 @@ block scripts
|
||||||
window.recomendedCurrency = '#{recomendedCurrency}'
|
window.recomendedCurrency = '#{recomendedCurrency}'
|
||||||
window.recurlyApiKey = "!{settings.apis.recurly.publicKey}"
|
window.recurlyApiKey = "!{settings.apis.recurly.publicKey}"
|
||||||
window.taxRate = #{taxRate}
|
window.taxRate = #{taxRate}
|
||||||
|
window.subscription = !{JSON.stringify(subscription)}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,32 +36,32 @@ mixin printPlans(plans)
|
||||||
mixin printPlan(plan)
|
mixin printPlan(plan)
|
||||||
|
|
||||||
block content
|
block content
|
||||||
.content.content-alt
|
.content.content-alt(ng-cloak)
|
||||||
.container
|
.container(ng-controller="UserSubscriptionController")
|
||||||
.row
|
.row
|
||||||
.col-md-8.col-md-offset-2
|
.col-md-8.col-md-offset-2
|
||||||
.card
|
.card(ng-if="view == 'overview'")
|
||||||
.page-header
|
.page-header
|
||||||
h1 #{translate("your_subscription")}
|
h1 #{translate("your_subscription")}
|
||||||
-if (groups.length != 0)
|
-if (groups.length != 0)
|
||||||
each groupSubscription in groups
|
each groupSubscription in groups
|
||||||
p !{translate("member_of_group_subscription", {admin_email: "<strong>" + groupSubscription.admin_id.email + "</strong>"})}
|
p !{translate("member_of_group_subscription", {admin_email: "<strong>" + groupSubscription.admin_id.email + "</strong>"})}
|
||||||
|
span
|
||||||
|
button.btn.btn-danger(ng-click="removeSelfFromGroup('#{groupSubscription.admin_id._id}')") #{translate("leave_group")}
|
||||||
- else if (subscription)
|
- else if (subscription)
|
||||||
case subscription.state
|
case subscription.state
|
||||||
when "free-trial"
|
when "free-trial"
|
||||||
p !{translate("on_free_trial_expiring_at", {expiresAt:"<strong>" + subscription.expiresAt + "</strong>"})}
|
p !{translate("on_free_trial_expiring_at", {expiresAt:"<strong>" + subscription.expiresAt + "</strong>"})}
|
||||||
p !{translate("choose_a_plan_below")}
|
|
||||||
when "active"
|
when "active"
|
||||||
p !{translate("currently_subscribed_to_plan", {planName:"<strong>" + subscription.name + "</strong>"})}
|
p !{translate("currently_subscribed_to_plan", {planName:"<strong>" + subscription.name + "</strong>"})}
|
||||||
a(href, ng-click="changePlan = true") !{translate("change_plan")}.
|
a(href, ng-click="changePlan = true") !{translate("change_plan")}.
|
||||||
p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"<strong>" + subscription.price + "</strong>", collectionDate:"<strong>" + subscription.nextPaymentDueAt + "</strong>"})}
|
p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"<strong>" + subscription.price + "</strong>", collectionDate:"<strong>" + subscription.nextPaymentDueAt + "</strong>"})}
|
||||||
p.pull-right
|
p.pull-right
|
||||||
|
p
|
||||||
p: form(action="/user/subscription/cancel",method="post")
|
|
||||||
input(type="hidden", name="_csrf", value=csrfToken)
|
|
||||||
a(href="/user/subscription/billing-details/edit").btn.btn-info #{translate("update_your_billing_details")}
|
a(href="/user/subscription/billing-details/edit").btn.btn-info #{translate("update_your_billing_details")}
|
||||||
|
|
|
|
||||||
input(type="submit", value="Cancel your subscription").btn.btn-primary#cancelSubscription
|
a(href, ng-click="switchToCancelationView()").btn.btn-primary !{translate("cancel_your_subscription")}
|
||||||
when "canceled"
|
when "canceled"
|
||||||
p !{translate("currently_subscribed_to_plan", {planName:"<strong>" + subscription.name + "</strong>"})}
|
p !{translate("currently_subscribed_to_plan", {planName:"<strong>" + subscription.name + "</strong>"})}
|
||||||
p !{translate("subscription_canceled_and_terminate_on_x", {terminateDate:"<strong>" + subscription.nextPaymentDueAt + "</strong>"})}
|
p !{translate("subscription_canceled_and_terminate_on_x", {terminateDate:"<strong>" + subscription.nextPaymentDueAt + "</strong>"})}
|
||||||
|
@ -76,7 +76,7 @@ block content
|
||||||
p !{translate("problem_with_subscription_contact_us")}
|
p !{translate("problem_with_subscription_contact_us")}
|
||||||
|
|
||||||
-if(subscription.groupPlan)
|
-if(subscription.groupPlan)
|
||||||
a(href="/subscription/group").btn.btn-success !{translate("manage_group")}
|
a(href="/subscription/group").btn.btn-primary !{translate("manage_group")}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,17 +105,50 @@ block content
|
||||||
mixin printPlans(plans.individualMonthlyPlans)
|
mixin printPlans(plans.individualMonthlyPlans)
|
||||||
mixin printPlans(plans.individualAnnualPlans)
|
mixin printPlans(plans.individualAnnualPlans)
|
||||||
|
|
||||||
|
.card(ng-if="view == 'cancelation'")
|
||||||
|
.page-header
|
||||||
|
h1 #{translate("Cancel Subscription")}
|
||||||
|
|
||||||
|
span(ng-if="sixpackOpt == 'downgrade-options'")
|
||||||
|
|
||||||
|
div(ng-show="showExtendFreeTrial", style="text-align: center")
|
||||||
|
p !{translate("have_more_days_to_try", {days:14})}
|
||||||
|
button(type="submit", ng-click="exendTrial()", ng-disabled='inflight').btn.btn-success #{translate("ill_take_it")}
|
||||||
|
p
|
||||||
|
|
|
||||||
|
p
|
||||||
|
a(href, ng-click="cancelSubscription()", ng-disabled='inflight') #{translate("no_thanks_cancel_now")}
|
||||||
|
|
||||||
|
|
||||||
|
div(ng-show="showDowngradeToStudent", style="text-align: center")
|
||||||
|
span(ng-controller="ChangePlanFormController")
|
||||||
|
p !{translate("interested_in_cheaper_plan",{price:'{{studentPrice}}'})}
|
||||||
|
button(type="submit", ng-click="downgradeToStudent()", ng-disabled='inflight').btn.btn-success #{translate("yes_please")}
|
||||||
|
p
|
||||||
|
|
|
||||||
|
p
|
||||||
|
a(href, ng-click="cancelSubscription()", ng-disabled='inflight') #{translate("no_thanks_cancel_now")}
|
||||||
|
|
||||||
|
div(ng-show="showBasicCancel")
|
||||||
|
p #{translate("sure_you_want_to_cancel")}
|
||||||
|
a(href="/project").btn.btn-info #{translate("i_want_to_stay")}
|
||||||
|
|
|
||||||
|
a(ng-click="cancelSubscription()", ng-disabled='inflight').btn.btn-primary #{translate("cancel_my_account")}
|
||||||
|
|
||||||
|
span(ng-if="sixpackOpt == 'basic'")
|
||||||
|
p #{translate("sure_you_want_to_cancel")}
|
||||||
|
a(href="/project").btn.btn-info #{translate("i_want_to_stay")}
|
||||||
|
|
|
||||||
|
a(ng-click="cancelSubscription()", ng-disabled='inflight').btn.btn-primary #{translate("cancel_my_account")}
|
||||||
|
|
||||||
script(type="text/javascript").
|
script(type="text/javascript").
|
||||||
$('#cancelSubscription').on("click", function() {
|
$('#cancelSubscription').on("click", function() {
|
||||||
ga('send', 'event', 'subscription-funnel', 'cancelation')
|
ga('send', 'event', 'subscription-funnel', 'cancelation')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
script(type='text/ng-template', id='confirmChangePlanModalTemplate')
|
script(type='text/ng-template', id='confirmChangePlanModalTemplate')
|
||||||
.modal-header
|
.modal-header
|
||||||
h3 Change plan?
|
h3 #{translate("change_plan")}
|
||||||
.modal-body
|
.modal-body
|
||||||
p !{translate("sure_you_want_to_change_plan", {planName:"<strong>{{plan.name}}</strong>"})}
|
p !{translate("sure_you_want_to_change_plan", {planName:"<strong>{{plan.name}}</strong>"})}
|
||||||
.modal-footer
|
.modal-footer
|
||||||
|
@ -131,4 +164,22 @@ block content
|
||||||
span(ng-show="inflight") #{translate("processing")}...
|
span(ng-show="inflight") #{translate("processing")}...
|
||||||
|
|
||||||
|
|
||||||
|
script(type='text/ng-template', id='LeaveGroupModalTemplate')
|
||||||
|
.modal-header
|
||||||
|
h3 #{translate("leave_group")}
|
||||||
|
.modal-body
|
||||||
|
p #{translate("sure_you_want_to_leave_group")}
|
||||||
|
.modal-footer
|
||||||
|
button.btn.btn-default(
|
||||||
|
ng-disabled="inflight"
|
||||||
|
ng-click="cancel()"
|
||||||
|
) #{translate("cancel")}
|
||||||
|
button.btn.btn-danger(
|
||||||
|
ng-disabled="state.inflight"
|
||||||
|
ng-click="confirmLeaveGroup()"
|
||||||
|
)
|
||||||
|
span(ng-hide="inflight") #{translate("leave_now")}
|
||||||
|
span(ng-show="inflight") #{translate("processing")}...
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -112,7 +112,6 @@ block content
|
||||||
.form-group(ng-class="validation.correctExpiry == false || validation.errorFields.year ? 'has-error' : ''")
|
.form-group(ng-class="validation.correctExpiry == false || validation.errorFields.year ? 'has-error' : ''")
|
||||||
select.form-control(data-recurly='year', ng-change="validateExpiry()", ng-model='data.year')
|
select.form-control(data-recurly='year', ng-change="validateExpiry()", ng-model='data.year')
|
||||||
option(value="", disabled, selected) Year
|
option(value="", disabled, selected) Year
|
||||||
option(value="2015") 2015
|
|
||||||
option(value="2016") 2016
|
option(value="2016") 2016
|
||||||
option(value="2017") 2017
|
option(value="2017") 2017
|
||||||
option(value="2018") 2018
|
option(value="2018") 2018
|
||||||
|
|
|
@ -3,6 +3,12 @@ block scripts
|
||||||
script(type='text/javascript').
|
script(type='text/javascript').
|
||||||
window.recomendedCurrency = '#{recomendedCurrency}'
|
window.recomendedCurrency = '#{recomendedCurrency}'
|
||||||
window.abCurrencyFlag = '#{abCurrencyFlag}'
|
window.abCurrencyFlag = '#{abCurrencyFlag}'
|
||||||
|
|
||||||
|
script(type='text/javascript').
|
||||||
|
(function() {var s=document.createElement('script'); s.type='text/javascript';s.async=true;
|
||||||
|
s.src=('https:'==document.location.protocol?'https':'http') + '://sharelatex-accounts.groovehq.com/widgets/f5ad3b09-7d99-431b-8af5-c5725e3760ce/ticket/api.js';
|
||||||
|
var q = document.getElementsByTagName('script')[0];q.parentNode.insertBefore(s, q);})();
|
||||||
|
|
||||||
block content
|
block content
|
||||||
.content-alt
|
.content-alt
|
||||||
.content.plans(ng-controller="PlansController")
|
.content.plans(ng-controller="PlansController")
|
||||||
|
@ -186,30 +192,30 @@ block content
|
||||||
.modal-header
|
.modal-header
|
||||||
h3 #{translate("group_plan_enquiry")}
|
h3 #{translate("group_plan_enquiry")}
|
||||||
.modal-body
|
.modal-body
|
||||||
form(name='form1', autocomplete='off', enctype='multipart/form-data', method='post', novalidate='', action='https://sharelatex.wufoo.com/forms/z7x3p3/#public', _lpchecked='1')
|
form.text-left.form(ng-controller="UniverstiesContactController", ng-submit="contactUs()", ng-cloak)
|
||||||
.form-group
|
span(ng-show="sent == false")
|
||||||
label(for='Field9') #{translate("name")}
|
.form-group
|
||||||
input.form-control(name='Field9', type='text', value='', maxlength='255', tabindex='1', onkeyup='')
|
label#title9(for='Field9')
|
||||||
|
| Name
|
||||||
.form-group
|
input#Field9.field.text.medium.span8.form-control(ng-model="form.name", maxlength='255', tabindex='1', onkeyup='')
|
||||||
label(for='Field11') #{translate("email")}
|
label#title11.desc(for='Field11')
|
||||||
input.form-control(name='Field11', type='email', spellcheck='false', value='', maxlength='255', tabindex='2')
|
| Email
|
||||||
|
.form-group
|
||||||
.form-group
|
input#Field11.field.text.medium.span8.form-control(ng-model="form.email", name='Field11', type='email', spellcheck='false', value='', maxlength='255', tabindex='2')
|
||||||
label(for='Field12') #{translate("university")}
|
label#title12.desc(for='Field12')
|
||||||
input.form-control(name='Field12', type='text', value='', maxlength='255', tabindex='3', onkeyup='')
|
| University / Company
|
||||||
|
.form-group
|
||||||
.form-group
|
input#Field12.field.text.medium.span8.form-control(ng-model="form.university", name='Field12', type='text', value='', maxlength='255', tabindex='3', onkeyup='')
|
||||||
label(for='Field13') #{translate("position")}
|
label#title13.desc(for='Field13')
|
||||||
input.form-control(name='Field13', type='text', value='', maxlength='255', tabindex='4', onkeyup='')
|
| Position
|
||||||
|
.form-group
|
||||||
.form-group
|
input#Field13.field.text.medium.span8.form-control(ng-model="form.position", name='Field13', type='text', value='', maxlength='255', tabindex='4', onkeyup='')
|
||||||
input.btn.btn-primary.btn-large(name='saveForm', type='submit', value='Send')
|
.form-group
|
||||||
div(style='display: none;')
|
input(ng-model="form.source", type="hidden", ng-init="form.source = '__ref__'; form.subject = 'ShareLaTeX for Universities';")
|
||||||
label(for='comment') Do Not Fill This Out
|
.form-group.text-center
|
||||||
textarea#comment(name='comment', rows='1', cols='1')
|
input#saveForm.btn-success.btn.btn-lg(name='saveForm', type='submit', ng-disabled="sending", value='Request a quote')
|
||||||
input#idstamp(type='hidden', name='idstamp', value='xkgLkZnS/AQW71jCS1d0XrrFjq26lJryIPVk2rx0YkU=')
|
span(ng-show="sent")
|
||||||
|
p Request Sent, Thank you.
|
||||||
|
|
||||||
.row
|
.row
|
||||||
.col-md-12
|
.col-md-12
|
||||||
|
|
|
@ -5,14 +5,18 @@ block content
|
||||||
.container
|
.container
|
||||||
.row
|
.row
|
||||||
.col-md-8.col-md-offset-2
|
.col-md-8.col-md-offset-2
|
||||||
.card
|
.card(ng-cloak)
|
||||||
.page-header
|
.page-header
|
||||||
h2 #{translate("thanks_for_subscribing")}
|
h2 #{translate("thanks_for_subscribing")}
|
||||||
.alert.alert-success
|
.alert.alert-success
|
||||||
p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"<strong>"+subscription.price+"</strong>", collectionDate:"<strong>"+subscription.nextPaymentDueAt+"</strong>"})}
|
p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"<strong>"+subscription.price+"</strong>", collectionDate:"<strong>"+subscription.nextPaymentDueAt+"</strong>"})}
|
||||||
p #{translate("if_you_dont_want_to_be_charged")}
|
span(sixpack-switch="upgrade-success-message")
|
||||||
a(href="/user/subscription") #{translate("click_here_to_cancel")}.
|
span(sixpack-default)
|
||||||
|
p #{translate("if_you_dont_want_to_be_charged")}
|
||||||
|
a(href="/user/subscription") #{translate("click_here_to_cancel")}.
|
||||||
|
span(sixpack-when="manage-subscription")
|
||||||
|
p #{translate("to_modify_your_subscription_go_to")}
|
||||||
|
a(href="/user/subscription") #{translate("manage_subscription")}.
|
||||||
p
|
p
|
||||||
- if (subscription.groupPlan == true)
|
- if (subscription.groupPlan == true)
|
||||||
a.btn.btn-success.btn-large(href="/subscription/group") #{translate("add_your_first_group_member_now")}
|
a.btn.btn-success.btn-large(href="/subscription/group") #{translate("add_your_first_group_member_now")}
|
||||||
|
|
64
services/web/app/views/user/activate.jade
Normal file
64
services/web/app/views/user/activate.jade
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
extends ../layout
|
||||||
|
|
||||||
|
block content
|
||||||
|
.content.content-alt
|
||||||
|
.container
|
||||||
|
.row
|
||||||
|
.col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4
|
||||||
|
.alert.alert-success #{translate("nearly_activated")}
|
||||||
|
.row
|
||||||
|
.col-md-6.col-md-offset-3.col-lg-4.col-lg-offset-4
|
||||||
|
.card
|
||||||
|
.page-header
|
||||||
|
h1 #{translate("please_set_a_password")}
|
||||||
|
form(
|
||||||
|
async-form="activate",
|
||||||
|
name="activationForm",
|
||||||
|
action="/user/password/set",
|
||||||
|
method="POST",
|
||||||
|
ng-cloak
|
||||||
|
)
|
||||||
|
input(name='_csrf', type='hidden', value=csrfToken)
|
||||||
|
input(
|
||||||
|
type="hidden",
|
||||||
|
name="passwordResetToken",
|
||||||
|
value=token
|
||||||
|
)
|
||||||
|
input(name='login_after', type='hidden', value="true")
|
||||||
|
.alert.alert-danger(ng-show="activationForm.response.error")
|
||||||
|
| #{translate("activation_token_expired")}
|
||||||
|
|
||||||
|
.form-group
|
||||||
|
label(for='email') #{translate("email")}
|
||||||
|
input.form-control(
|
||||||
|
type='email',
|
||||||
|
name='email',
|
||||||
|
placeholder="email@example.com"
|
||||||
|
required,
|
||||||
|
ng-model="email",
|
||||||
|
ng-init="email = #{JSON.stringify(email)}",
|
||||||
|
ng-model-options="{ updateOn: 'blur' }",
|
||||||
|
disabled
|
||||||
|
)
|
||||||
|
.form-group
|
||||||
|
label(for='password') #{translate("password")}
|
||||||
|
input.form-control#passwordField(
|
||||||
|
type='password',
|
||||||
|
name='password',
|
||||||
|
placeholder="********",
|
||||||
|
required,
|
||||||
|
ng-model="password",
|
||||||
|
complex-password,
|
||||||
|
focus="true"
|
||||||
|
)
|
||||||
|
span.small.text-primary(ng-show="activationForm.password.$error.complexPassword", ng-bind-html="complexPasswordErrorMessage")
|
||||||
|
.actions
|
||||||
|
button.btn-primary.btn(
|
||||||
|
type='submit'
|
||||||
|
ng-disabled="activationForm.inflight || activationForm.password.$error.required|| activationForm.password.$error.complexPassword"
|
||||||
|
)
|
||||||
|
span(ng-show="!activationForm.inflight") #{translate("activate")}
|
||||||
|
span(ng-show="activationForm.inflight") #{translate("activating")}...
|
||||||
|
|
||||||
|
script(type='text/javascript').
|
||||||
|
window.passwordStrengthOptions = !{JSON.stringify(settings.passwordStrengthOptions || {})}
|
|
@ -20,6 +20,7 @@ block content
|
||||||
placeholder='email@example.com',
|
placeholder='email@example.com',
|
||||||
ng-model="email",
|
ng-model="email",
|
||||||
ng-model-options="{ updateOn: 'blur' }",
|
ng-model-options="{ updateOn: 'blur' }",
|
||||||
|
ng-init="email = #{JSON.stringify(email)}",
|
||||||
focus="true"
|
focus="true"
|
||||||
)
|
)
|
||||||
span.small.text-primary(ng-show="loginForm.email.$invalid && loginForm.email.$dirty")
|
span.small.text-primary(ng-show="loginForm.email.$invalid && loginForm.email.$dirty")
|
||||||
|
|
|
@ -16,10 +16,11 @@ block content
|
||||||
ng-cloak
|
ng-cloak
|
||||||
)
|
)
|
||||||
input(type="hidden", name="_csrf", value=csrfToken)
|
input(type="hidden", name="_csrf", value=csrfToken)
|
||||||
form-messages(for="passwordResetForm")
|
.alert.alert-success(ng-show="passwordResetForm.response.success")
|
||||||
.alert.alert-success(ng-show="passwordResetForm.response.success")
|
| #{translate("password_has_been_reset")}.
|
||||||
| #{translate("password_has_been_reset")}.
|
a(href='/login') #{translate("login_here")}
|
||||||
a(href='/login') #{translate("login_here")}
|
.alert.alert-danger(ng-show="passwordResetForm.response.error")
|
||||||
|
| #{translate("password_reset_token_expired")}
|
||||||
|
|
||||||
.form-group
|
.form-group
|
||||||
input.form-control#passwordField(
|
input.form-control#passwordField(
|
||||||
|
|
|
@ -106,6 +106,10 @@ module.exports =
|
||||||
url: "http://localhost:3036"
|
url: "http://localhost:3036"
|
||||||
sixpack:
|
sixpack:
|
||||||
url: ""
|
url: ""
|
||||||
|
references:
|
||||||
|
url: "http://localhost:3040"
|
||||||
|
notifications:
|
||||||
|
url: "http://localhost:3042"
|
||||||
|
|
||||||
templates:
|
templates:
|
||||||
user_id: process.env.TEMPLATES_USER_ID or "5395eb7aad1f29a88756c7f2"
|
user_id: process.env.TEMPLATES_USER_ID or "5395eb7aad1f29a88756c7f2"
|
||||||
|
@ -125,6 +129,9 @@ module.exports =
|
||||||
# Same, but with http auth credentials.
|
# Same, but with http auth credentials.
|
||||||
httpAuthSiteUrl: 'http://#{httpAuthUser}:#{httpAuthPass}@localhost:3000'
|
httpAuthSiteUrl: 'http://#{httpAuthUser}:#{httpAuthPass}@localhost:3000'
|
||||||
|
|
||||||
|
|
||||||
|
maxEntitiesPerProject: 2000
|
||||||
|
|
||||||
# Security
|
# Security
|
||||||
# --------
|
# --------
|
||||||
security:
|
security:
|
||||||
|
@ -143,6 +150,8 @@ module.exports =
|
||||||
versioning: true
|
versioning: true
|
||||||
compileTimeout: 60
|
compileTimeout: 60
|
||||||
compileGroup: "standard"
|
compileGroup: "standard"
|
||||||
|
references: true
|
||||||
|
templates: true
|
||||||
|
|
||||||
plans: plans = [{
|
plans: plans = [{
|
||||||
planCode: "personal"
|
planCode: "personal"
|
||||||
|
@ -291,7 +300,7 @@ module.exports =
|
||||||
title: "ShareLaTeX Community Edition"
|
title: "ShareLaTeX Community Edition"
|
||||||
|
|
||||||
left_footer: [{
|
left_footer: [{
|
||||||
text: "Powered by <a href='https://www.sharelatex.com'>ShareLaTeX</a> © 2015"
|
text: "Powered by <a href='https://www.sharelatex.com'>ShareLaTeX</a> © 2016"
|
||||||
}]
|
}]
|
||||||
|
|
||||||
right_footer: [{
|
right_footer: [{
|
||||||
|
|
|
@ -27,18 +27,23 @@
|
||||||
"http-proxy": "^1.8.1",
|
"http-proxy": "^1.8.1",
|
||||||
"jade": "~1.3.1",
|
"jade": "~1.3.1",
|
||||||
"ldapjs": "^0.7.1",
|
"ldapjs": "^0.7.1",
|
||||||
|
<<<<<<< HEAD
|
||||||
"logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master",
|
"logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master",
|
||||||
|
=======
|
||||||
|
"logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.3.1",
|
||||||
|
>>>>>>> master
|
||||||
"lynx": "0.1.1",
|
"lynx": "0.1.1",
|
||||||
"marked": "^0.3.3",
|
"marked": "^0.3.3",
|
||||||
"method-override": "^2.3.3",
|
"method-override": "^2.3.3",
|
||||||
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.2.0",
|
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.3.0",
|
||||||
"mimelib": "0.2.14",
|
"mimelib": "0.2.14",
|
||||||
"mocha": "1.17.1",
|
"mocha": "1.17.1",
|
||||||
"mongojs": "0.18.2",
|
"mongojs": "0.18.2",
|
||||||
"mongoose": "4.1.0",
|
"mongoose": "4.1.0",
|
||||||
"multer": "^0.1.8",
|
"multer": "^0.1.8",
|
||||||
"node-uuid": "1.4.1",
|
"node-uuid": "1.4.1",
|
||||||
"nodemailer": "0.6.1",
|
"nodemailer": "2.1.0",
|
||||||
|
"nodemailer-ses-transport": "^1.3.0",
|
||||||
"optimist": "0.6.1",
|
"optimist": "0.6.1",
|
||||||
"redback": "0.4.0",
|
"redback": "0.4.0",
|
||||||
"redis": "0.10.1",
|
"redis": "0.10.1",
|
||||||
|
|
|
@ -16,7 +16,11 @@ define [
|
||||||
onUploadCallback: "="
|
onUploadCallback: "="
|
||||||
onValidateBatch: "="
|
onValidateBatch: "="
|
||||||
onErrorCallback: "="
|
onErrorCallback: "="
|
||||||
|
onSubmitCallback: "="
|
||||||
|
onCancelCallback: "="
|
||||||
|
autoUpload: "="
|
||||||
params: "="
|
params: "="
|
||||||
|
control: "="
|
||||||
}
|
}
|
||||||
link: (scope, element, attrs) ->
|
link: (scope, element, attrs) ->
|
||||||
multiple = scope.multiple or false
|
multiple = scope.multiple or false
|
||||||
|
@ -32,19 +36,27 @@ define [
|
||||||
uploadButton: scope.uploadButtonText or "Upload"
|
uploadButton: scope.uploadButtonText or "Upload"
|
||||||
dragAreaText = scope.dragAreaText or "drag here"
|
dragAreaText = scope.dragAreaText or "drag here"
|
||||||
hintText = scope.hintText or ""
|
hintText = scope.hintText or ""
|
||||||
|
maxConnections = scope.maxConnections or 1
|
||||||
onComplete = scope.onCompleteCallback or () ->
|
onComplete = scope.onCompleteCallback or () ->
|
||||||
onUpload = scope.onUploadCallback or () ->
|
onUpload = scope.onUploadCallback or () ->
|
||||||
onError = scope.onErrorCallback or () ->
|
onError = scope.onErrorCallback or () ->
|
||||||
onValidateBatch = scope.onValidateBatch or () ->
|
onValidateBatch = scope.onValidateBatch or () ->
|
||||||
|
onSubmit = scope.onSubmitCallback or () ->
|
||||||
|
onCancel = scope.onCancelCallback or () ->
|
||||||
|
if !scope.autoUpload?
|
||||||
|
autoUpload = true
|
||||||
|
else
|
||||||
|
autoUpload = scope.autoUpload
|
||||||
params = scope.params or {}
|
params = scope.params or {}
|
||||||
params._csrf = window.csrfToken
|
params._csrf = window.csrfToken
|
||||||
|
|
||||||
q = new qq.FineUploader
|
q = new qq.FineUploader
|
||||||
element: element[0]
|
element: element[0]
|
||||||
multiple: multiple
|
multiple: multiple
|
||||||
|
autoUpload: autoUpload
|
||||||
disabledCancelForFormUploads: true
|
disabledCancelForFormUploads: true
|
||||||
validation: validation
|
validation: validation
|
||||||
|
maxConnections: maxConnections
|
||||||
request:
|
request:
|
||||||
endpoint: endpoint
|
endpoint: endpoint
|
||||||
forceMultipart: true
|
forceMultipart: true
|
||||||
|
@ -55,6 +67,8 @@ define [
|
||||||
onUpload: onUpload
|
onUpload: onUpload
|
||||||
onValidateBatch: onValidateBatch
|
onValidateBatch: onValidateBatch
|
||||||
onError: onError
|
onError: onError
|
||||||
|
onSubmit: onSubmit
|
||||||
|
onCancel: onCancel
|
||||||
text: text
|
text: text
|
||||||
template: """
|
template: """
|
||||||
<div class="qq-uploader">
|
<div class="qq-uploader">
|
||||||
|
@ -69,5 +83,7 @@ define [
|
||||||
<ul class="qq-upload-list"></ul>
|
<ul class="qq-upload-list"></ul>
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
window.q = q
|
||||||
|
scope.control?.q = q
|
||||||
return q
|
return q
|
||||||
}
|
}
|
|
@ -8,6 +8,7 @@ define [
|
||||||
"ide/permissions/PermissionsManager"
|
"ide/permissions/PermissionsManager"
|
||||||
"ide/pdf/PdfManager"
|
"ide/pdf/PdfManager"
|
||||||
"ide/binary-files/BinaryFilesManager"
|
"ide/binary-files/BinaryFilesManager"
|
||||||
|
"ide/references/ReferencesManager"
|
||||||
"ide/settings/index"
|
"ide/settings/index"
|
||||||
"ide/share/index"
|
"ide/share/index"
|
||||||
"ide/chat/index"
|
"ide/chat/index"
|
||||||
|
@ -24,6 +25,7 @@ define [
|
||||||
"directives/onEnter"
|
"directives/onEnter"
|
||||||
"directives/stopPropagation"
|
"directives/stopPropagation"
|
||||||
"directives/rightClick"
|
"directives/rightClick"
|
||||||
|
"services/queued-http"
|
||||||
"filters/formatDate"
|
"filters/formatDate"
|
||||||
"main/event"
|
"main/event"
|
||||||
"main/account-upgrade"
|
"main/account-upgrade"
|
||||||
|
@ -37,6 +39,7 @@ define [
|
||||||
PermissionsManager
|
PermissionsManager
|
||||||
PdfManager
|
PdfManager
|
||||||
BinaryFilesManager
|
BinaryFilesManager
|
||||||
|
ReferencesManager
|
||||||
) ->
|
) ->
|
||||||
|
|
||||||
App.controller "IdeController", ($scope, $timeout, ide, localStorage) ->
|
App.controller "IdeController", ($scope, $timeout, ide, localStorage) ->
|
||||||
|
@ -66,12 +69,13 @@ define [
|
||||||
|
|
||||||
$scope.chat = {}
|
$scope.chat = {}
|
||||||
|
|
||||||
|
|
||||||
window._ide = ide
|
window._ide = ide
|
||||||
|
|
||||||
ide.project_id = $scope.project_id = window.project_id
|
ide.project_id = $scope.project_id = window.project_id
|
||||||
ide.$scope = $scope
|
ide.$scope = $scope
|
||||||
|
|
||||||
|
ide.referencesSearchManager = new ReferencesManager(ide, $scope)
|
||||||
ide.connectionManager = new ConnectionManager(ide, $scope)
|
ide.connectionManager = new ConnectionManager(ide, $scope)
|
||||||
ide.fileTreeManager = new FileTreeManager(ide, $scope)
|
ide.fileTreeManager = new FileTreeManager(ide, $scope)
|
||||||
ide.editorManager = new EditorManager(ide, $scope)
|
ide.editorManager = new EditorManager(ide, $scope)
|
||||||
|
@ -80,7 +84,7 @@ define [
|
||||||
ide.pdfManager = new PdfManager(ide, $scope)
|
ide.pdfManager = new PdfManager(ide, $scope)
|
||||||
ide.permissionsManager = new PermissionsManager(ide, $scope)
|
ide.permissionsManager = new PermissionsManager(ide, $scope)
|
||||||
ide.binaryFilesManager = new BinaryFilesManager(ide, $scope)
|
ide.binaryFilesManager = new BinaryFilesManager(ide, $scope)
|
||||||
|
|
||||||
inited = false
|
inited = false
|
||||||
$scope.$on "project:joined", () ->
|
$scope.$on "project:joined", () ->
|
||||||
return if inited
|
return if inited
|
||||||
|
@ -91,7 +95,7 @@ define [
|
||||||
We don't want to delete your data on ShareLaTeX, so this project still contains your history and collaborators.
|
We don't want to delete your data on ShareLaTeX, so this project still contains your history and collaborators.
|
||||||
If the project has been renamed please look in your project list for a new project under the new name.
|
If the project has been renamed please look in your project list for a new project under the new name.
|
||||||
""")
|
""")
|
||||||
|
|
||||||
DARK_THEMES = [
|
DARK_THEMES = [
|
||||||
"ambiance", "chaos", "clouds_midnight", "cobalt", "idle_fingers",
|
"ambiance", "chaos", "clouds_midnight", "cobalt", "idle_fingers",
|
||||||
"merbivore", "merbivore_soft", "mono_industrial", "monokai",
|
"merbivore", "merbivore_soft", "mono_industrial", "monokai",
|
||||||
|
|
|
@ -37,8 +37,12 @@ define [
|
||||||
|
|
||||||
# Restore previously recorded state
|
# Restore previously recorded state
|
||||||
if (state = ide.localStorage("layout.#{name}"))?
|
if (state = ide.localStorage("layout.#{name}"))?
|
||||||
options.west = state.west
|
if state.east?
|
||||||
options.east = state.east
|
if !attrs.minimumRestoreSizeEast? or (state.east.size >= attrs.minimumRestoreSizeEast and !state.east.initClosed)
|
||||||
|
options.east = state.east
|
||||||
|
if state.west?
|
||||||
|
if !attrs.minimumRestoreSizeWest? or (state.west.size >= attrs.minimumRestoreSizeWest and !state.west.initClosed)
|
||||||
|
options.west = state.west
|
||||||
|
|
||||||
repositionControls = () ->
|
repositionControls = () ->
|
||||||
state = element.layout().readState()
|
state = element.layout().readState()
|
||||||
|
|
|
@ -36,6 +36,7 @@ define [
|
||||||
@doc?.detachFromAce()
|
@doc?.detachFromAce()
|
||||||
editorDoc = @ace?.getSession().getDocument()
|
editorDoc = @ace?.getSession().getDocument()
|
||||||
editorDoc?.off "change", @_checkConsistency
|
editorDoc?.off "change", @_checkConsistency
|
||||||
|
@ide.$scope.$emit 'document:closed', @doc
|
||||||
|
|
||||||
_checkConsistency: () ->
|
_checkConsistency: () ->
|
||||||
# We've been seeing a lot of errors when I think there shouldn't be
|
# We've been seeing a lot of errors when I think there shouldn't be
|
||||||
|
@ -116,6 +117,26 @@ define [
|
||||||
flush: () ->
|
flush: () ->
|
||||||
@doc?.flushPendingOps()
|
@doc?.flushPendingOps()
|
||||||
|
|
||||||
|
chaosMonkey: (line = 0, char = "a") ->
|
||||||
|
orig = char
|
||||||
|
copy = null
|
||||||
|
pos = 0
|
||||||
|
timer = () =>
|
||||||
|
unless copy? and copy.length
|
||||||
|
copy = orig.slice() + ' ' + new Date() + '\n'
|
||||||
|
line += if Math.random() > 0.1 then 1 else -2
|
||||||
|
line = 0 if line < 0
|
||||||
|
pos = 0
|
||||||
|
char = copy[0]
|
||||||
|
copy = copy.slice(1)
|
||||||
|
@ace.session.insert({row: line, column: pos}, char)
|
||||||
|
pos += 1
|
||||||
|
@_cm = setTimeout timer, 100 + if Math.random() < 0.1 then 1000 else 0
|
||||||
|
@_cm = timer()
|
||||||
|
|
||||||
|
clearChaosMonkey: () ->
|
||||||
|
clearTimeout @_cm
|
||||||
|
|
||||||
pollSavedStatus: () ->
|
pollSavedStatus: () ->
|
||||||
# returns false if doc has ops waiting to be acknowledged or
|
# returns false if doc has ops waiting to be acknowledged or
|
||||||
# sent that haven't changed since the last time we checked.
|
# sent that haven't changed since the last time we checked.
|
||||||
|
|
|
@ -86,6 +86,7 @@ define [
|
||||||
@_bindToDocumentEvents(doc, new_sharejs_doc)
|
@_bindToDocumentEvents(doc, new_sharejs_doc)
|
||||||
callback null, new_sharejs_doc
|
callback null, new_sharejs_doc
|
||||||
|
|
||||||
|
|
||||||
_bindToDocumentEvents: (doc, sharejs_doc) ->
|
_bindToDocumentEvents: (doc, sharejs_doc) ->
|
||||||
sharejs_doc.on "error", (error, meta) =>
|
sharejs_doc.on "error", (error, meta) =>
|
||||||
if error?.message?.match "maxDocLength"
|
if error?.message?.match "maxDocLength"
|
||||||
|
@ -98,7 +99,7 @@ define [
|
||||||
@ide.reportError(error, meta)
|
@ide.reportError(error, meta)
|
||||||
@ide.showGenericMessageModal(
|
@ide.showGenericMessageModal(
|
||||||
"Out of sync"
|
"Out of sync"
|
||||||
"Sorry, this file has gone out of sync and we need to do a full refresh. Please let us know if this happens frequently."
|
"Sorry, this file has gone out of sync and we need to do a full refresh. <br> <a href='http://sharelatex.tenderapp.com/help/kb/browsers/editor-out-of-sync-problems'>Please see this help guide for more information</a>"
|
||||||
)
|
)
|
||||||
@openDoc(doc, forceReopen: true)
|
@openDoc(doc, forceReopen: true)
|
||||||
|
|
||||||
|
|
|
@ -18,7 +18,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) ->
|
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory) ->
|
||||||
monkeyPatchSearch($rootScope, $compile)
|
monkeyPatchSearch($rootScope, $compile)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -29,6 +29,7 @@ define [
|
||||||
fontSize: "="
|
fontSize: "="
|
||||||
autoComplete: "="
|
autoComplete: "="
|
||||||
sharejsDoc: "="
|
sharejsDoc: "="
|
||||||
|
spellCheck: "="
|
||||||
spellCheckLanguage: "="
|
spellCheckLanguage: "="
|
||||||
highlights: "="
|
highlights: "="
|
||||||
text: "="
|
text: "="
|
||||||
|
@ -55,7 +56,9 @@ define [
|
||||||
scope.name = attrs.aceEditor
|
scope.name = attrs.aceEditor
|
||||||
|
|
||||||
autoCompleteManager = new AutoCompleteManager(scope, editor, element)
|
autoCompleteManager = new AutoCompleteManager(scope, editor, element)
|
||||||
spellCheckManager = new SpellCheckManager(scope, editor, element)
|
if scope.spellCheck # only enable spellcheck when explicitly required
|
||||||
|
spellCheckCache = $cacheFactory("spellCheck-#{scope.name}", {capacity: 1000})
|
||||||
|
spellCheckManager = new SpellCheckManager(scope, editor, element, spellCheckCache)
|
||||||
undoManager = new UndoManager(scope, editor, element)
|
undoManager = new UndoManager(scope, editor, element)
|
||||||
highlightsManager = new HighlightsManager(scope, editor, element)
|
highlightsManager = new HighlightsManager(scope, editor, element)
|
||||||
cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage)
|
cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage)
|
||||||
|
@ -69,6 +72,21 @@ define [
|
||||||
editor.commands.removeCommand "transposeletters"
|
editor.commands.removeCommand "transposeletters"
|
||||||
editor.commands.removeCommand "showSettingsMenu"
|
editor.commands.removeCommand "showSettingsMenu"
|
||||||
editor.commands.removeCommand "foldall"
|
editor.commands.removeCommand "foldall"
|
||||||
|
|
||||||
|
# For European keyboards, the / is above 7 so needs Shift pressing.
|
||||||
|
# This comes through as Ctrl-Shift-/ which is mapped to toggleBlockComment.
|
||||||
|
# This doesn't do anything for LaTeX, so remap this to togglecomment to
|
||||||
|
# work for European keyboards as normal.
|
||||||
|
editor.commands.removeCommand "toggleBlockComment"
|
||||||
|
editor.commands.removeCommand "togglecomment"
|
||||||
|
|
||||||
|
editor.commands.addCommand {
|
||||||
|
name: "togglecomment",
|
||||||
|
bindKey: { win: "Ctrl-/|Ctrl-Shift-/", mac: "Command-/|Command-Shift-/" },
|
||||||
|
exec: (editor) -> editor.toggleCommentLines(),
|
||||||
|
multiSelectAction: "forEachLine",
|
||||||
|
scrollIntoView: "selectionPart"
|
||||||
|
}
|
||||||
|
|
||||||
# Trigger search AND replace on CMD+F
|
# Trigger search AND replace on CMD+F
|
||||||
editor.commands.addCommand
|
editor.commands.addCommand
|
||||||
|
@ -77,7 +95,6 @@ define [
|
||||||
exec: (editor) ->
|
exec: (editor) ->
|
||||||
ace.require("ace/ext/searchbox").Search(editor, true)
|
ace.require("ace/ext/searchbox").Search(editor, true)
|
||||||
readOnly: true
|
readOnly: true
|
||||||
editor.commands.removeCommand "replace"
|
|
||||||
|
|
||||||
# Bold text on CMD+B
|
# Bold text on CMD+B
|
||||||
editor.commands.addCommand
|
editor.commands.addCommand
|
||||||
|
|
|
@ -16,7 +16,7 @@ define [
|
||||||
constructor: (@$scope, @editor) ->
|
constructor: (@$scope, @editor) ->
|
||||||
@suggestionManager = new SuggestionManager()
|
@suggestionManager = new SuggestionManager()
|
||||||
|
|
||||||
@monkeyPatchAutocomplete()
|
@monkeyPatchAutocomplete()
|
||||||
|
|
||||||
@$scope.$watch "autoComplete", (autocomplete) =>
|
@$scope.$watch "autoComplete", (autocomplete) =>
|
||||||
if autocomplete
|
if autocomplete
|
||||||
|
@ -37,11 +37,47 @@ define [
|
||||||
enableSnippets: true,
|
enableSnippets: true,
|
||||||
enableLiveAutocompletion: false
|
enableLiveAutocompletion: false
|
||||||
})
|
})
|
||||||
|
|
||||||
SnippetCompleter =
|
SnippetCompleter =
|
||||||
getCompletions: (editor, session, pos, prefix, callback) ->
|
getCompletions: (editor, session, pos, prefix, callback) ->
|
||||||
callback null, Snippets
|
callback null, Snippets
|
||||||
@editor.completers = [@suggestionManager, SnippetCompleter]
|
|
||||||
|
references = @$scope.$root._references
|
||||||
|
ReferencesCompleter =
|
||||||
|
getCompletions: (editor, session, pos, prefix, callback) ->
|
||||||
|
range = new Range(pos.row, 0, pos.row, pos.column)
|
||||||
|
lineUpToCursor = editor.getSession().getTextRange(range)
|
||||||
|
commandFragment = getLastCommandFragment(lineUpToCursor)
|
||||||
|
if commandFragment
|
||||||
|
citeMatch = commandFragment.match(/^~?\\([a-z]*cite[a-z]?){(.*,)?(\w*)/)
|
||||||
|
if citeMatch
|
||||||
|
commandName = citeMatch[1]
|
||||||
|
previousArgs = citeMatch[2]
|
||||||
|
currentArg = citeMatch[3]
|
||||||
|
if previousArgs == undefined
|
||||||
|
previousArgs = ""
|
||||||
|
previousArgsCaption = if previousArgs.length > 8 then "…," else previousArgs
|
||||||
|
result = []
|
||||||
|
result.push {
|
||||||
|
caption: "\\#{commandName}{",
|
||||||
|
snippet: "\\#{commandName}{",
|
||||||
|
meta: "reference",
|
||||||
|
score: 11000
|
||||||
|
}
|
||||||
|
if references.keys and references.keys.length > 0
|
||||||
|
references.keys.forEach (key) ->
|
||||||
|
if !(key in [null, undefined])
|
||||||
|
result.push({
|
||||||
|
caption: "\\#{commandName}{#{previousArgsCaption}#{key}",
|
||||||
|
value: "\\#{commandName}{#{previousArgs}#{key}",
|
||||||
|
meta: "reference",
|
||||||
|
score: 10000
|
||||||
|
})
|
||||||
|
callback null, result
|
||||||
|
else
|
||||||
|
callback null, result
|
||||||
|
|
||||||
|
@editor.completers = [@suggestionManager, SnippetCompleter, ReferencesCompleter]
|
||||||
|
|
||||||
disable: () ->
|
disable: () ->
|
||||||
@editor.setOptions({
|
@editor.setOptions({
|
||||||
|
@ -83,7 +119,7 @@ define [
|
||||||
# since it will be adding in with the autocomplete of \begin{item}...
|
# since it will be adding in with the autocomplete of \begin{item}...
|
||||||
if this.completions.filterText.match(/^\\begin\{/) and nextChar == "}"
|
if this.completions.filterText.match(/^\\begin\{/) and nextChar == "}"
|
||||||
editor.session.remove(range)
|
editor.session.remove(range)
|
||||||
|
|
||||||
Autocomplete::_insertMatch.call this, data
|
Autocomplete::_insertMatch.call this, data
|
||||||
|
|
||||||
# Overwrite this to set autoInsert = false and set font size
|
# Overwrite this to set autoInsert = false and set font size
|
||||||
|
|
|
@ -14,7 +14,7 @@ define () ->
|
||||||
caption: "\\begin{#{env}}..."
|
caption: "\\begin{#{env}}..."
|
||||||
snippet: """
|
snippet: """
|
||||||
\\begin{#{env}}
|
\\begin{#{env}}
|
||||||
$1
|
\t$1
|
||||||
\\end{#{env}}
|
\\end{#{env}}
|
||||||
"""
|
"""
|
||||||
meta: "env"
|
meta: "env"
|
||||||
|
@ -24,8 +24,8 @@ define () ->
|
||||||
caption: "\\begin{array}..."
|
caption: "\\begin{array}..."
|
||||||
snippet: """
|
snippet: """
|
||||||
\\begin{array}{${1:cc}}
|
\\begin{array}{${1:cc}}
|
||||||
$2 & $3 \\\\\\\\
|
\t$2 & $3 \\\\\\\\
|
||||||
$4 & $5
|
\t$4 & $5
|
||||||
\\end{array}
|
\\end{array}
|
||||||
"""
|
"""
|
||||||
meta: "env"
|
meta: "env"
|
||||||
|
@ -33,10 +33,10 @@ define () ->
|
||||||
caption: "\\begin{figure}..."
|
caption: "\\begin{figure}..."
|
||||||
snippet: """
|
snippet: """
|
||||||
\\begin{figure}
|
\\begin{figure}
|
||||||
\\centering
|
\t\\centering
|
||||||
\\includegraphics{$1}
|
\t\\includegraphics{$1}
|
||||||
\\caption{${2:Caption}}
|
\t\\caption{${2:Caption}}
|
||||||
\\label{${3:fig:my_label}}
|
\t\\label{${3:fig:my_label}}
|
||||||
\\end{figure}
|
\\end{figure}
|
||||||
"""
|
"""
|
||||||
meta: "env"
|
meta: "env"
|
||||||
|
@ -44,8 +44,8 @@ define () ->
|
||||||
caption: "\\begin{tabular}..."
|
caption: "\\begin{tabular}..."
|
||||||
snippet: """
|
snippet: """
|
||||||
\\begin{tabular}{${1:c|c}}
|
\\begin{tabular}{${1:c|c}}
|
||||||
$2 & $3 \\\\\\\\
|
\t$2 & $3 \\\\\\\\
|
||||||
$4 & $5
|
\t$4 & $5
|
||||||
\\end{tabular}
|
\\end{tabular}
|
||||||
"""
|
"""
|
||||||
meta: "env"
|
meta: "env"
|
||||||
|
@ -53,13 +53,13 @@ define () ->
|
||||||
caption: "\\begin{table}..."
|
caption: "\\begin{table}..."
|
||||||
snippet: """
|
snippet: """
|
||||||
\\begin{table}[$1]
|
\\begin{table}[$1]
|
||||||
\\centering
|
\t\\centering
|
||||||
\\begin{tabular}{${2:c|c}}
|
\t\\begin{tabular}{${2:c|c}}
|
||||||
$3 & $4 \\\\\\\\
|
\t\t$3 & $4 \\\\\\\\
|
||||||
$5 & $6
|
\t\t$5 & $6
|
||||||
\\end{tabular}
|
\t\\end{tabular}
|
||||||
\\caption{${7:Caption}}
|
\t\\caption{${7:Caption}}
|
||||||
\\label{${8:tab:my_label}}
|
\t\\label{${8:tab:my_label}}
|
||||||
\\end{table}
|
\\end{table}
|
||||||
"""
|
"""
|
||||||
meta: "env"
|
meta: "env"
|
||||||
|
@ -67,7 +67,7 @@ define () ->
|
||||||
caption: "\\begin{list}..."
|
caption: "\\begin{list}..."
|
||||||
snippet: """
|
snippet: """
|
||||||
\\begin{list}
|
\\begin{list}
|
||||||
\\item $1
|
\t\\item $1
|
||||||
\\end{list}
|
\\end{list}
|
||||||
"""
|
"""
|
||||||
meta: "env"
|
meta: "env"
|
||||||
|
@ -75,7 +75,7 @@ define () ->
|
||||||
caption: "\\begin{enumerate}..."
|
caption: "\\begin{enumerate}..."
|
||||||
snippet: """
|
snippet: """
|
||||||
\\begin{enumerate}
|
\\begin{enumerate}
|
||||||
\\item $1
|
\t\\item $1
|
||||||
\\end{enumerate}
|
\\end{enumerate}
|
||||||
"""
|
"""
|
||||||
meta: "env"
|
meta: "env"
|
||||||
|
@ -83,7 +83,7 @@ define () ->
|
||||||
caption: "\\begin{itemize}..."
|
caption: "\\begin{itemize}..."
|
||||||
snippet: """
|
snippet: """
|
||||||
\\begin{itemize}
|
\\begin{itemize}
|
||||||
\\item $1
|
\t\\item $1
|
||||||
\\end{itemize}
|
\\end{itemize}
|
||||||
"""
|
"""
|
||||||
meta: "env"
|
meta: "env"
|
||||||
|
@ -91,7 +91,7 @@ define () ->
|
||||||
caption: "\\begin{frame}..."
|
caption: "\\begin{frame}..."
|
||||||
snippet: """
|
snippet: """
|
||||||
\\begin{frame}{${1:Frame Title}}
|
\\begin{frame}{${1:Frame Title}}
|
||||||
$2
|
\t$2
|
||||||
\\end{frame}
|
\\end{frame}
|
||||||
"""
|
"""
|
||||||
meta: "env"
|
meta: "env"
|
||||||
|
|
|
@ -5,7 +5,7 @@ define [
|
||||||
Range = ace.require("ace/range").Range
|
Range = ace.require("ace/range").Range
|
||||||
|
|
||||||
class SpellCheckManager
|
class SpellCheckManager
|
||||||
constructor: (@$scope, @editor, @element) ->
|
constructor: (@$scope, @editor, @element, @cache) ->
|
||||||
$(document.body).append @element.find(".spell-check-menu")
|
$(document.body).append @element.find(".spell-check-menu")
|
||||||
|
|
||||||
@updatedLines = []
|
@updatedLines = []
|
||||||
|
@ -102,6 +102,8 @@ define [
|
||||||
learnWord: (highlight) ->
|
learnWord: (highlight) ->
|
||||||
@apiRequest "/learn", word: highlight.word
|
@apiRequest "/learn", word: highlight.word
|
||||||
@highlightedWordManager.removeWord highlight.word
|
@highlightedWordManager.removeWord highlight.word
|
||||||
|
language = @$scope.spellCheckLanguage
|
||||||
|
@cache?.put("#{language}:#{highlight.word}", true)
|
||||||
|
|
||||||
getHighlightedWordAtCursor: () ->
|
getHighlightedWordAtCursor: () ->
|
||||||
cursor = @editor.getCursorPosition()
|
cursor = @editor.getCursorPosition()
|
||||||
|
@ -143,24 +145,67 @@ define [
|
||||||
runSpellCheck: (linesToProcess) ->
|
runSpellCheck: (linesToProcess) ->
|
||||||
{words, positions} = @getWords(linesToProcess)
|
{words, positions} = @getWords(linesToProcess)
|
||||||
language = @$scope.spellCheckLanguage
|
language = @$scope.spellCheckLanguage
|
||||||
@apiRequest "/check", {language: language, words: words}, (error, result) =>
|
|
||||||
if error? or !result? or !result.misspellings?
|
|
||||||
return null
|
|
||||||
|
|
||||||
|
highlights = []
|
||||||
|
seen = {}
|
||||||
|
newWords = []
|
||||||
|
newPositions = []
|
||||||
|
|
||||||
|
# iterate through all words, building up a list of
|
||||||
|
# newWords/newPositions not in the cache
|
||||||
|
for word, i in words
|
||||||
|
key = "#{language}:#{word}"
|
||||||
|
seen[key] ?= @cache.get(key) # avoid hitting the cache unnecessarily
|
||||||
|
cached = seen[key]
|
||||||
|
if not cached?
|
||||||
|
newWords.push words[i]
|
||||||
|
newPositions.push positions[i]
|
||||||
|
else if cached is true
|
||||||
|
# word is correct
|
||||||
|
else
|
||||||
|
highlights.push
|
||||||
|
column: positions[i].column
|
||||||
|
row: positions[i].row
|
||||||
|
word: word
|
||||||
|
suggestions: cached
|
||||||
|
words = newWords
|
||||||
|
positions = newPositions
|
||||||
|
|
||||||
|
displayResult = (highlights) =>
|
||||||
if linesToProcess?
|
if linesToProcess?
|
||||||
for shouldProcess, row in linesToProcess
|
for shouldProcess, row in linesToProcess
|
||||||
@highlightedWordManager.clearRows(row, row) if shouldProcess
|
@highlightedWordManager.clearRows(row, row) if shouldProcess
|
||||||
else
|
else
|
||||||
@highlightedWordManager.clearRows()
|
@highlightedWordManager.clearRows()
|
||||||
|
for highlight in highlights
|
||||||
|
@highlightedWordManager.addHighlight highlight
|
||||||
|
|
||||||
for misspelling in result.misspellings
|
if not words.length
|
||||||
word = words[misspelling.index]
|
displayResult highlights
|
||||||
position = positions[misspelling.index]
|
else
|
||||||
@highlightedWordManager.addHighlight
|
@apiRequest "/check", {language: language, words: words}, (error, result) =>
|
||||||
column: position.column
|
if error? or !result? or !result.misspellings?
|
||||||
row: position.row
|
return null
|
||||||
word: word
|
mispelled = []
|
||||||
suggestions: misspelling.suggestions
|
for misspelling in result.misspellings
|
||||||
|
word = words[misspelling.index]
|
||||||
|
position = positions[misspelling.index]
|
||||||
|
mispelled[misspelling.index] = true
|
||||||
|
highlights.push
|
||||||
|
column: position.column
|
||||||
|
row: position.row
|
||||||
|
word: word
|
||||||
|
suggestions: misspelling.suggestions
|
||||||
|
key = "#{language}:#{word}"
|
||||||
|
if not seen[key]
|
||||||
|
@cache.put key, misspelling.suggestions
|
||||||
|
seen[key] = true
|
||||||
|
for word, i in words when not mispelled[i]
|
||||||
|
key = "#{language}:#{word}"
|
||||||
|
if not seen[key]
|
||||||
|
@cache.put(key, true)
|
||||||
|
seen[key] = true
|
||||||
|
displayResult highlights
|
||||||
|
|
||||||
getWords: (linesToProcess) ->
|
getWords: (linesToProcess) ->
|
||||||
lines = @editor.getValue().split("\n")
|
lines = @editor.getValue().split("\n")
|
||||||
|
|
|
@ -19,6 +19,12 @@ define [
|
||||||
@recalculateDocList()
|
@recalculateDocList()
|
||||||
|
|
||||||
@_bindToSocketEvents()
|
@_bindToSocketEvents()
|
||||||
|
|
||||||
|
@$scope.multiSelectedCount = 0
|
||||||
|
|
||||||
|
$(document).on "click", =>
|
||||||
|
@clearMultiSelectedEntities()
|
||||||
|
$scope.$digest()
|
||||||
|
|
||||||
_bindToSocketEvents: () ->
|
_bindToSocketEvents: () ->
|
||||||
@ide.socket.on "reciveNewDoc", (parent_folder_id, doc) =>
|
@ide.socket.on "reciveNewDoc", (parent_folder_id, doc) =>
|
||||||
|
@ -65,6 +71,7 @@ define [
|
||||||
@$scope.$apply () =>
|
@$scope.$apply () =>
|
||||||
@_deleteEntityFromScope entity
|
@_deleteEntityFromScope entity
|
||||||
@recalculateDocList()
|
@recalculateDocList()
|
||||||
|
@$scope.$emit "entity:deleted", entity
|
||||||
|
|
||||||
@ide.socket.on "reciveEntityMove", (entity_id, folder_id) =>
|
@ide.socket.on "reciveEntityMove", (entity_id, folder_id) =>
|
||||||
entity = @findEntityById(entity_id)
|
entity = @findEntityById(entity_id)
|
||||||
|
@ -78,7 +85,62 @@ define [
|
||||||
@ide.fileTreeManager.forEachEntity (entity) ->
|
@ide.fileTreeManager.forEachEntity (entity) ->
|
||||||
entity.selected = false
|
entity.selected = false
|
||||||
entity.selected = true
|
entity.selected = true
|
||||||
|
|
||||||
|
toggleMultiSelectEntity: (entity) ->
|
||||||
|
entity.multiSelected = !entity.multiSelected
|
||||||
|
@$scope.multiSelectedCount = @multiSelectedCount()
|
||||||
|
|
||||||
|
multiSelectedCount: () ->
|
||||||
|
count = 0
|
||||||
|
@forEachEntity (entity) ->
|
||||||
|
if entity.multiSelected
|
||||||
|
count++
|
||||||
|
return count
|
||||||
|
|
||||||
|
getMultiSelectedEntities: () ->
|
||||||
|
entities = []
|
||||||
|
@forEachEntity (e) ->
|
||||||
|
if e.multiSelected
|
||||||
|
entities.push e
|
||||||
|
return entities
|
||||||
|
|
||||||
|
getMultiSelectedEntityChildNodes: () ->
|
||||||
|
entities = @getMultiSelectedEntities()
|
||||||
|
paths = {}
|
||||||
|
for entity in entities
|
||||||
|
paths[@getEntityPath(entity)] = entity
|
||||||
|
prefixes = {}
|
||||||
|
for path, entity of paths
|
||||||
|
parts = path.split("/")
|
||||||
|
if parts.length <= 1
|
||||||
|
continue
|
||||||
|
else
|
||||||
|
# Record prefixes a/b/c.tex -> 'a' and 'a/b'
|
||||||
|
for i in [1..(parts.length - 1)]
|
||||||
|
prefixes[parts.slice(0,i).join("/")] = true
|
||||||
|
child_entities = []
|
||||||
|
for path, entity of paths
|
||||||
|
# If the path is in the prefixes, then it's a parent folder and
|
||||||
|
# should be ignore
|
||||||
|
if !prefixes[path]?
|
||||||
|
child_entities.push entity
|
||||||
|
return child_entities
|
||||||
|
|
||||||
|
clearMultiSelectedEntities: () ->
|
||||||
|
return if @$scope.multiSelectedCount == 0 # Be efficient, this is called a lot on 'click'
|
||||||
|
@forEachEntity (entity) ->
|
||||||
|
entity.multiSelected = false
|
||||||
|
@$scope.multiSelectedCount = 0
|
||||||
|
|
||||||
|
multiSelectSelectedEntity: () ->
|
||||||
|
@findSelectedEntity()?.multiSelected = true
|
||||||
|
|
||||||
|
existsInFolder: (folder_id, name) ->
|
||||||
|
folder = @findEntityById(folder_id)
|
||||||
|
return false if !folder?
|
||||||
|
entity = @_findEntityByPathInFolder(folder, name)
|
||||||
|
return entity?
|
||||||
|
|
||||||
findSelectedEntity: () ->
|
findSelectedEntity: () ->
|
||||||
selected = null
|
selected = null
|
||||||
@forEachEntity (entity) ->
|
@forEachEntity (entity) ->
|
||||||
|
@ -277,7 +339,7 @@ define [
|
||||||
deleteEntity: (entity, callback = (error) ->) ->
|
deleteEntity: (entity, callback = (error) ->) ->
|
||||||
# We'll wait for the socket.io notification to
|
# We'll wait for the socket.io notification to
|
||||||
# delete from scope.
|
# delete from scope.
|
||||||
return @ide.$http {
|
return @ide.queuedHttp {
|
||||||
method: "DELETE"
|
method: "DELETE"
|
||||||
url: "/project/#{@ide.project_id}/#{entity.type}/#{entity.id}"
|
url: "/project/#{@ide.project_id}/#{entity.type}/#{entity.id}"
|
||||||
headers:
|
headers:
|
||||||
|
@ -289,7 +351,7 @@ define [
|
||||||
# since that would break the tree structure.
|
# since that would break the tree structure.
|
||||||
return if @_isChildFolder(entity, parent_folder)
|
return if @_isChildFolder(entity, parent_folder)
|
||||||
@_moveEntityInScope(entity, parent_folder)
|
@_moveEntityInScope(entity, parent_folder)
|
||||||
return @ide.$http.post "/project/#{@ide.project_id}/#{entity.type}/#{entity.id}/move", {
|
return @ide.queuedHttp.post "/project/#{@ide.project_id}/#{entity.type}/#{entity.id}/move", {
|
||||||
folder_id: parent_folder.id
|
folder_id: parent_folder.id
|
||||||
_csrf: window.csrfToken
|
_csrf: window.csrfToken
|
||||||
}
|
}
|
||||||
|
@ -316,8 +378,6 @@ define [
|
||||||
entity.deleted = true
|
entity.deleted = true
|
||||||
@$scope.deletedDocs.push entity
|
@$scope.deletedDocs.push entity
|
||||||
|
|
||||||
@$scope.$emit "entity:deleted", entity
|
|
||||||
|
|
||||||
_moveEntityInScope: (entity, parent_folder) ->
|
_moveEntityInScope: (entity, parent_folder) ->
|
||||||
return if entity in parent_folder.children
|
return if entity in parent_folder.children
|
||||||
@_deleteEntityFromScope(entity, moveToDeleted: false)
|
@_deleteEntityFromScope(entity, moveToDeleted: false)
|
||||||
|
|
|
@ -61,6 +61,8 @@ define [
|
||||||
$scope.state.inflight = true
|
$scope.state.inflight = true
|
||||||
ide.fileTreeManager
|
ide.fileTreeManager
|
||||||
.createDoc(name, parent_folder)
|
.createDoc(name, parent_folder)
|
||||||
|
.error (e)->
|
||||||
|
$scope.error = e
|
||||||
.success () ->
|
.success () ->
|
||||||
$scope.state.inflight = false
|
$scope.state.inflight = false
|
||||||
$modalInstance.close()
|
$modalInstance.close()
|
||||||
|
@ -90,6 +92,8 @@ define [
|
||||||
$scope.state.inflight = true
|
$scope.state.inflight = true
|
||||||
ide.fileTreeManager
|
ide.fileTreeManager
|
||||||
.createFolder(name, parent_folder)
|
.createFolder(name, parent_folder)
|
||||||
|
.error (e)->
|
||||||
|
$scope.error = e
|
||||||
.success () ->
|
.success () ->
|
||||||
$scope.state.inflight = false
|
$scope.state.inflight = false
|
||||||
$modalInstance.close()
|
$modalInstance.close()
|
||||||
|
@ -99,17 +103,30 @@ define [
|
||||||
]
|
]
|
||||||
|
|
||||||
App.controller "UploadFileModalController", [
|
App.controller "UploadFileModalController", [
|
||||||
"$scope", "ide", "$modalInstance", "$timeout", "parent_folder",
|
"$scope", "ide", "$modalInstance", "$timeout", "parent_folder", "$window"
|
||||||
($scope, ide, $modalInstance, $timeout, parent_folder) ->
|
($scope, ide, $modalInstance, $timeout, parent_folder, $window) ->
|
||||||
$scope.parent_folder_id = parent_folder?.id
|
$scope.parent_folder_id = parent_folder?.id
|
||||||
$scope.tooManyFiles = false
|
$scope.tooManyFiles = false
|
||||||
$scope.rateLimitHit = false
|
$scope.rateLimitHit = false
|
||||||
|
$scope.secondsToRedirect = 10
|
||||||
|
$scope.notLoggedIn = false
|
||||||
|
$scope.conflicts = []
|
||||||
|
$scope.control = {}
|
||||||
|
|
||||||
uploadCount = 0
|
needToLogBackIn = ->
|
||||||
$scope.onUpload = () ->
|
$scope.notLoggedIn = true
|
||||||
uploadCount++
|
decreseTimeout = ->
|
||||||
|
$timeout (() ->
|
||||||
|
if $scope.secondsToRedirect == 0
|
||||||
|
$window.location.href = "/login?redir=/project/#{ide.project_id}"
|
||||||
|
else
|
||||||
|
decreseTimeout()
|
||||||
|
$scope.secondsToRedirect = $scope.secondsToRedirect - 1
|
||||||
|
), 1000
|
||||||
|
|
||||||
$scope.max_files = 20
|
decreseTimeout()
|
||||||
|
|
||||||
|
$scope.max_files = 40
|
||||||
$scope.onComplete = (error, name, response) ->
|
$scope.onComplete = (error, name, response) ->
|
||||||
$timeout (() ->
|
$timeout (() ->
|
||||||
uploadCount--
|
uploadCount--
|
||||||
|
@ -127,8 +144,40 @@ define [
|
||||||
return true
|
return true
|
||||||
|
|
||||||
$scope.onError = (id, name, reason)->
|
$scope.onError = (id, name, reason)->
|
||||||
|
console.log(id, name, reason)
|
||||||
if reason.indexOf("429") != -1
|
if reason.indexOf("429") != -1
|
||||||
$scope.rateLimitHit = true
|
$scope.rateLimitHit = true
|
||||||
|
else if reason.indexOf("403") != -1
|
||||||
|
needToLogBackIn()
|
||||||
|
|
||||||
|
_uploadTimer = null
|
||||||
|
uploadIfNoConflicts = () ->
|
||||||
|
if $scope.conflicts.length == 0
|
||||||
|
$scope.doUpload()
|
||||||
|
|
||||||
|
uploadCount = 0
|
||||||
|
$scope.onSubmit = (id, name) ->
|
||||||
|
uploadCount++
|
||||||
|
if ide.fileTreeManager.existsInFolder($scope.parent_folder_id, name)
|
||||||
|
$scope.conflicts.push name
|
||||||
|
$scope.$apply()
|
||||||
|
if !_uploadTimer?
|
||||||
|
_uploadTimer = setTimeout () ->
|
||||||
|
_uploadTimer = null
|
||||||
|
uploadIfNoConflicts()
|
||||||
|
, 0
|
||||||
|
return true
|
||||||
|
|
||||||
|
$scope.onCancel = (id, name) ->
|
||||||
|
uploadCount--
|
||||||
|
index = $scope.conflicts.indexOf(name)
|
||||||
|
if index > -1
|
||||||
|
$scope.conflicts.splice(index, 1)
|
||||||
|
$scope.$apply()
|
||||||
|
uploadIfNoConflicts()
|
||||||
|
|
||||||
|
$scope.doUpload = () ->
|
||||||
|
$scope.control?.q?.uploadStoredFiles()
|
||||||
|
|
||||||
$scope.cancel = () ->
|
$scope.cancel = () ->
|
||||||
$modalInstance.dismiss('cancel')
|
$modalInstance.dismiss('cancel')
|
||||||
|
|
|
@ -2,9 +2,23 @@ define [
|
||||||
"base"
|
"base"
|
||||||
], (App) ->
|
], (App) ->
|
||||||
App.controller "FileTreeEntityController", ["$scope", "ide", "$modal", ($scope, ide, $modal) ->
|
App.controller "FileTreeEntityController", ["$scope", "ide", "$modal", ($scope, ide, $modal) ->
|
||||||
$scope.select = () ->
|
$scope.select = (e) ->
|
||||||
ide.fileTreeManager.selectEntity($scope.entity)
|
if e.ctrlKey or e.metaKey
|
||||||
$scope.$emit "entity:selected", $scope.entity
|
e.stopPropagation()
|
||||||
|
initialMultiSelectCount = ide.fileTreeManager.multiSelectedCount()
|
||||||
|
ide.fileTreeManager.toggleMultiSelectEntity($scope.entity) == 0
|
||||||
|
if initialMultiSelectCount == 0
|
||||||
|
# On first multi selection, also include the current active/open file.
|
||||||
|
ide.fileTreeManager.multiSelectSelectedEntity()
|
||||||
|
else
|
||||||
|
ide.fileTreeManager.selectEntity($scope.entity)
|
||||||
|
$scope.$emit "entity:selected", $scope.entity
|
||||||
|
|
||||||
|
$scope.draggableHelper = () ->
|
||||||
|
if ide.fileTreeManager.multiSelectedCount() > 0
|
||||||
|
return $("<strong style='z-index:100'>#{ide.fileTreeManager.multiSelectedCount()} Files</strong>")
|
||||||
|
else
|
||||||
|
return $("<strong style='z-index:100'>#{$scope.entity.name}</strong>")
|
||||||
|
|
||||||
$scope.inputs =
|
$scope.inputs =
|
||||||
name: $scope.entity.name
|
name: $scope.entity.name
|
||||||
|
@ -24,10 +38,15 @@ define [
|
||||||
$scope.startRenaming() if $scope.entity.selected
|
$scope.startRenaming() if $scope.entity.selected
|
||||||
|
|
||||||
$scope.openDeleteModal = () ->
|
$scope.openDeleteModal = () ->
|
||||||
|
if ide.fileTreeManager.multiSelectedCount() > 0
|
||||||
|
entities = ide.fileTreeManager.getMultiSelectedEntityChildNodes()
|
||||||
|
else
|
||||||
|
entities = [$scope.entity]
|
||||||
$modal.open(
|
$modal.open(
|
||||||
templateUrl: "deleteEntityModalTemplate"
|
templateUrl: "deleteEntityModalTemplate"
|
||||||
controller: "DeleteEntityModalController"
|
controller: "DeleteEntityModalController"
|
||||||
scope: $scope
|
resolve:
|
||||||
|
entities: () -> entities
|
||||||
)
|
)
|
||||||
|
|
||||||
$scope.$on "delete:selected", () ->
|
$scope.$on "delete:selected", () ->
|
||||||
|
@ -35,18 +54,18 @@ define [
|
||||||
]
|
]
|
||||||
|
|
||||||
App.controller "DeleteEntityModalController", [
|
App.controller "DeleteEntityModalController", [
|
||||||
"$scope", "ide", "$modalInstance",
|
"$scope", "ide", "$modalInstance", "entities"
|
||||||
($scope, ide, $modalInstance) ->
|
($scope, ide, $modalInstance, entities) ->
|
||||||
$scope.state =
|
$scope.state =
|
||||||
inflight: false
|
inflight: false
|
||||||
|
|
||||||
|
$scope.entities = entities
|
||||||
|
|
||||||
$scope.delete = () ->
|
$scope.delete = () ->
|
||||||
$scope.state.inflight = true
|
$scope.state.inflight = true
|
||||||
ide.fileTreeManager
|
for entity in $scope.entities
|
||||||
.deleteEntity($scope.entity)
|
ide.fileTreeManager.deleteEntity(entity)
|
||||||
.success () ->
|
$modalInstance.close()
|
||||||
$scope.state.inflight = false
|
|
||||||
$modalInstance.close()
|
|
||||||
|
|
||||||
$scope.cancel = () ->
|
$scope.cancel = () ->
|
||||||
$modalInstance.dismiss('cancel')
|
$modalInstance.dismiss('cancel')
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue