mirror of
https://github.com/overleaf/overleaf.git
synced 2024-12-03 12:23:35 -05:00
Merge branch 'master' into pr-v2-history-ui
This commit is contained in:
commit
8e5032fb34
39 changed files with 1060 additions and 105 deletions
|
@ -5,7 +5,8 @@ logger = require 'logger-sharelatex'
|
|||
|
||||
module.exports = LinkedFilesController = {
|
||||
Agents: {
|
||||
url: require('./UrlAgent')
|
||||
url: require('./UrlAgent'),
|
||||
project_file: require('./ProjectFileAgent')
|
||||
}
|
||||
|
||||
createLinkedFile: (req, res, next) ->
|
||||
|
@ -22,11 +23,17 @@ module.exports = LinkedFilesController = {
|
|||
|
||||
linkedFileData = Agent.sanitizeData(data)
|
||||
linkedFileData.provider = provider
|
||||
Agent.writeIncomingFileToDisk project_id, linkedFileData, user_id, (error, fsPath) ->
|
||||
if error?
|
||||
logger.error {err: error, project_id, name, linkedFileData, parent_folder_id, user_id}, 'error writing linked file to disk'
|
||||
return Agent.handleError(error, req, res, next)
|
||||
EditorController.upsertFile project_id, parent_folder_id, name, fsPath, linkedFileData, "upload", user_id, (error) ->
|
||||
return next(error) if error?
|
||||
res.send(204) # created
|
||||
}
|
||||
Agent.checkAuth project_id, linkedFileData, user_id, (err, allowed) ->
|
||||
return Agent.handleError(err, req, res, next) if err?
|
||||
return res.sendStatus(403) if !allowed
|
||||
Agent.decorateLinkedFileData linkedFileData, (err, newLinkedFileData) ->
|
||||
return Agent.handleError(err) if err?
|
||||
linkedFileData = newLinkedFileData
|
||||
Agent.writeIncomingFileToDisk project_id, linkedFileData, user_id, (error, fsPath) ->
|
||||
if error?
|
||||
logger.error {err: error, project_id, name, linkedFileData, parent_folder_id, user_id}, 'error writing linked file to disk'
|
||||
return Agent.handleError(error, req, res, next)
|
||||
EditorController.upsertFile project_id, parent_folder_id, name, fsPath, linkedFileData, "upload", user_id, (error, file) ->
|
||||
return next(error) if error?
|
||||
res.json(new_file_id: file._id) # created
|
||||
}
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
FileWriter = require('../../infrastructure/FileWriter')
|
||||
AuthorizationManager = require('../Authorization/AuthorizationManager')
|
||||
ProjectLocator = require('../Project/ProjectLocator')
|
||||
ProjectGetter = require('../Project/ProjectGetter')
|
||||
DocstoreManager = require('../Docstore/DocstoreManager')
|
||||
FileStoreHandler = require('../FileStore/FileStoreHandler')
|
||||
FileWriter = require('../../infrastructure/FileWriter')
|
||||
_ = require "underscore"
|
||||
Settings = require 'settings-sharelatex'
|
||||
|
||||
|
||||
AccessDeniedError = (message) ->
|
||||
error = new Error(message)
|
||||
error.name = 'AccessDenied'
|
||||
error.__proto__ = AccessDeniedError.prototype
|
||||
return error
|
||||
AccessDeniedError.prototype.__proto__ = Error.prototype
|
||||
|
||||
|
||||
BadEntityTypeError = (message) ->
|
||||
error = new Error(message)
|
||||
error.name = 'BadEntityType'
|
||||
error.__proto__ = BadEntityTypeError.prototype
|
||||
return error
|
||||
BadEntityTypeError.prototype.__proto__ = Error.prototype
|
||||
|
||||
|
||||
BadDataError = (message) ->
|
||||
error = new Error(message)
|
||||
error.name = 'BadData'
|
||||
error.__proto__ = BadDataError.prototype
|
||||
return error
|
||||
BadDataError.prototype.__proto__ = Error.prototype
|
||||
|
||||
|
||||
ProjectNotFoundError = (message) ->
|
||||
error = new Error(message)
|
||||
error.name = 'ProjectNotFound'
|
||||
error.__proto__ = ProjectNotFoundError.prototype
|
||||
return error
|
||||
ProjectNotFoundError.prototype.__proto__ = Error.prototype
|
||||
|
||||
|
||||
SourceFileNotFoundError = (message) ->
|
||||
error = new Error(message)
|
||||
error.name = 'BadData'
|
||||
error.__proto__ = SourceFileNotFoundError.prototype
|
||||
return error
|
||||
SourceFileNotFoundError.prototype.__proto__ = Error.prototype
|
||||
|
||||
|
||||
module.exports = ProjectFileAgent =
|
||||
|
||||
sanitizeData: (data) ->
|
||||
return _.pick(
|
||||
data,
|
||||
'source_project_id',
|
||||
'source_entity_path'
|
||||
)
|
||||
|
||||
_validate: (data) ->
|
||||
return (
|
||||
data.source_project_id? &&
|
||||
data.source_entity_path?
|
||||
)
|
||||
|
||||
decorateLinkedFileData: (data, callback = (err, newData) ->) ->
|
||||
callback = _.once(callback)
|
||||
{ source_project_id } = data
|
||||
return callback(new BadDataError()) if !source_project_id?
|
||||
ProjectGetter.getProject source_project_id, (err, project) ->
|
||||
return callback(err) if err?
|
||||
return callback(new ProjectNotFoundError()) if !project?
|
||||
callback(err, _.extend(data, {source_project_display_name: project.name}))
|
||||
|
||||
checkAuth: (project_id, data, current_user_id, callback = (error, allowed)->) ->
|
||||
callback = _.once(callback)
|
||||
if !ProjectFileAgent._validate(data)
|
||||
return callback(new BadDataError())
|
||||
{source_project_id, source_entity_path} = data
|
||||
AuthorizationManager.canUserReadProject current_user_id, source_project_id, null, (err, canRead) ->
|
||||
return callback(err) if err?
|
||||
callback(null, canRead)
|
||||
|
||||
writeIncomingFileToDisk:
|
||||
(project_id, data, current_user_id, callback = (error, fsPath) ->) ->
|
||||
callback = _.once(callback)
|
||||
if !ProjectFileAgent._validate(data)
|
||||
return callback(new BadDataError())
|
||||
{source_project_id, source_entity_path} = data
|
||||
ProjectLocator.findElementByPath {
|
||||
project_id: source_project_id,
|
||||
path: source_entity_path
|
||||
}, (err, entity, type) ->
|
||||
if err?
|
||||
if err.toString().match(/^not found.*/)
|
||||
err = new SourceFileNotFoundError()
|
||||
return callback(err)
|
||||
ProjectFileAgent._writeEntityToDisk source_project_id, entity._id, type, callback
|
||||
|
||||
_writeEntityToDisk: (project_id, entity_id, type, callback=(err, location)->) ->
|
||||
callback = _.once(callback)
|
||||
if type == 'doc'
|
||||
DocstoreManager.getDoc project_id, entity_id, (err, lines) ->
|
||||
return callback(err) if err?
|
||||
FileWriter.writeLinesToDisk entity_id, lines, callback
|
||||
else if type == 'file'
|
||||
FileStoreHandler.getFileStream project_id, entity_id, null, (err, fileStream) ->
|
||||
return callback(err) if err?
|
||||
FileWriter.writeStreamToDisk entity_id, fileStream, callback
|
||||
else
|
||||
callback(new BadEntityTypeError())
|
||||
|
||||
handleError: (error, req, res, next) ->
|
||||
if error instanceof AccessDeniedError
|
||||
res.status(403).send("You do not have access to this project")
|
||||
else if error instanceof BadDataError
|
||||
res.status(400).send("The submitted data is not valid")
|
||||
else if error instanceof BadEntityTypeError
|
||||
res.status(400).send("The file is the wrong type")
|
||||
else if error instanceof SourceFileNotFoundError
|
||||
res.status(404).send("Source file not found")
|
||||
else if error instanceof ProjectNotFoundError
|
||||
res.status(404).send("Project not found")
|
||||
else
|
||||
next(error)
|
||||
next()
|
|
@ -27,6 +27,12 @@ module.exports = UrlAgent = {
|
|||
url: @._prependHttpIfNeeded(data.url)
|
||||
}
|
||||
|
||||
decorateLinkedFileData: (data, callback = (err, newData) ->) ->
|
||||
return callback(null, data)
|
||||
|
||||
checkAuth: (project_id, data, current_user_id, callback = (error, allowed)->) ->
|
||||
callback(null, true)
|
||||
|
||||
writeIncomingFileToDisk: (project_id, data, current_user_id, callback = (error, fsPath) ->) ->
|
||||
callback = _.once(callback)
|
||||
url = data.url
|
||||
|
@ -65,4 +71,4 @@ module.exports = UrlAgent = {
|
|||
if !Settings.apis?.linkedUrlProxy?.url?
|
||||
throw new Error('no linked url proxy configured')
|
||||
return "#{Settings.apis.linkedUrlProxy.url}?url=#{encodeURIComponent(url)}"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ Sources = require "../Authorization/Sources"
|
|||
TokenAccessHandler = require '../TokenAccess/TokenAccessHandler'
|
||||
CollaboratorsHandler = require '../Collaborators/CollaboratorsHandler'
|
||||
Modules = require '../../infrastructure/Modules'
|
||||
ProjectEntityHandler = require './ProjectEntityHandler'
|
||||
crypto = require 'crypto'
|
||||
|
||||
module.exports = ProjectController =
|
||||
|
@ -138,6 +139,33 @@ module.exports = ProjectController =
|
|||
return next(err) if err?
|
||||
res.sendStatus 200
|
||||
|
||||
userProjectsJson: (req, res, next) ->
|
||||
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||
ProjectGetter.findAllUsersProjects user_id,
|
||||
'name lastUpdated publicAccesLevel archived owner_ref tokens', (err, projects) ->
|
||||
return next(err) if err?
|
||||
projects = ProjectController._buildProjectList(projects)
|
||||
.filter((p) -> !p.archived)
|
||||
.filter((p) -> !p.isV1Project)
|
||||
.map((p) -> {_id: p.id, name: p.name, accessLevel: p.accessLevel})
|
||||
|
||||
res.json({projects: projects})
|
||||
|
||||
projectEntitiesJson: (req, res, next) ->
|
||||
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||
project_id = req.params.Project_id
|
||||
ProjectGetter.getProject project_id, (err, project) ->
|
||||
return next(err) if err?
|
||||
ProjectEntityHandler.getAllEntitiesFromProject project, (err, docs, files) ->
|
||||
return next(err) if err?
|
||||
entities = docs.concat(files)
|
||||
.sort (a, b) -> a.path > b.path # Sort by path ascending
|
||||
.map (e) -> {
|
||||
path: e.path,
|
||||
type: if e.doc? then 'doc' else 'file'
|
||||
}
|
||||
res.json({project_id: project_id, entities: entities})
|
||||
|
||||
projectListPage: (req, res, next)->
|
||||
timer = new metrics.Timer("project-list")
|
||||
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||
|
@ -313,6 +341,7 @@ module.exports = ProjectController =
|
|||
maxDocLength: Settings.max_doc_length
|
||||
useV2History: !!project.overleaf?.history?.display
|
||||
showRichText: req.query?.rt == 'true'
|
||||
showTestControls: req.query?.tc == 'true' || user.isAdmin
|
||||
showPublishModal: req.query?.pm == 'true'
|
||||
timer.done()
|
||||
|
||||
|
|
|
@ -45,37 +45,40 @@ wrapWithLock = (methodWithoutLock) ->
|
|||
methodWithLock
|
||||
|
||||
module.exports = ProjectEntityUpdateHandler = self =
|
||||
# this doesn't need any locking because it's only called by ProjectDuplicator
|
||||
copyFileFromExistingProjectWithProject: (project, folder_id, originalProject_id, origonalFileRef, userId, callback = (error, fileRef, folder_id) ->)->
|
||||
project_id = project._id
|
||||
projectHistoryId = project.overleaf?.history?.id
|
||||
logger.log { project_id, folder_id, originalProject_id, origonalFileRef }, "copying file in s3 with project"
|
||||
return callback(err) if err?
|
||||
ProjectEntityMongoUpdateHandler._confirmFolder project, folder_id, (folder_id)=>
|
||||
if !origonalFileRef?
|
||||
logger.err { project_id, folder_id, originalProject_id, origonalFileRef }, "file trying to copy is null"
|
||||
return callback()
|
||||
# convert any invalid characters in original file to '_'
|
||||
fileRef = new File name : SafePath.clean(origonalFileRef.name)
|
||||
FileStoreHandler.copyFile originalProject_id, origonalFileRef._id, project._id, fileRef._id, (err, fileStoreUrl)->
|
||||
if err?
|
||||
logger.err { err, project_id, folder_id, originalProject_id, origonalFileRef }, "error coping file in s3"
|
||||
return callback(err)
|
||||
ProjectEntityMongoUpdateHandler._putElement project, folder_id, fileRef, "file", (err, result)=>
|
||||
if err?
|
||||
logger.err { err, project_id, folder_id }, "error putting element as part of copy"
|
||||
return callback(err)
|
||||
TpdsUpdateSender.addFile { project_id, file_id:fileRef._id, path:result?.path?.fileSystem, rev:fileRef.rev, project_name:project.name}, (err) ->
|
||||
copyFileFromExistingProjectWithProject: wrapWithLock
|
||||
beforeLock: (next) ->
|
||||
(project, folder_id, originalProject_id, origonalFileRef, userId, callback = (error, fileRef, folder_id) ->)->
|
||||
project_id = project._id
|
||||
logger.log { project_id, folder_id, originalProject_id, origonalFileRef }, "copying file in s3 with project"
|
||||
ProjectEntityMongoUpdateHandler._confirmFolder project, folder_id, (folder_id) ->
|
||||
if !origonalFileRef?
|
||||
logger.err { project_id, folder_id, originalProject_id, origonalFileRef }, "file trying to copy is null"
|
||||
return callback()
|
||||
# convert any invalid characters in original file to '_'
|
||||
fileRef = new File name : SafePath.clean(origonalFileRef.name)
|
||||
FileStoreHandler.copyFile originalProject_id, origonalFileRef._id, project._id, fileRef._id, (err, fileStoreUrl)->
|
||||
if err?
|
||||
logger.err { err, project_id, folder_id, originalProject_id, origonalFileRef }, "error sending file to tpds worker"
|
||||
newFiles = [
|
||||
file: fileRef
|
||||
path: result?.path?.fileSystem
|
||||
url: fileStoreUrl
|
||||
]
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, {newFiles}, (error) ->
|
||||
return callback(error) if error?
|
||||
callback null, fileRef, folder_id
|
||||
logger.err { err, project_id, folder_id, originalProject_id, origonalFileRef }, "error coping file in s3"
|
||||
return callback(err)
|
||||
next(project, folder_id, originalProject_id, origonalFileRef, userId, fileRef, fileStoreUrl, callback)
|
||||
withLock: (project, folder_id, originalProject_id, origonalFileRef, userId, fileRef, fileStoreUrl, callback = (error, fileRef, folder_id) ->)->
|
||||
project_id = project._id
|
||||
projectHistoryId = project.overleaf?.history?.id
|
||||
ProjectEntityMongoUpdateHandler._putElement project, folder_id, fileRef, "file", (err, result, newProject) ->
|
||||
if err?
|
||||
logger.err { err, project_id, folder_id }, "error putting element as part of copy"
|
||||
return callback(err)
|
||||
TpdsUpdateSender.addFile { project_id, file_id:fileRef._id, path:result?.path?.fileSystem, rev:fileRef.rev, project_name:project.name}, (err) ->
|
||||
if err?
|
||||
logger.err { err, project_id, folder_id, originalProject_id, origonalFileRef }, "error sending file to tpds worker"
|
||||
newFiles = [
|
||||
file: fileRef
|
||||
path: result?.path?.fileSystem
|
||||
url: fileStoreUrl
|
||||
]
|
||||
DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, {newFiles, newProject}, (error) ->
|
||||
return callback(error) if error?
|
||||
callback null, fileRef, folder_id
|
||||
|
||||
updateDocLines: (project_id, doc_id, lines, version, ranges, callback = (error) ->)->
|
||||
ProjectGetter.getProjectWithoutDocLines project_id, (err, project)->
|
||||
|
|
|
@ -10,6 +10,8 @@ Async = require('async')
|
|||
oneMinInMs = 60 * 1000
|
||||
fiveMinsInMs = oneMinInMs * 5
|
||||
|
||||
if !settings.apis?.references?.url?
|
||||
logger.log "references search not enabled"
|
||||
|
||||
module.exports = ReferencesHandler =
|
||||
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
path = require('path')
|
||||
Project = require('../../../js/models/Project').Project
|
||||
ProjectUploadManager = require('../../../js/Features/Uploads/ProjectUploadManager')
|
||||
ProjectOptionsHandler = require('../../../js/Features/Project/ProjectOptionsHandler')
|
||||
AuthenticationController = require('../../../js/Features/Authentication/AuthenticationController')
|
||||
settings = require('settings-sharelatex')
|
||||
fs = require('fs')
|
||||
request = require('request')
|
||||
uuid = require('uuid')
|
||||
logger = require('logger-sharelatex')
|
||||
async = require("async")
|
||||
|
||||
|
||||
module.exports = TemplatesController =
|
||||
|
||||
getV1Template: (req, res)->
|
||||
templateVersionId = req.params.Template_version_id
|
||||
templateId = req.query.id
|
||||
if !/^[0-9]+$/.test(templateVersionId) || !/^[0-9]+$/.test(templateId)
|
||||
logger.err templateVersionId:templateVersionId, templateId: templateId, "invalid template id or version"
|
||||
return res.sendStatus 400
|
||||
data = {}
|
||||
data.templateVersionId = templateVersionId
|
||||
data.templateId = templateId
|
||||
data.name = req.query.templateName
|
||||
data.compiler = req.query.latexEngine
|
||||
res.render path.resolve(__dirname, "../../../views/project/editor/new_from_template"), data
|
||||
|
||||
createProjectFromV1Template: (req, res)->
|
||||
currentUserId = AuthenticationController.getLoggedInUserId(req)
|
||||
zipUrl = "#{settings.apis.v1.url}/api/v1/sharelatex/templates/#{req.body.templateVersionId}"
|
||||
zipReq = request(zipUrl, {
|
||||
'auth': {
|
||||
'user': settings.apis.v1.user,
|
||||
'pass': settings.apis.v1.pass
|
||||
}
|
||||
})
|
||||
|
||||
TemplatesController.createFromZip(
|
||||
zipReq,
|
||||
{
|
||||
templateName: req.body.templateName,
|
||||
currentUserId: currentUserId,
|
||||
compiler: req.body.compiler
|
||||
docId: req.body.docId
|
||||
templateId: req.body.templateId
|
||||
templateVersionId: req.body.templateVersionId
|
||||
},
|
||||
req,
|
||||
res
|
||||
)
|
||||
|
||||
createFromZip: (zipReq, options, req, res)->
|
||||
dumpPath = "#{settings.path.dumpFolder}/#{uuid.v4()}"
|
||||
writeStream = fs.createWriteStream(dumpPath)
|
||||
|
||||
zipReq.on "error", (error) ->
|
||||
logger.error err: error, "error getting zip from template API"
|
||||
zipReq.pipe(writeStream)
|
||||
writeStream.on 'close', ->
|
||||
ProjectUploadManager.createProjectFromZipArchive options.currentUserId, options.templateName, dumpPath, (err, project)->
|
||||
if err?
|
||||
logger.err err:err, zipReq:zipReq, "problem building project from zip"
|
||||
return res.sendStatus 500
|
||||
setCompiler project._id, options.compiler, ->
|
||||
fs.unlink dumpPath, ->
|
||||
delete req.session.templateData
|
||||
conditions = {_id:project._id}
|
||||
update = {
|
||||
fromV1TemplateId:options.templateId,
|
||||
fromV1TemplateVersionId:options.templateVersionId
|
||||
}
|
||||
Project.update conditions, update, {}, (err)->
|
||||
res.redirect "/project/#{project._id}"
|
||||
|
||||
setCompiler = (project_id, compiler, callback)->
|
||||
if compiler?
|
||||
ProjectOptionsHandler.setCompiler project_id, compiler, callback
|
||||
else
|
||||
callback()
|
|
@ -0,0 +1,8 @@
|
|||
settings = require("settings-sharelatex")
|
||||
logger = require("logger-sharelatex")
|
||||
|
||||
module.exports =
|
||||
saveTemplateDataInSession: (req, res, next)->
|
||||
if req.query.templateName
|
||||
req.session.templateData = req.query
|
||||
next()
|
|
@ -0,0 +1,9 @@
|
|||
settings = require("settings-sharelatex")
|
||||
logger = require("logger-sharelatex")
|
||||
|
||||
|
||||
module.exports =
|
||||
saveTemplateDataInSession: (req, res, next)->
|
||||
if req.query.templateName
|
||||
req.session.templateData = req.query
|
||||
next()
|
|
@ -0,0 +1,10 @@
|
|||
AuthenticationController = require('../Authentication/AuthenticationController')
|
||||
TemplatesController = require("./TemplatesController")
|
||||
TemplatesMiddlewear = require('./TemplatesMiddlewear')
|
||||
|
||||
module.exports =
|
||||
apply: (app)->
|
||||
|
||||
app.get '/project/new/template/:Template_version_id', TemplatesMiddlewear.saveTemplateDataInSession, AuthenticationController.requireLogin(), TemplatesController.getV1Template
|
||||
|
||||
app.post '/project/new/template', AuthenticationController.requireLogin(), TemplatesController.createProjectFromV1Template
|
|
@ -6,16 +6,31 @@ Settings = require 'settings-sharelatex'
|
|||
request = require 'request'
|
||||
|
||||
module.exports = FileWriter =
|
||||
|
||||
_ensureDumpFolderExists: (callback=(error)->) ->
|
||||
fs.mkdir Settings.path.dumpFolder, (error) ->
|
||||
if error? and error.code != 'EEXIST'
|
||||
# Ignore error about already existing
|
||||
return callback(error)
|
||||
callback(null)
|
||||
|
||||
writeLinesToDisk: (identifier, lines, callback = (error, fsPath)->) ->
|
||||
callback = _.once(callback)
|
||||
fsPath = "#{Settings.path.dumpFolder}/#{identifier}_#{uuid.v4()}"
|
||||
FileWriter._ensureDumpFolderExists (error) ->
|
||||
return callback(error) if error?
|
||||
fs.writeFile fsPath, lines.join('\n'), (error) ->
|
||||
return callback(error) if error?
|
||||
callback(null, fsPath)
|
||||
|
||||
writeStreamToDisk: (identifier, stream, callback = (error, fsPath) ->) ->
|
||||
callback = _.once(callback)
|
||||
fsPath = "#{Settings.path.dumpFolder}/#{identifier}_#{uuid.v4()}"
|
||||
|
||||
stream.pause()
|
||||
fs.mkdir Settings.path.dumpFolder, (error) ->
|
||||
FileWriter._ensureDumpFolderExists (error) ->
|
||||
return callback(error) if error?
|
||||
stream.resume()
|
||||
if error? and error.code != 'EEXIST'
|
||||
# Ignore error about already existing
|
||||
return callback(error)
|
||||
|
||||
writeStream = fs.createWriteStream(fsPath)
|
||||
stream.pipe(writeStream)
|
||||
|
@ -39,4 +54,4 @@ module.exports = FileWriter =
|
|||
else
|
||||
err = new Error("bad response from url: #{response.statusCode}")
|
||||
logger.err {err, identifier, url}, err.message
|
||||
callback(err)
|
||||
callback(err)
|
||||
|
|
|
@ -48,6 +48,7 @@ MetaController = require('./Features/Metadata/MetaController')
|
|||
TokenAccessController = require('./Features/TokenAccess/TokenAccessController')
|
||||
Features = require('./infrastructure/Features')
|
||||
LinkedFilesRouter = require './Features/LinkedFiles/LinkedFilesRouter'
|
||||
TemplatesRouter = require './Features/Templates/TemplatesRouter'
|
||||
|
||||
logger = require("logger-sharelatex")
|
||||
_ = require("underscore")
|
||||
|
@ -80,10 +81,10 @@ module.exports = class Router
|
|||
ContactRouter.apply(webRouter, privateApiRouter)
|
||||
AnalyticsRouter.apply(webRouter, privateApiRouter, publicApiRouter)
|
||||
LinkedFilesRouter.apply(webRouter, privateApiRouter, publicApiRouter)
|
||||
TemplatesRouter.apply(webRouter)
|
||||
|
||||
Modules.applyRouter(webRouter, privateApiRouter, publicApiRouter)
|
||||
|
||||
|
||||
if Settings.enableSubscriptions
|
||||
webRouter.get '/user/bonus', AuthenticationController.requireLogin(), ReferalController.bonus
|
||||
|
||||
|
@ -119,6 +120,11 @@ module.exports = class Router
|
|||
webRouter.get '/user/personal_info', AuthenticationController.requireLogin(), UserInfoController.getLoggedInUsersPersonalInfo
|
||||
privateApiRouter.get '/user/:user_id/personal_info', AuthenticationController.httpAuth, UserInfoController.getPersonalInfo
|
||||
|
||||
webRouter.get '/user/projects', AuthenticationController.requireLogin(), ProjectController.userProjectsJson
|
||||
webRouter.get '/project/:Project_id/entities', AuthenticationController.requireLogin(),
|
||||
AuthorizationMiddlewear.ensureUserCanReadProject,
|
||||
ProjectController.projectEntitiesJson
|
||||
|
||||
webRouter.get '/project', AuthenticationController.requireLogin(), ProjectController.projectListPage
|
||||
webRouter.post '/project/new', AuthenticationController.requireLogin(), ProjectController.newProject
|
||||
|
||||
|
@ -202,7 +208,7 @@ module.exports = class Router
|
|||
webRouter.get "/project/:Project_id/doc/:doc_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
||||
webRouter.get "/project/:Project_id/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApiAndInjectUserDetails
|
||||
webRouter.get "/project/:Project_id/filetree/diff", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
||||
webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", AuthorizationMiddlewear.ensureUserCanReadProject, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
||||
webRouter.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.selectHistoryApi, HistoryController.proxyToHistoryApi
|
||||
webRouter.post '/project/:project_id/doc/:doc_id/restore', AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreDocFromDeletedDoc
|
||||
webRouter.post "/project/:project_id/restore_file", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreFileFromV2
|
||||
privateApiRouter.post "/project/:Project_id/history/resync", AuthenticationController.httpAuth, HistoryController.resyncProjectHistory
|
||||
|
|
|
@ -47,7 +47,18 @@ div.binary-file.full-size(
|
|||
|
|
||||
| at {{ openFile.created | formatDate:'h:mm a' }} {{ openFile.created | relativeDate }}
|
||||
|
||||
span(ng-if="openFile.linkedFileData.provider == 'url'")
|
||||
div(ng-if="openFile.linkedFileData.provider == 'project_file'")
|
||||
p
|
||||
i.fa.fa-fw.fa-external-link-square.fa-rotate-180.linked-file-icon
|
||||
| Imported from
|
||||
|
|
||||
a(ng-href='/project/{{openFile.linkedFileData.source_project_id}}' target="_blank")
|
||||
| {{ openFile.linkedFileData.source_project_display_name }}
|
||||
| /{{ openFile.linkedFileData.source_entity_path.slice(1) }},
|
||||
|
|
||||
| at {{ openFile.created | formatDate:'h:mm a' }} {{ openFile.created | relativeDate }}
|
||||
|
||||
span(ng-if="openFile.linkedFileData.provider == 'url' || openFile.linkedFileData.provider == 'project_file'")
|
||||
button.btn.btn-success(
|
||||
href, ng-click="refreshFile(openFile)",
|
||||
ng-disabled="refreshing"
|
||||
|
@ -63,3 +74,7 @@ div.binary-file.full-size(
|
|||
i.fa.fa-fw.fa-download
|
||||
|
|
||||
| #{translate("download")}
|
||||
div(ng-if="refreshError")
|
||||
br
|
||||
.alert.alert-danger.col-md-6.col-md-offset-3
|
||||
| Error: {{ refreshError}}
|
||||
|
|
|
@ -33,11 +33,11 @@ div.full-size(
|
|||
i.fa.fa-arrow-left
|
||||
| #{translate("open_a_file_on_the_left")}
|
||||
|
||||
!= moduleIncludes('editor:toolbar', locals)
|
||||
!= moduleIncludes('editor:main', locals)
|
||||
|
||||
#editor(
|
||||
ace-editor="editor",
|
||||
ng-if="!editor.richText",
|
||||
ng-if="!editor.showRichText",
|
||||
ng-show="!!editor.sharejs_doc && !editor.opening",
|
||||
style=showRichText ? "top: 32px" : "",
|
||||
theme="settings.theme",
|
||||
|
@ -73,8 +73,6 @@ div.full-size(
|
|||
line-height="settings.lineHeight || ui.defaultLineHeight"
|
||||
)
|
||||
|
||||
!= moduleIncludes('editor:body', locals)
|
||||
|
||||
include ./review-panel
|
||||
|
||||
.ui-layout-east
|
||||
|
|
|
@ -342,6 +342,77 @@ script(type='text/ng-template', id='newDocModalTemplate')
|
|||
span(ng-show="state.inflight") #{translate("creating")}...
|
||||
|
||||
|
||||
// Project Linked Files Modal
|
||||
script(type='text/ng-template', id='projectLinkedFileModalTemplate')
|
||||
.modal-header
|
||||
h3 New file from Project
|
||||
|
||||
.modal-body
|
||||
div
|
||||
div.alert.alert-danger(ng-if="state.error") Error, something went wrong!
|
||||
div
|
||||
form
|
||||
.form-controls
|
||||
label(for="project-select") Select a Project
|
||||
span(ng-show="state.inFlight.projects")
|
||||
|
|
||||
i.fa.fa-spinner.fa-spin
|
||||
select.form-control(
|
||||
name="project-select"
|
||||
ng-model="data.selectedProjectId"
|
||||
ng-disabled="!shouldEnableProjectSelect()"
|
||||
)
|
||||
option(value="" disabled selected) - Please Select a Project
|
||||
option(
|
||||
ng-repeat="project in data.projects"
|
||||
value="{{ project._id }}"
|
||||
) {{ project.name }}
|
||||
|
||||
br
|
||||
.form-controls
|
||||
label(for="project-entity-select") Select a File
|
||||
span(ng-show="state.inFlight.entities")
|
||||
|
|
||||
i.fa.fa-spinner.fa-spin
|
||||
select.form-control(
|
||||
name="project-entity-select"
|
||||
ng-model="data.selectedProjectEntity"
|
||||
ng-disabled="!shouldEnableProjectEntitySelect()"
|
||||
)
|
||||
option(value="" disabled selected) - Please Select a File
|
||||
option(
|
||||
ng-repeat="projectEntity in data.projectEntities"
|
||||
value="{{ projectEntity.path }}"
|
||||
) {{ projectEntity.path.slice(1) }}
|
||||
br
|
||||
|
||||
.form-controls
|
||||
label(for="name") File Name In This Project
|
||||
input.form-control(
|
||||
type="text"
|
||||
placeholder="example.tex"
|
||||
required
|
||||
ng-model="data.name"
|
||||
name="name"
|
||||
)
|
||||
br
|
||||
|
||||
.modal-footer
|
||||
span(ng-show="state.inFlight.create")
|
||||
i.fa.fa-spinner.fa-spin
|
||||
|
|
||||
button.btn.btn-default(
|
||||
ng-disabled="state.inflight"
|
||||
ng-click="cancel()"
|
||||
) #{translate("cancel")}
|
||||
button.btn.btn-primary(
|
||||
ng-disabled="!shouldEnableCreateButton()"
|
||||
ng-click="create()"
|
||||
)
|
||||
span(ng-hide="state.inflight") #{translate("create")}
|
||||
span(ng-show="state.inflight") #{translate("creating")}...
|
||||
|
||||
|
||||
script(type='text/ng-template', id='linkedFileModalTemplate')
|
||||
.modal-header
|
||||
h3 New file from URL
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
}"
|
||||
)
|
||||
| in <strong>{{history.diff.pathname}}</strong>
|
||||
.toolbar-right
|
||||
.toolbar-right(ng-if="permissions.write")
|
||||
a.btn.btn-danger.btn-sm(
|
||||
href,
|
||||
ng-click="openRestoreDiffModal()"
|
||||
|
|
|
@ -62,6 +62,23 @@ aside#left-menu.full-size(
|
|||
!= moduleIncludes("editorLeftMenu:editing_services", locals)
|
||||
|
||||
|
||||
if showTestControls
|
||||
h4 Test Controls
|
||||
ul.list-unstyled.nav(ng-controller="TestControlsController")
|
||||
li
|
||||
a(href="#" ng-click="richText()")
|
||||
i.fa.fa-exclamation.fa-fw
|
||||
| Rich Text
|
||||
li
|
||||
a(href="#" ng-click="openProjectLinkedFileModal()")
|
||||
i.fa.fa-exclamation.fa-fw
|
||||
| Project-Linked-File Modal
|
||||
li
|
||||
a(href="#" ng-click="openLinkedFileModal()")
|
||||
i.fa.fa-exclamation.fa-fw
|
||||
| URL-Linked-File Modal
|
||||
|
||||
|
||||
h4(ng-show="!anonymous") #{translate("settings")}
|
||||
form.settings(ng-controller="SettingsController", ng-show="!anonymous")
|
||||
.containter-fluid
|
||||
|
@ -179,6 +196,7 @@ aside#left-menu.full-size(
|
|||
option(value="pdfjs") #{translate("built_in")}
|
||||
option(value="native") #{translate("native")}
|
||||
|
||||
|
||||
h4 #{translate("hotkeys")}
|
||||
ul.list-unstyled.nav
|
||||
li(ng-controller="HotkeysController")
|
||||
|
|
26
services/web/app/views/project/editor/new_from_template.pug
Normal file
26
services/web/app/views/project/editor/new_from_template.pug
Normal file
|
@ -0,0 +1,26 @@
|
|||
extends ../../layout
|
||||
|
||||
block content
|
||||
script.
|
||||
$(document).ready(function(){
|
||||
$('#create_form').submit();
|
||||
});
|
||||
|
||||
.editor.full-size
|
||||
.loading-screen()
|
||||
.loading-screen-brand-container
|
||||
.loading-screen-brand(
|
||||
style="height: 20%;"
|
||||
)
|
||||
|
||||
h3.loading-screen-label() #{translate("Opening template")}
|
||||
span.loading-screen-ellip .
|
||||
span.loading-screen-ellip .
|
||||
span.loading-screen-ellip .
|
||||
|
||||
form(id='create_form' method='POST' action='/project/new/template/')
|
||||
input(type="hidden", name="_csrf", value=csrfToken)
|
||||
input(type="hidden" name="templateId" value=templateId)
|
||||
input(type="hidden" name="templateVersionId" value=templateVersionId)
|
||||
input(type="hidden" name="templateName" value=name)
|
||||
input(type="hidden" name="compiler" value=compiler)
|
|
@ -1,4 +1,7 @@
|
|||
.col-xs-6
|
||||
- var titleClasses = settings.overleaf ? "col-xs-6 col-sm-4 col-md-6" : "col-xs-6"
|
||||
- var lastUpdatedClasses = settings.overleaf ? " col-xs-4 col-sm-3 col-md-2" : "col-xs-4"
|
||||
|
||||
div(class=titleClasses)
|
||||
input.select-item(
|
||||
select-individual,
|
||||
type="checkbox",
|
||||
|
@ -37,8 +40,42 @@
|
|||
tooltip-placement="right"
|
||||
tooltip-append-to-body="true"
|
||||
)
|
||||
.col-xs-4
|
||||
|
||||
div(class=lastUpdatedClasses)
|
||||
if settings.overleaf
|
||||
span.last-modified(tooltip="{{project.lastUpdated | formatDate}}") {{project.lastUpdated | fromNowDate}}
|
||||
else
|
||||
span.last-modified {{project.lastUpdated | formatDate}}
|
||||
|
||||
if settings.overleaf
|
||||
.hidden-xs.col-sm-3.col-md-2.action-btn-row
|
||||
button.btn.btn-link.action-btn(
|
||||
tooltip=translate('copy'),
|
||||
tooltip-placement="top",
|
||||
tooltip-append-to-body="true",
|
||||
ng-click="clone($event)"
|
||||
)
|
||||
i.icon.fa.fa-files-o
|
||||
button.btn.btn-link.action-btn(
|
||||
tooltip=translate('download'),
|
||||
tooltip-placement="top",
|
||||
tooltip-append-to-body="true",
|
||||
ng-click="download($event)"
|
||||
)
|
||||
i.icon.fa.fa-cloud-download
|
||||
button.btn.btn-link.action-btn(
|
||||
ng-if="!project.archived"
|
||||
tooltip=translate('archive'),
|
||||
tooltip-placement="top",
|
||||
tooltip-append-to-body="true",
|
||||
ng-click="archive($event)"
|
||||
)
|
||||
i.icon.fa.fa-inbox
|
||||
button.btn.btn-link.action-btn(
|
||||
ng-if="project.archived"
|
||||
tooltip=translate('unarchive'),
|
||||
tooltip-placement="top",
|
||||
tooltip-append-to-body="true",
|
||||
ng-click="restore($event)"
|
||||
)
|
||||
i.icon.fa.fa-reply
|
|
@ -131,7 +131,10 @@
|
|||
)
|
||||
li.container-fluid
|
||||
.row
|
||||
.col-xs-6
|
||||
- var titleClasses = settings.overleaf ? " col-xs-6 col-sm-4 col-md-6" : "col-xs-6"
|
||||
- var lastUpdatedClasses = settings.overleaf ? " col-xs-4 col-sm-3 col-md-2" : "col-xs-4"
|
||||
|
||||
div(class=titleClasses)
|
||||
input.select-all(
|
||||
select-all,
|
||||
type="checkbox"
|
||||
|
@ -142,9 +145,12 @@
|
|||
.col-xs-2
|
||||
span.header.clickable(ng-click="changePredicate('accessLevel')") #{translate("owner")}
|
||||
i.tablesort.fa(ng-class="getSortIconClass('accessLevel')")
|
||||
.col-xs-4
|
||||
div(class=lastUpdatedClasses)
|
||||
span.header.clickable(ng-click="changePredicate('lastUpdated')") #{translate("last_modified")}
|
||||
i.tablesort.fa(ng-class="getSortIconClass('lastUpdated')")
|
||||
if settings.overleaf
|
||||
.hidden-xs.col-sm-3.col-md-2.action-btn-row-header
|
||||
span.header #{translate("actions")}
|
||||
li.project_entry.container-fluid(
|
||||
ng-repeat="project in visibleProjects | orderBy:predicate:reverse",
|
||||
ng-controller="ProjectListItemController"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.col-xs-6
|
||||
.col-xs-6.col-sm-4.col-md-6
|
||||
.select-item
|
||||
span.v1-badge(
|
||||
aria-label=translate("v1_badge")
|
||||
|
@ -21,5 +21,5 @@
|
|||
.col-xs-2
|
||||
span.owner {{ownerName()}}
|
||||
|
||||
.col-xs-4
|
||||
.col-xs-4.col-sm-3.col-md-2
|
||||
span.last-modified(tooltip="{{project.lastUpdated | formatDate}}") {{project.lastUpdated | fromNowDate}}
|
|
@ -146,8 +146,8 @@ module.exports = settings =
|
|||
url: "http://#{process.env['CONTACTS_HOST'] or 'localhost'}:3036"
|
||||
sixpack:
|
||||
url: ""
|
||||
# references:
|
||||
# url: "http://localhost:3040"
|
||||
references:
|
||||
url: "http://#{process.env['REFERENCES_HOST'] or 'localhost'}:3040"
|
||||
notifications:
|
||||
url: "http://#{process.env['NOTIFICATIONS_HOST'] or 'localhost'}:3042"
|
||||
analytics:
|
||||
|
|
|
@ -17,6 +17,7 @@ services:
|
|||
PROJECT_HISTORY_ENABLED: 'true'
|
||||
ENABLED_LINKED_FILE_TYPES: 'url'
|
||||
LINKED_URL_PROXY: 'http://localhost:6543'
|
||||
ENABLED_LINKED_FILE_TYPES: 'url,project_file'
|
||||
SHARELATEX_CONFIG: /app/test/acceptance/config/settings.test.coffee
|
||||
depends_on:
|
||||
- redis
|
||||
|
|
|
@ -18,6 +18,7 @@ define [
|
|||
"ide/chat/index"
|
||||
"ide/clone/index"
|
||||
"ide/hotkeys/index"
|
||||
"ide/test-controls/index"
|
||||
"ide/wordcount/index"
|
||||
"ide/directives/layout"
|
||||
"ide/directives/validFile"
|
||||
|
@ -34,6 +35,7 @@ define [
|
|||
"directives/videoPlayState"
|
||||
"services/queued-http"
|
||||
"services/validateCaptcha"
|
||||
"services/wait-for"
|
||||
"filters/formatDate"
|
||||
"main/event"
|
||||
"main/account-upgrade"
|
||||
|
@ -54,7 +56,7 @@ define [
|
|||
SafariScrollPatcher
|
||||
) ->
|
||||
|
||||
App.controller "IdeController", ($scope, $timeout, ide, localStorage, sixpack, event_tracking, metadata) ->
|
||||
App.controller "IdeController", ($scope, $timeout, ide, localStorage, sixpack, event_tracking, metadata, $q) ->
|
||||
# Don't freak out if we're already in an apply callback
|
||||
$scope.$originalApply = $scope.$apply
|
||||
$scope.$apply = (fn = () ->) ->
|
||||
|
|
|
@ -2,7 +2,7 @@ define [
|
|||
"base"
|
||||
"moment"
|
||||
], (App, moment) ->
|
||||
App.controller "BinaryFileController", ["$scope", "$rootScope", "$http", "$timeout", "$element", "ide", ($scope, $rootScope, $http, $timeout, $element, ide) ->
|
||||
App.controller "BinaryFileController", ["$scope", "$rootScope", "$http", "$timeout", "$element", "ide", "waitFor", ($scope, $rootScope, $http, $timeout, $element, ide, waitFor) ->
|
||||
|
||||
TWO_MEGABYTES = 2 * 1024 * 1024
|
||||
|
||||
|
@ -31,6 +31,7 @@ define [
|
|||
data: null
|
||||
|
||||
$scope.refreshing = false
|
||||
$scope.refreshError = null
|
||||
|
||||
MAX_URL_LENGTH = 60
|
||||
FRONT_OF_URL_LENGTH = 35
|
||||
|
@ -48,9 +49,27 @@ define [
|
|||
|
||||
$scope.refreshFile = (file) ->
|
||||
$scope.refreshing = true
|
||||
$scope.refreshError = null
|
||||
ide.fileTreeManager.refreshLinkedFile(file)
|
||||
.then () ->
|
||||
loadTextFileFilePreview()
|
||||
.then (response) ->
|
||||
{ data } = response
|
||||
{ new_file_id } = data
|
||||
$timeout(
|
||||
() ->
|
||||
waitFor(
|
||||
() ->
|
||||
ide.fileTreeManager.findEntityById(new_file_id)
|
||||
5000
|
||||
)
|
||||
.then (newFile) ->
|
||||
ide.binaryFilesManager.openFile(newFile)
|
||||
.catch (err) ->
|
||||
console.warn(err)
|
||||
, 0
|
||||
)
|
||||
$scope.refreshError = null
|
||||
.catch (response) ->
|
||||
$scope.refreshError = response.data
|
||||
.finally () ->
|
||||
$scope.refreshing = false
|
||||
|
||||
|
@ -86,11 +105,9 @@ define [
|
|||
# show dots when payload is closs to cutoff
|
||||
if data.length >= (TWO_MEGABYTES - 200)
|
||||
$scope.textPreview.shouldShowDots = true
|
||||
try
|
||||
# remove last partial line
|
||||
data = data.replace(/\n.*$/, '')
|
||||
finally
|
||||
$scope.textPreview.data = data
|
||||
data = data?.replace?(/\n.*$/, '')
|
||||
$scope.textPreview.data = data
|
||||
$timeout(setHeight, 0)
|
||||
.catch (error) ->
|
||||
console.error(error)
|
||||
|
|
|
@ -14,7 +14,7 @@ define [
|
|||
opening: true
|
||||
trackChanges: false
|
||||
wantTrackChanges: false
|
||||
richText: false
|
||||
showRichText: false
|
||||
}
|
||||
|
||||
@$scope.$on "entity:selected", (event, entity) =>
|
||||
|
|
|
@ -335,6 +335,11 @@ define [
|
|||
|
||||
return null
|
||||
|
||||
projectContainsFolder: () ->
|
||||
for entity in @$scope.rootFolder.children
|
||||
return true if entity.type == 'folder'
|
||||
return false
|
||||
|
||||
existsInThisFolder: (folder, name) ->
|
||||
for entity in folder?.children or []
|
||||
return true if entity.name is name
|
||||
|
|
|
@ -43,6 +43,19 @@ define [
|
|||
}
|
||||
)
|
||||
|
||||
$scope.openProjectLinkedFileModal = window.openProjectLinkedFileModal = () ->
|
||||
unless 'project_file' in window.data.enabledLinkedFileTypes
|
||||
console.warn("Project linked files are not enabled")
|
||||
return
|
||||
$modal.open(
|
||||
templateUrl: "projectLinkedFileModalTemplate"
|
||||
controller: "ProjectLinkedFileModalController"
|
||||
scope: $scope
|
||||
resolve: {
|
||||
parent_folder: () -> ide.fileTreeManager.getCurrentFolder()
|
||||
}
|
||||
)
|
||||
|
||||
$scope.orderByFoldersFirst = (entity) ->
|
||||
return '0' if entity?.type == "folder"
|
||||
return '1'
|
||||
|
@ -201,6 +214,117 @@ define [
|
|||
$modalInstance.dismiss('cancel')
|
||||
]
|
||||
|
||||
App.controller "ProjectLinkedFileModalController", [
|
||||
"$scope", "ide", "$modalInstance", "$timeout", "parent_folder",
|
||||
($scope, ide, $modalInstance, $timeout, parent_folder) ->
|
||||
$scope.data =
|
||||
projects: null # or []
|
||||
selectedProjectId: null
|
||||
projectEntities: null # or []
|
||||
selectedProjectEntity: null
|
||||
name: null
|
||||
$scope.state =
|
||||
inFlight:
|
||||
projects: false
|
||||
entities: false
|
||||
create: false
|
||||
error: false
|
||||
|
||||
$scope.$watch 'data.selectedProjectId', (newVal, oldVal) ->
|
||||
return if !newVal
|
||||
$scope.data.selectedProjectEntity = null
|
||||
$scope.getProjectEntities($scope.data.selectedProjectId)
|
||||
|
||||
# auto-set filename based on selected file
|
||||
$scope.$watch 'data.selectedProjectEntity', (newVal, oldVal) ->
|
||||
return if !newVal
|
||||
fileName = newVal.split('/').reverse()[0]
|
||||
if fileName
|
||||
$scope.data.name = fileName
|
||||
|
||||
_setInFlight = (type) ->
|
||||
$scope.state.inFlight[type] = true
|
||||
|
||||
_reset = (opts) ->
|
||||
isError = opts.err == true
|
||||
inFlight = $scope.state.inFlight
|
||||
inFlight.projects = inFlight.entities = inFlight.create = false
|
||||
$scope.state.error = isError
|
||||
|
||||
$scope.shouldEnableProjectSelect = () ->
|
||||
{ state, data } = $scope
|
||||
return !state.inFlight.projects && data.projects
|
||||
|
||||
$scope.shouldEnableProjectEntitySelect = () ->
|
||||
{ state, data } = $scope
|
||||
return !state.inFlight.projects && !state.inFlight.entities && data.projects && data.selectedProjectId
|
||||
|
||||
$scope.shouldEnableCreateButton = () ->
|
||||
state = $scope.state
|
||||
data = $scope.data
|
||||
return !state.inFlight.projects &&
|
||||
!state.inFlight.entities &&
|
||||
data.projects &&
|
||||
data.selectedProjectId &&
|
||||
data.projectEntities &&
|
||||
data.selectedProjectEntity &&
|
||||
data.name
|
||||
|
||||
$scope.getUserProjects = () ->
|
||||
_setInFlight('projects')
|
||||
ide.$http.get("/user/projects", {
|
||||
_csrf: window.csrfToken
|
||||
})
|
||||
.then (resp) ->
|
||||
$scope.data.projectEntities = null
|
||||
$scope.data.projects = resp.data.projects.filter (p) ->
|
||||
p._id != ide.project_id
|
||||
_reset(err: false)
|
||||
.catch (err) ->
|
||||
_reset(err: true)
|
||||
|
||||
$scope.getProjectEntities = (project_id) =>
|
||||
_setInFlight('entities')
|
||||
ide.$http.get("/project/#{project_id}/entities", {
|
||||
_csrf: window.csrfToken
|
||||
})
|
||||
.then (resp) ->
|
||||
if $scope.data.selectedProjectId == resp.data.project_id
|
||||
$scope.data.projectEntities = resp.data.entities
|
||||
_reset(err: false)
|
||||
.catch (err) ->
|
||||
_reset(err: true)
|
||||
|
||||
$scope.init = () ->
|
||||
$scope.getUserProjects()
|
||||
$timeout($scope.init, 0)
|
||||
|
||||
$scope.create = () ->
|
||||
projectId = $scope.data.selectedProjectId
|
||||
path = $scope.data.selectedProjectEntity
|
||||
name = $scope.data.name
|
||||
if !name || !path || !projectId
|
||||
_reset(err: true)
|
||||
return
|
||||
_setInFlight('create')
|
||||
ide.fileTreeManager
|
||||
.createLinkedFile(name, parent_folder, 'project_file', {
|
||||
source_project_id: projectId,
|
||||
source_entity_path: path
|
||||
})
|
||||
.then () ->
|
||||
_reset(err: false)
|
||||
$modalInstance.close()
|
||||
.catch (response)->
|
||||
{ data } = response
|
||||
_reset(err: true)
|
||||
|
||||
$scope.cancel = () ->
|
||||
$modalInstance.dismiss('cancel')
|
||||
|
||||
]
|
||||
|
||||
# TODO: rename all this to UrlLinkedFilModalController
|
||||
App.controller "LinkedFileModalController", [
|
||||
"$scope", "ide", "$modalInstance", "$timeout", "parent_folder",
|
||||
($scope, ide, $modalInstance, $timeout, parent_folder) ->
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.controller "HistoryV2DiffController", ($scope, ide, event_tracking) ->
|
||||
App.controller "HistoryV2DiffController", ($scope, ide, event_tracking, waitFor) ->
|
||||
$scope.restoreState =
|
||||
inflight: false
|
||||
error: false
|
||||
|
@ -24,17 +24,16 @@ define [
|
|||
$scope.restoreState.inflight = false
|
||||
|
||||
openEntity = (data) ->
|
||||
iterations = 0
|
||||
{id, type} = data
|
||||
do tryOpen = () ->
|
||||
if iterations > 5
|
||||
return
|
||||
iterations += 1
|
||||
entity = ide.fileTreeManager.findEntityById(id)
|
||||
if entity? and type == 'doc'
|
||||
ide.editorManager.openDoc(entity)
|
||||
else if entity? and type == 'file'
|
||||
ide.binaryFilesManager.openFile(entity)
|
||||
else
|
||||
setTimeout(tryOpen, 500)
|
||||
|
||||
waitFor(
|
||||
() ->
|
||||
ide.fileTreeManager.findEntityById(id)
|
||||
3000
|
||||
)
|
||||
.then (entity) ->
|
||||
if type == 'doc'
|
||||
ide.editorManager.openDoc(entity)
|
||||
else if type == 'file'
|
||||
ide.binaryFilesManager.openFile(entity)
|
||||
.catch (err) ->
|
||||
console.warn(err)
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
define [
|
||||
"base"
|
||||
"ace/ace"
|
||||
], (App) ->
|
||||
App.controller "TestControlsController", ($scope) ->
|
||||
|
||||
$scope.openProjectLinkedFileModal = () ->
|
||||
window.openProjectLinkedFileModal()
|
||||
|
||||
$scope.openLinkedFileModal = () ->
|
||||
window.openLinkedFileModal()
|
||||
|
||||
$scope.richText = () ->
|
||||
current = window.location.toString()
|
||||
target = "#{current}#{if window.location.search then '&' else '?'}rt=true"
|
||||
window.location.href = target
|
|
@ -0,0 +1,3 @@
|
|||
define [
|
||||
"ide/test-controls/controllers/TestControlsController"
|
||||
], () ->
|
|
@ -350,14 +350,15 @@ define [
|
|||
$scope.archiveOrLeaveSelectedProjects()
|
||||
|
||||
$scope.archiveOrLeaveSelectedProjects = () ->
|
||||
selected_projects = $scope.getSelectedProjects()
|
||||
selected_project_ids = $scope.getSelectedProjectIds()
|
||||
$scope.archiveOrLeaveProjects($scope.getSelectedProjects())
|
||||
|
||||
$scope.archiveOrLeaveProjects = (projects) ->
|
||||
projectIds = projects.map (p) -> p.id
|
||||
# Remove project from any tags
|
||||
for tag in $scope.tags
|
||||
$scope._removeProjectIdsFromTagArray(tag, selected_project_ids)
|
||||
$scope._removeProjectIdsFromTagArray(tag, projectIds)
|
||||
|
||||
for project in selected_projects
|
||||
for project in projects
|
||||
project.tags = []
|
||||
if project.accessLevel == "owner"
|
||||
project.archived = true
|
||||
|
@ -414,16 +415,17 @@ define [
|
|||
$scope.updateVisibleProjects()
|
||||
|
||||
$scope.restoreSelectedProjects = () ->
|
||||
selected_projects = $scope.getSelectedProjects()
|
||||
selected_project_ids = $scope.getSelectedProjectIds()
|
||||
$scope.restoreProjects($scope.getSelectedProjects())
|
||||
|
||||
for project in selected_projects
|
||||
$scope.restoreProjects = (projects) ->
|
||||
projectIds = projects.map (p) -> p.id
|
||||
for project in projects
|
||||
project.archived = false
|
||||
|
||||
for project_id in selected_project_ids
|
||||
for projectId in projectIds
|
||||
queuedHttp {
|
||||
method: "POST"
|
||||
url: "/project/#{project_id}/restore"
|
||||
url: "/project/#{projectId}/restore"
|
||||
headers:
|
||||
"X-CSRF-Token": window.csrfToken
|
||||
}
|
||||
|
@ -437,13 +439,14 @@ define [
|
|||
)
|
||||
|
||||
$scope.downloadSelectedProjects = () ->
|
||||
selected_project_ids = $scope.getSelectedProjectIds()
|
||||
event_tracking.send 'project-list-page-interaction', 'project action', 'Download Zip'
|
||||
if selected_project_ids.length > 1
|
||||
path = "/project/download/zip?project_ids=#{selected_project_ids.join(',')}"
|
||||
else
|
||||
path = "/project/#{selected_project_ids[0]}/download/zip"
|
||||
$scope.downloadProjectsById($scope.getSelectedProjectIds())
|
||||
|
||||
$scope.downloadProjectsById = (projectIds) ->
|
||||
event_tracking.send 'project-list-page-interaction', 'project action', 'Download Zip'
|
||||
if projectIds.length > 1
|
||||
path = "/project/download/zip?project_ids=#{projectIds.join(',')}"
|
||||
else
|
||||
path = "/project/#{projectIds[0]}/download/zip"
|
||||
window.location = path
|
||||
|
||||
$scope.openV1ImportModal = (project) ->
|
||||
|
@ -490,3 +493,19 @@ define [
|
|||
$scope.$watch "project.selected", (value) ->
|
||||
if value?
|
||||
$scope.updateSelectedProjects()
|
||||
|
||||
$scope.clone = (e) ->
|
||||
e.stopPropagation()
|
||||
$scope.cloneProject($scope.project, "#{$scope.project.name} (Copy)")
|
||||
|
||||
$scope.download = (e) ->
|
||||
e.stopPropagation()
|
||||
$scope.downloadProjectsById([$scope.project.id])
|
||||
|
||||
$scope.archive = (e) ->
|
||||
e.stopPropagation()
|
||||
$scope.archiveOrLeaveProjects([$scope.project])
|
||||
|
||||
$scope.restore = (e) ->
|
||||
e.stopPropagation()
|
||||
$scope.restoreProjects([$scope.project])
|
||||
|
|
20
services/web/public/coffee/services/wait-for.coffee
Normal file
20
services/web/public/coffee/services/wait-for.coffee
Normal file
|
@ -0,0 +1,20 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.factory "waitFor", ($q) ->
|
||||
waitFor = (testFunction, timeout, pollInterval=500) ->
|
||||
iterationLimit = Math.floor(timeout / pollInterval)
|
||||
iterations = 0
|
||||
$q(
|
||||
(resolve, reject) ->
|
||||
do tryIteration = () ->
|
||||
if iterations > iterationLimit
|
||||
return reject(new Error("waiting too long, #{JSON.stringify({timeout, pollInterval})}"))
|
||||
iterations += 1
|
||||
result = testFunction()
|
||||
if result?
|
||||
resolve(result)
|
||||
else
|
||||
setTimeout(tryIteration, pollInterval)
|
||||
)
|
||||
return waitFor
|
|
@ -369,6 +369,16 @@ ul.project-list {
|
|||
.v1-badge {
|
||||
margin-left: -4px;
|
||||
}
|
||||
|
||||
.action-btn-row-header, .action-btn-row {
|
||||
padding-right: 20px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
padding: 0 0.3em;
|
||||
margin-left: 0.2em;
|
||||
}
|
||||
}
|
||||
i.tablesort {
|
||||
padding-left: 8px;
|
||||
|
|
|
@ -145,10 +145,15 @@ output {
|
|||
opacity: 1; // iOS fix for unreadable disabled content
|
||||
}
|
||||
|
||||
// Reset height for `textarea`s
|
||||
// Reset height for `textarea`s, and smaller border-radius
|
||||
textarea& {
|
||||
height: auto;
|
||||
border-radius: @border-radius-base;
|
||||
}
|
||||
// Smaller border-radius for `select` inputs
|
||||
select& {
|
||||
border-radius: @border-radius-base;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -27,6 +27,122 @@ describe "LinkedFiles", ->
|
|||
@owner.login ->
|
||||
mkdirp Settings.path.dumpFolder, done
|
||||
|
||||
describe "creating a project linked file", ->
|
||||
before (done) ->
|
||||
@source_doc_name = 'test.txt'
|
||||
async.series [
|
||||
(cb) =>
|
||||
@owner.createProject 'plf-test-one', {template: 'blank'}, (error, project_id) =>
|
||||
@project_one_id = project_id
|
||||
cb(error)
|
||||
(cb) =>
|
||||
@owner.getProject @project_one_id, (error, project) =>
|
||||
@project_one = project
|
||||
@project_one_root_folder_id = project.rootFolder[0]._id.toString()
|
||||
cb(error)
|
||||
(cb) =>
|
||||
@owner.createProject 'plf-test-two', {template: 'blank'}, (error, project_id) =>
|
||||
@project_two_id = project_id
|
||||
cb(error)
|
||||
(cb) =>
|
||||
@owner.getProject @project_two_id, (error, project) =>
|
||||
@project_two = project
|
||||
@project_two_root_folder_id = project.rootFolder[0]._id.toString()
|
||||
cb(error)
|
||||
(cb) =>
|
||||
@owner.createDocInProject @project_two_id,
|
||||
@project_two_root_folder_id,
|
||||
@source_doc_name,
|
||||
(error, doc_id) =>
|
||||
@source_doc_id = doc_id
|
||||
cb(error)
|
||||
(cb) =>
|
||||
@owner.createDocInProject @project_two_id,
|
||||
@project_two_root_folder_id,
|
||||
'some-harmless-doc.txt',
|
||||
(error, doc_id) =>
|
||||
cb(error)
|
||||
], done
|
||||
|
||||
it 'should produce a list of the users projects', (done) ->
|
||||
@owner.request.get {
|
||||
url: "/user/projects",
|
||||
json: true
|
||||
}, (err, response, body) =>
|
||||
expect(err).to.not.exist
|
||||
expect(body).to.deep.equal {
|
||||
projects: [
|
||||
{ _id: @project_one_id, name: 'plf-test-one', accessLevel: 'owner' },
|
||||
{ _id: @project_two_id, name: 'plf-test-two', accessLevel: 'owner' }
|
||||
]
|
||||
}
|
||||
done()
|
||||
|
||||
it 'should produce a list of entities in the project', (done) ->
|
||||
@owner.request.get {
|
||||
url: "/project/#{@project_two_id}/entities",
|
||||
json: true
|
||||
}, (err, response, body) =>
|
||||
expect(err).to.not.exist
|
||||
expect(body).to.deep.equal {
|
||||
project_id: @project_two_id,
|
||||
entities: [
|
||||
{ path: '/main.tex', type: 'doc' },
|
||||
{ path: '/some-harmless-doc.txt', type: 'doc' },
|
||||
{ path: '/test.txt', type: 'doc' }
|
||||
]
|
||||
}
|
||||
done()
|
||||
|
||||
|
||||
it 'should import a file from the source project', (done) ->
|
||||
@owner.request.post {
|
||||
url: "/project/#{@project_one_id}/linked_file",
|
||||
json:
|
||||
name: 'test-link.txt',
|
||||
parent_folder_id: @project_one_root_folder_id,
|
||||
provider: 'project_file',
|
||||
data:
|
||||
source_project_id: @project_two_id,
|
||||
source_entity_path: "/#{@source_doc_name}",
|
||||
}, (error, response, body) =>
|
||||
new_file_id = body.new_file_id
|
||||
@existing_file_id = new_file_id
|
||||
expect(new_file_id).to.exist
|
||||
@owner.getProject @project_one_id, (error, project) =>
|
||||
return done(error) if error?
|
||||
firstFile = project.rootFolder[0].fileRefs[0]
|
||||
expect(firstFile._id.toString()).to.equal(new_file_id.toString())
|
||||
expect(firstFile.linkedFileData).to.deep.equal {
|
||||
provider: 'project_file',
|
||||
source_project_id: @project_two_id,
|
||||
source_entity_path: "/#{@source_doc_name}",
|
||||
source_project_display_name: "plf-test-two"
|
||||
}
|
||||
expect(firstFile.name).to.equal('test-link.txt')
|
||||
done()
|
||||
|
||||
it 'should refresh the file', (done) ->
|
||||
@owner.request.post {
|
||||
url: "/project/#{@project_one_id}/linked_file",
|
||||
json:
|
||||
name: 'test-link.txt',
|
||||
parent_folder_id: @project_one_root_folder_id,
|
||||
provider: 'project_file',
|
||||
data:
|
||||
source_project_id: @project_two_id,
|
||||
source_entity_path: "/#{@source_doc_name}",
|
||||
}, (error, response, body) =>
|
||||
new_file_id = body.new_file_id
|
||||
expect(new_file_id).to.exist
|
||||
expect(new_file_id).to.not.equal @existing_file_id
|
||||
@owner.getProject @project_one_id, (error, project) =>
|
||||
return done(error) if error?
|
||||
firstFile = project.rootFolder[0].fileRefs[0]
|
||||
expect(firstFile._id.toString()).to.equal(new_file_id.toString())
|
||||
expect(firstFile.name).to.equal('test-link.txt')
|
||||
done()
|
||||
|
||||
describe "creating a URL based linked file", ->
|
||||
before (done) ->
|
||||
@owner.createProject "url-linked-files-project", {template: "blank"}, (error, project_id) =>
|
||||
|
@ -50,7 +166,7 @@ describe "LinkedFiles", ->
|
|||
name: 'url-test-file-1'
|
||||
}, (error, response, body) =>
|
||||
throw error if error?
|
||||
expect(response.statusCode).to.equal 204
|
||||
expect(response.statusCode).to.equal 200
|
||||
@owner.getProject @project_id, (error, project) =>
|
||||
throw error if error?
|
||||
file = project.rootFolder[0].fileRefs[0]
|
||||
|
@ -76,7 +192,7 @@ describe "LinkedFiles", ->
|
|||
name: 'url-test-file-2'
|
||||
}, (error, response, body) =>
|
||||
throw error if error?
|
||||
expect(response.statusCode).to.equal 204
|
||||
expect(response.statusCode).to.equal 200
|
||||
@owner.request.post {
|
||||
url: "/project/#{@project_id}/linked_file",
|
||||
json:
|
||||
|
@ -88,7 +204,7 @@ describe "LinkedFiles", ->
|
|||
name: 'url-test-file-2'
|
||||
}, (error, response, body) =>
|
||||
throw error if error?
|
||||
expect(response.statusCode).to.equal 204
|
||||
expect(response.statusCode).to.equal 200
|
||||
@owner.getProject @project_id, (error, project) =>
|
||||
throw error if error?
|
||||
file = project.rootFolder[0].fileRefs[1]
|
||||
|
@ -168,7 +284,7 @@ describe "LinkedFiles", ->
|
|||
name: 'url-test-file-6'
|
||||
}, (error, response, body) =>
|
||||
throw error if error?
|
||||
expect(response.statusCode).to.equal 204
|
||||
expect(response.statusCode).to.equal 200
|
||||
@owner.getProject @project_id, (error, project) =>
|
||||
throw error if error?
|
||||
file = _.find project.rootFolder[0].fileRefs, (file) ->
|
||||
|
|
|
@ -143,6 +143,18 @@ class User
|
|||
return callback(err)
|
||||
callback(null)
|
||||
|
||||
createDocInProject: (project_id, parent_folder_id, name, callback=(error, doc_id)->) ->
|
||||
@getCsrfToken (error) =>
|
||||
return callback(error) if error?
|
||||
@request.post {
|
||||
url: "/project/#{project_id}/doc",
|
||||
json: {
|
||||
name: name,
|
||||
parent_folder_id: parent_folder_id
|
||||
}
|
||||
}, (error, response, body) =>
|
||||
callback(null, body._id)
|
||||
|
||||
addUserToProject: (project_id, user, privileges, callback = (error, user) ->) ->
|
||||
if privileges == 'readAndWrite'
|
||||
updateOp = {$addToSet: {collaberator_refs: user._id.toString()}}
|
||||
|
|
|
@ -67,6 +67,7 @@ describe "ProjectController", ->
|
|||
protectTokens: sinon.stub()
|
||||
@CollaboratorsHandler =
|
||||
userIsTokenMember: sinon.stub().callsArgWith(2, null, false)
|
||||
@ProjectEntityHandler = {}
|
||||
@Modules =
|
||||
hooks:
|
||||
fire: sinon.stub()
|
||||
|
@ -98,6 +99,7 @@ describe "ProjectController", ->
|
|||
"../TokenAccess/TokenAccessHandler": @TokenAccessHandler
|
||||
"../Collaborators/CollaboratorsHandler": @CollaboratorsHandler
|
||||
"../../infrastructure/Modules": @Modules
|
||||
"./ProjectEntityHandler": @ProjectEntityHandler
|
||||
|
||||
@projectName = "£12321jkj9ujkljds"
|
||||
@req =
|
||||
|
@ -520,7 +522,62 @@ describe "ProjectController", ->
|
|||
@ProjectUpdateHandler.markAsOpened.calledWith(@project_id).should.equal true
|
||||
done()
|
||||
@ProjectController.loadEditor @req, @res
|
||||
|
||||
|
||||
describe 'userProjectsJson', ->
|
||||
beforeEach (done) ->
|
||||
projects = [
|
||||
{archived: true, id: 'a', name: 'A', accessLevel: 'a', somethingElse: 1}
|
||||
{archived: false, id: 'b', name: 'B', accessLevel: 'b', somethingElse: 1}
|
||||
{archived: false, id: 'c', name: 'C', accessLevel: 'c', somethingElse: 1}
|
||||
{archived: false, id: 'd', name: 'D', accessLevel: 'd', somethingElse: 1}
|
||||
]
|
||||
@ProjectGetter.findAllUsersProjects = sinon.stub().callsArgWith(2, null, [])
|
||||
@ProjectController._buildProjectList = sinon.stub().returns(projects)
|
||||
@AuthenticationController.getLoggedInUserId = sinon.stub().returns 'abc'
|
||||
done()
|
||||
|
||||
it 'should produce a list of projects', (done) ->
|
||||
@res.json = (data) =>
|
||||
expect(data).to.deep.equal {
|
||||
projects: [
|
||||
{_id: 'b', name: 'B', accessLevel: 'b'},
|
||||
{_id: 'c', name: 'C', accessLevel: 'c'},
|
||||
{_id: 'd', name: 'D', accessLevel: 'd'}
|
||||
]
|
||||
}
|
||||
done()
|
||||
@ProjectController.userProjectsJson @req, @res, @next
|
||||
|
||||
describe 'projectEntitiesJson', ->
|
||||
beforeEach () ->
|
||||
@AuthenticationController.getLoggedInUserId = sinon.stub().returns 'abc'
|
||||
@req.params = {Project_id: 'abcd'}
|
||||
@project = { _id: 'abcd' }
|
||||
@docs = [
|
||||
{path: '/things/b.txt', doc: true},
|
||||
{path: '/main.tex', doc: true}
|
||||
]
|
||||
@files = [
|
||||
{path: '/things/a.txt'}
|
||||
]
|
||||
@ProjectGetter.getProject = sinon.stub().callsArgWith(1, null, @project)
|
||||
@ProjectEntityHandler.getAllEntitiesFromProject = sinon.stub().callsArgWith(1, null, @docs, @files)
|
||||
|
||||
it 'should produce a list of entities', (done) ->
|
||||
@res.json = (data) =>
|
||||
expect(data).to.deep.equal {
|
||||
project_id: 'abcd',
|
||||
entities: [
|
||||
{path: '/main.tex', type: 'doc'},
|
||||
{path: '/things/a.txt', type: 'file'},
|
||||
{path: '/things/b.txt', type: 'doc'}
|
||||
]
|
||||
}
|
||||
expect(@ProjectGetter.getProject.callCount).to.equal 1
|
||||
expect(@ProjectEntityHandler.getAllEntitiesFromProject.callCount).to.equal 1
|
||||
done()
|
||||
@ProjectController.projectEntitiesJson @req, @res, @next
|
||||
|
||||
describe '_isInPercentageRollout', ->
|
||||
before ->
|
||||
@ids = [
|
||||
|
|
|
@ -0,0 +1,76 @@
|
|||
should = require('chai').should()
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
assert = require('assert')
|
||||
path = require('path')
|
||||
sinon = require('sinon')
|
||||
modulePath = '../../../../app/js/Features/Templates/TemplatesController'
|
||||
|
||||
|
||||
describe 'TemplatesController', ->
|
||||
|
||||
project_id = "213432"
|
||||
|
||||
beforeEach ->
|
||||
@request = sinon.stub()
|
||||
@request.returns {
|
||||
pipe:->
|
||||
on:->
|
||||
}
|
||||
@fs = {
|
||||
unlink : sinon.stub()
|
||||
createWriteStream : sinon.stub().returns(on:(_, cb)->cb())
|
||||
}
|
||||
@ProjectUploadManager = {createProjectFromZipArchive : sinon.stub().callsArgWith(3, null, {_id:project_id})}
|
||||
@dumpFolder = "dump/path"
|
||||
@ProjectOptionsHandler = {setCompiler:sinon.stub().callsArgWith(2)}
|
||||
@uuid = "1234"
|
||||
@ProjectDetailsHandler =
|
||||
getProjectDescription:sinon.stub()
|
||||
@Project =
|
||||
update: sinon.stub().callsArgWith(3, null)
|
||||
@controller = SandboxedModule.require modulePath, requires:
|
||||
'../../../js/Features/Uploads/ProjectUploadManager':@ProjectUploadManager
|
||||
'../../../js/Features/Project/ProjectOptionsHandler':@ProjectOptionsHandler
|
||||
'../../../js/Features/Authentication/AuthenticationController': @AuthenticationController = {getLoggedInUserId: sinon.stub()}
|
||||
'./TemplatesPublisher':@TemplatesPublisher
|
||||
"logger-sharelatex":
|
||||
log:->
|
||||
err:->
|
||||
"settings-sharelatex":
|
||||
path:
|
||||
dumpFolder:@dumpFolder
|
||||
siteUrl: @siteUrl = "http://localhost:3000"
|
||||
apis:
|
||||
v1:
|
||||
url: @v1Url="http://overleaf.com"
|
||||
user: "sharelatex"
|
||||
pass: "password"
|
||||
overleaf:
|
||||
host: @v1Url
|
||||
"uuid":v4:=>@uuid
|
||||
"request": @request
|
||||
"fs":@fs
|
||||
"../../../../app/js/models/Project": {Project: @Project}
|
||||
@zipUrl = "%2Ftemplates%2F52fb86a81ae1e566597a25f6%2Fv%2F4%2Fzip&templateName=Moderncv%20Banking&compiler=pdflatex"
|
||||
@templateName = "project name here"
|
||||
@user_id = "1234"
|
||||
@req =
|
||||
session:
|
||||
user: _id:@user_id
|
||||
templateData:
|
||||
zipUrl: @zipUrl
|
||||
templateName: @templateName
|
||||
@redirect = {}
|
||||
@AuthenticationController.getLoggedInUserId.returns(@user_id)
|
||||
|
||||
describe 'v1Templates', ->
|
||||
|
||||
it "should fetch zip from v1 based on template id", (done)->
|
||||
@templateVersionId = 15
|
||||
@req.body = {templateVersionId: @templateVersionId}
|
||||
|
||||
redirect = =>
|
||||
@request.calledWith("#{@v1Url}/api/v1/sharelatex/templates/#{@templateVersionId}").should.equal true
|
||||
done()
|
||||
res = redirect:redirect
|
||||
@controller.createProjectFromV1Template @req, res
|
Loading…
Reference in a new issue