Merge branch 'master' into dcl-i850

Conflicts repaired:
app/views/user/settings.pug

modified:   app/coffee/Features/Authentication/AuthenticationController.coffee
modified:   app/coffee/Features/Editor/EditorHttpController.coffee
modified:   app/coffee/Features/Email/EmailBuilder.coffee
modified:   app/coffee/Features/Errors/Errors.coffee
modified:   app/coffee/Features/Exports/ExportsController.coffee
modified:   app/coffee/Features/Exports/ExportsHandler.coffee
modified:   app/coffee/Features/Notifications/NotificationsBuilder.coffee
modified:   app/coffee/Features/Notifications/NotificationsHandler.coffee
modified:   app/coffee/Features/Project/ProjectController.coffee
modified:   app/coffee/Features/StaticPages/HomeController.coffee
modified:   app/coffee/Features/Subscription/planFeatures.coffee
modified:   app/coffee/Features/TokenAccess/TokenAccessController.coffee
modified:   app/coffee/Features/TokenAccess/TokenAccessHandler.coffee
modified:   app/coffee/Features/User/UserGetter.coffee
modified:   app/coffee/infrastructure/RedirectManager.coffee
modified:   app/coffee/infrastructure/Server.coffee
modified:   app/coffee/models/User.coffee
modified:   app/coffee/router.coffee
modified:   app/views/_mixins/links.pug
modified:   app/views/general/500.pug
modified:   app/views/layout/navbar.pug
modified:   app/views/project/editor/header.pug
modified:   app/views/project/editor/share.pug
modified:   app/views/project/list.pug
modified:   app/views/project/list/modals.pug
modified:   app/views/project/list/notifications.pug
modified:   app/views/project/list/side-bar.pug
modified:   app/views/project/list/v1-item.pug
modified:   app/views/subscriptions/_modal_group_inquiry.pug
modified:   app/views/subscriptions/dashboard.pug
modified:   app/views/user/settings.pug
modified:   config/settings.defaults.coffee
modified:   npm-shrinkwrap.json
modified:   package.json
new file:   public/apple-touch-icon.png
new file:   public/atlassian-domain-verification.html
modified:   public/coffee/ide/editor/directives/aceEditor.coffee
modified:   public/coffee/ide/editor/directives/aceEditor/cursor-position/CursorPositionManager.coffee
modified:   public/coffee/ide/share/controllers/ShareController.coffee
modified:   public/coffee/main.coffee
deleted:    public/coffee/main/account-merge-checker.coffee
modified:   public/coffee/main/contact-us.coffee
modified:   public/coffee/main/project-list/left-hand-menu-promo-controller.coffee
new file:   public/google4f15e48c48709a75.html
new file:   public/googleef256f97939bd9b7.html
new file:   public/img/advocates/friend.jpeg
new file:   public/img/grid.png
new file:   public/img/homepage.png
new file:   public/img/homepage@2x.png
new file:   public/img/other-brands/logo_google.svg
new file:   public/img/other-brands/logo_google_alt.svg
new file:   public/img/other-brands/logo_ieee.svg
new file:   public/img/other-brands/logo_orcid.svg
new file:   public/img/other-brands/logo_orcid_alt.svg
new file:   public/img/other-brands/logo_sharelatex.svg
new file:   public/img/other-brands/logo_twitter.svg
new file:   public/img/overleaf-partner/overleaf-greygreen-410.png
new file:   public/img/overleaf-partner/overleaf-white-410.png
new file:   public/static/brochures/Overleaf-Information-v8.pdf
new file:   public/static/brochures/Overleaf-Institutional-Solutions-v1.pdf
new file:   public/static/brochures/Overleaf-Tri-Fold-leaflet.pdf
new file:   public/static/brochures/Overleaf-request-for-service.pdf
new file:   public/static/brochures/Overleaf_Institutional.pdf
new file:   public/static/campus-challenge/campus-challenge-2016-flyer.pdf
new file:   public/static/campus-challenge/campus-challenge-2016-poster.pdf
new file:   public/static/campus-challenge/campus-challenge-2017-flyer.pdf
new file:   public/static/campus-challenge/campus-challenge-2017-poster.pdf
new file:   public/static/latex/learn/free-online-introduction-to-latex-part-1.pdf
new file:   public/static/latex/learn/free-online-introduction-to-latex-part-2.pdf
new file:   public/static/latex/learn/free-online-introduction-to-latex-part-3.pdf
new file:   public/static/partners/caltech-library.gif
new file:   public/static/partners/stanford-building.jpg
modified:   public/stylesheets/_ol_style_includes.less
modified:   public/stylesheets/app/blog-posts.less
modified:   public/stylesheets/app/cms-page.less
new file:   public/stylesheets/app/content_page.less
modified:   public/stylesheets/app/editor/share.less
modified:   public/stylesheets/app/editor/toolbar.less
modified:   public/stylesheets/app/features.less
modified:   public/stylesheets/app/homepage.less
new file:   public/stylesheets/app/login-register.less
modified:   public/stylesheets/app/plans.less
modified:   public/stylesheets/app/portals.less
modified:   public/stylesheets/app/templates-v2.less
modified:   public/stylesheets/app/wiki.less
modified:   public/stylesheets/components/alerts.less
modified:   public/stylesheets/components/buttons.less
modified:   public/stylesheets/components/icons.less
new file:   public/stylesheets/components/images.less
new file:   public/stylesheets/components/tabs.less
modified:   public/stylesheets/core/_common-variables.less
modified:   public/stylesheets/core/ol-variables.less
modified:   public/stylesheets/ol-style.less
modified:   test/acceptance/coffee/ExportsTests.coffee
new file:   test/acceptance/coffee/RedirectUrlsTests.coffee
modified:   test/acceptance/coffee/TokenAccessTests.coffee
modified:   test/acceptance/coffee/helpers/User.coffee
modified:   test/acceptance/config/settings.test.coffee
modified:   test/unit/coffee/Authentication/AuthenticationControllerTests.coffee
modified:   test/unit/coffee/Editor/EditorHttpControllerTests.coffee
modified:   test/unit/coffee/Exports/ExportsControllerTests.coffee
modified:   test/unit/coffee/Exports/ExportsHandlerTests.coffee
new file:   test/unit/coffee/Notifications/NotificationsBuilderTests.coffee
modified:   test/unit/coffee/Project/ProjectControllerTests.coffee
modified:   test/unit/coffee/TokenAccess/TokenAccessControllerTests.coffee
modified:   test/unit/coffee/User/UserGetterTests.coffee
deleted:    test/unit/coffee/infrastructure/RedirectManagerTests.coffee
This commit is contained in:
Douglas Lovell
2018-09-24 09:11:48 -03:00
107 changed files with 4644 additions and 354 deletions
@@ -11,6 +11,7 @@ UserHandler = require("../User/UserHandler")
UserSessionsManager = require("../User/UserSessionsManager")
Analytics = require "../Analytics/AnalyticsManager"
passport = require 'passport'
NotificationsBuilder = require("../Notifications/NotificationsBuilder")
module.exports = AuthenticationController =
@@ -43,7 +44,7 @@ module.exports = AuthenticationController =
return callback(err)
req.sessionStore.generate(req)
for key, value of oldSession
req.session[key] = value
req.session[key] = value unless key == '__tmp'
# copy to the old `session.user` location, for backward-comptability
req.session.user = req.session.passport.user
req.session.save (err) ->
@@ -112,6 +113,7 @@ module.exports = AuthenticationController =
UserHandler.setupLoginData(user, ()->)
LoginRateLimiter.recordSuccessfulLogin(user.email)
AuthenticationController._recordSuccessfulLogin(user._id)
AuthenticationController.ipMatchCheck(req, user)
Analytics.recordEvent(user._id, "user-logged-in", {ip:req.ip})
Analytics.identifyUser(user._id, req.sessionID)
logger.log email: user.email, user_id: user._id.toString(), "successful log in"
@@ -119,6 +121,13 @@ module.exports = AuthenticationController =
# capture the request ip for use when creating the session
user._login_req_ip = req.ip
ipMatchCheck: (req, user) ->
if req.ip != user.lastLoginIp
NotificationsBuilder.ipMatcherAffiliation(user._id, req.ip).create()
UserUpdater.updateUser user._id.toString(), {
$set: { "lastLoginIp": req.ip }
}
setInSessionUser: (req, props) ->
for key, value of props
if req?.session?.passport?.user?
@@ -41,21 +41,19 @@ module.exports = EditorHttpController =
return callback(new Error("not found")) if !project?
CollaboratorsHandler.getInvitedMembersWithPrivilegeLevels project_id, (error, members) ->
return callback(error) if error?
UserGetter.getUser user_id, { isAdmin: true }, (error, user) ->
token = TokenAccessHandler.getRequestToken(req, project_id)
AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, token, (error, privilegeLevel) ->
return callback(error) if error?
token = TokenAccessHandler.getRequestToken(req, project_id)
AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, token, (error, privilegeLevel) ->
if !privilegeLevel? or privilegeLevel == PrivilegeLevels.NONE
logger.log {project_id, user_id, privilegeLevel}, "not an acceptable privilege level, returning null"
return callback null, null, false
CollaboratorsInviteHandler.getAllInvites project_id, (error, invites) ->
return callback(error) if error?
if !privilegeLevel? or privilegeLevel == PrivilegeLevels.NONE
logger.log {project_id, user_id, privilegeLevel}, "not an acceptable privilege level, returning null"
return callback null, null, false
CollaboratorsInviteHandler.getAllInvites project_id, (error, invites) ->
return callback(error) if error?
logger.log {project_id, user_id, memberCount: members.length, inviteCount: invites.length, privilegeLevel}, "returning project model view"
callback(null,
ProjectEditorHandler.buildProjectModelView(project, members, invites),
privilegeLevel
)
logger.log {project_id, user_id, memberCount: members.length, inviteCount: invites.length, privilegeLevel}, "returning project model view"
callback(null,
ProjectEditorHandler.buildProjectModelView(project, members, invites),
privilegeLevel
)
_nameIsAcceptableLength: (name)->
return name? and name.length < 150 and name.length != 0
@@ -40,6 +40,23 @@ The #{settings.appName} Team - #{settings.siteUrl}
templates = {}
templates.accountMergeToOverleafAddress = CTAEmailTemplate({
subject: () -> "Confirm Account Merge - #{settings.appName}"
title: () -> "Confirm Account Merge"
message: () ->
"""
To merge your ShareLaTeX and Overleaf accounts, click the button below.
If you think you have received this message in error,
please contact us at https://www.overleaf.com/contact
"""
ctaText: () -> "Confirm Account Merge"
ctaURL: (opts) -> opts.tokenLinkUrl
secondaryMessage: (opts) ->
"If the button does not appear, open this link in your browser: #{opts.tokenLinkUrl}"
})
templates.accountMergeToSharelatexAddress = templates.accountMergeToOverleafAddress
templates.registered = CTAEmailTemplate({
subject: () -> "Activate your #{settings.appName} Account"
message: (opts) -> """
@@ -31,56 +31,56 @@ UnsupportedFileTypeError = (message) ->
error.name = "UnsupportedFileTypeError"
error.__proto__ = UnsupportedFileTypeError.prototype
return error
UnsupportedFileTypeError.prototype.__proto___ = Error.prototype
UnsupportedFileTypeError.prototype.__proto__ = Error.prototype
UnsupportedBrandError = (message) ->
error = new Error(message)
error.name = "UnsupportedBrandError"
error.__proto__ = UnsupportedBrandError.prototype
return error
UnsupportedBrandError.prototype.__proto___ = Error.prototype
UnsupportedBrandError.prototype.__proto__ = Error.prototype
UnsupportedExportRecordsError = (message) ->
error = new Error(message)
error.name = "UnsupportedExportRecordsError"
error.__proto__ = UnsupportedExportRecordsError.prototype
return error
UnsupportedExportRecordsError.prototype.__proto___ = Error.prototype
UnsupportedExportRecordsError.prototype.__proto__ = Error.prototype
V1HistoryNotSyncedError = (message) ->
error = new Error(message)
error.name = "V1HistoryNotSyncedError"
error.__proto__ = V1HistoryNotSyncedError.prototype
return error
V1HistoryNotSyncedError.prototype.__proto___ = Error.prototype
V1HistoryNotSyncedError.prototype.__proto__ = Error.prototype
ProjectHistoryDisabledError = (message) ->
error = new Error(message)
error.name = "ProjectHistoryDisabledError"
error.__proto__ = ProjectHistoryDisabledError.prototype
return error
ProjectHistoryDisabledError.prototype.__proto___ = Error.prototype
ProjectHistoryDisabledError.prototype.__proto__ = Error.prototype
V1ConnectionError = (message) ->
error = new Error(message)
error.name = "V1ConnectionError"
error.__proto__ = V1ConnectionError.prototype
return error
V1ConnectionError.prototype.__proto___ = Error.prototype
V1ConnectionError.prototype.__proto__ = Error.prototype
UnconfirmedEmailError = (message) ->
error = new Error(message)
error.name = "UnconfirmedEmailError"
error.__proto__ = UnconfirmedEmailError.prototype
return error
UnconfirmedEmailError.prototype.__proto___ = Error.prototype
UnconfirmedEmailError.prototype.__proto__ = Error.prototype
EmailExistsError = (message) ->
error = new Error(message)
error.name = "EmailExistsError"
error.__proto__ = EmailExistsError.prototype
return error
EmailExistsError.prototype.__proto___ = Error.prototype
EmailExistsError.prototype.__proto__ = Error.prototype
module.exports = Errors =
NotFoundError: NotFoundError
@@ -13,13 +13,19 @@ module.exports =
user_id: user_id
}
if req.body && req.body.firstName && req.body.lastName
export_params.first_name = req.body.firstName.trim()
export_params.last_name = req.body.lastName.trim()
if req.body
export_params.first_name = req.body.firstName.trim() if req.body.firstName
export_params.last_name = req.body.lastName.trim() if req.body.lastName
# additional parameters for gallery exports
export_params.title = req.body.title.trim() if req.body.title
export_params.description = req.body.description.trim() if req.body.description
export_params.author = req.body.author.trim() if req.body.author
export_params.license = req.body.license.trim() if req.body.license
export_params.show_source = req.body.show_source if req.body.show_source
ExportsHandler.exportProject export_params, (err, export_data) ->
return next(err) if err?
logger.log
return err if err?
logger.log
user_id:user_id
project_id: project_id
brand_variation_id:brand_variation_id
@@ -30,11 +36,13 @@ module.exports =
exportStatus: (req, res) ->
{export_id} = req.params
ExportsHandler.fetchExport export_id, (err, export_json) ->
return next(err) if err?
return err if err?
parsed_export = JSON.parse(export_json)
json = {
status_summary: parsed_export.status_summary,
status_detail: parsed_export.status_detail
status_detail: parsed_export.status_detail,
partner_submission_id: parsed_export.partner_submission_id,
token: parsed_export.token
}
res.send export_json: json
@@ -20,9 +20,7 @@ module.exports = ExportsHandler = self =
callback null, export_data
_buildExport: (export_params, callback=(err, export_data) ->) ->
project_id = export_params.project_id
user_id = export_params.user_id
brand_variation_id = export_params.brand_variation_id
{project_id, user_id, brand_variation_id, title, description, author, license, show_source} = export_params
jobs =
project: (cb) ->
ProjectGetter.getProject project_id, cb
@@ -60,6 +58,11 @@ module.exports = ExportsHandler = self =
metadata:
compiler: project.compiler
imageName: project.imageName
title: title
description: description
author: author
license: license
show_source: show_source
user:
id: user_id
firstName: user.first_name
@@ -1,5 +1,7 @@
logger = require("logger-sharelatex")
NotificationsHandler = require("./NotificationsHandler")
request = require "request"
settings = require "settings-sharelatex"
module.exports =
@@ -29,3 +31,29 @@ module.exports =
NotificationsHandler.createNotification user._id, @key, "notification_project_invite", messageOpts, invite.expires, callback
read: (callback=()->) ->
NotificationsHandler.markAsReadByKeyOnly @key, callback
ipMatcherAffiliation: (userId, ip) ->
key: "ip-matched-affiliation-#{ip}"
create: (callback=()->) ->
return null unless settings?.apis?.v1?.url # service is not configured
_key = @key
request {
method: 'GET'
url: "#{settings.apis.v1.url}/api/v2/users/#{userId}/ip_matcher"
auth: { user: settings.apis.v1.user, pass: settings.apis.v1.pass }
body: { ip: ip }
json: true
timeout: 20 * 1000
}, (error, response, body) ->
return error if error?
return null unless response.statusCode == 200
messageOpts =
university_id: body.id
university_name: body.name
content: body.enrolment_ad_html
logger.log user_id:userId, key:_key, "creating notification key for user"
NotificationsHandler.createNotification userId, _key, "notification_ip_matched_affiliation", messageOpts, null, false, callback
read: (callback = ->)->
NotificationsHandler.markAsReadWithKey userId, @key, callback
@@ -29,12 +29,15 @@ module.exports =
unreadNotifications = []
callback(null, unreadNotifications)
createNotification: (user_id, key, templateKey, messageOpts, expiryDateTime, callback)->
createNotification: (user_id, key, templateKey, messageOpts, expiryDateTime, forceCreate, callback)->
if !callback
callback = forceCreate
forceCreate = true
payload = {
key:key
messageOpts:messageOpts
templateKey:templateKey
forceCreate: true
forceCreate:forceCreate
}
if expiryDateTime?
payload.expires = expiryDateTime
@@ -26,6 +26,8 @@ TokenAccessHandler = require '../TokenAccess/TokenAccessHandler'
CollaboratorsHandler = require '../Collaborators/CollaboratorsHandler'
Modules = require '../../infrastructure/Modules'
ProjectEntityHandler = require './ProjectEntityHandler'
UserGetter = require("../User/UserGetter")
NotificationsBuilder = require("../Notifications/NotificationsBuilder")
crypto = require 'crypto'
{ V1ConnectionError } = require '../Errors/Errors'
Features = require('../../infrastructure/Features')
@@ -209,6 +211,11 @@ module.exports = ProjectController =
user = results.user
warnings = ProjectController._buildWarningsList results.v1Projects
# in v2 add notifications for matching university IPs
if Settings.overleaf?
UserGetter.getUser user_id, { 'lastLoginIp': 1 }, (error, user) ->
if req.ip != user.lastLoginIp
NotificationsBuilder.ipMatcherAffiliation(user._id, req.ip).create()
ProjectController._injectProjectOwners projects, (error, projects) ->
return next(error) if error?
@@ -9,7 +9,8 @@ fs = require "fs"
ErrorController = require "../Errors/ErrorController"
AuthenticationController = require('../Authentication/AuthenticationController')
homepageExists = fs.existsSync Path.resolve(__dirname + "/../../../views/external/home.pug")
slHomepageExists = fs.existsSync Path.resolve(__dirname + "/../../../views/external/home/sl.pug")
v2HomepageExists = fs.existsSync Path.resolve(__dirname + "/../../../views/external/home/v2.pug")
module.exports = HomeController =
index : (req,res)->
@@ -21,11 +22,13 @@ module.exports = HomeController =
else
HomeController.home(req, res)
home: (req, res)->
if Features.hasFeature('homepage') and homepageExists
res.render 'external/home'
home: (req, res, next)->
if Features.hasFeature('homepage') and !Settings.overleaf and slHomepageExists
res.render 'external/home/sl'
else if Features.hasFeature('homepage') and Settings.overleaf and v2HomepageExists
res.render 'external/home/v2'
else
res.redirect "/login"
res.redirect '/login'
externalPage: (page, title) ->
return (req, res, next = (error) ->) ->
@@ -33,7 +33,7 @@ module.exports =
student: true
}
{
feature: 'hundreds_templates'
feature: 'thousands_templates'
value: 'bool'
info: 'hundreds_templates_info'
plans: {
@@ -3,6 +3,7 @@ AuthenticationController = require '../Authentication/AuthenticationController'
TokenAccessHandler = require './TokenAccessHandler'
Errors = require '../Errors/Errors'
logger = require 'logger-sharelatex'
settings = require 'settings-sharelatex'
module.exports = TokenAccessController =
@@ -11,11 +12,16 @@ module.exports = TokenAccessController =
return ProjectController.loadEditor(req, res, next)
_tryHigherAccess: (token, userId, req, res, next) ->
TokenAccessHandler.findProjectWithHigherAccess token, userId, (err, project) ->
TokenAccessHandler.findProjectWithHigherAccess token, userId, (err, project, projectExists) ->
if err?
logger.err {err, token, userId},
"[TokenAccess] error finding project with higher access"
return next(err)
if !projectExists and settings.overleaf
logger.log {token, userId},
"[TokenAccess] no project found for this token"
# Project does not exist, but may be unimported - try it on v1
return res.redirect(settings.overleaf.host + req.url)
if !project?
logger.log {token, userId},
"[TokenAccess] no project with higher access found for this user and token"
@@ -22,7 +22,7 @@ module.exports = TokenAccessHandler =
'publicAccesLevel': PublicAccessLevels.TOKEN_BASED
}, {_id: 1, publicAccesLevel: 1, owner_ref: 1}, callback
findProjectWithHigherAccess: (token, userId, callback=(err, project)->) ->
findProjectWithHigherAccess: (token, userId, callback=(err, project, projectExists)->) ->
Project.findOne {
$or: [
{'tokens.readAndWrite': token},
@@ -32,12 +32,16 @@ module.exports = TokenAccessHandler =
if err?
return callback(err)
if !project?
return callback(null, null)
return callback(null, null, false) # Project doesn't exist, so we handle differently
projectId = project._id
CollaboratorsHandler.isUserInvitedMemberOfProject userId, projectId, (err, isMember) ->
if err?
return callback(err)
callback(null, if isMember == true then project else null)
callback(
null,
if isMember == true then project else null,
true # Project does exist, but user doesn't have access
)
addReadOnlyUserToProject: (userId, projectId, callback=(err)->) ->
userId = ObjectId(userId.toString())
@@ -8,6 +8,7 @@ Errors = require("../Errors/Errors")
module.exports = UserGetter =
getUser: (query, projection, callback = (error, user) ->) ->
return callback(new Error("no query provided")) unless query?
if query?.email?
return callback(new Error("Don't use getUser to find user by email"), null)
if arguments.length == 2
@@ -34,7 +35,7 @@ module.exports = UserGetter =
getUserAffiliations userId, (error, affiliationsData) ->
return callback error if error?
callback null, decorateFullEmails(user.email, user.emails, affiliationsData)
callback null, decorateFullEmails(user.email, user.emails or [], affiliationsData)
getUserByMainEmail: (email, projection, callback = (error, user) ->) ->
email = email.trim()
@@ -1,20 +1,33 @@
settings = require("settings-sharelatex")
logger = require("logger-sharelatex")
module.exports = (req, res, next)->
requestedUrl = req.url
module.exports = RedirectManager =
apply: (webRouter) ->
for redirectUrl, target of settings.redirects
for method in (target.methods or ['get'])
webRouter[method] redirectUrl, RedirectManager.createRedirect(target)
redirectUrl = settings.redirects[requestedUrl]
#remove starting slash
if !redirectUrl? and requestedUrl[requestedUrl.length-1] == "/"
requestedUrl = requestedUrl.substring(0, requestedUrl.length - 1)
redirectUrl = settings.redirects[requestedUrl]
if redirectUrl?
logger.log redirectUrl:redirectUrl, reqUrl:req.url, "redirecting to new path"
res.redirect 301, "#{redirectUrl}"
else
next()
createRedirect: (target) ->
(req, res, next) ->
code = 302
if typeof target is 'string'
url = target
else
if req.method == "POST"
code = 307
if typeof target.url == "function"
url = target.url(req.params)
if !url
return next()
else
url = target.url
if target.baseUrl?
url = "#{target.baseUrl}#{url}"
res.redirect code, url + getQueryString(req)
# Naively get the query params string. Stringifying the req.query object may
# have differences between Express and Rails, so safer to just pass the raw
# string
getQueryString = (req) ->
qs = req.url.match(/\?.*$/)
if qs? then qs[0] else ""
@@ -73,7 +73,7 @@ app.use multer(dest: Settings.path.uploadFolder)
app.use methodOverride()
app.use metrics.http.monitor(logger)
app.use RedirectManager
RedirectManager.apply(webRouter)
ProxyManager.apply(publicApiRouter)
@@ -22,6 +22,7 @@ UserSchema = new Schema
confirmed : {type : Boolean, default : false}
signUpDate : {type : Date, default: () -> new Date() }
lastLoggedIn : {type : Date}
lastLoginIp : {type : String, default : ''}
loginCount : {type : Number, default: 0}
holdingAccount : {type : Boolean, default: false}
ace : {
+16
View File
@@ -68,6 +68,7 @@ module.exports = class Router
webRouter.get '/logout', UserController.logout
webRouter.get '/restricted', AuthorizationMiddlewear.restricted
if Features.hasFeature('registration')
webRouter.get '/register', UserPagesController.registerPage
AuthenticationController.addEndpointToLoginWhitelist '/register'
@@ -329,6 +330,21 @@ module.exports = class Router
AuthenticationController.httpAuth,
CompileController.getFileFromClsiWithoutUser
webRouter.get '/teams', (req, res, next) ->
# Match v1 behaviour - if the user is signed in, show their teams list
# Otherwise show some information about teams
if AuthenticationController.isUserLoggedIn(req)
res.redirect('/user/subscription')
else
res.redirect("#{settings.v1Api.host}/teams")
webRouter.get '/chrome', (req, res, next) ->
# Match v1 behaviour - this is used for a Chrome web app
if AuthenticationController.isUserLoggedIn(req)
res.redirect('/project')
else
res.redirect('/register')
#Admin Stuff
webRouter.get '/admin', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.index
webRouter.get '/admin/user', AuthorizationMiddlewear.ensureUserIsSiteAdmin, (req, res)-> res.redirect("/admin/register") #this gets removed by admin-panel addon
+4 -8
View File
@@ -18,8 +18,7 @@ mixin linkAdvisors(linkText, linkClass, track)
| #{linkText ? linkText : 'advisor programme'}
mixin linkBenefits(linkText, linkClass)
//- To Do: verify path
a(href="/benefits" class=linkClass ? linkClass : '')
a(href="/for/authors" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'benefits'}
mixin linkBlog(linkText, linkClass, slug)
@@ -36,8 +35,7 @@ mixin linkDash(linkText, linkClass)
| #{linkText ? linkText : 'project dashboard'}
mixin linkEducation(linkText, linkClass)
//- To Do: verify path
a(href="/plans" class=linkClass ? linkClass : '')
a(href="/for/edu" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'teaching toolkit'}
mixin linkEmail(linkText, linkClass, email)
@@ -66,8 +64,7 @@ mixin linkInvite(linkText, linkClass, track)
| #{linkText ? linkText : 'invite your friends'}
mixin linkPlansAndPricing(linkText, linkClass)
//- To Do: verify path
a(href="/plans" class=linkClass ? linkClass : '')
a(href="/user/subscription/plans" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'plans and pricing'}
mixin linkPrintNewTab(linkText, linkClass, icon, track)
@@ -121,6 +118,5 @@ mixin linkTweet(linkText, linkClass, tweetText, track)
) #{linkText ? linkText : 'tweet'}
mixin linkUniversities(linkText, linkClass)
//- To Do: verify path
a(href="/universities" class=linkClass ? linkClass : '')
a(href="/for/universities" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'universities'}
+1 -1
View File
@@ -2,7 +2,7 @@ doctype html
html.full-height(itemscope, itemtype='http://schema.org/Product')
head
title Something went wrong
link(rel="icon", href="/favicon.ico")
link(rel="icon", href="/" + settings.brandPrefix + "favicon.ico")
if buildCssPath
link(rel="stylesheet", href=buildCssPath("/" + settings.brandPrefix + "style.css"))
link(href="//netdna.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css",rel="stylesheet")
+11 -1
View File
@@ -27,8 +27,18 @@ nav.navbar.navbar-default.navbar-main
// loop over header_extras
each item in nav.header_extras
-
if ((item.only_when_logged_in && getSessionUser())
|| (item.only_when_logged_out && (!getSessionUser()))
|| (!item.only_when_logged_out && !item.only_when_logged_in && !item.only_content_pages)
|| (item.only_content_pages && (typeof(suppressNavContentLinks) == "undefined" || !suppressNavContentLinks))
){
var showNavItem = true
} else {
var showNavItem = false
}
if ((item.only_when_logged_in && getSessionUser()) || (item.only_when_logged_out && (!getSessionUser())) || (!item.only_when_logged_out && !item.only_when_logged_in))
if showNavItem
if item.dropdown
li.dropdown(class=item.class, dropdown)
a.dropdown-toggle(href, dropdown-toggle)
@@ -103,12 +103,8 @@ header.toolbar.toolbar-header.toolbar-with-labels(
a.btn.btn-full-height(
href
ng-class="{ 'btn-full-height-disabled' : !permissions.admin }"
ng-click="openShareProjectModal(permissions.admin);"
ng-controller="ShareController"
tooltip-enable="!permissions.admin"
tooltip="Only the project owner can use the Share menu at the moment, but we're working on making it accessible to collaborators, too."
tooltip-placement="bottom"
)
i.fa.fa-fw.fa-group
p.toolbar-label #{translate("share")}
@@ -8,9 +8,8 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
h3 #{translate("share_project")}
.modal-body.modal-body-share
.container-fluid
//- Private (with token-access available)
.row.public-access-level(ng-show="project.publicAccesLevel == 'private'")
.row.public-access-level(ng-show="isAdmin && project.publicAccesLevel == 'private'")
.col-xs-12.text-center
| #{translate('link_sharing_is_off')}
| &nbsp;&nbsp;
@@ -28,7 +27,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
)
//- Token-based access
.row.public-access-level(ng-show="project.publicAccesLevel == 'tokenBased'")
.row.public-access-level(ng-show="isAdmin && project.publicAccesLevel == 'tokenBased'")
.col-xs-12.text-center
strong
| #{translate('link_sharing_is_on')}.
@@ -57,7 +56,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
pre.access-token(ng-hide="readOnlyTokenLink") #{translate('loading')}...
//- legacy public-access
.row.public-access-level(ng-show="project.publicAccesLevel == 'readAndWrite' || project.publicAccesLevel == 'readOnly'")
.row.public-access-level(ng-show="isAdmin && (project.publicAccesLevel == 'readAndWrite' || project.publicAccesLevel == 'readOnly')")
.col-xs-12.text-center
strong(ng-if="project.publicAccesLevel == 'readAndWrite'") #{translate("this_project_is_public")}
strong(ng-if="project.publicAccesLevel == 'readOnly'") #{translate("this_project_is_public_read_only")}
@@ -77,7 +76,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
.col-xs-3.text-left
span(ng-show="member.privileges == 'readAndWrite'") #{translate("can_edit")}
span(ng-show="member.privileges == 'readOnly'") #{translate("read_only")}
.col-xs-1
.col-xs-1(ng-show="isAdmin")
a(
href
tooltip=translate('remove_collaborator')
@@ -102,7 +101,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
ng-click="revokeInvite(invite)"
)
i.fa.fa-times
.row.invite-controls
.row.invite-controls(ng-show="isAdmin")
form(ng-show="canAddCollaborators")
.small #{translate("share_with_your_collabs")}
.form-group
@@ -177,8 +176,9 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
) #{translate("start_free_trial")}
p.small(ng-show="startedFreeTrial")
| #{translate("refresh_page_after_starting_free_trial")}.
| #{translate("refresh_page_after_starting_free_trial")}
.row.public-access-level.public-access-level--notice(ng-show="!isAdmin")
.col-xs-12.text-center #{translate("to_add_more_collaborators")}
.modal-footer.modal-footer-share
.modal-footer-left
i.fa.fa-refresh.fa-spin(ng-show="state.inflight")
+4 -1
View File
@@ -1,5 +1,8 @@
extends ../layout
block vars
- var suppressNavContentLinks = true
block content
//- We need to do .replace(/\//g, '\\/') do that '</script>' -> '<\/script>'
//- and doesn't prematurely end the script tag.
@@ -98,7 +101,7 @@ block content
| To tag or rename your v1 projects, please go back to Overleaf v1.
div(ng-show="visible")
a.project-list-sidebar-v1-link(
href=settings.overleaf.host + "/dash?prefer-v1-dash=1"
href='/sign_in_to_v1?return_to=/dash%3Fprefer-v1-dash%3D1'
) Go back to v1
if userIsFromSL(user)
div(ng-show="visible")
@@ -356,7 +356,7 @@ script(type="text/ng-template", id="v1ImportModalTemplate")
form-messages(for="v1ImportForm")
if settings.overleaf && settings.overleaf.host
a.btn.btn-primary.v1-import-btn(
ng-href=settings.overleaf.host + "/{{project.id}}"
ng-href='/sign_in_to_v1?return_to=/{{project.id}}'
ng-class="{disabled: v1ImportForm.inflight || v1ImportForm.response.success}"
) No thanks, open in v1
input.btn.btn-primary.v1-import-btn(
@@ -60,6 +60,21 @@ span(ng-controller="NotificationsController").userNotifications
button(ng-click="dismiss(notification)").close.pull-right
span(aria-hidden="true") &times;
span.sr-only #{translate("close")}
.alert.alert-info(ng-switch-when="notification_ip_matched_affiliation")
div.notification_inner
.notification_body
| It looks like you're at
strong {{ notification.messageOpts.university_name }}! <br/>
| Did you know that {{notification.messageOpts.university_name}} is providing
strong free Overleaf Professional accounts
| to everyone at {{notification.messageOpts.university_name}}? <br/>
| Add an institutional email address to claim your account.
a.pull-right.btn.btn-sm.btn-info(href="/user/settings")
| Add Affiliation
span().notification_close
button(ng-click="dismiss(notification)").close.pull-right
span(aria-hidden="true") &times;
span.sr-only #{translate("close")}
.alert.alert-info(ng-switch-default)
div.notification_inner
span(ng-bind-html="notification.html").notification_body
@@ -115,19 +115,14 @@
span(ng-controller="LeftHandMenuPromoController", ng-cloak)
.row-spaced#userProfileInformation(ng-if="hasProjects")
div(ng-controller="UserProfileController")
hr(ng-show="percentComplete < 100")
.text-centered.user-profile(ng-show="percentComplete < 100")
.progress
.progress-bar.progress-bar-info(ng-style="{'width' : (percentComplete+'%')}")
p.small #{translate("profile_complete_percentage", {percentval:"{{percentComplete}}"})}
div(ng-show="userEmails.length > 0 && userAffiliations.length == 0", ng-cloak)
hr
.text-centered.user-profile
p Are you affiliated with an institution?
button#completeUserProfileInformation.btn.btn-info(
ng-hide="formVisable",
ng-click="openUserProfileModal()"
) #{translate("complete")}
a.btn.btn-info(
href="/user/settings"
) Add Affiliation
.row-spaced(ng-if="hasProjects && userHasNoSubscription", ng-cloak).text-centered
hr
@@ -13,7 +13,7 @@
ng-show="project.accessLevel == 'owner'"
) {{project.name}}
a.projectName(
href=settings.overleaf.host + "/{{project.id}}"
href='/sign_in_to_v1?return_to=/{{project.id}}'
target="_blank"
ng-hide="project.accessLevel == 'owner'"
) {{project.name}}
@@ -2,7 +2,7 @@ script(type="text/ng-template", id="groupPlanModalTemplate")
.modal-header
h3 #{translate("group_plan_enquiry")}
.modal-body
form.text-left.form(ng-controller="UniverstiesContactController", ng-submit="contactUs()")
form.text-left.form(ng-controller="GroupPlanContactController", ng-submit="contactUs()")
span(ng-show="sent == false && error == false")
.form-group
label#title9(for='Field9')
@@ -122,7 +122,7 @@ block content
p
| You are subscribed to Overleaf through Overleaf v1
p
a.btn.btn-primary(href=settings.overleaf.host+"/users/edit#status") Manage v1 Subscription
a.btn.btn-primary(href='/sign_in_to_v1?return_to=/users/edit%23status') Manage v1 Subscription
hr
if settings.overleaf && v1Subscriptions && v1Subscriptions.teams && v1Subscriptions.teams.length > 0
@@ -130,7 +130,7 @@ block content
p
| You are a member of the Overleaf v1 team: <strong>#{team.name}</strong>
p
a.btn.btn-primary(href=settings.overleaf.host+"/teams") Manage v1 Team Membership
a.btn.btn-primary(href="/sign_in_to_v1?return_to=/teams") Manage v1 Team Membership
hr
.card(ng-if="view == 'cancelation'")
+2 -2
View File
@@ -197,7 +197,7 @@ block content
if settings.createV1AccountOnLogin && settings.overleaf
p
strong
| This will also delete your user account on #[a(href=settings.overleaf.host target="_blank") Overleaf v1].
| This will also delete your user account on #[a(href='/sign_in_to_v1?return_to=/dash%3Fprefer-v1-dash%3D1' target="_blank") Overleaf v1].
| If you want to remove your projects from Overleaf v1, you must do this before you
| delete your account by going to your My Projects page in Overleaf v1, moving your
| projects to the Trash, and then from there either leaving or purging them, as appropriate.
@@ -248,7 +248,7 @@ block content
div.alert.alert-info
| If you can't remember your password, or if you are using Single-Sign-On with another provider
| to sign in (such as Twitter or Google), please
| #[a(href=settings.overleaf.host+'/users/password/new', target='_blank') reset your password],
| #[a(href='/sign_in_to_v1?return_to=/users/password/new', target='_blank') reset your password],
| and try again.
.modal-footer
button.btn.btn-default(