Merge branch 'master' into i18n
Conflicts: package.json
This commit is contained in:
@@ -63,6 +63,8 @@ public/minjs/
|
||||
public/js/main.js
|
||||
Gemfile.lock
|
||||
|
||||
app/views/external
|
||||
|
||||
*.swp
|
||||
.DS_Store
|
||||
|
||||
|
||||
@@ -17,10 +17,7 @@ Unit test status
|
||||
License and Credits
|
||||
-------------------
|
||||
|
||||
### Code
|
||||
|
||||
All coffeescript files (files ended in *.coffee) are licensed under the
|
||||
[AGPLv3 license](http://www.gnu.org/licenses/agpl-3.0.html).
|
||||
This project is licensed under the [AGPLv3 license](http://www.gnu.org/licenses/agpl-3.0.html)
|
||||
|
||||
### Stylesheets
|
||||
|
||||
|
||||
@@ -33,26 +33,50 @@ processingFuncs =
|
||||
else
|
||||
callback()
|
||||
|
||||
pipeStreamFrom: (options, callback)->
|
||||
pipeStreamFrom: (options, _callback)->
|
||||
callback = (args...) ->
|
||||
_callback(args...)
|
||||
_callback = () ->
|
||||
|
||||
if options.filePath == "/droppy/main.tex"
|
||||
request options.streamOrigin, (err,res, body)->
|
||||
logger.log options:options, body:body
|
||||
|
||||
origin = request(options.streamOrigin)
|
||||
|
||||
cancelled = false
|
||||
gotResponse = false
|
||||
origin.on 'response', (res) ->
|
||||
return if cancelled
|
||||
gotResponse = true
|
||||
if 200 <= res.statusCode < 300
|
||||
dest = request(options)
|
||||
origin.pipe(dest)
|
||||
|
||||
dest.on "error", (err)->
|
||||
logger.error err:err, options:options, "something went wrong in pipeStreamFrom dest"
|
||||
callback(err)
|
||||
|
||||
dest.on 'end', callback
|
||||
else
|
||||
error = new Error("received non-success status code: #{res.statusCode}")
|
||||
logger.error err: error, options: options, "something went wrong connecting to origin"
|
||||
callback(error)
|
||||
|
||||
origin.on 'error', (err)->
|
||||
return if cancelled
|
||||
gotResponse = true
|
||||
logger.error err:err, options:options, "something went wrong in pipeStreamFrom origin"
|
||||
if err?
|
||||
callback(err)
|
||||
else
|
||||
callback()
|
||||
dest = request(options)
|
||||
origin.pipe(dest)
|
||||
dest.on "error", (err)->
|
||||
logger.error err:err, options:options, "something went wrong in pipeStreamFrom dest"
|
||||
if err?
|
||||
callback(err)
|
||||
else
|
||||
callback()
|
||||
dest.on 'end', callback
|
||||
callback(err)
|
||||
|
||||
setTimeout () ->
|
||||
return if gotResponse
|
||||
cancelled = true
|
||||
error = new Error("timeout")
|
||||
logger.error err: error, options: options, "timeout"
|
||||
callback(error)
|
||||
, 2000
|
||||
|
||||
|
||||
|
||||
workerRegistration = (groupKey, method, options, callback)->
|
||||
|
||||
@@ -15,7 +15,7 @@ argv = require("optimist")
|
||||
.argv
|
||||
|
||||
Server.app.use (error, req, res, next) ->
|
||||
logger.error err: error
|
||||
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
|
||||
if res.statusCode == 500
|
||||
res.end("Oops, something went wrong with your request, sorry. If this continues, please contact us at team@sharelatex.com")
|
||||
|
||||
@@ -44,7 +44,11 @@ module.exports = AuthenticationController =
|
||||
res.send(auth_token)
|
||||
|
||||
getLoggedInUserId: (req, callback = (error, user_id) ->) ->
|
||||
callback null, req.session.user._id.toString()
|
||||
if req?.session?.user?._id?
|
||||
callback null, req.session.user._id.toString()
|
||||
else
|
||||
e = new Error("user is not on req session")
|
||||
callback e
|
||||
|
||||
getLoggedInUser: (req, options = {allow_auth_token: false}, callback = (error, user) ->) ->
|
||||
if req.session?.user?._id?
|
||||
|
||||
@@ -19,6 +19,9 @@ module.exports = CompileController =
|
||||
res.header('Content-Disposition', "filename=#{project.getSafeProjectName()}.pdf")
|
||||
CompileController.proxyToClsi("/project/#{project_id}/output/output.pdf", req, res, next)
|
||||
|
||||
deleteAuxFiles: (req, res, next) ->
|
||||
project_id = req.params.Project_id
|
||||
CompileController.proxyToClsi("/project/#{project_id}", req, res, next)
|
||||
|
||||
compileAndDownloadPdf: (req, res, next)->
|
||||
project_id = req.params.project_id
|
||||
@@ -29,8 +32,6 @@ module.exports = CompileController =
|
||||
url = "/project/#{project_id}/output/output.pdf"
|
||||
CompileController.proxyToClsi url, req, res, next
|
||||
|
||||
|
||||
|
||||
getFileFromClsi: (req, res, next = (error) ->) ->
|
||||
CompileController.proxyToClsi("/project/#{req.params.Project_id}/output/#{req.params.file}", req, res, next)
|
||||
|
||||
@@ -38,8 +39,8 @@ module.exports = CompileController =
|
||||
logger.log url: url, "proxying to CLSI"
|
||||
url = "#{Settings.apis.clsi.url}#{url}"
|
||||
oneMinute = 60 * 1000
|
||||
proxy = request.get(url: url, timeout: oneMinute)
|
||||
proxy = request(url: url, method: req.method, timeout: oneMinute)
|
||||
proxy.pipe(res)
|
||||
proxy.on "error", (error) ->
|
||||
logger.error err: error, url: url, "CLSI proxy error"
|
||||
logger.warn err: error, url: url, "CLSI proxy error"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
logger = require('logger-sharelatex')
|
||||
Metrics = require('../../infrastructure/Metrics')
|
||||
sanitize = require('validator').sanitize
|
||||
sanitize = require('sanitizer')
|
||||
ProjectEditorHandler = require('../Project/ProjectEditorHandler')
|
||||
ProjectEntityHandler = require('../Project/ProjectEntityHandler')
|
||||
ProjectOptionsHandler = require('../Project/ProjectOptionsHandler')
|
||||
@@ -163,7 +163,7 @@ module.exports = EditorController =
|
||||
|
||||
addDoc: (project_id, folder_id, docName, docLines, sl_req_id, callback = (error, doc)->)->
|
||||
{callback, sl_req_id} = slReqIdHelper.getCallbackAndReqId(callback, sl_req_id)
|
||||
docName = sanitize(docName).xss()
|
||||
docName = sanitize.escape(docName)
|
||||
logger.log sl_req_id:sl_req_id, "sending new doc to project #{project_id}"
|
||||
Metrics.inc "editor.add-doc"
|
||||
ProjectEntityHandler.addDoc project_id, folder_id, docName, docLines, sl_req_id, (err, doc, folder_id)=>
|
||||
@@ -172,7 +172,7 @@ module.exports = EditorController =
|
||||
|
||||
addFile: (project_id, folder_id, fileName, path, sl_req_id, callback = (error, file)->)->
|
||||
{callback, sl_req_id} = slReqIdHelper.getCallbackAndReqId(callback, sl_req_id)
|
||||
fileName = sanitize(fileName).xss()
|
||||
fileName = sanitize.escape(fileName)
|
||||
logger.log sl_req_id:sl_req_id, "sending new file to project #{project_id} with folderid: #{folder_id}"
|
||||
Metrics.inc "editor.add-file"
|
||||
ProjectEntityHandler.addFile project_id, folder_id, fileName, path, (err, fileRef, folder_id)=>
|
||||
@@ -185,7 +185,7 @@ module.exports = EditorController =
|
||||
|
||||
addFolder: (project_id, folder_id, folderName, sl_req_id, callback = (error, folder)->)->
|
||||
{callback, sl_req_id} = slReqIdHelper.getCallbackAndReqId(callback, sl_req_id)
|
||||
folderName = sanitize(folderName).xss()
|
||||
folderName = sanitize.escape(folderName)
|
||||
logger.log "sending new folder to project #{project_id}"
|
||||
Metrics.inc "editor.add-folder"
|
||||
ProjectEntityHandler.addFolder project_id, folder_id, folderName, (err, folder, folder_id)=>
|
||||
@@ -237,6 +237,44 @@ module.exports = EditorController =
|
||||
EditorRealTimeController.emitToRoom(project_id, 'projectDescriptionUpdated', description)
|
||||
callback()
|
||||
|
||||
deleteProject: (project_id, callback)->
|
||||
Metrics.inc "editor.delete-project"
|
||||
logger.log project_id:project_id, "recived message to delete project"
|
||||
ProjectHandler.deleteProject project_id, callback
|
||||
|
||||
renameEntity: (project_id, entity_id, entityType, newName, callback)->
|
||||
newName = sanitize.escape(newName)
|
||||
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"
|
||||
ProjectHandler.renameEntity project_id, entity_id, entityType, newName, =>
|
||||
if newName.length > 0
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveEntityRename', entity_id, newName
|
||||
callback?()
|
||||
|
||||
moveEntity: (project_id, entity_id, folder_id, entityType, callback)->
|
||||
Metrics.inc "editor.move-entity"
|
||||
ProjectEntityHandler.moveEntity project_id, entity_id, folder_id, entityType, =>
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveEntityMove', entity_id, folder_id
|
||||
callback?()
|
||||
|
||||
renameProject: (project_id, window_id, newName, callback)->
|
||||
newName = sanitize.escape(newName)
|
||||
ProjectHandler.renameProject project_id, window_id, newName, =>
|
||||
newName = sanitize.escape(newName)
|
||||
EditorRealTimeController.emitToRoom project_id, 'projectNameUpdated', window_id, newName
|
||||
callback?()
|
||||
|
||||
setPublicAccessLevel : (project_id, newAccessLevel, callback)->
|
||||
ProjectHandler.setPublicAccessLevel project_id, newAccessLevel, =>
|
||||
EditorRealTimeController.emitToRoom project_id, 'publicAccessLevelUpdated', newAccessLevel
|
||||
callback?()
|
||||
|
||||
setRootDoc: (project_id, newRootDocID, callback)->
|
||||
ProjectEntityHandler.setRootDoc project_id, newRootDocID, () =>
|
||||
EditorRealTimeController.emitToRoom project_id, 'rootDocUpdated', newRootDocID
|
||||
callback?()
|
||||
|
||||
|
||||
p:
|
||||
notifyProjectUsersOfNewFolder: (project_id, folder_id, folder, callback = (error)->)->
|
||||
logger.log project_id:project_id, folder:folder, parentFolder_id:folder_id, "sending newly created folder out to users"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
ProjectGetter = require("./ProjectGetter")
|
||||
UserGetter = require("../User/UserGetter")
|
||||
Project = require('../../models/Project').Project
|
||||
logger = require("logger-sharelatex")
|
||||
|
||||
@@ -9,12 +10,15 @@ module.exports =
|
||||
if err?
|
||||
logger.err err:err, project_id:project_id, "error getting project"
|
||||
return callback(err)
|
||||
details =
|
||||
name : project.name
|
||||
description: project.description
|
||||
compiler: project.compiler
|
||||
logger.log project_id:project_id, details:details, "getting project details"
|
||||
callback(err, details)
|
||||
UserGetter.getUser project.owner_ref, (err, user) ->
|
||||
return callback(err) if err?
|
||||
details =
|
||||
name : project.name
|
||||
description: project.description
|
||||
compiler: project.compiler
|
||||
features: user.features
|
||||
logger.log project_id:project_id, details:details, "getting project details"
|
||||
callback(err, details)
|
||||
|
||||
setProjectDescription: (project_id, description, callback)->
|
||||
conditions = _id:project_id
|
||||
|
||||
@@ -28,6 +28,9 @@ module.exports =
|
||||
jobs = _.map subscription.member_ids, (user_id)->
|
||||
return (cb)->
|
||||
UserLocator.findById user_id, (err, user)->
|
||||
if err? or !user?
|
||||
users.push _id:user_id
|
||||
return cb()
|
||||
userViewModel = buildUserViewModel(user)
|
||||
users.push(userViewModel)
|
||||
cb()
|
||||
|
||||
@@ -38,6 +38,8 @@ module.exports =
|
||||
buildViewModel : ->
|
||||
plans = Settings.plans
|
||||
|
||||
console.log plans
|
||||
console.log(typeof(plans))
|
||||
allPlans = {}
|
||||
plans.forEach (plan)->
|
||||
allPlans[plan.planCode] = plan
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ self = module.exports =
|
||||
callback()
|
||||
|
||||
_getUserIdsWithDropbox: (callback)->
|
||||
User.find {"dropbox.access_token.oauth_token_secret":{"$exists":true}}, "_id", (err, users)->
|
||||
User.find {"dropbox.access_token.oauth_token_secret":{"$exists":true}, "features.dropbox":true}, "_id", (err, users)->
|
||||
ids = users.map (user)->
|
||||
return user._id+""
|
||||
callback err, ids
|
||||
|
||||
@@ -5,7 +5,7 @@ logger = require('logger-sharelatex')
|
||||
Settings = require('settings-sharelatex')
|
||||
slReqIdHelper = require('soa-req-id')
|
||||
FileTypeManager = require('../Uploads/FileTypeManager')
|
||||
GuidManager = require '../../managers/GuidManager'
|
||||
uuid = require('node-uuid')
|
||||
fs = require('fs')
|
||||
|
||||
module.exports =
|
||||
@@ -75,7 +75,7 @@ module.exports =
|
||||
|
||||
writeStreamToDisk: (project_id, file_id, stream, callback = (err, fsPath)->)->
|
||||
if !file_id?
|
||||
file_id = GuidManager.newGuid()
|
||||
file_id = uuid.v4()
|
||||
dumpPath = "#{Settings.path.dumpFolder}/#{project_id}_#{file_id}"
|
||||
|
||||
writeStream = fs.createWriteStream(dumpPath)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
User = require("../../models/User").User
|
||||
NewsletterManager = require "../../managers/NewsletterManager"
|
||||
NewsletterManager = require "../Newsletter/NewsletterManager"
|
||||
ProjectDeleter = require("../Project/ProjectDeleter")
|
||||
logger = require("logger-sharelatex")
|
||||
SubscriptionHandler = require("../Subscription/SubscriptionHandler")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
sanitize = require('validator').sanitize
|
||||
sanitize = require('sanitizer')
|
||||
|
||||
module.exports =
|
||||
validateEmail : (email) ->
|
||||
@@ -13,7 +13,7 @@ module.exports =
|
||||
return hasZeroLength
|
||||
|
||||
validateRegisterRequest : (req, callback)->
|
||||
email = sanitize(req.body.email).xss().trim().toLowerCase()
|
||||
email = sanitize.escape(req.body.email).trim().toLowerCase()
|
||||
password = req.body.password
|
||||
username = email.match(/^[^@]*/)
|
||||
if username?
|
||||
|
||||
@@ -3,8 +3,11 @@ _ = require('underscore')
|
||||
User = require('./UserController')
|
||||
Quotes = require('../models/Quote').Quote
|
||||
|
||||
Path = require "path"
|
||||
fs = require "fs"
|
||||
homepageExists = fs.existsSync Path.resolve(__dirname + "/../../views/external/home.jade")
|
||||
|
||||
module.exports =
|
||||
module.exports = HomeController =
|
||||
index : (req,res)->
|
||||
if req.session.user
|
||||
if req.query.scribtex_path?
|
||||
@@ -12,42 +15,23 @@ module.exports =
|
||||
else
|
||||
res.redirect '/project'
|
||||
else
|
||||
res.render 'homepage/home',
|
||||
title: 'ShareLaTeX.com'
|
||||
if homepageExists
|
||||
res.render 'external/home',
|
||||
title: 'ShareLaTeX.com'
|
||||
else
|
||||
res.redirect "/login"
|
||||
|
||||
comments : (req, res)->
|
||||
res.render 'homepage/comments.jade',
|
||||
title: 'User Comments'
|
||||
|
||||
resources : (req, res)->
|
||||
res.render 'resources.jade',
|
||||
title: 'LaTeX Resources'
|
||||
|
||||
tos : (req, res) ->
|
||||
res.render 'about/tos',
|
||||
title: "Terms of Service"
|
||||
|
||||
privacy : (req, res) ->
|
||||
res.render 'about/privacy',
|
||||
title: "Privacy Policy"
|
||||
|
||||
about : (req, res) ->
|
||||
res.render 'about/about',
|
||||
title: "About us"
|
||||
externalPage: (page, title) ->
|
||||
return (req, res, next = (error) ->) ->
|
||||
path = Path.resolve(__dirname + "/../../views/external/#{page}.jade")
|
||||
fs.exists path, (exists) -> # No error in this callback - old method in Node.js!
|
||||
if exists
|
||||
res.render "external/#{page}.jade",
|
||||
title: title
|
||||
else
|
||||
HomeController.notFound(req, res, next)
|
||||
|
||||
notFound: (req, res)->
|
||||
res.statusCode = 404
|
||||
res.render 'general/404',
|
||||
title: "Page Not Found"
|
||||
|
||||
security : (req, res) ->
|
||||
res.render 'about/security',
|
||||
title: "Security"
|
||||
|
||||
attribution: (req, res) ->
|
||||
res.render 'about/attribution',
|
||||
title: "Attribution"
|
||||
|
||||
planned_maintenance: (req, res) ->
|
||||
res.render 'about/planned_maintenance',
|
||||
title: "Planned Maintenance"
|
||||
title: "Page Not Found"
|
||||
@@ -1,13 +1,12 @@
|
||||
User = require('../models/User').User
|
||||
Project = require('../models/Project').Project
|
||||
sanitize = require('validator').sanitize
|
||||
sanitize = require('sanitizer')
|
||||
path = require "path"
|
||||
logger = require('logger-sharelatex')
|
||||
_ = require('underscore')
|
||||
fs = require('fs')
|
||||
ProjectHandler = require '../handlers/ProjectHandler'
|
||||
SecurityManager = require '../managers/SecurityManager'
|
||||
GuidManager = require '../managers/GuidManager'
|
||||
Settings = require('settings-sharelatex')
|
||||
projectCreationHandler = require '../Features/Project/ProjectCreationHandler'
|
||||
projectDuplicator = require('../Features/Project/ProjectDuplicator')
|
||||
@@ -19,7 +18,7 @@ SubscriptionFormatters = require("../Features/Subscription/SubscriptionFormatter
|
||||
FileStoreHandler = require("../Features/FileStore/FileStoreHandler")
|
||||
|
||||
module.exports = class ProjectController
|
||||
constructor: (@collaberationManager)->
|
||||
constructor: ()->
|
||||
ProjectHandler = new ProjectHandler()
|
||||
|
||||
list: (req, res, next)->
|
||||
@@ -72,8 +71,8 @@ module.exports = class ProjectController
|
||||
|
||||
apiNewProject: (req, res)->
|
||||
user = req.session.user
|
||||
projectName = sanitize(req.body.projectName).xss()
|
||||
template = sanitize(req.body.template).xss()
|
||||
projectName = sanitize.escape(req.body.projectName)
|
||||
template = sanitize.escape(req.body.template)
|
||||
logger.log user: user, type: template, name: projectName, "creating project"
|
||||
if template == 'example'
|
||||
projectCreationHandler.createExampleProject user._id, projectName, (err, project)->
|
||||
@@ -123,7 +122,7 @@ module.exports = class ProjectController
|
||||
trackChanges: false
|
||||
else
|
||||
anonymous = false
|
||||
SubscriptionLocator.getUsersSubscription user._id, (err, subscription)->
|
||||
SubscriptionLocator.getUsersSubscription user?._id, (err, subscription)->
|
||||
SecurityManager.userCanAccessProject user, project, (canAccess, privlageLevel)->
|
||||
allowedFreeTrial = true
|
||||
if subscription? and subscription.freeTrial? and subscription.freeTrial.expiresAt?
|
||||
@@ -154,7 +153,7 @@ module.exports = class ProjectController
|
||||
spellCheckLanguage: user.ace.spellCheckLanguage
|
||||
pdfViewer : user.ace.pdfViewer
|
||||
docPositions: {}
|
||||
trackChanges: user.featureSwitches.trackChanges
|
||||
oldHistory: !!user.featureSwitches?.oldHistory
|
||||
})
|
||||
sharelatexObject : JSON.stringify({
|
||||
siteUrl: Settings.siteUrl,
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
User = require('../models/User').User
|
||||
sanitize = require('validator').sanitize
|
||||
sanitize = require('sanitizer')
|
||||
fs = require('fs')
|
||||
_ = require('underscore')
|
||||
logger = require('logger-sharelatex')
|
||||
Security = require('../managers/SecurityManager')
|
||||
Settings = require('settings-sharelatex')
|
||||
newsLetterManager = require('../managers/NewsletterManager')
|
||||
newsLetterManager = require('../Features/Newsletter/NewsletterManager')
|
||||
dropboxHandler = require('../Features/Dropbox/DropboxHandler')
|
||||
userRegistrationHandler = require('../Features/User/UserRegistrationHandler')
|
||||
metrics = require('../infrastructure/Metrics')
|
||||
@@ -95,8 +95,8 @@ module.exports =
|
||||
title: 'Password Reset',
|
||||
|
||||
doRequestPasswordReset : (req, res, next = (error) ->)->
|
||||
email = sanitize(req.body.email).xss()
|
||||
email = sanitize(email).trim()
|
||||
email = sanitize.escape(req.body.email)
|
||||
email = sanitize.escape(email).trim()
|
||||
email = email.toLowerCase()
|
||||
logger.log email: email, "password reset requested"
|
||||
User.findOne {'email':email}, (err, user)->
|
||||
@@ -156,11 +156,11 @@ module.exports =
|
||||
metrics.inc "user.settings-update"
|
||||
User.findById req.session.user._id, (err, user)->
|
||||
if(user)
|
||||
user.first_name = sanitize(req.body.first_name).xss().trim()
|
||||
user.last_name = sanitize(req.body.last_name).xss().trim()
|
||||
user.ace.mode = sanitize(req.body.mode).xss().trim()
|
||||
user.ace.theme = sanitize(req.body.theme).xss().trim()
|
||||
user.ace.fontSize = sanitize(req.body.fontSize).xss().trim()
|
||||
user.first_name = sanitize.escape(req.body.first_name).trim()
|
||||
user.last_name = sanitize.escape(req.body.last_name).trim()
|
||||
user.ace.mode = sanitize.escape(req.body.mode).trim()
|
||||
user.ace.theme = sanitize.escape(req.body.theme).trim()
|
||||
user.ace.fontSize = sanitize.escape(req.body.fontSize).trim()
|
||||
user.ace.autoComplete = req.body.autoComplete == "true"
|
||||
user.ace.spellCheckLanguage = req.body.spellCheckLanguage
|
||||
user.ace.pdfViewer = req.body.pdfViewer
|
||||
@@ -171,7 +171,7 @@ module.exports =
|
||||
metrics.inc "user.password-change"
|
||||
oldPass = req.body.currentPassword
|
||||
AuthenticationManager.authenticate _id: req.session.user._id, oldPass, (err, user)->
|
||||
return callback(err) if err?
|
||||
return next(err) if err?
|
||||
if(user)
|
||||
logger.log user: req.session.user, "changing password"
|
||||
newPassword1 = req.body.newPassword1
|
||||
@@ -197,23 +197,23 @@ module.exports =
|
||||
type:'error'
|
||||
text:'Your old password is wrong'
|
||||
|
||||
redirectUserToDropboxAuth: (req, res)->
|
||||
redirectUserToDropboxAuth: (req, res, next)->
|
||||
user_id = req.session.user._id
|
||||
dropboxHandler.getDropboxRegisterUrl user_id, (err, url)->
|
||||
return callback(err) if err?
|
||||
return next(err) if err?
|
||||
logger.log url:url, "redirecting user for dropbox auth"
|
||||
res.redirect url
|
||||
|
||||
completeDropboxRegistration: (req, res)->
|
||||
completeDropboxRegistration: (req, res, next)->
|
||||
user_id = req.session.user._id
|
||||
dropboxHandler.completeRegistration user_id, (err, success)->
|
||||
return callback(err) if err?
|
||||
return next(err) if err?
|
||||
res.redirect('/user/settings#dropboxSettings')
|
||||
|
||||
unlinkDropbox: (req, res)->
|
||||
unlinkDropbox: (req, res, next)->
|
||||
user_id = req.session.user._id
|
||||
dropboxHandler.unlinkAccount user_id, (err, success)->
|
||||
return callback(err) if err?
|
||||
return next(err) if err?
|
||||
res.redirect('/user/settings#dropboxSettings')
|
||||
|
||||
deleteUser: (req, res)->
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
#this file is being slowly refactored out
|
||||
|
||||
logger = require('logger-sharelatex')
|
||||
sanitize = require('validator').sanitize
|
||||
projectHandler = require('../handlers/ProjectHandler')
|
||||
projectHandler = new projectHandler()
|
||||
SecurityManager = require('./SecurityManager')
|
||||
_ = require('underscore')
|
||||
projectEditorHandler = require('../Features/Project/ProjectEditorHandler')
|
||||
projectEntityHandler = require('../Features/Project/ProjectEntityHandler')
|
||||
versioningApiHandler = require('../Features/Versioning/VersioningApiHandler')
|
||||
metrics = require('../infrastructure/Metrics')
|
||||
EditorRealTimeController = require('../Features/Editor/EditorRealTimeController')
|
||||
|
||||
module.exports = class CollaberationManager
|
||||
constructor: (@io)->
|
||||
|
||||
deleteProject: (project_id, callback)->
|
||||
metrics.inc "editor.delete-project"
|
||||
logger.log project_id:project_id, "recived message to delete project"
|
||||
projectHandler.deleteProject project_id, callback
|
||||
|
||||
renameEntity: (project_id, entity_id, entityType, newName, callback)->
|
||||
newName = sanitize(newName).xss()
|
||||
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"
|
||||
projectHandler.renameEntity project_id, entity_id, entityType, newName, =>
|
||||
if newName.length > 0
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveEntityRename', entity_id, newName
|
||||
callback?()
|
||||
|
||||
moveEntity: (project_id, entity_id, folder_id, entityType, callback)->
|
||||
metrics.inc "editor.move-entity"
|
||||
projectEntityHandler.moveEntity project_id, entity_id, folder_id, entityType, =>
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveEntityMove', entity_id, folder_id
|
||||
callback?()
|
||||
|
||||
renameProject: (project_id, window_id, newName, callback)->
|
||||
newName = sanitize(newName).xss()
|
||||
projectHandler.renameProject project_id, window_id, newName, =>
|
||||
newName = sanitize(newName).xss()
|
||||
EditorRealTimeController.emitToRoom project_id, 'projectNameUpdated', window_id, newName
|
||||
callback?()
|
||||
|
||||
setPublicAccessLevel : (project_id, newAccessLevel, callback)->
|
||||
projectHandler.setPublicAccessLevel project_id, newAccessLevel, =>
|
||||
EditorRealTimeController.emitToRoom project_id, 'publicAccessLevelUpdated', newAccessLevel
|
||||
callback?()
|
||||
|
||||
distributMessage: (project_id, client, message)->
|
||||
message = sanitize(message).xss()
|
||||
metrics.inc "editor.instant-message"
|
||||
client.get "first_name", (err, first_name)=>
|
||||
EditorRealTimeController.emitToRoom project_id, 'reciveNewMessage', first_name, message
|
||||
|
||||
setRootDoc: (project_id, newRootDocID, callback)->
|
||||
projectEntityHandler.setRootDoc project_id, newRootDocID, () =>
|
||||
EditorRealTimeController.emitToRoom project_id, 'rootDocUpdated', newRootDocID
|
||||
callback?()
|
||||
|
||||
takeVersionSnapShot : (project_id, message, callback)->
|
||||
versioningApiHandler.takeVersionSnapshot project_id, message, callback
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports =
|
||||
newGuid : ()->
|
||||
S4 = ()->
|
||||
return (((1+Math.random())*0x10000)|0).toString(16).substring(1)
|
||||
return (S4()+S4()+"-"+S4()+"-"+S4()+"-"+S4()+"-"+S4()+S4()+S4())
|
||||
|
||||
@@ -166,7 +166,9 @@ getRequestUserAndProject = (req, res, options, callback)->
|
||||
callback err, user, project
|
||||
|
||||
getProjectIdFromRef = (ref)->
|
||||
if ref._id?
|
||||
if !ref?
|
||||
return null
|
||||
else if ref._id?
|
||||
return ref._id+''
|
||||
else
|
||||
return ref+''
|
||||
|
||||
@@ -3,7 +3,7 @@ Settings = require 'settings-sharelatex'
|
||||
_ = require('underscore')
|
||||
FolderSchema = require('./Folder.js').FolderSchema
|
||||
logger = require('logger-sharelatex')
|
||||
sanitize = require('validator').sanitize
|
||||
sanitize = require('sanitizer')
|
||||
concreteObjectId = require('mongoose').Types.ObjectId
|
||||
Errors = require "../errors"
|
||||
|
||||
@@ -112,7 +112,7 @@ applyToAllFilesRecursivly = ProjectSchema.statics.applyToAllFilesRecursivly = (f
|
||||
|
||||
ProjectSchema.methods.getSafeProjectName = ->
|
||||
safeProjectName = this.name.replace(new RegExp("\\W", "g"), '_')
|
||||
return sanitize(safeProjectName).xss()
|
||||
return sanitize.escape(safeProjectName)
|
||||
|
||||
conn = mongoose.createConnection(Settings.mongo.url, server: poolSize: Settings.mongo.poolSize || 10)
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ UserSchema = new Schema
|
||||
}
|
||||
featureSwitches : {
|
||||
dropbox: {type:Boolean, default:true},
|
||||
trackChanges: {type:Boolean, default:false}
|
||||
oldHistory: {type:Boolean}
|
||||
}
|
||||
referal_id : {type:String, default:() -> uuid.v4().split("-")[0]}
|
||||
refered_users: [ type:ObjectId, ref:'User' ]
|
||||
|
||||
@@ -5,8 +5,7 @@ ProjectController = require("./controllers/ProjectController")
|
||||
ProjectApiController = require("./Features/Project/ProjectApiController")
|
||||
InfoController = require('./controllers/InfoController')
|
||||
SpellingController = require('./Features/Spelling/SpellingController')
|
||||
CollaberationManager = require('./managers/CollaberationManager')
|
||||
SecutiryManager = require('./managers/SecurityManager')
|
||||
SecurityManager = require('./managers/SecurityManager')
|
||||
AuthorizationManager = require('./Features/Security/AuthorizationManager')
|
||||
versioningController = require("./Features/Versioning/VersioningApiController")
|
||||
EditorController = require("./Features/Editor/EditorController")
|
||||
@@ -46,9 +45,7 @@ module.exports = class Router
|
||||
constructor: (app, io, socketSessions)->
|
||||
app.use(app.router)
|
||||
|
||||
collaberationManager = new CollaberationManager(io)
|
||||
|
||||
Project = new ProjectController(collaberationManager)
|
||||
Project = new ProjectController()
|
||||
projectHandler = new ProjectHandler()
|
||||
|
||||
app.get '/', HomeController.index
|
||||
@@ -56,16 +53,16 @@ module.exports = class Router
|
||||
app.get '/login', UserController.loginForm
|
||||
app.post '/login', AuthenticationController.login
|
||||
app.get '/logout', UserController.logout
|
||||
app.get '/restricted', SecutiryManager.restricted
|
||||
app.get '/restricted', SecurityManager.restricted
|
||||
|
||||
app.get '/resources', HomeController.externalPage("resources", "LaTeX Resources")
|
||||
app.get '/tos', HomeController.externalPage("tos", "Terms of Service")
|
||||
app.get '/about', HomeController.externalPage("about", "About Us")
|
||||
app.get '/attribution', HomeController.externalPage("attribution", "Attribution")
|
||||
app.get '/security', HomeController.externalPage("security", "Security")
|
||||
app.get '/privacy_policy', HomeController.externalPage("privacy", "Privacy Policy")
|
||||
app.get '/planned_maintenance', HomeController.externalPage("planned_mainteance", "Planned Maintenance")
|
||||
|
||||
app.get '/resources', HomeController.resources
|
||||
app.get '/comments', HomeController.comments
|
||||
app.get '/tos', HomeController.tos
|
||||
app.get '/about', HomeController.about
|
||||
app.get '/attribution', HomeController.attribution
|
||||
app.get '/security', HomeController.security
|
||||
app.get '/privacy_policy', HomeController.privacy
|
||||
app.get '/planned_maintenance', HomeController.planned_maintenance
|
||||
app.get '/themes', InfoController.themes
|
||||
app.get '/advisor', InfoController.advisor
|
||||
app.get '/dropbox', InfoController.dropbox
|
||||
@@ -99,13 +96,10 @@ module.exports = class Router
|
||||
app.post '/project/new', AuthenticationController.requireLogin(), Project.apiNewProject
|
||||
app.get '/project/new/template', TemplatesMiddlewear.saveTemplateDataInSession, AuthenticationController.requireLogin(), TemplatesController.createProjectFromZipTemplate
|
||||
|
||||
app.get '/Project/:Project_id', SecutiryManager.requestCanAccessProject, Project.loadEditor
|
||||
app.get '/Project/:Project_id/file/:File_id', SecutiryManager.requestCanAccessProject, FileStoreController.getFile
|
||||
app.get '/Project/:Project_id', SecurityManager.requestCanAccessProject, Project.loadEditor
|
||||
app.get '/Project/:Project_id/file/:File_id', SecurityManager.requestCanAccessProject, FileStoreController.getFile
|
||||
|
||||
# This is left for legacy reasons and can be removed once all editors have had a chance to refresh:
|
||||
app.get '/Project/:Project_id/download/pdf', SecutiryManager.requestCanAccessProject, CompileController.downloadPdf
|
||||
|
||||
app.get '/Project/:Project_id/output/output.pdf', SecutiryManager.requestCanAccessProject, CompileController.downloadPdf
|
||||
app.get '/Project/:Project_id/output/output.pdf', SecurityManager.requestCanAccessProject, CompileController.downloadPdf
|
||||
app.get /^\/project\/([^\/]*)\/output\/(.*)$/,
|
||||
((req, res, next) ->
|
||||
params =
|
||||
@@ -113,25 +107,26 @@ module.exports = class Router
|
||||
"file": req.params[1]
|
||||
req.params = params
|
||||
next()
|
||||
), SecutiryManager.requestCanAccessProject, CompileController.getFileFromClsi
|
||||
), SecurityManager.requestCanAccessProject, CompileController.getFileFromClsi
|
||||
app.del "/project/:Project_id/output", SecurityManager.requestCanAccessProject, CompileController.deleteAuxFiles
|
||||
|
||||
app.del '/Project/:Project_id', SecutiryManager.requestIsOwner, Project.deleteProject
|
||||
app.post '/Project/:Project_id/clone', SecutiryManager.requestCanAccessProject, Project.cloneProject
|
||||
app.del '/Project/:Project_id', SecurityManager.requestIsOwner, Project.deleteProject
|
||||
app.post '/Project/:Project_id/clone', SecurityManager.requestCanAccessProject, Project.cloneProject
|
||||
|
||||
app.post '/Project/:Project_id/snapshot', SecutiryManager.requestCanModifyProject, versioningController.takeSnapshot
|
||||
app.get '/Project/:Project_id/version', SecutiryManager.requestCanAccessProject, versioningController.listVersions
|
||||
app.get '/Project/:Project_id/version/:Version_id', SecutiryManager.requestCanAccessProject, versioningController.getVersion
|
||||
app.get '/Project/:Project_id/version', SecutiryManager.requestCanAccessProject, versioningController.listVersions
|
||||
app.get '/Project/:Project_id/version/:Version_id', SecutiryManager.requestCanAccessProject, versioningController.getVersion
|
||||
app.post '/Project/:Project_id/snapshot', SecurityManager.requestCanModifyProject, versioningController.takeSnapshot
|
||||
app.get '/Project/:Project_id/version', SecurityManager.requestCanAccessProject, versioningController.listVersions
|
||||
app.get '/Project/:Project_id/version/:Version_id', SecurityManager.requestCanAccessProject, versioningController.getVersion
|
||||
app.get '/Project/:Project_id/version', SecurityManager.requestCanAccessProject, versioningController.listVersions
|
||||
app.get '/Project/:Project_id/version/:Version_id', SecurityManager.requestCanAccessProject, versioningController.getVersion
|
||||
|
||||
app.get "/project/:Project_id/updates", SecutiryManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
|
||||
app.get "/project/:Project_id/doc/:doc_id/diff", SecutiryManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
|
||||
app.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", SecutiryManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
|
||||
app.get "/project/:Project_id/updates", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
|
||||
app.get "/project/:Project_id/doc/:doc_id/diff", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
|
||||
app.post "/project/:Project_id/doc/:doc_id/version/:version_id/restore", SecurityManager.requestCanAccessProject, TrackChangesController.proxyToTrackChangesApi
|
||||
|
||||
app.post '/project/:project_id/leave', AuthenticationController.requireLogin(), CollaboratorsController.removeSelfFromProject
|
||||
app.get '/project/:Project_id/collaborators', SecutiryManager.requestCanAccessProject(allow_auth_token: true), CollaboratorsController.getCollaborators
|
||||
app.get '/project/:Project_id/collaborators', SecurityManager.requestCanAccessProject(allow_auth_token: true), CollaboratorsController.getCollaborators
|
||||
|
||||
app.get '/Project/:Project_id/download/zip', SecutiryManager.requestCanAccessProject, ProjectDownloadsController.downloadProject
|
||||
app.get '/Project/:Project_id/download/zip', SecurityManager.requestCanAccessProject, ProjectDownloadsController.downloadProject
|
||||
|
||||
|
||||
app.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
|
||||
@@ -164,21 +159,21 @@ module.exports = class Router
|
||||
req.params = params
|
||||
next()
|
||||
),
|
||||
SecutiryManager.requestCanAccessProject, versioningController.getVersionFile
|
||||
SecurityManager.requestCanAccessProject, versioningController.getVersionFile
|
||||
|
||||
app.post "/spelling/check", AuthenticationController.requireLogin(), SpellingController.proxyRequestToSpellingApi
|
||||
app.post "/spelling/learn", AuthenticationController.requireLogin(), SpellingController.proxyRequestToSpellingApi
|
||||
|
||||
#Admin Stuff
|
||||
app.get '/admin', SecutiryManager.requestIsAdmin, AdminController.index
|
||||
app.post '/admin/closeEditor', SecutiryManager.requestIsAdmin, AdminController.closeEditor
|
||||
app.post '/admin/dissconectAllUsers', SecutiryManager.requestIsAdmin, AdminController.dissconectAllUsers
|
||||
app.post '/admin/writeAllDocsToMongo', SecutiryManager.requestIsAdmin, AdminController.writeAllToMongo
|
||||
app.post '/admin/addquote', SecutiryManager.requestIsAdmin, AdminController.addQuote
|
||||
app.post '/admin/syncUserToSubscription', SecutiryManager.requestIsAdmin, AdminController.syncUserToSubscription
|
||||
app.post '/admin/flushProjectToTpds', SecutiryManager.requestIsAdmin, AdminController.flushProjectToTpds
|
||||
app.post '/admin/pollUsersWithDropbox', SecutiryManager.requestIsAdmin, AdminController.pollUsersWithDropbox
|
||||
app.post '/admin/updateProjectCompiler', SecutiryManager.requestIsAdmin, AdminController.updateProjectCompiler
|
||||
app.get '/admin', SecurityManager.requestIsAdmin, AdminController.index
|
||||
app.post '/admin/closeEditor', SecurityManager.requestIsAdmin, AdminController.closeEditor
|
||||
app.post '/admin/dissconectAllUsers', SecurityManager.requestIsAdmin, AdminController.dissconectAllUsers
|
||||
app.post '/admin/writeAllDocsToMongo', SecurityManager.requestIsAdmin, AdminController.writeAllToMongo
|
||||
app.post '/admin/addquote', SecurityManager.requestIsAdmin, AdminController.addQuote
|
||||
app.post '/admin/syncUserToSubscription', SecurityManager.requestIsAdmin, AdminController.syncUserToSubscription
|
||||
app.post '/admin/flushProjectToTpds', SecurityManager.requestIsAdmin, AdminController.flushProjectToTpds
|
||||
app.post '/admin/pollUsersWithDropbox', SecurityManager.requestIsAdmin, AdminController.pollUsersWithDropbox
|
||||
app.post '/admin/updateProjectCompiler', SecurityManager.requestIsAdmin, AdminController.updateProjectCompiler
|
||||
|
||||
app.get '/perfTest', (req,res)->
|
||||
res.send("hello")
|
||||
@@ -190,7 +185,7 @@ module.exports = class Router
|
||||
|
||||
app.get '/health_check', HealthCheckController.check
|
||||
|
||||
app.get "/status/compiler/:Project_id", SecutiryManager.requestCanAccessProject, (req, res) ->
|
||||
app.get "/status/compiler/:Project_id", SecurityManager.requestCanAccessProject, (req, res) ->
|
||||
sendRes = _.once (statusCode, message)->
|
||||
res.writeHead statusCode
|
||||
res.end message
|
||||
@@ -293,15 +288,15 @@ module.exports = class Router
|
||||
|
||||
client.on 'renameEntity', (entity_id, entityType, newName, callback)->
|
||||
AuthorizationManager.ensureClientCanEditProject client, (error, project_id) =>
|
||||
collaberationManager.renameEntity(project_id, entity_id, entityType, newName, callback)
|
||||
EditorController.renameEntity(project_id, entity_id, entityType, newName, callback)
|
||||
|
||||
client.on 'moveEntity', (entity_id, folder_id, entityType, callback)->
|
||||
AuthorizationManager.ensureClientCanEditProject client, (error, project_id) =>
|
||||
collaberationManager.moveEntity(project_id, entity_id, folder_id, entityType, callback)
|
||||
EditorController.moveEntity(project_id, entity_id, folder_id, entityType, callback)
|
||||
|
||||
client.on 'setProjectName', (window_id, newName, callback)->
|
||||
AuthorizationManager.ensureClientCanEditProject client, (error, project_id) =>
|
||||
collaberationManager.renameProject(project_id, window_id, newName, callback)
|
||||
EditorController.renameProject(project_id, window_id, newName, callback)
|
||||
|
||||
client.on 'getProject',(callback)->
|
||||
AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
|
||||
@@ -309,15 +304,15 @@ module.exports = class Router
|
||||
|
||||
client.on 'setRootDoc', (newRootDocID, callback)->
|
||||
AuthorizationManager.ensureClientCanEditProject client, (error, project_id) =>
|
||||
collaberationManager.setRootDoc(project_id, newRootDocID, callback)
|
||||
EditorController.setRootDoc(project_id, newRootDocID, callback)
|
||||
|
||||
client.on 'deleteProject', (callback)->
|
||||
AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
|
||||
collaberationManager.deleteProject(project_id, callback)
|
||||
EditorController.deleteProject(project_id, callback)
|
||||
|
||||
client.on 'setPublicAccessLevel', (newAccessLevel, callback)->
|
||||
AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
|
||||
collaberationManager.setPublicAccessLevel(project_id, newAccessLevel, callback)
|
||||
EditorController.setPublicAccessLevel(project_id, newAccessLevel, callback)
|
||||
|
||||
client.on 'pdfProject', (opts, callback)->
|
||||
AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
|
||||
@@ -328,10 +323,6 @@ module.exports = class Router
|
||||
AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
|
||||
CompileManager.getLogLines project_id, callback
|
||||
|
||||
client.on 'distributMessage', (message)->
|
||||
AuthorizationManager.ensureClientCanViewProject client, (error, project_id) =>
|
||||
collaberationManager.distributMessage project_id, client, message
|
||||
|
||||
client.on 'changeUsersPrivlageLevel', (user_id, newPrivalageLevel)->
|
||||
AuthorizationManager.ensureClientCanAdminProject client, (error, project_id) =>
|
||||
projectHandler.changeUsersPrivlageLevel project_id, user_id, newPrivalageLevel
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
extends ../layout
|
||||
|
||||
block content
|
||||
.container
|
||||
.row
|
||||
.span8.offset2.span-box
|
||||
.page-header
|
||||
h1 About us
|
||||
h3 Meet the team behind your favourite online LaTeX editor
|
||||
p.team-profile
|
||||
img(src='/img/about/henry_oswald.jpg')
|
||||
strong Henry Oswald
|
||||
| built an experimental LaTeX editor in 2011 which later became ShareLaTeX. He is a trained software engineer who lives in London.
|
||||
| Henry has been responsible for building up a reliable platform for ShareLaTeX that allows instant real-time collaboration.
|
||||
| Henry is a strong advocate of Test Driven Development and makes sure we keep the ShareLaTeX code clean and easy to maintain.
|
||||
p
|
||||
a(href='https://twitter.com/henryoswald') Follow me on Twitter
|
||||
p.team-profile
|
||||
img(src='/img/about/james_allen.jpg')
|
||||
strong James Allen
|
||||
| started working with Henry early in 2012 and finished his PhD in theoretical physics early in 2013. James began working on
|
||||
| one of the first online LaTeX editors, ScribTeX, in 2008 and he has played a large role in developing the technologies and
|
||||
| concepts that made ScribTeX and now ShareLaTeX possible. James is also slightly too obsessed with typography and learning the internals of how LaTeX works.
|
||||
p(style="clear: both")
|
||||
|
||||
h3 Motivation
|
||||
p Our first priority with ShareLaTeX is to build a tool which makes life easier for all the LaTeX users out there.
|
||||
| The "thank you"s and success stories are a strong motivator for us, and we hope to always be able to offer a fully-functional
|
||||
| LaTeX editor which anyone can use for free.
|
||||
p We also believe that charging money for tools like ShareLaTeX is important since it helps to guarantee the future of the site and
|
||||
| the safety of your work, as well as allowing us to focus on it full time to develop new features. We prefer to provide a valuable
|
||||
| service at a fair price rather than having to try run the site from adverts, or worse. As our customers, we are answerable to you and only you.
|
||||
|
||||
h3 Technologies
|
||||
p
|
||||
| We use a lot of exciting technologies to run ShareLaTeX. We have mutliple APIs behind the scenes that we've written in Node.js, but the one exception is our
|
||||
a(href='https://github.com/scribtex/clsi') open sourced compiler API.
|
||||
| This is written in Ruby on Rails. We generally write in
|
||||
a(href='http://coffeescript.org/') coffee-script
|
||||
| as we find it helps to speed up development and make our code more readable. Your data is stored in MongoDB, Redis and Amazon S3. We practice Test Driven Development (TDD) to help produce robust and clean code which is easy to refactor.
|
||||
include ../general/small-footer
|
||||
@@ -1,31 +0,0 @@
|
||||
extends ../layout
|
||||
|
||||
block content
|
||||
.container
|
||||
.row
|
||||
.span6.offset3.span-box
|
||||
.page-header
|
||||
h1 Attribution
|
||||
p
|
||||
| We've only been able to create ShareLaTeX thanks to the many amazing free and
|
||||
| open source technologies that exist. Here are some that we have been using and would
|
||||
| like to say thank you to:
|
||||
ul
|
||||
li
|
||||
a(href="http://nodejs.org/") Node.js
|
||||
| and the massive set of modules that are available.
|
||||
li
|
||||
a(href="https://github.com/jviereck/pdfListView") pdfListView.
|
||||
| A wrapper for PDF.js that powers our built in PDF viewer.
|
||||
li
|
||||
a(href="http://www.iconshock.com/") Icons from Iconshock.
|
||||
li
|
||||
a(href="http://twitter.github.com/bootstrap/") Bootstrap
|
||||
| for letting us survive without being designers.
|
||||
|
||||
include ../general/small-footer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
extends ../layout
|
||||
|
||||
block content
|
||||
.container
|
||||
.row
|
||||
.span6.offset3.span-box
|
||||
.page-header
|
||||
h1 Planned Maintenance
|
||||
p There is currently no planned maintenance
|
||||
|
||||
include ../general/small-footer
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
extends ../layout
|
||||
|
||||
block content
|
||||
.container
|
||||
.row
|
||||
.span8.offset2.span-box
|
||||
.page-header
|
||||
h1 Privacy Policy
|
||||
h3 Information gathering
|
||||
p When you register for ShareLaTeX we collect information such as your name and email address. ShareLaTeX uses this information to be able to provide our service, for identification and authorization of users, and to be able to contact you.
|
||||
p The information we collect is not shared with other organisations except as detailed below for the provisioning and improvement of our service. The data we collect will never be sold to third parties for commercial purposes.
|
||||
|
||||
h3 Cookies
|
||||
p ShareLaTeX uses a cookie, which is a small amount of data stored by your web browser on your computer. The cookie stores your current session and allows you to stay logged in. Cookies are required to use the ShareLaTeX service.
|
||||
|
||||
h3 Data storage
|
||||
p ShareLaTeX uses third parties to host our services and store your data. You retain all rights to the data you upload to ShareLaTeX.
|
||||
|
||||
h3 Credit cards and billing details
|
||||
p We use a third party vendor,
|
||||
a(href="recurly.com") Recurly
|
||||
| , to store and process credit card transactions. Your email address, credit card details and billing address are passed on to Recurly and are not stored with ShareLaTeX.
|
||||
|
||||
h3 Third party tracking
|
||||
p We use Google Analytics, Mixpanel and HeapAnalytics to track users' interactions with ShareLaTeX. This data is used for the improvement of our service.
|
||||
include ../general/small-footer
|
||||
@@ -1,94 +0,0 @@
|
||||
extends ../layout
|
||||
|
||||
block content
|
||||
.container
|
||||
.row
|
||||
.span6.offset3.span-box
|
||||
.page-header
|
||||
h1 Security
|
||||
p
|
||||
| Keeping your data safe is one of our top priorities.
|
||||
| We work hard to make sure that ShareLaTeX is as secure as we can make it,
|
||||
| and your input and feedback on our security is always appreciated.
|
||||
h3 Responsible disclosure
|
||||
p
|
||||
| Please send reports of any urgent or sensitive security issues to
|
||||
a(href="mailto:team@sharelatex.com") team@sharelatex.com.
|
||||
| Use our
|
||||
a(href="/sharelatex-security.pub") public key
|
||||
| to encrypt your message and please provide us with a secure way to
|
||||
| contact you.
|
||||
|
||||
p
|
||||
| Note that the URLs at <strong>/learn</strong>, <strong>/help</strong>
|
||||
| and <strong>ctan.sharelatex.com</strong> are not under our direct control,
|
||||
| and vulnerabilities should be reported to either MediaWiki or TenderApp
|
||||
| respectively.
|
||||
|
||||
p
|
||||
| We are very grateful for all the responsibly reported security vulnerabilities,
|
||||
| however listing on the hall of fame is reserved for people who report
|
||||
| vulnerabilities that were previously unknown and we regard as serious.
|
||||
|
||||
h3 Acknowledgements
|
||||
p
|
||||
| We'd like to thank the following people who have responsibly disclosed
|
||||
| vulnerabilities to us and helped improved the security of ShareLaTeX:
|
||||
|
||||
ul
|
||||
li
|
||||
a(href="https://twitter.com/Abdulahhusam", rel="nofollow") Abdullah Hussam Gazi
|
||||
li
|
||||
a(href="http://adamziaja.com", rel="nofollow") Adam Ziaja
|
||||
li
|
||||
a(href="http://alihassanpenetrationtester.blogspot.com/", rel="nofollow") Ali Hasan Ghauri
|
||||
li
|
||||
a(href="https://twitter.com/EhArvindSingh") Arvind Singh Shekhawat
|
||||
li
|
||||
a(href="https://www.facebook.com/Dakshxss", rel="nofollow") Daksh Patel
|
||||
li
|
||||
a(href="https://twitter.com/dibsyhex", rel="nofollow") Dibyendu Sikdar
|
||||
li
|
||||
a(href="https://www.facebook.com/jaymark.pestano", rel="nofollow") Jaymark Pestaño
|
||||
li
|
||||
a(href="https://twitter.com/korapsyon", rel="nofollow") Jerold Camacho
|
||||
li
|
||||
a(href="https://twitter.com/kamilsevi", rel="nofollow") Kamil Sevi
|
||||
li
|
||||
a(href="https://twitter.com/kingkaustubhp", rel="nofollow") Kaustubh Padwad
|
||||
li 'KoF2002' & 'Sr33h4r!(XSS no0B)'
|
||||
li
|
||||
a(href="http://twitter.com/umenmactech", rel="nofollow") Manish Bhattacharya
|
||||
li
|
||||
a(href="http://twitter.com/Manjesh24", rel="nofollow") Manjesh S
|
||||
li
|
||||
a(href="https://www.facebook.com/Shahmeer.1994", rel="nofollow") Muhammad Shahmeer
|
||||
li
|
||||
a(href="http://nbsriharsha.blogspot.in", rel="nofollow") N B Sri Harsha
|
||||
li
|
||||
a(href="http://www.linkedin.com/in/osandamalith", rel="nofollow") Osanda Malith Jayathissa
|
||||
li
|
||||
a(href="https://twitter.com/prasadk14", rel="nofollow") Prasad Kancharla
|
||||
li
|
||||
a(href="https://www.facebook.com/c0m4dr3404", rel="nofollow") Praveen Nair (Kerala Cyber Squad - India)
|
||||
li
|
||||
a(href="https://twitter.com/iAmPr3m", rel="nofollow") Prem Kumar
|
||||
li
|
||||
a(href="https://www.facebook.com/HardNocksHittnHard", rel="nofollow") Sherin Panikar (Kerala Cyber Squad - India)
|
||||
li
|
||||
a(href="https://twitter.com/Simon90_Italy", rel="nofollow") Simone Memoli
|
||||
li
|
||||
a(href="https://twitter.com/tareksiddiki", rel="nofollow") Tarek Siddiki
|
||||
li
|
||||
a(href="https://twitter.com/venugopalt", rel="nofollow") Venugopal Thotakura
|
||||
li
|
||||
a(href="http://softproweb.blogspot.com/", rel="nofollow") Waqeeh Ul Hasan
|
||||
li
|
||||
a(href="https://twitter.com/zerodayguys", rel="nofollow") Zeroday Guys (Rakesh Singh & V.Harish Kumar)
|
||||
li
|
||||
a(href="https://twitter.com/_prashantnegi", rel="nofollow") Prashant Negi
|
||||
span and
|
||||
a(href="https://twitter.com/AjaySinghNegi", rel="nofollow") Singh Negi
|
||||
li
|
||||
a(href="https://twitter.com/mohitnitrr") Monendra Sahu
|
||||
include ../general/small-footer
|
||||
@@ -1,34 +0,0 @@
|
||||
extends ../layout
|
||||
|
||||
block content
|
||||
.container
|
||||
.row
|
||||
.span8.offset2.span-box
|
||||
.page-header
|
||||
h1 Terms of Service
|
||||
p
|
||||
| Thank you for taking the time to read our terms of service.
|
||||
| We've tried to keep it simple, but you must agree to these terms in order to use ShareLaTeX.
|
||||
h3 You must provide a valid email address
|
||||
p
|
||||
| We expect you to register with a valid email address.
|
||||
| This will be our only way to communicate with you.
|
||||
| Changes to these terms of service and any other notifications about updates
|
||||
| to the service will be sent to the email address you provide.
|
||||
| We will not be held responsible for any information not being received.
|
||||
h3 You own your content, not us
|
||||
p You retain all ownership, copyright and intellectual property rights to any content uploaded to ShareLaTeX. Your content will only be shared with other users of your choosing and we will never share your content with third parties without your consent. The staff of ShareLaTeX have access to your content, but we make an effort to only access it when absolutely necessary.
|
||||
h3 You're not allowed to abuse the service
|
||||
p ShareLaTeX is provided assuming users will act in good faith. However, we retain the right to remove any account or content that we feel is abusing the service. In the rare event of this happening, it will most likely be for one of the following reasons: using the service for illegal reasons; uploading illegal, unauthorised or objectionable content; consuming an excessive amount of computing resources.
|
||||
h3 We're not infallible
|
||||
p ShareLaTeX is provided on an 'as is' basis, without future promise of availability. ShareLaTeX is not liable for any damages caused by the loss or inability to access your data.
|
||||
p [Founder's note: We try hard to provide a service which is reliable, secure and regularly backed up. While the above paragraph sounds grim, we need it there to cover ourselves incase something does go wrong. We certainly aren't planning to need to fall back on it. This assurance aside, it's always good practice to back up your own data.]
|
||||
h3 Cancellation and refunds policy
|
||||
p ShareLaTeX's services are paid for monthly or yearly in advance and are non-refundable. There will be no refunds or credits for partial months/years of service or downgrades. Subscription downgrades and cancellations will come into effect when your next payment is due. Upgrades to your plan will be billed immediately at the new rate for the remainder of the billing period. You may cancel your account at any time from your account subscription settings.
|
||||
h3 Paypal Reference Transactions
|
||||
p PayPal Reference Transactions are used to allow ShareLaTeX to make subsequent transactions on a monthly or annual basis until you cancel your account.
|
||||
h3 Pricing changes
|
||||
p We will notify you of any changes to our pricing plans that affect you at least 30 days before your next payment is due. We will notify you via the email address you provided when subscribing for a paid plan.
|
||||
h3 You must be human
|
||||
p To protect ourselves from accidental or malicious abuse, automated scripts and programs are not permitted to register an account or access the service.
|
||||
include ../general/small-footer
|
||||
@@ -1,46 +0,0 @@
|
||||
include header
|
||||
|
||||
mixin quote(quote, author)
|
||||
blockquote
|
||||
p #{quote}
|
||||
small #{author}
|
||||
|
||||
extends ../layout
|
||||
|
||||
block content
|
||||
.container.box
|
||||
.page-header
|
||||
h2 What a few of the thousands of people using sharelatex are saying
|
||||
.row
|
||||
.span4
|
||||
<blockquote class="twitter-tweet w-align-left"><p>Yes, finally! @<a href="https://twitter.com/sharelatex">sharelatex</a> <a href="https://t.co/5qcwmrdW" title="https://www.sharelatex.com/">sharelatex.com</a> via @<a href="https://twitter.com/henryoswald">henryoswald</a></p>— Nav (@keyboardkitteh) <a href="https://twitter.com/keyboardkitteh/status/165623092637474816" data-datetime="2012-02-04T02:29:57+00:00">February 4, 2012</a></blockquote>
|
||||
.span4
|
||||
<blockquote class="twitter-tweet w-align-left"><p>Finally someone programmed it: The Real Time LaTeX Collaborative Editor in Your Browser <a href="https://t.co/Ke32JVdG" title="https://www.sharelatex.com">sharelatex.com</a></p>— Bar Fooz (@barfooz) <a href="https://twitter.com/barfooz/status/165889791538376705" data-datetime="2012-02-04T20:09:43+00:00">February 4, 2012</a></blockquote>
|
||||
.span4
|
||||
<blockquote class="twitter-tweet"><p>Wow, really awesome real time LaTeX editor <a href="https://t.co/VgXQezzr" title="https://www.sharelatex.com/">sharelatex.com</a> /via @<a href="https://twitter.com/cybunk">cybunk</a></p>— Nicolas (@philogb) <a href="https://twitter.com/philogb/status/168030573229649921" data-datetime="2012-02-10T17:56:25+00:00">February 10, 2012</a></blockquote>
|
||||
.row
|
||||
.span4
|
||||
<blockquote class="twitter-tweet"><p>Its <a href="https://twitter.com/search/%2523GoogleDocs">#GoogleDocs</a> but with added <a href="https://twitter.com/search/%2523LaTeX">#LaTeX</a>, <a href="https://t.co/ZkMh44V2" title="https://www.sharelatex.com/">sharelatex.com</a>. For physicists and astronomers, collaborative editing just became possible.</p>— Alasdair Allan (@aallan) <a href="https://twitter.com/aallan/status/166472955314966528" data-datetime="2012-02-06T10:47:00+00:00">February 6, 2012</a></blockquote>
|
||||
.span4
|
||||
<blockquote class="twitter-tweet"><p>ShareLaTeX, a real time LaTeX collaborative editor. I *have* to try this. <a href="https://t.co/UVi5f3XY" title="https://www.sharelatex.com/">sharelatex.com</a> HT: @<a href="https://twitter.com/yokofakun">yokofakun</a></p>— Luis Apiolaza (@zentree) <a href="https://twitter.com/zentree/status/169896014604546048" data-datetime="2012-02-15T21:29:01+00:00">February 15, 2012</a></blockquote>
|
||||
.span4
|
||||
<blockquote class="twitter-tweet"><p>Finally! <a href="https://twitter.com/search/%2523ShareLaTeX">#ShareLaTeX</a> - Real Time LaTeX Collaborative Editor <a href="http://t.co/DYEaIOLi" title="http://ShareLaTeX.com">ShareLaTeX.com</a> (via @<a href="https://twitter.com/infoveille">infoveille</a> @<a href="https://twitter.com/gallypette">gallypette</a> @<a href="https://twitter.com/UrfistNice">UrfistNice</a>) @<a href="https://twitter.com/AdrienSaladin">AdrienSaladin</a></p>— Pierre Poulain (@pierrepo) <a href="https://twitter.com/pierrepo/status/167642895946498048" data-datetime="2012-02-09T16:15:55+00:00">February 9, 2012</a></blockquote>
|
||||
.row
|
||||
.span4
|
||||
<blockquote class="twitter-tweet"><p>LaTeX geeks, here's a collaborative online LaTeX editor <a href="https://t.co/5xjcRbSy" title="https://www.sharelatex.com/">sharelatex.com</a></p>— Stephen Kinsella (@stephenkinsella) <a href="https://twitter.com/stephenkinsella/status/166838934968598529" data-datetime="2012-02-07T11:01:16+00:00">February 7, 2012</a></blockquote>
|
||||
.span4
|
||||
<blockquote class="twitter-tweet"><p><a href="https://t.co/uRiailxg" title="https://www.sharelatex.com/">sharelatex.com</a>amazing!</p>— Guillermo Rauch (@rauchg) <a href="https://twitter.com/rauchg/status/168031797047865344" data-datetime="2012-02-10T18:01:16+00:00">February 10, 2012</a></blockquote>
|
||||
.span4
|
||||
<blockquote class="twitter-tweet"><p>ShareLatex <a href="https://t.co/70tEkjEb" title="https://www.sharelatex.com/">sharelatex.com</a> FInally, the real time collaboration in browser! <a href="https://twitter.com/search/%2523latex">#latex</a></p>— Ivana Kojovic (@muvica1987) <a href="https://twitter.com/muvica1987/status/166200765638119424" data-datetime="2012-02-05T16:45:25+00:00">February 5, 2012</a></blockquote>
|
||||
.row
|
||||
.span4
|
||||
<blockquote class="twitter-tweet"><p>I just fell in love on my first run with @<a href="https://twitter.com/sharelatex">sharelatex</a> <a href="https://t.co/fygg14Nj" title="https://www.sharelatex.com/">sharelatex.com</a> via @<a href="https://twitter.com/henryoswald">henryoswald</a> . CC @<a href="https://twitter.com/timcameronryan">timcameronryan</a></p>— Cory Dolphin (@wcdolphin) <a href="https://twitter.com/wcdolphin/status/200411830370312192" data-datetime="2012-05-10T02:27:58+00:00">May 10, 2012</a></blockquote>
|
||||
.span4
|
||||
<blockquote class="twitter-tweet"><p>Tired of sending TeX files back and forth with your coauthors?Try sharelatex to collaborate on tex files in real time <a href="http://t.co/PjoOAayE" title="http://bit.ly/xcksY7">bit.ly/xcksY7</a></p>— Max Lin (@m4xl1n) <a href="https://twitter.com/m4xl1n/status/171596254864871424" data-datetime="2012-02-20T14:05:09+00:00">February 20, 2012</a></blockquote>
|
||||
.span4
|
||||
<blockquote class="twitter-tweet"><p><a href="https://twitter.com/search/%2523edtech">#edtech</a> <a href="https://twitter.com/search/%2523mathchat">#mathchat</a> Collaborative LaTeX document editing (like Google docs, but for LaTeX!) <a href="https://t.co/XXwnl7SA" title="https://www.sharelatex.com/">sharelatex.com</a></p>— Joe DiNoto (@mathteacher1729) <a href="https://twitter.com/mathteacher1729/status/185889900615303168" data-datetime="2012-03-31T00:43:00+00:00">March 31, 2012</a></blockquote>
|
||||
|
||||
<script src="//platform.twitter.com/widgets.js" charset="utf-8"></script>
|
||||
|
||||
- locals.supressDefaultJs = true
|
||||
script(data-main=jsPath+'main.js', src='js/libs/require.js', baseurl=jsPath)
|
||||
@@ -1,15 +0,0 @@
|
||||
.container
|
||||
#logoArea
|
||||
.span8
|
||||
h1.logo.large
|
||||
a(href='/').plain ShareLaTeX
|
||||
span.sub.logo Online LaTeX Editor
|
||||
.span3#homePagePills
|
||||
ul.nav.nav-pills
|
||||
li
|
||||
a(href='/') Home
|
||||
li
|
||||
a(href='/blog') Blog
|
||||
li
|
||||
a(href='/comments') Comments
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
link(rel='canonical', href='https://www.sharelatex.com')
|
||||
|
||||
extends ../layout
|
||||
|
||||
block content
|
||||
.masthead
|
||||
.container
|
||||
.row
|
||||
.title.span8
|
||||
h1 Online LaTeX Editor
|
||||
h2 Quickly start using LaTeX and work together in real-time
|
||||
div
|
||||
img(src="/img/screenshot.png")
|
||||
.register.span4
|
||||
h2 Register now
|
||||
.messageArea
|
||||
include ../general/partial/registerForm
|
||||
|
||||
.universities
|
||||
.container
|
||||
.row
|
||||
p Used by students and academics at:
|
||||
#slides
|
||||
.clearfix
|
||||
.span3
|
||||
img(src="/img/crests/harvard.gif", alt="harvard university logo")
|
||||
.span3
|
||||
img(src="/img/crests/mit.gif", alt="mit university logo")
|
||||
.span3
|
||||
img(src="/img/crests/oxford.gif", alt="oxford university logo")
|
||||
.span3
|
||||
img(src="/img/crests/tokyo.png", alt="tokyo university logo")
|
||||
.clearfix
|
||||
.span3
|
||||
img(src="/img/crests/cambridge.png", alt="cambridge university logo")
|
||||
.span3
|
||||
img(src="/img/crests/liverpool.jpg", alt="liverpool university logo")
|
||||
.span3
|
||||
img(src="/img/crests/icl.png", alt="icl university logo")
|
||||
.span3
|
||||
img(src="/img/crests/yale.png", alt="yale university logo")
|
||||
.clearfix
|
||||
.span3
|
||||
img(src="/img/crests/durham.png", alt="durham university logo")
|
||||
.span3
|
||||
img(src="/img/crests/nasa.png", alt="nasa university logo")
|
||||
.span3
|
||||
img(src="/img/crests/toronto.gif", alt="toronto university logo")
|
||||
.span3
|
||||
img(src="/img/crests/stanford.png", alt="stanford university logo")
|
||||
|
||||
|
||||
.container
|
||||
include ../general/long-form-features
|
||||
|
||||
.row
|
||||
.span12.lower-signup-button
|
||||
a(href="/user/subscription/plans").btn.btn-success.btn-huge Sign up now!
|
||||
|
||||
include ../general/social-footer
|
||||
include ../general/small-footer
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
|
||||
.span2.offset3
|
||||
include ../referal/tweet
|
||||
.span2
|
||||
include ../referal/googleplus
|
||||
.span1
|
||||
include ../referal/facebookLike
|
||||
@@ -28,7 +28,7 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||
ga('send', 'pageview');
|
||||
- else
|
||||
script(type='text/javascript')
|
||||
window.ga = function() {};
|
||||
window.ga = function() { console.log("Sending to GA", arguments) };
|
||||
|
||||
script
|
||||
window.csrfToken = "#{csrfToken}";
|
||||
|
||||
@@ -36,8 +36,9 @@
|
||||
ul.dropdown-menu
|
||||
li
|
||||
a(href='/user/settings').userSettingsLink User Settings
|
||||
li
|
||||
a(href='/user/subscription').subscriptionLink Subscription
|
||||
- if (settings.enableSubscriptions)
|
||||
li
|
||||
a(href='/user/subscription').subscriptionLink Subscription
|
||||
li
|
||||
a(href='/logout').logoutLink Logout
|
||||
-else
|
||||
|
||||
@@ -41,6 +41,11 @@ block content
|
||||
.title
|
||||
a(href='#link-modal', data-toggle="modal").link Link to us from your website
|
||||
|
||||
.row
|
||||
.span4.offset4.bonus-banner
|
||||
h2.direct-link Direct Link
|
||||
.well #{buildReferalUrl("d")}
|
||||
|
||||
.row.ab-bonus
|
||||
.span6.offset3
|
||||
p.thanks When someone starts using ShareLaTeX after your recommendation we'll give you some <strong>free stuff</strong> to say thanks! Check your progress below.
|
||||
@@ -103,7 +108,7 @@ block content
|
||||
method: 'feed',
|
||||
redirect_uri: 'https://www.sharelatex.com',
|
||||
link: '!{buildReferalUrl("fb")}',
|
||||
picture: 'https://www.sharelatex.com/img/logo/logosmall.png',
|
||||
picture: 'https://www.sharelatex.com/brand/logo/logosmall.png',
|
||||
name: 'ShareLaTeX - Online LaTeX Editor',
|
||||
caption: 'Free Unlimited Projects and Compiles',
|
||||
description: 'ShareLaTeX is a free online LaTeX Editor. Real time collaboration like Google Docs, with Dropbox, history and auto-complete'
|
||||
|
||||
@@ -11,7 +11,7 @@ script(type='text/javascript')
|
||||
method: 'feed',
|
||||
redirect_uri: 'https://www.sharelatex.com',
|
||||
link: '#{buildReferalUrl()}',
|
||||
picture: 'https://www.sharelatex.com/img/logo/logosmall.png',
|
||||
picture: 'https://www.sharelatex.com/brand/logo/logosmall.png',
|
||||
name: 'ShareLaTeX - Online LaTeX Editor',
|
||||
caption: 'Free Unlimited Projects and Compiles',
|
||||
description: 'ShareLaTeX is a free Online LaTeX Editor. Real time collaboration like Google Docs, with Dropbox, history and Auto Complete'
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
extends layout
|
||||
|
||||
block content
|
||||
.container
|
||||
.row
|
||||
.span12.span-box
|
||||
.page-header
|
||||
h1 LaTeX Resources
|
||||
small We are the LaTeX Editor, here are some other LaTeX resources we like
|
||||
div
|
||||
ul
|
||||
li
|
||||
a(href='http://en.wikibooks.org/wiki/LaTeX') LaTeX Wikibook
|
||||
span - Covering 95% of what you need to know about LaTeX in a clear and simple way with great examples you can often copy and paste
|
||||
li
|
||||
a(href='http://detexify.kirelabs.org/classify.html') Detexify
|
||||
span - A great way of finding LaTeX symbols
|
||||
li
|
||||
a(href='http://www.tug.dk/FontCatalogue/seriffonts.html') LaTeX Fonts
|
||||
span - A collection of LaTeX fonts
|
||||
li
|
||||
a(href='http://mathurl.com/') MathUrl
|
||||
span - allows for live equation editing
|
||||
li
|
||||
a(href='http://webdemo.visionobjects.com/equation.html') WebEquation
|
||||
span - draw the symbol on the screen to see the LaTeX equivalent
|
||||
li
|
||||
a(href='http://www.texample.net/') TeXample.net
|
||||
span - a great site for tikz reference
|
||||
li
|
||||
a(href='http://www.howtotex.com/') howtoTeX.com
|
||||
span - a useful collection of templates, tutorials and how-tos
|
||||
li
|
||||
a(href='http://truben.no/latex/table/') LaTeX table editor
|
||||
span - if you struggle with tables in LaTeX this tool gives you a nice 'excel like' view to help you along
|
||||
li
|
||||
a(href="http://www.math.binghamton.edu/erik/beameruserguide.pdf") Beamer user guide
|
||||
span - A good guide into beamer
|
||||
div
|
||||
h3 Mobile Apps
|
||||
ul
|
||||
li
|
||||
a(href='https://play.google.com/store/apps/details?id=coolcherrytrees.software.detexify&feature=search_result#?t=W251bGwsMSwxLDEsImNvb2xjaGVycnl0cmVlcy5zb2Z0d2FyZS5kZXRleGlmeSJd') Android
|
||||
li
|
||||
a(href='https://itunes.apple.com/app/detexify/id328805329?mt=8') Detexify for ios
|
||||
li
|
||||
a(href='http://www.windowsphone.com/en-us/store/app/detexify/3e6813bd-04b1-455b-bc14-14dfe904c54b') Detexify for Windows Phone
|
||||
li
|
||||
a(href='http://www.windowsphone.com/en-us/store/app/repotex/694085e0-e825-425b-a40d-40bce5cecb3b') Repotex
|
||||
li
|
||||
a(href='http://www.windowsphone.com/en-us/store/app/fasttexdraw/3e1b4255-7798-45ec-b679-a43e74e82769') FastTeXDraw for windows phone
|
||||
|
||||
hr
|
||||
div
|
||||
| If you know of other resources, please
|
||||
a.js-tender-widget(href='#') recommend
|
||||
them to us.
|
||||
include ./general/small-footer
|
||||
|
||||
@@ -15,6 +15,7 @@ block content
|
||||
thead
|
||||
tr
|
||||
th
|
||||
input(type="checkbox").select-all
|
||||
th email
|
||||
th Name
|
||||
th Registered
|
||||
@@ -23,7 +24,7 @@ block content
|
||||
-each user in users
|
||||
tr
|
||||
td
|
||||
input(type="checkbox")
|
||||
input(type="checkbox").select-one
|
||||
td #{user.email}
|
||||
td #{user.first_name} #{user.last_name}
|
||||
td #{!user.holdingAccount}
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
input.rename.js-rename
|
||||
.dropdown-caret
|
||||
i.icon-chevron-down
|
||||
.entity-label.label.label-success
|
||||
|
||||
script(type="text/template")#folderTemplate
|
||||
.entity-list-item(class="entity-{{ type }}", entity-type="{{ type }}", id="{{ id }}")
|
||||
@@ -80,6 +81,7 @@
|
||||
input.rename.js-rename
|
||||
.dropdown-caret
|
||||
i.icon-chevron-down
|
||||
.entity-label.label.label-success
|
||||
|
||||
script(type="text/template")#entityListTemplate
|
||||
.contents
|
||||
@@ -180,7 +182,11 @@
|
||||
button#downloadPdf.btn Download
|
||||
button#downloadLinksButton.btn.dropdown-toggle(data-toggle="dropdown")
|
||||
span.caret
|
||||
ul.dropdown-menu#downloadLinks
|
||||
ul.dropdown-menu
|
||||
#downloadLinks
|
||||
li.divider
|
||||
li.delete-cached-files
|
||||
a(href="#") Clear cached files
|
||||
.btn-group.pull-right(data-toggle="buttons-radio")
|
||||
button(type="button", title="Flat view")#flatViewButton.btn
|
||||
i.icon-flatview
|
||||
@@ -286,32 +292,31 @@
|
||||
|
||||
|
||||
script(type="text/template")#publishProjectTemplate
|
||||
-if(session && session.user && session.user.isAdmin)
|
||||
.box
|
||||
.page-header
|
||||
h2 Publish project as template
|
||||
|
||||
#publishedAsTemplateArea.show-when-published.alert.alert-success
|
||||
p
|
||||
.btn.btn-warning#unPublishProjectAsTemplate.pull-right Unpublish
|
||||
i.icon-ok
|
||||
| Your project is currently published.
|
||||
a#templateLink(href='{{canonicalUrl}}') View in template gallery.
|
||||
p
|
||||
| Lastest version: {{publishedDate}}.
|
||||
.box
|
||||
.page-header
|
||||
h2 Publish project as template
|
||||
|
||||
#publishedAsTemplateArea.show-when-published.alert.alert-success
|
||||
p
|
||||
.btn.btn-warning#unPublishProjectAsTemplate.pull-right Unpublish
|
||||
i.icon-ok
|
||||
| Your project is currently published.
|
||||
a#templateLink(href='{{canonicalUrl}}') View in template gallery.
|
||||
p
|
||||
| Lastest version: {{publishedDate}}.
|
||||
|
||||
#problemWithPublishingArea
|
||||
p There is a problem with our publishing service, please try again in a few minutes.
|
||||
#publishWorkingArea
|
||||
p Working...
|
||||
div.show-when-published.show-when-unpublished.project-description
|
||||
label(for="project-description") Description
|
||||
.row-fluid
|
||||
textarea(placeholder="Template description", name="project-description").span12#projectDescription {{description}}
|
||||
#unpublishedAsTemplateArea.show-when-unpublished
|
||||
.btn.btn-success#publishProjectAsTemplate Publish
|
||||
p.show-when-published
|
||||
button.btn.btn-success#republishProjectAsTemplate Re-Publish
|
||||
#problemWithPublishingArea
|
||||
p There is a problem with our publishing service, please try again in a few minutes.
|
||||
#publishWorkingArea
|
||||
p Working...
|
||||
div.show-when-published.show-when-unpublished.project-description
|
||||
label(for="project-description") Description
|
||||
.row-fluid
|
||||
textarea(placeholder="Template description", name="project-description").span12#projectDescription {{description}}
|
||||
#unpublishedAsTemplateArea.show-when-unpublished
|
||||
.btn.btn-success#publishProjectAsTemplate Publish
|
||||
p.show-when-published
|
||||
button.btn.btn-success#republishProjectAsTemplate Re-Publish
|
||||
|
||||
|
||||
script(type="text/template")#settingsPanelTemplate
|
||||
@@ -425,6 +430,10 @@
|
||||
div
|
||||
a(href="#", title='Show Hot Keys List')#hotkeysLink Hot keys
|
||||
|
||||
script(type='text/template')#DebugLinkTemplate
|
||||
div
|
||||
a(href="#", title='Show Debug Information')#debugLink Debug
|
||||
|
||||
script(type='text/template')#trackChangesPanelTemplate
|
||||
#trackChangesPanel
|
||||
.track-changes-side-bar
|
||||
@@ -434,6 +443,12 @@
|
||||
i.icon-remove
|
||||
.change-list-area
|
||||
.track-changes-diff
|
||||
.track-changes-upgrade-popup(style="display: none;")
|
||||
.message.show-when-owner
|
||||
p You need to upgrade your plan to use the History feature.
|
||||
button.btn.btn-primary.start-free-trial Start free trial
|
||||
.message.show-when-not-owner
|
||||
p Please ask the project owner to upgrade to use the History feature.
|
||||
|
||||
script(type='text/template')#trackChangesDiffTemplate
|
||||
.track-changes-diff-toolbar.btn-toolbar
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
var keys = require('./app/js/infrastructure/Keys');
|
||||
var settings = require('settings-sharelatex');
|
||||
var queueName = process.argv[2];
|
||||
var projectQueueName = process.argv[3];
|
||||
var queue = require('fairy').connect(settings.redis.web).queue(queueName);
|
||||
console.log("cleaning up queue "+ queueName + " " + projectQueueName);
|
||||
queue._requeue_group(projectQueueName);
|
||||
|
||||
//fairy should kill the process but just in case
|
||||
thirtySeconds = 30 * 1000
|
||||
setTimeout(process.exit, thirtySeconds)
|
||||
+15
-19
@@ -5,40 +5,36 @@
|
||||
"public": "./public"
|
||||
},
|
||||
"dependencies": {
|
||||
"heapdump": "0.1.0",
|
||||
"express": "3.3.4",
|
||||
"mongoose": "3.6.19",
|
||||
"mongoose": "3.8.8",
|
||||
"mongojs": "0.10.1",
|
||||
"jade": "0.28.1",
|
||||
"validator": "0.4.22",
|
||||
"underscore": "1.4.4",
|
||||
"node-fs": "0.1.5",
|
||||
"rimraf": "2.1.2",
|
||||
"underscore": "1.6.0",
|
||||
"rimraf": "2.2.6",
|
||||
"connect-redis": "1.4.5",
|
||||
"redis": "0.8.4",
|
||||
"request": "2.14.0",
|
||||
"redis": "0.10.1",
|
||||
"request": "2.34.0",
|
||||
"xml2js": "0.2.0",
|
||||
"dateformat": "1.0.4-1.2.3",
|
||||
"optimist": "0.3.5",
|
||||
"async": "0.2.9",
|
||||
"lynx": "0.0.11",
|
||||
"optimist": "0.6.1",
|
||||
"async": "0.6.2",
|
||||
"lynx": "0.1.1",
|
||||
"session.socket.io": "0.1.4",
|
||||
"socket.io": "0.9.15",
|
||||
"mimelib": "0.2.8",
|
||||
"bufferedstream": "1.4.1",
|
||||
"mixpanel": "0.0.18",
|
||||
"socket.io": "0.9.16",
|
||||
"mimelib": "0.2.14",
|
||||
"bufferedstream": "1.6.0",
|
||||
"settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#master",
|
||||
"logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master",
|
||||
"translations-sharelatex": "git+https://github.com/sharelatex/translations-sharelatex.git#master",
|
||||
"soa-req-id": "git+https://github.com/sharelatex/soa-req-id.git#master",
|
||||
"fairy": "0.0.2",
|
||||
"node-uuid": "1.4.0",
|
||||
"mongojs": "0.9.8",
|
||||
"node-uuid": "1.4.1",
|
||||
"nodemailer": "0.6.1",
|
||||
"bcrypt": "0.7.5",
|
||||
"archiver": "0.5.1",
|
||||
"nodetime": "0.8.15",
|
||||
"mocha": "1.17.1",
|
||||
"redback": "0.3.7"
|
||||
"redback": "0.4.0",
|
||||
"sanitizer": "0.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "",
|
||||
|
||||
@@ -7,12 +7,12 @@ require [
|
||||
|
||||
tableRowTemplate = '''
|
||||
<tr>
|
||||
<td> <input type="checkbox"></td>
|
||||
<td> <input type="checkbox" class="select-one"></td>
|
||||
<td> {{ email }} </td>
|
||||
<td> {{ first_name }} {{ last_name }} </td>
|
||||
<td> {{ !holdingAccount }} </td>
|
||||
<td>
|
||||
<input type="hidden" name="user_id" value="{{user_id}}" class="user_id">
|
||||
<input type="hidden" name="user_id" value="{{_id}}" class="user_id">
|
||||
</td>
|
||||
</tr>
|
||||
'''
|
||||
@@ -58,16 +58,19 @@ require [
|
||||
$form.find("input").val('')
|
||||
|
||||
removeUsers = (e)->
|
||||
selectedUserRows = $('td input:checked').closest('tr').find(".user_id")
|
||||
selectedUserRows.each (index, userRow)->
|
||||
user_id = $(userRow).val()
|
||||
$.ajax
|
||||
url: "/subscription/group/user/#{user_id}"
|
||||
type: 'DELETE'
|
||||
data:
|
||||
_csrf: csrfToken
|
||||
success: ->
|
||||
$(userRow).parents("tr").fadeOut(250)
|
||||
selectedUserRows = $('td input.select-one:checked').closest('tr').find(".user_id").toArray()
|
||||
do deleteNext = () ->
|
||||
row = selectedUserRows.pop()
|
||||
if row?
|
||||
user_id = $(row).val()
|
||||
$.ajax
|
||||
url: "/subscription/group/user/#{user_id}"
|
||||
type: 'DELETE'
|
||||
data:
|
||||
_csrf: csrfToken
|
||||
success: ->
|
||||
$(row).parents("tr").fadeOut(250)
|
||||
deleteNext()
|
||||
|
||||
$form.on 'keypress', (e)->
|
||||
if(e.keyCode == 13)
|
||||
@@ -76,3 +79,10 @@ require [
|
||||
$form.find(".addUser").on 'click', addUser
|
||||
|
||||
$('#deleteUsers').on 'click', removeUsers
|
||||
|
||||
$('input.select-all').on "change", () ->
|
||||
if $(@).is(":checked")
|
||||
$("input.select-one").prop( "checked", true )
|
||||
else
|
||||
$("input.select-one").prop( "checked", false )
|
||||
|
||||
|
||||
@@ -25,8 +25,19 @@ define [
|
||||
},{
|
||||
text: "Enter Billing Information"
|
||||
class: "btn-primary"
|
||||
callback: () ->
|
||||
window.location = "/user/subscription/new?planCode=student_free_trial"
|
||||
callback: () =>
|
||||
options.onUpgrade?()
|
||||
@gotoSubscriptionsPage()
|
||||
}]
|
||||
|
||||
gotoSubscriptionsPage: () ->
|
||||
window.open("/user/subscription/new?planCode=student_free_trial")
|
||||
Modal.createModal
|
||||
title: "Please refresh"
|
||||
message: "Please refresh this page after starting your free trial. This will make sure all of your features are enabled."
|
||||
buttons: [{
|
||||
text: "OK"
|
||||
class: ""
|
||||
}]
|
||||
|
||||
showUpgradeDialog: (ide, options = {}) ->
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
define () ->
|
||||
define [
|
||||
"libs/md5"
|
||||
], () ->
|
||||
class AnalyticsManager
|
||||
constructor: (@ide) ->
|
||||
@ide.editor.on "update:doc", () =>
|
||||
@@ -14,3 +16,18 @@ define () ->
|
||||
ga('send', 'event', 'editor-interaction', 'single-compile')
|
||||
if @compileCount == 3
|
||||
ga('send', 'event', 'editor-interaction', 'multi-compile')
|
||||
|
||||
getABTestBucket: (test_name, buckets = []) ->
|
||||
hash = CryptoJS.MD5("#{@ide.user.get("id")}:#{test_name}")
|
||||
bucketIndex = parseInt(hash.toString().slice(0,2), 16) % buckets.length
|
||||
return buckets[bucketIndex]
|
||||
|
||||
startABTest: (test_name, buckets = []) ->
|
||||
value = @getABTestBucket(test_name, buckets)
|
||||
ga('send', 'event', 'ab_tests', test_name, "viewed-#{value}")
|
||||
return value
|
||||
|
||||
endABTest: (test_name, buckets = []) ->
|
||||
value = @getABTestBucket(test_name, buckets)
|
||||
ga('send', 'event', 'ab_tests', test_name, "converted-#{value}")
|
||||
return value
|
||||
@@ -2,10 +2,12 @@ define [
|
||||
"auto-complete/SuggestionManager"
|
||||
"auto-complete/Snippets"
|
||||
"ace/autocomplete/util"
|
||||
"ace/autocomplete"
|
||||
"ace/range"
|
||||
"ace/ext/language_tools"
|
||||
], (SuggestionManager, Snippets, Util) ->
|
||||
], (SuggestionManager, Snippets, Util, AutoComplete) ->
|
||||
Range = require("ace/range").Range
|
||||
Autocomplete = AutoComplete.Autocomplete
|
||||
|
||||
Util.retrievePrecedingIdentifier = (text, pos, regex) ->
|
||||
currentLineOffset = 0
|
||||
@@ -38,6 +40,20 @@ define [
|
||||
|
||||
@aceEditor.completers = [@suggestionManager, SnippetCompleter]
|
||||
|
||||
insertMatch = Autocomplete::insertMatch
|
||||
editor = @aceEditor
|
||||
Autocomplete::insertMatch = (data) ->
|
||||
pos = editor.getCursorPosition()
|
||||
range = new Range(pos.row, pos.column, pos.row, pos.column + 1)
|
||||
nextChar = editor.session.getTextRange(range)
|
||||
|
||||
# If we are in \begin{it|}, then we need to remove the trailing }
|
||||
# since it will be adding in with the autocomplete of \begin{item}...
|
||||
if this.completions.filterText.match(/^\\begin\{/) and nextChar == "}"
|
||||
editor.session.remove(range)
|
||||
|
||||
insertMatch.call editor.completer, data
|
||||
|
||||
@bindToEditorEvents()
|
||||
|
||||
bindToEditorEvents: () ->
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
define [
|
||||
"utils/Modal"
|
||||
], (Modal) ->
|
||||
class DebugManager
|
||||
template: $("#DebugLinkTemplate").html()
|
||||
|
||||
constructor: (@ide) ->
|
||||
@$el = $(@template)
|
||||
$("#toolbar-footer").append(@$el)
|
||||
@$el.on "click", (e) =>
|
||||
e.preventDefault()
|
||||
@showDebugModal()
|
||||
|
||||
showDebugModal: () ->
|
||||
useragent = navigator.userAgent
|
||||
server_id = document.cookie.match(/SERVERID=([^;]*)/)?[1]
|
||||
transport = @ide.socket.socket.transport.name
|
||||
|
||||
new Modal(
|
||||
title: "Debug info"
|
||||
message: """
|
||||
Please give this information to the ShareLaTeX team:
|
||||
<p><pre>
|
||||
user-agent: #{useragent}
|
||||
server-id: #{server_id}
|
||||
transport: #{transport}
|
||||
</pre></p>
|
||||
"""
|
||||
buttons: [
|
||||
text: "OK"
|
||||
]
|
||||
)
|
||||
@@ -81,6 +81,17 @@ define [
|
||||
delete @_joinCallbacks
|
||||
|
||||
_onUpdateApplied: (update) ->
|
||||
@ide.pushEvent "received-update",
|
||||
doc_id: @doc_id
|
||||
remote_doc_id: update?.doc
|
||||
wantToBeJoined: @wantToBeJoined
|
||||
update: update
|
||||
|
||||
if Math.random() < (@ide.disconnectRate or 0)
|
||||
console.log "Simulating disconnect"
|
||||
@ide.connectionManager.disconnect()
|
||||
return
|
||||
|
||||
if update?.doc == @doc_id and @doc?
|
||||
@doc.processUpdateFromServer update
|
||||
|
||||
@@ -93,6 +104,8 @@ define [
|
||||
@doc?.updateConnectionState "disconnected"
|
||||
|
||||
_onReconnect: () ->
|
||||
@ide.pushEvent "reconnected:afterJoinProject"
|
||||
|
||||
@connected = true
|
||||
if @wantToBeJoined or @doc?.hasBufferedOps()
|
||||
@_joinDoc (error) =>
|
||||
@@ -137,10 +150,37 @@ define [
|
||||
|
||||
_bindToShareJsDocEvents: () ->
|
||||
@doc.on "error", (error, meta) => @_onError error, meta
|
||||
@doc.on "externalUpdate", () => @trigger "externalUpdate"
|
||||
@doc.on "remoteop", () => @trigger "remoteop"
|
||||
@doc.on "op:sent", () => @trigger "op:sent"
|
||||
@doc.on "op:acknowledged", () => @trigger "op:acknowledged"
|
||||
@doc.on "externalUpdate", () =>
|
||||
@ide.pushEvent "externalUpdate",
|
||||
doc_id: @doc_id
|
||||
@trigger "externalUpdate"
|
||||
@doc.on "remoteop", () =>
|
||||
@ide.pushEvent "remoteop",
|
||||
doc_id: @doc_id
|
||||
@trigger "remoteop"
|
||||
@doc.on "op:sent", (op) =>
|
||||
@ide.pushEvent "op:sent",
|
||||
doc_id: @doc_id
|
||||
op: op
|
||||
@trigger "op:sent"
|
||||
@doc.on "op:acknowledged", (op) =>
|
||||
@ide.pushEvent "op:acknowledged",
|
||||
doc_id: @doc_id
|
||||
op: op
|
||||
@trigger "op:acknowledged"
|
||||
@doc.on "op:timeout", (op) =>
|
||||
@ide.pushEvent "op:timeout",
|
||||
doc_id: @doc_id
|
||||
op: op
|
||||
@trigger "op:timeout"
|
||||
ga?('send', 'event', 'error', "op timeout", "Op was now acknowledged - #{ide.socket.socket.transport.name}" )
|
||||
@ide.connectionManager.reconnectImmediately()
|
||||
@doc.on "flush", (inflightOp, pendingOp, version) =>
|
||||
@ide.pushEvent "flush",
|
||||
doc_id: @doc_id,
|
||||
inflightOp: inflightOp,
|
||||
pendingOp: pendingOp
|
||||
v: version
|
||||
|
||||
_onError: (error, meta = {}) ->
|
||||
console.error "ShareJS error", error, meta
|
||||
|
||||
@@ -71,7 +71,7 @@ define [
|
||||
_saveSplitterState: () ->
|
||||
if $("#editorSplitter").is(":visible")
|
||||
state = $("#editorSplitter").layout().readState()
|
||||
eastWidth = state.east.size
|
||||
eastWidth = state.east.size + $("#editorSplitter .ui-layout-resizer-east").width()
|
||||
percentWidth = eastWidth / $("#editorSplitter").width() * 100 + "%"
|
||||
state.east.size = percentWidth
|
||||
$.localStorage("layout.editor", state)
|
||||
|
||||
@@ -98,7 +98,7 @@ define [
|
||||
v: update.v
|
||||
op_sent_at: new Date()
|
||||
timer = setTimeout () =>
|
||||
@_handleError new Error("Doc op was not acknowledged in time"), meta
|
||||
@trigger "op:timeout", update
|
||||
, @INFLIGHT_OP_TIMEOUT
|
||||
@_doc.inflightCallbacks.push () =>
|
||||
clearTimeout timer
|
||||
@@ -109,11 +109,16 @@ define [
|
||||
_bindToDocChanges: (doc) ->
|
||||
submitOp = doc.submitOp
|
||||
doc.submitOp = (args...) =>
|
||||
@trigger "op:sent"
|
||||
@trigger "op:sent", args...
|
||||
doc.pendingCallbacks.push () =>
|
||||
@trigger "op:acknowledged"
|
||||
@trigger "op:acknowledged", args...
|
||||
submitOp.apply(doc, args)
|
||||
|
||||
flush = doc.flush
|
||||
doc.flush = (args...) =>
|
||||
@trigger "flush", doc.inflightOp, doc.pendingOp, doc.version
|
||||
flush.apply(doc, args)
|
||||
|
||||
_.extend(ShareJsDoc::, Backbone.Events)
|
||||
|
||||
return ShareJsDoc
|
||||
|
||||
@@ -15,6 +15,7 @@ define [
|
||||
events: () ->
|
||||
events = {}
|
||||
events["click ##{@model.id} > .js-clickable"] = "parentOnClick"
|
||||
events["click ##{@model.id} > .entity-label"] = "parentOnClick"
|
||||
events["click .dropdown-caret"] = "showContextMenuFromCaret"
|
||||
events["contextmenu"] = "showContextMenuFromRightClick"
|
||||
return events
|
||||
@@ -29,6 +30,7 @@ define [
|
||||
@$nameEl = @$(".name")
|
||||
@$inputEl = @$("input.js-rename")
|
||||
@$entityListItemEl = @$el.children(".entity-list-item")
|
||||
@$labelEl = @$entityListItemEl.children(".entity-label")
|
||||
|
||||
_makeEditable: () ->
|
||||
if @ide.isAllowedToDoIt "readAndWrite"
|
||||
@@ -48,6 +50,17 @@ define [
|
||||
@$nameEl.hide()
|
||||
@$inputEl.show()
|
||||
|
||||
setLabels: (labels) ->
|
||||
label = labels[@model.get("id")]
|
||||
if label?
|
||||
@$entityListItemEl.addClass("show-label")
|
||||
@$labelEl.text("±")
|
||||
return true
|
||||
else
|
||||
@$entityListItemEl.removeClass("show-label")
|
||||
@$labelEl.text("")
|
||||
return false
|
||||
|
||||
select: () ->
|
||||
@selected = true
|
||||
@$entityListItemEl.addClass("selected")
|
||||
|
||||
@@ -75,6 +75,7 @@ define [
|
||||
children.add(entity)
|
||||
|
||||
openDoc: (doc, line) ->
|
||||
return if !doc?
|
||||
doc_id = doc.id or doc
|
||||
@trigger "open:doc", doc_id, line: line
|
||||
@selectEntity(doc_id)
|
||||
@@ -158,6 +159,7 @@ define [
|
||||
|
||||
|
||||
renameEntity: (entity, name) ->
|
||||
name = name?.trim()
|
||||
@ide.socket.emit 'renameEntity', entity.id, entity.get("type"), name
|
||||
entity.set("name", name)
|
||||
|
||||
@@ -196,7 +198,7 @@ define [
|
||||
el = $($("#newEntityModalTemplate").html())
|
||||
input = el.find("input")
|
||||
create = _.once () =>
|
||||
name = input.val()
|
||||
name = input.val()?.trim()
|
||||
if name != ""
|
||||
callback(name)
|
||||
modal = new Modal
|
||||
@@ -277,6 +279,5 @@ define [
|
||||
entity.collection?.remove(entity)
|
||||
delete @views[entity_id]
|
||||
|
||||
|
||||
|
||||
|
||||
setLabels: (labels) ->
|
||||
@view.setLabels(labels)
|
||||
|
||||
@@ -19,4 +19,7 @@ define [
|
||||
@rootFolderView = new RootFolderView(model: rootFolder, manager: @manager)
|
||||
entities.append(@rootFolderView.$el)
|
||||
@rootFolderView.render()
|
||||
|
||||
setLabels: (labels) ->
|
||||
@rootFolderView.setLabels(labels)
|
||||
|
||||
|
||||
@@ -107,11 +107,13 @@ define [
|
||||
@$contents.hide()
|
||||
@$toggle.find(".js-open").hide()
|
||||
@$toggle.find(".js-closed").show()
|
||||
@$entityListItemEl.removeClass("folder-open")
|
||||
|
||||
showEntries: () ->
|
||||
@$contents.show()
|
||||
@$toggle.find(".js-open").show()
|
||||
@$toggle.find(".js-closed").hide()
|
||||
@$entityListItemEl.addClass("folder-open")
|
||||
|
||||
onToggle: (e) ->
|
||||
e.preventDefault()
|
||||
@@ -146,3 +148,17 @@ define [
|
||||
@manager.showUploadFileModal(@model)
|
||||
}]
|
||||
|
||||
setLabels: (labels) ->
|
||||
showLabel = false
|
||||
for entity in @views
|
||||
if entity.setLabels(labels)
|
||||
showLabel = true
|
||||
|
||||
if showLabel
|
||||
@$entityListItemEl.addClass("show-label")
|
||||
@$labelEl.text("±")
|
||||
return true
|
||||
else
|
||||
@$entityListItemEl.removeClass("show-label")
|
||||
@$labelEl.text("")
|
||||
return false
|
||||
|
||||
@@ -26,6 +26,7 @@ define [
|
||||
"tour/IdeTour"
|
||||
"analytics/AnalyticsManager"
|
||||
"track-changes/TrackChangesManager"
|
||||
"debug/DebugManager"
|
||||
"ace/ace"
|
||||
"libs/jquery.color"
|
||||
"libs/jquery-layout"
|
||||
@@ -59,6 +60,7 @@ define [
|
||||
IdeTour,
|
||||
AnalyticsManager,
|
||||
TrackChangesManager
|
||||
DebugManager
|
||||
) ->
|
||||
|
||||
|
||||
@@ -117,10 +119,10 @@ define [
|
||||
@cursorManager = new CursorManager(@)
|
||||
@fileViewManager = new FileViewManager(@)
|
||||
@analyticsManager = new AnalyticsManager(@)
|
||||
if @userSettings.trackChanges
|
||||
@trackChangesManager = new TrackChangesManager(@)
|
||||
else
|
||||
if @userSettings.oldHistory
|
||||
@historyManager = new HistoryManager(@)
|
||||
else
|
||||
@trackChangesManager = new TrackChangesManager(@)
|
||||
|
||||
@setLoadingMessage("Connecting")
|
||||
firstConnect = true
|
||||
@@ -159,14 +161,18 @@ define [
|
||||
buttons: [ text: "OK" ]
|
||||
}
|
||||
|
||||
recentEvents: []
|
||||
|
||||
pushEvent: (type, meta = {}) ->
|
||||
@recentEvents.push type: type, meta: meta, date: new Date()
|
||||
if @recentEvents.length > 40
|
||||
@recentEvents.shift()
|
||||
|
||||
reportError: (error, meta = {}) ->
|
||||
meta.client_id = @socket?.socket?.sessionid
|
||||
meta.transport = @socket?.socket?.transport?.name
|
||||
meta.client_now = new Date()
|
||||
meta.last_connected = @connectionManager.lastConnected
|
||||
meta.second_last_connected = @connectionManager.secondLastConnected
|
||||
meta.last_disconnected = @connectionManager.lastDisconnected
|
||||
meta.second_last_disconnected = @connectionManager.secondLastDisconnected
|
||||
meta.recent_events = @recentEvents
|
||||
errorObj = {}
|
||||
for key in Object.getOwnPropertyNames(error)
|
||||
errorObj[key] = error[key]
|
||||
@@ -194,6 +200,7 @@ define [
|
||||
ide.hotkeysManager = new HotkeysManager ide
|
||||
ide.layoutManager.resizeAllSplitters()
|
||||
ide.tourManager = new IdeTour ide
|
||||
ide.debugManager = new DebugManager(ide)
|
||||
|
||||
ide.savingAreaManager =
|
||||
$savingArea : $('#saving-area')
|
||||
|
||||
@@ -8,15 +8,13 @@ define [
|
||||
@socket = @ide.socket
|
||||
@socket.on "connect", () =>
|
||||
@connected = true
|
||||
@secondLastConnected = @lastConnected
|
||||
@lastConnected = new Date()
|
||||
@ide.pushEvent("connected")
|
||||
@hideModal()
|
||||
@cancelReconnect()
|
||||
|
||||
@socket.on 'disconnect', () =>
|
||||
@connected = false
|
||||
@secondLastDisconnected = @lastDisconnected
|
||||
@lastDisconnected = new Date()
|
||||
@ide.pushEvent("disconnected")
|
||||
@ide.trigger "disconnect"
|
||||
setTimeout(=>
|
||||
ga('send', 'event', 'editor-interaction', 'disconnect')
|
||||
@@ -35,6 +33,13 @@ define [
|
||||
e.preventDefault()
|
||||
@tryReconnect()
|
||||
@hideModal()
|
||||
|
||||
reconnectImmediately: () ->
|
||||
@disconnect()
|
||||
@tryReconnect()
|
||||
|
||||
disconnect: () ->
|
||||
@socket.disconnect()
|
||||
|
||||
showModalAndStartAutoReconnect: () ->
|
||||
@hideModal()
|
||||
|
||||
@@ -144,7 +144,7 @@ require [
|
||||
$confirm.click (e) =>
|
||||
$confirm.attr("disabled", true)
|
||||
$confirm.text("Creating...")
|
||||
projectName = $modal.find('input').val()
|
||||
projectName = $modal.find('input').val()?.trim()
|
||||
$.ajax
|
||||
url: '/project/new'
|
||||
type:'POST'
|
||||
|
||||
@@ -51,6 +51,7 @@ define [
|
||||
"click #splitViewButton": ->
|
||||
$.localStorage("layout.pdf", "split")
|
||||
@options.manager.switchToSplitView()
|
||||
"click .delete-cached-files > a": -> @options.manager.deleteCachedFiles()
|
||||
|
||||
initialize: (@options) ->
|
||||
@ide = @options.ide
|
||||
@@ -90,7 +91,7 @@ define [
|
||||
logButtonHtml = "Logs"
|
||||
|
||||
if compileErrors?
|
||||
for error in compileErrors.all
|
||||
for error in compileErrors.errors.concat(compileErrors.warnings).concat(compileErrors.typesetting)
|
||||
errorView = new LatexErrorView(@options.manager.ide, error)
|
||||
errorView.render()
|
||||
@errorViews.push(errorView)
|
||||
|
||||
@@ -187,3 +187,26 @@ define [
|
||||
|
||||
downloadPdf: () ->
|
||||
@ide.mainAreaManager.setIframeSrc "/project/#{@ide.project_id}/output/output.pdf?popupDownload=true"
|
||||
|
||||
deleteCachedFiles: () ->
|
||||
modal = new Modal
|
||||
title: "Clear cache?"
|
||||
message: "This will clear all hidden LaTeX files like .aux, .bbl, etc, from our compile server. You generally don't need to do this unless you're having trouble with references. Your project files will not be deleted or changed."
|
||||
buttons: [{
|
||||
text: "Cancel"
|
||||
}, {
|
||||
text: "Clear from cache",
|
||||
class: "btn-primary",
|
||||
close: false
|
||||
callback: ($button) =>
|
||||
$button.text("Clearing...")
|
||||
$button.prop("disabled", true)
|
||||
$.ajax({
|
||||
url: "/project/#{@ide.project_id}/output"
|
||||
type: "DELETE"
|
||||
headers:
|
||||
"X-CSRF-Token": window.csrfToken
|
||||
complete: () -> modal.remove()
|
||||
})
|
||||
|
||||
}]
|
||||
|
||||
@@ -25,10 +25,11 @@ define [
|
||||
@publishProjectView?.refreshPublishStatus()
|
||||
|
||||
setupPublish = _.once =>
|
||||
@publishProjectView = new PublishProjectView
|
||||
ide: @ide
|
||||
el: $("#publishProject")
|
||||
@publishProjectView.render()
|
||||
if @ide.security? and @ide.security.permissionsLevel == "owner"
|
||||
@publishProjectView = new PublishProjectView
|
||||
ide: @ide
|
||||
el: $("#publishProject")
|
||||
@publishProjectView.render()
|
||||
|
||||
setupArea()
|
||||
if @ide?
|
||||
@@ -76,17 +77,18 @@ define [
|
||||
@ide.socket.emit "removeUserFromProject", member.id
|
||||
|
||||
addMember: (email, privileges) ->
|
||||
console.log "Adding member", email
|
||||
@ide.socket.emit "addUserToProject", email, privileges, (error, added) =>
|
||||
if error?
|
||||
@ide.showGenericServerErrorMessage()
|
||||
return
|
||||
if !added
|
||||
console.log "got response", error, added
|
||||
ga('send', 'event', 'subscription-funnel', 'askToUpgrade', "projectMemebrs")
|
||||
AccountManager.askToUpgrade @ide,
|
||||
why: "to add additional collaborators"
|
||||
onUpgrade: () =>
|
||||
ga('send', 'event', 'subscription-funnel', 'upgraded-free-trial', "projectMemebrs")
|
||||
@addMember(email, privileges)
|
||||
|
||||
afterMemberRemoved: (memberId) ->
|
||||
for member in @members.models
|
||||
|
||||
@@ -17,11 +17,11 @@ define [
|
||||
@tab.empty()
|
||||
if !@ide.isAllowedToDoIt "owner"
|
||||
else if !@project.get('features').dropbox
|
||||
ga('send', 'event', 'subscription-funnel', 'askToUpgrade', "dropdown")
|
||||
ga('send', 'event', 'subscription-funnel', 'askToUpgrade', "dropbox")
|
||||
accountManager.askToUpgrade @ide,
|
||||
onUpgrade: =>
|
||||
@checkIfUserIsLinkedToDropbox()
|
||||
ga('send', 'event', 'subscription-funnel', 'upgraded-free-trial', "dropdown")
|
||||
ga('send', 'event', 'subscription-funnel', 'upgraded-free-trial', "dropbox")
|
||||
else
|
||||
@checkIfUserIsLinkedToDropbox()
|
||||
|
||||
|
||||
@@ -126,7 +126,10 @@ define [
|
||||
# http://stackoverflow.com/questions/6692031/check-if-event-is-triggered-by-a-human
|
||||
if e.originalEvent?
|
||||
if @ide.isAllowedToDoIt "readAndWrite"
|
||||
@project.set("name", e.target.value)
|
||||
newName = e.target.value?.trim()
|
||||
$("input.projectName").val(newName)
|
||||
@project.set("name", newName)
|
||||
|
||||
|
||||
bindToCompiler: ->
|
||||
$('select#compilers').val(@project.get("compiler"))
|
||||
|
||||
@@ -40,6 +40,9 @@ define [
|
||||
overflow: "scroll"
|
||||
this
|
||||
|
||||
remove: () ->
|
||||
@undelegateEvents()
|
||||
|
||||
addItem: (model) ->
|
||||
index = @collection.indexOf(model)
|
||||
previousModel = @collection.models[index - 1]
|
||||
@@ -208,6 +211,17 @@ define [
|
||||
else
|
||||
@$el.addClass("first-in-day")
|
||||
|
||||
@$(".change-selector-from").tooltip({
|
||||
title: "Show back to this change",
|
||||
placement: "left",
|
||||
animation: false
|
||||
})
|
||||
@$(".change-selector-to").tooltip({
|
||||
title: "Show up to this change",
|
||||
placement: "left",
|
||||
animation: false
|
||||
})
|
||||
|
||||
return this
|
||||
|
||||
onClick: (e) ->
|
||||
|
||||
@@ -3,10 +3,11 @@ define [
|
||||
"track-changes/models/Diff"
|
||||
"track-changes/ChangeListView"
|
||||
"track-changes/DiffView"
|
||||
"account/AccountManager"
|
||||
"utils/Modal"
|
||||
"models/Doc"
|
||||
"moment"
|
||||
], (ChangeList, Diff, ChangeListView, DiffView, Modal, Doc, moment) ->
|
||||
], (ChangeList, Diff, ChangeListView, DiffView, AccountManager, Modal, Doc, moment) ->
|
||||
class TrackChangesManager
|
||||
template: $("#trackChangesPanelTemplate").html()
|
||||
|
||||
@@ -39,23 +40,30 @@ define [
|
||||
|
||||
bindToFileTreeEvents: () ->
|
||||
@ide.fileTreeManager.on "open:doc", (doc_id) =>
|
||||
@doc_id = doc_id
|
||||
if @enabled
|
||||
@doc_id = doc_id
|
||||
@updateDiff()
|
||||
|
||||
AB_BUCKETS: ["control", "one-week", "pop-up"]
|
||||
show: () ->
|
||||
@changes = new ChangeList([], project_id: @project_id, ide: @ide)
|
||||
|
||||
if @changeListView?
|
||||
@changeListView.remove()
|
||||
@changeListView = new ChangeListView(
|
||||
collection : @changes,
|
||||
el : @$el.find(".change-list-area")
|
||||
el: @$el.find(".change-list-area")
|
||||
collection: @changes
|
||||
)
|
||||
@changeListView.render()
|
||||
@changeListView.loadUntilFull (error) =>
|
||||
@autoSelectDiff()
|
||||
|
||||
@changeListView.on "change_diff", (fromIndex, toIndex) =>
|
||||
@selectDocAndUpdateDiff(fromIndex, toIndex)
|
||||
@findDocsInChange(fromIndex, toIndex)
|
||||
@updateLabels()
|
||||
@updateDiff()
|
||||
|
||||
@showUpgradeView()
|
||||
|
||||
if @diffView?
|
||||
@diffView.remove()
|
||||
@@ -65,12 +73,26 @@ define [
|
||||
@ide.fileViewManager.disable()
|
||||
@enable()
|
||||
|
||||
showUpgradeView: () ->
|
||||
@$el.find("button.start-free-trial").off "click.track-changes"
|
||||
@$el.find("button.start-free-trial").on "click.track-changes", () => @gotoFreeTrial()
|
||||
|
||||
if !@ide.project.get("features").versioning
|
||||
ga('send', 'event', 'subscription-funnel', 'askToUgrade', "trackchanges")
|
||||
@$el.find(".track-changes-upgrade-popup").show()
|
||||
|
||||
if @ide.project.get("owner") == @ide.user
|
||||
@$el.find(".show-when-not-owner").hide()
|
||||
else
|
||||
@$el.find(".show-when-owner").hide()
|
||||
|
||||
hide: () ->
|
||||
@ide.editor.enable()
|
||||
@ide.fileViewManager.enable()
|
||||
@disable()
|
||||
@ide.fileTreeManager.openDoc(@doc_id)
|
||||
@ide.tabManager.show "code"
|
||||
@resetLabels()
|
||||
|
||||
autoSelectDiff: () ->
|
||||
if @changes.models.length == 0
|
||||
@@ -92,17 +114,27 @@ define [
|
||||
@changeListView.setSelectionRange(fromIndex, 0)
|
||||
@updateDiff()
|
||||
|
||||
selectDocAndUpdateDiff: (fromIndex, toIndex) ->
|
||||
doc_ids = []
|
||||
findDocsInChange: (fromIndex, toIndex) ->
|
||||
@changed_doc_ids = []
|
||||
for change in @changes.models.slice(toIndex, fromIndex + 1)
|
||||
for doc in change.get("docs") or []
|
||||
doc_ids.push doc.id if doc.id not in doc_ids
|
||||
@changed_doc_ids.push doc.id if doc.id not in @changed_doc_ids
|
||||
|
||||
if !@doc_id? or @doc_id not in doc_ids
|
||||
@doc_id = doc_ids[0]
|
||||
if !@doc_id? or @doc_id not in @changed_doc_ids
|
||||
@doc_id = @changed_doc_ids[0]
|
||||
|
||||
@updateDiff()
|
||||
|
||||
updateLabels: () ->
|
||||
labels = {}
|
||||
for doc_id in @changed_doc_ids
|
||||
labels[doc_id] = true
|
||||
@ide.fileTreeManager.setLabels(labels)
|
||||
|
||||
resetLabels: () ->
|
||||
@ide.fileTreeManager.setLabels({})
|
||||
|
||||
|
||||
updateDiff: () ->
|
||||
fromIndex = @changeListView.selectedFromIndex
|
||||
toIndex = @changeListView.selectedToIndex
|
||||
@@ -191,4 +223,8 @@ define [
|
||||
disable: () ->
|
||||
@enabled = false
|
||||
|
||||
gotoFreeTrial: () ->
|
||||
AccountManager.gotoSubscriptionsPage()
|
||||
ga('send', 'event', 'subscription-funnel', 'upgraded-free-trial', "trackchanges")
|
||||
|
||||
return TrackChangesManager
|
||||
|
||||
@@ -445,6 +445,7 @@ var RenderingStates = {
|
||||
|
||||
var idCounter = 0;
|
||||
|
||||
|
||||
/**
|
||||
* The view for a single page.
|
||||
*/
|
||||
@@ -477,16 +478,30 @@ PageView.prototype = {
|
||||
// Only change the width/height property of the canvas if it really
|
||||
// changed. Every assignment to the width/height property clears the
|
||||
// content of the canvas.
|
||||
|
||||
var outputScale = this.getOutputScale();
|
||||
|
||||
var scaledWidth = (Math.floor(viewport.width) * outputScale.sx) | 0;
|
||||
var scaledHeight = (Math.floor(viewport.height) * outputScale.sy) | 0;
|
||||
|
||||
var newWidth = Math.floor(viewport.width);
|
||||
var newHeight = Math.floor(viewport.height);
|
||||
|
||||
if (this.canvas.width !== newWidth) {
|
||||
this.canvas.width = newWidth;
|
||||
this.canvas.width = scaledWidth;
|
||||
this.canvas.style.width = newWidth + 'px';
|
||||
this.resetRenderState();
|
||||
}
|
||||
if (this.canvas.height !== newHeight) {
|
||||
this.canvas.height = newHeight;
|
||||
this.canvas.height = scaledHeight;
|
||||
this.canvas.style.height = newHeight + 'px';
|
||||
this.resetRenderState();
|
||||
}
|
||||
|
||||
if(outputScale.scaled){
|
||||
var ctx = this.getCanvasContext()
|
||||
ctx.scale(outputScale.sx, outputScale.sy)
|
||||
}
|
||||
|
||||
this.width = viewport.width;
|
||||
this.height = viewport.height;
|
||||
@@ -523,6 +538,28 @@ PageView.prototype = {
|
||||
return this.canvas.getContext('2d');
|
||||
},
|
||||
|
||||
/**
|
||||
* Returns scale factor for the canvas. It makes sense for the HiDPI displays.
|
||||
* @return {Object} The object with horizontal (sx) and vertical (sy)
|
||||
scales. The scaled property is set to false if scaling is
|
||||
not required, true otherwise.
|
||||
*/
|
||||
|
||||
getOutputScale: function(){
|
||||
var ctx = this.getCanvasContext()
|
||||
var devicePixelRatio = window.devicePixelRatio || 1;
|
||||
var backingStoreRatio = ctx.webkitBackingStorePixelRatio ||
|
||||
ctx.mozBackingStorePixelRatio ||
|
||||
ctx.msBackingStorePixelRatio ||
|
||||
ctx.oBackingStorePixelRatio ||
|
||||
ctx.backingStorePixelRatio || 1;
|
||||
var pixelRatio = devicePixelRatio / backingStoreRatio;
|
||||
return {
|
||||
sx: pixelRatio,
|
||||
sy: pixelRatio,
|
||||
scaled: pixelRatio != 1
|
||||
};
|
||||
},
|
||||
createNewCanvas: function() {
|
||||
if (this.canvas) {
|
||||
this.dom.removeChild(this.canvas);
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
margin-bottom: 10px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.bonus-banner {
|
||||
@@ -121,6 +122,8 @@
|
||||
background-repeat: no-repeat;
|
||||
background-position: 16px center;
|
||||
}
|
||||
h2.direct-link {
|
||||
}
|
||||
}
|
||||
|
||||
p.thanks {
|
||||
|
||||
@@ -467,6 +467,9 @@ body.editor {
|
||||
.dropdown-caret {
|
||||
display: none;
|
||||
}
|
||||
.entity-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.entity-folder {
|
||||
@@ -527,6 +530,33 @@ body.editor {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.entity-list-item.show-label {
|
||||
.dropdown-caret {
|
||||
display: none;
|
||||
}
|
||||
.entity-label {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 3px;
|
||||
right: 3px;
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
padding: 2px 6px 3px;
|
||||
background-color: hsl(100, 80%, 42%);
|
||||
font-weight: normal;
|
||||
text-shadow: none;
|
||||
&:hover {
|
||||
background-color: hsl(100, 80%, 35%)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.entity-list-item.folder-open.show-label {
|
||||
.entity-label {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
li img, .entity-list-item i {
|
||||
margin: 4px;
|
||||
|
||||
@@ -79,6 +79,39 @@
|
||||
}
|
||||
}
|
||||
|
||||
.track-changes-upgrade-control, .track-changes-upgrade-popup {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.track-changes-upgrade-popup {
|
||||
background-color: rgba(128,128,128,0.4);
|
||||
.message {
|
||||
margin: auto;
|
||||
margin-top: 200px;
|
||||
padding: 10px 10px 14px 10px;
|
||||
width: 400px;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
background-color: white;
|
||||
.border-radius(8px);
|
||||
}
|
||||
}
|
||||
|
||||
.track-changes-upgrade-control {
|
||||
background-color: #eeeeee;
|
||||
text-align: center;
|
||||
.message {
|
||||
font-size: 18px;
|
||||
margin: 12px;
|
||||
margin-top: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
.deleted-change-background,
|
||||
.deleted-change-foreground,
|
||||
.inserted-change-background,
|
||||
@@ -193,6 +226,10 @@
|
||||
}
|
||||
li.loading-changes, li.empty-message {
|
||||
padding: 6px;
|
||||
cursor: default;
|
||||
&:hover {
|
||||
background-color: inherit;
|
||||
}
|
||||
}
|
||||
li.selected-change {
|
||||
background-color: #eaeaea;
|
||||
@@ -242,6 +279,11 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
li.track-changes-upgrade-oneweek {
|
||||
padding: 15px;
|
||||
background-color: rgb(255, 251, 210);
|
||||
cursor: default;
|
||||
}
|
||||
}
|
||||
ul.change-list.hover-state {
|
||||
li {
|
||||
@@ -256,6 +298,7 @@
|
||||
li.hover-selected {
|
||||
.change-selectors {
|
||||
.range {
|
||||
top: 0;
|
||||
background-color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
+31
@@ -122,6 +122,37 @@ describe "AuthenticationController", ->
|
||||
it "should only redirect to the local path", ->
|
||||
expect(@res.body).to.deep.equal redir: "/test"
|
||||
|
||||
describe "getLoggedInUserId", ->
|
||||
|
||||
beforeEach ->
|
||||
@req =
|
||||
session :{}
|
||||
|
||||
it "should return the user id from the session", (done)->
|
||||
@user_id = "2134"
|
||||
@req.session.user =
|
||||
_id:@user_id
|
||||
@AuthenticationController.getLoggedInUserId @req, (err, user_id)=>
|
||||
expect(user_id).to.equal @user_id
|
||||
done()
|
||||
|
||||
it "should return an error if there is no user on the session", (done)->
|
||||
@AuthenticationController.getLoggedInUserId @req, (err, user_id)=>
|
||||
expect(err).to.exist
|
||||
done()
|
||||
|
||||
it "should return an error if there is no session", (done)->
|
||||
@req = {}
|
||||
@AuthenticationController.getLoggedInUserId @req, (err, user_id)=>
|
||||
expect(err).to.exist
|
||||
done()
|
||||
|
||||
it "should return an error if there is no req", (done)->
|
||||
@req = {}
|
||||
@AuthenticationController.getLoggedInUserId @req, (err, user_id)=>
|
||||
expect(err).to.exist
|
||||
done()
|
||||
|
||||
describe "getLoggedInUser", ->
|
||||
beforeEach ->
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(1, null, @user)
|
||||
|
||||
@@ -16,7 +16,7 @@ describe "CompileController", ->
|
||||
apis:
|
||||
clsi:
|
||||
url: "clsi.example.com"
|
||||
"request": @request = {}
|
||||
"request": @request = sinon.stub()
|
||||
"../../models/Project": Project: @Project = {}
|
||||
"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
|
||||
"../../infrastructure/Metrics": @Metrics = { inc: sinon.stub() }
|
||||
@@ -68,15 +68,23 @@ describe "CompileController", ->
|
||||
|
||||
describe "proxyToClsi", ->
|
||||
beforeEach ->
|
||||
@request.get = sinon.stub().returns(@proxy = {
|
||||
@request.returns(@proxy = {
|
||||
pipe: sinon.stub()
|
||||
on: sinon.stub()
|
||||
})
|
||||
@upstream =
|
||||
statusCode: 204
|
||||
headers: { "mock": "header" }
|
||||
@req.method = "mock-method"
|
||||
@CompileController.proxyToClsi(@url = "/test", @req, @res, @next)
|
||||
|
||||
it "should open a request to the CLSI", ->
|
||||
@request.get
|
||||
.calledWith(url: "#{@settings.apis.clsi.url}#{@url}", timeout: 60 * 1000)
|
||||
@request
|
||||
.calledWith(
|
||||
method: @req.method
|
||||
url: "#{@settings.apis.clsi.url}#{@url}",
|
||||
timeout: 60 * 1000
|
||||
)
|
||||
.should.equal true
|
||||
|
||||
it "should pass the request on to the client", ->
|
||||
@@ -87,6 +95,17 @@ describe "CompileController", ->
|
||||
it "should bind an error handle to the request proxy", ->
|
||||
@proxy.on.calledWith("error").should.equal true
|
||||
|
||||
describe "deleteAuxFiles", ->
|
||||
beforeEach ->
|
||||
@CompileController.proxyToClsi = sinon.stub()
|
||||
@req.params =
|
||||
Project_id: @project_id
|
||||
@CompileController.deleteAuxFiles @req, @res, @next
|
||||
|
||||
it "should proxy to the CLSI", ->
|
||||
@CompileController.proxyToClsi
|
||||
.calledWith("/project/#{@project_id}", @req, @res, @next)
|
||||
.should.equal true
|
||||
|
||||
describe "compileAndDownloadPdf", ->
|
||||
beforeEach ->
|
||||
|
||||
@@ -609,3 +609,114 @@ describe "EditorController", ->
|
||||
done()
|
||||
|
||||
|
||||
describe "deleteProject", ->
|
||||
|
||||
beforeEach ->
|
||||
@err = "errro"
|
||||
@ProjectHandler::deleteProject = sinon.stub().callsArgWith(1, @err)
|
||||
|
||||
it "should call the project handler", (done)->
|
||||
@EditorController.deleteProject @project_id, (err)=>
|
||||
err.should.equal @err
|
||||
@ProjectHandler::deleteProject.calledWith(@project_id).should.equal true
|
||||
done()
|
||||
|
||||
|
||||
describe "renameEntity", ->
|
||||
|
||||
beforeEach ->
|
||||
@err = "errro"
|
||||
@entity_id = "entity_id_here"
|
||||
@entityType = "doc"
|
||||
@newName = "bobsfile.tex"
|
||||
@ProjectHandler::renameEntity = sinon.stub().callsArgWith(4, @err)
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
|
||||
it "should call the project handler", (done)->
|
||||
@EditorController.renameEntity @project_id, @entity_id, @entityType, @newName, =>
|
||||
@ProjectHandler::renameEntity.calledWith(@project_id, @entity_id, @entityType, @newName).should.equal true
|
||||
done()
|
||||
|
||||
|
||||
it "should emit the update to the room", (done)->
|
||||
@EditorController.renameEntity @project_id, @entity_id, @entityType, @newName, =>
|
||||
@EditorRealTimeController.emitToRoom.calledWith(@project_id, 'reciveEntityRename', @entity_id, @newName).should.equal true
|
||||
done()
|
||||
|
||||
describe "moveEntity", ->
|
||||
|
||||
beforeEach ->
|
||||
@err = "errro"
|
||||
@entity_id = "entity_id_here"
|
||||
@entityType = "doc"
|
||||
@folder_id = "313dasd21dasdsa"
|
||||
@ProjectEntityHandler.moveEntity = sinon.stub().callsArgWith(4, @err)
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
|
||||
it "should call the ProjectEntityHandler", (done)->
|
||||
@EditorController.moveEntity @project_id, @entity_id, @folder_id, @entityType, =>
|
||||
@ProjectEntityHandler.moveEntity.calledWith(@project_id, @entity_id, @folder_id, @entityType).should.equal true
|
||||
done()
|
||||
|
||||
|
||||
it "should emit the update to the room", (done)->
|
||||
@EditorController.moveEntity @project_id, @entity_id, @folder_id, @entityType, =>
|
||||
@EditorRealTimeController.emitToRoom.calledWith(@project_id, 'reciveEntityMove', @entity_id, @folder_id).should.equal true
|
||||
done()
|
||||
|
||||
describe "renameProject", ->
|
||||
|
||||
beforeEach ->
|
||||
@err = "errro"
|
||||
@window_id = "kdsjklj290jlk"
|
||||
@newName = "new name here"
|
||||
@ProjectHandler::renameProject = sinon.stub().callsArgWith(3, @err)
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
|
||||
it "should call the ProjectHandler", (done)->
|
||||
@EditorController.renameProject @project_id, @window_id, @newName, =>
|
||||
@ProjectHandler::renameProject.calledWith(@project_id, @window_id, @newName).should.equal true
|
||||
done()
|
||||
|
||||
|
||||
it "should emit the update to the room", (done)->
|
||||
@EditorController.renameProject @project_id, @window_id, @newName, =>
|
||||
@EditorRealTimeController.emitToRoom.calledWith(@project_id, 'projectNameUpdated', @window_id, @newName).should.equal true
|
||||
done()
|
||||
|
||||
|
||||
describe "setPublicAccessLevel", ->
|
||||
|
||||
beforeEach ->
|
||||
@err = "errro"
|
||||
@newAccessLevel = "public"
|
||||
@ProjectHandler::setPublicAccessLevel = sinon.stub().callsArgWith(2, @err)
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
|
||||
it "should call the ProjectHandler", (done)->
|
||||
@EditorController.setPublicAccessLevel @project_id, @newAccessLevel, =>
|
||||
@ProjectHandler::setPublicAccessLevel.calledWith(@project_id, @newAccessLevel).should.equal true
|
||||
done()
|
||||
|
||||
it "should emit the update to the room", (done)->
|
||||
@EditorController.setPublicAccessLevel @project_id, @newAccessLevel, =>
|
||||
@EditorRealTimeController.emitToRoom.calledWith(@project_id, 'publicAccessLevelUpdated', @newAccessLevel).should.equal true
|
||||
done()
|
||||
|
||||
describe "setRootDoc", ->
|
||||
|
||||
beforeEach ->
|
||||
@err = "errro"
|
||||
@newRootDocID = "21312321321"
|
||||
@ProjectEntityHandler.setRootDoc = sinon.stub().callsArgWith(2, @err)
|
||||
@EditorRealTimeController.emitToRoom = sinon.stub()
|
||||
|
||||
it "should call the ProjectEntityHandler", (done)->
|
||||
@EditorController.setRootDoc @project_id, @newRootDocID, =>
|
||||
@ProjectEntityHandler.setRootDoc.calledWith(@project_id, @newRootDocID).should.equal true
|
||||
done()
|
||||
|
||||
it "should emit the update to the room", (done)->
|
||||
@EditorController.setRootDoc @project_id, @newRootDocID, =>
|
||||
@EditorRealTimeController.emitToRoom.calledWith(@project_id, 'rootDocUpdated', @newRootDocID).should.equal true
|
||||
done()
|
||||
@@ -8,36 +8,41 @@ require('chai').should()
|
||||
describe 'Project details handler', ->
|
||||
|
||||
beforeEach ->
|
||||
@ProjectGetter =
|
||||
getProjectWithoutDocLines: sinon.stub()
|
||||
@ProjectModel =
|
||||
update: sinon.stub()
|
||||
@handler = SandboxedModule.require modulePath, requires:
|
||||
"./ProjectGetter":@ProjectGetter
|
||||
'../../models/Project': Project:@ProjectModel
|
||||
'logger-sharelatex':
|
||||
log:->
|
||||
err:->
|
||||
@project_id = "321l3j1kjkjl"
|
||||
@user_id = "user-id-123"
|
||||
@project =
|
||||
name: "project"
|
||||
description: "this is a great project"
|
||||
something:"should not exist"
|
||||
compiler: "latexxxxxx"
|
||||
|
||||
owner_ref: @user_id
|
||||
@user =
|
||||
features: "mock-features"
|
||||
@ProjectGetter =
|
||||
getProjectWithoutDocLines: sinon.stub().callsArgWith(1, null, @project)
|
||||
@ProjectModel =
|
||||
update: sinon.stub()
|
||||
@UserGetter =
|
||||
getUser: sinon.stub().callsArgWith(1, null, @user)
|
||||
@handler = SandboxedModule.require modulePath, requires:
|
||||
"./ProjectGetter":@ProjectGetter
|
||||
'../../models/Project': Project:@ProjectModel
|
||||
"../User/UserGetter": @UserGetter
|
||||
'logger-sharelatex':
|
||||
log:->
|
||||
err:->
|
||||
|
||||
describe "getDetails", ->
|
||||
|
||||
it "should find the project", (done)->
|
||||
@ProjectGetter.getProjectWithoutDocLines.callsArgWith(1, null, @project)
|
||||
@handler.getDetails @project_id, (err, details)=>
|
||||
it "should find the project and owner", (done)->
|
||||
@handler.getDetails @project_id, (err, details)=>
|
||||
details.name.should.equal @project.name
|
||||
details.description.should.equal @project.description
|
||||
details.compiler.should.equal @project.compiler
|
||||
details.features.should.equal @user.features
|
||||
assert.equal(details.something, undefined)
|
||||
done()
|
||||
|
||||
|
||||
it "should return the error", (done)->
|
||||
error = "some error"
|
||||
@ProjectGetter.getProjectWithoutDocLines.callsArgWith(1, error)
|
||||
|
||||
@@ -5,6 +5,7 @@ expect = chai.expect
|
||||
modulePath = "../../../../app/js/Features/Project/ProjectGetter.js"
|
||||
SandboxedModule = require('sandboxed-module')
|
||||
ObjectId = require("mongojs").ObjectId
|
||||
assert = require("chai").assert
|
||||
|
||||
describe "ProjectGetter", ->
|
||||
beforeEach ->
|
||||
@@ -57,7 +58,8 @@ describe "ProjectGetter", ->
|
||||
@db.users.find = (query, callback) =>
|
||||
callback null, [@user_lookup[query._id.toString()]]
|
||||
sinon.spy @db.users, "find"
|
||||
@ProjectGetter.populateProjectWithUsers @project, @callback
|
||||
@ProjectGetter.populateProjectWithUsers @project, (err, project)=>
|
||||
@callback err, project
|
||||
|
||||
it "should look up each user", ->
|
||||
for user in @users
|
||||
@@ -73,5 +75,5 @@ describe "ProjectGetter", ->
|
||||
expect(@project.collaberator_refs).to.deep.equal [@users[3], @users[4]]
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.calledWith(null, @project).should.equal true
|
||||
assert.deepEqual @callback.args[0][1], @project
|
||||
|
||||
|
||||
@@ -76,11 +76,13 @@ describe "Subscription Group Handler", ->
|
||||
@UserLocator.findById.callsArgWith(1, null, {_id:"31232"})
|
||||
|
||||
it "should locate the subscription", (done)->
|
||||
@UserLocator.findById.callsArgWith(1, null, {_id:"31232"})
|
||||
@Handler.getPopulatedListOfMembers @adminUser_id, (err, users)=>
|
||||
@SubscriptionLocator.getUsersSubscription.calledWith(@adminUser_id).should.equal true
|
||||
done()
|
||||
|
||||
it "should get the users by id", (done)->
|
||||
@UserLocator.findById.callsArgWith(1, null, {_id:"31232"})
|
||||
@subscription.member_ids = ["1234", "342432", "312312"]
|
||||
@Handler.getPopulatedListOfMembers @adminUser_id, (err, users)=>
|
||||
@UserLocator.findById.calledWith(@subscription.member_ids[0]).should.equal true
|
||||
@@ -88,3 +90,13 @@ describe "Subscription Group Handler", ->
|
||||
@UserLocator.findById.calledWith(@subscription.member_ids[2]).should.equal true
|
||||
users.length.should.equal @subscription.member_ids.length
|
||||
done()
|
||||
|
||||
it "should just return the id if the user can not be found as they may have deleted their account", (done)->
|
||||
@UserLocator.findById.callsArgWith(1)
|
||||
@subscription.member_ids = ["1234", "342432", "312312"]
|
||||
@Handler.getPopulatedListOfMembers @adminUser_id, (err, users)=>
|
||||
assert.deepEqual users[0], {_id:@subscription.member_ids[0]}
|
||||
assert.deepEqual users[1], {_id:@subscription.member_ids[1]}
|
||||
assert.deepEqual users[2], {_id:@subscription.member_ids[2]}
|
||||
done()
|
||||
|
||||
|
||||
+1
-1
@@ -32,7 +32,7 @@ describe 'third party data store', ->
|
||||
@poller._sendToTpds = sinon.stub().callsArgWith(1, null)
|
||||
@poller._markPollHappened = sinon.stub()
|
||||
@poller.pollUsersWithDropbox (err)=>
|
||||
@userModel.find.calledWith({"dropbox.access_token.oauth_token_secret":{"$exists":true}}, "_id").should.equal true
|
||||
@userModel.find.calledWith({"dropbox.access_token.oauth_token_secret":{"$exists":true}, "features.dropbox":true}, "_id").should.equal true
|
||||
@poller._sendToTpds.calledWith([users[0]._id, users[1]._id, users[2]._id,]).should.equal true
|
||||
@poller._markPollHappened.called.should.equal true
|
||||
done()
|
||||
|
||||
@@ -25,7 +25,7 @@ describe "UserDeleter", ->
|
||||
cancelSubscription: sinon.stub().callsArgWith(1)
|
||||
@UserDeleter = SandboxedModule.require modulePath, requires:
|
||||
"../../models/User": User: @User
|
||||
"../../managers/NewsletterManager": @NewsletterManager
|
||||
"../Newsletter/NewsletterManager": @NewsletterManager
|
||||
"../Subscription/SubscriptionHandler": @SubscriptionHandler
|
||||
"../Project/ProjectDeleter": @ProjectDeleter
|
||||
|
||||
|
||||
@@ -108,20 +108,15 @@ describe 'AutomaticSnapshotManager', ->
|
||||
callback(null, @project_ids)
|
||||
else
|
||||
throw new Error("unexpected key: #{key}")
|
||||
sinon.stub(@AutomaticSnapshotManager, "takeSnapshotIfRequired")
|
||||
.callsArgWith(1)
|
||||
@AutomaticSnapshotManager.takeAutomaticSnapshots(@callback)
|
||||
sinon.stub(@AutomaticSnapshotManager, "takeSnapshotIfRequired").callsArgWith(1, null)
|
||||
@AutomaticSnapshotManager.takeAutomaticSnapshots @callback
|
||||
|
||||
afterEach ->
|
||||
@AutomaticSnapshotManager.takeSnapshotIfRequired.restore()
|
||||
|
||||
it "should call takeSnapshotIfRequired for each project id", ->
|
||||
for project_id in @project_ids
|
||||
@AutomaticSnapshotManager.takeSnapshotIfRequired.calledWith(project_id)
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.calledWith(null).should.equal true
|
||||
@callback.calledWith(undefined).should.equal true
|
||||
|
||||
|
||||
describe "removing project from marked set", ->
|
||||
|
||||
@@ -1,18 +1,23 @@
|
||||
chai = require("chai")
|
||||
chai.should()
|
||||
expect = chai.expect
|
||||
request = require "request"
|
||||
Settings = require "settings-sharelatex"
|
||||
|
||||
# Monkey patch request cookies, because the new tough-cookie module
|
||||
# assumes it's not a secure cookie if the url is not HTTPS
|
||||
request = require "request"
|
||||
jar = request.jar()
|
||||
jar.getCookieString = (uri) ->
|
||||
return @_jar.getCookieStringSync uri, secure: true
|
||||
request = request.defaults jar: jar
|
||||
|
||||
port = Settings.internal?.web?.port or Settings.port or 3000
|
||||
buildUrl = (path) -> "http://localhost:#{port}/#{path}"
|
||||
|
||||
describe "Opening", ->
|
||||
before (done) ->
|
||||
@jar = request.jar()
|
||||
request.get {
|
||||
url: buildUrl("register")
|
||||
jar: @jar
|
||||
headers:
|
||||
"X-Forwarded-Proto": "https"
|
||||
}, (error, response, body) =>
|
||||
@@ -23,7 +28,6 @@ describe "Opening", ->
|
||||
email: Settings.smokeTest.user
|
||||
password: Settings.smokeTest.password
|
||||
_csrf: csrf
|
||||
jar: @jar
|
||||
headers:
|
||||
"X-Forwarded-Proto": "https"
|
||||
}, (error, response, body) ->
|
||||
@@ -33,7 +37,6 @@ describe "Opening", ->
|
||||
it "a project", (done) ->
|
||||
request {
|
||||
url: buildUrl("project/#{Settings.smokeTest.projectId}")
|
||||
jar: @jar
|
||||
headers:
|
||||
"X-Forwarded-Proto": "https"
|
||||
}, (error, response, body) ->
|
||||
@@ -47,12 +50,11 @@ describe "Opening", ->
|
||||
it "the project list", (done) ->
|
||||
request {
|
||||
url: buildUrl("project")
|
||||
jar: @jar
|
||||
headers:
|
||||
"X-Forwarded-Proto": "https"
|
||||
}, (error, response, body) ->
|
||||
expect(error, "smoke test: error returned in getting project list").to.not.exist
|
||||
expect(response.statusCode, "smoke test: response code is not 200 getting project life").to.equal(200)
|
||||
expect(response.statusCode, "smoke test: response code is not 200 getting project list").to.equal(200)
|
||||
expect(!!body.match("<title>Your Projects - Online LaTeX Editor ShareLaTeX</title>"), "smoke test: body does not have correct title").to.equal true
|
||||
expect(!!body.match("<h1>Projects</h1>"), "smoke test: body does not have correct h1").to.equal true
|
||||
done()
|
||||
|
||||
Reference in New Issue
Block a user