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") UserSessionsManager = require("../User/UserSessionsManager")
Analytics = require "../Analytics/AnalyticsManager" Analytics = require "../Analytics/AnalyticsManager"
passport = require 'passport' passport = require 'passport'
NotificationsBuilder = require("../Notifications/NotificationsBuilder")
module.exports = AuthenticationController = module.exports = AuthenticationController =
@@ -43,7 +44,7 @@ module.exports = AuthenticationController =
return callback(err) return callback(err)
req.sessionStore.generate(req) req.sessionStore.generate(req)
for key, value of oldSession 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 # copy to the old `session.user` location, for backward-comptability
req.session.user = req.session.passport.user req.session.user = req.session.passport.user
req.session.save (err) -> req.session.save (err) ->
@@ -112,6 +113,7 @@ module.exports = AuthenticationController =
UserHandler.setupLoginData(user, ()->) UserHandler.setupLoginData(user, ()->)
LoginRateLimiter.recordSuccessfulLogin(user.email) LoginRateLimiter.recordSuccessfulLogin(user.email)
AuthenticationController._recordSuccessfulLogin(user._id) AuthenticationController._recordSuccessfulLogin(user._id)
AuthenticationController.ipMatchCheck(req, user)
Analytics.recordEvent(user._id, "user-logged-in", {ip:req.ip}) Analytics.recordEvent(user._id, "user-logged-in", {ip:req.ip})
Analytics.identifyUser(user._id, req.sessionID) Analytics.identifyUser(user._id, req.sessionID)
logger.log email: user.email, user_id: user._id.toString(), "successful log in" 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 # capture the request ip for use when creating the session
user._login_req_ip = req.ip 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) -> setInSessionUser: (req, props) ->
for key, value of props for key, value of props
if req?.session?.passport?.user? if req?.session?.passport?.user?
@@ -41,21 +41,19 @@ module.exports = EditorHttpController =
return callback(new Error("not found")) if !project? return callback(new Error("not found")) if !project?
CollaboratorsHandler.getInvitedMembersWithPrivilegeLevels project_id, (error, members) -> CollaboratorsHandler.getInvitedMembersWithPrivilegeLevels project_id, (error, members) ->
return callback(error) if error? 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? return callback(error) if error?
token = TokenAccessHandler.getRequestToken(req, project_id) if !privilegeLevel? or privilegeLevel == PrivilegeLevels.NONE
AuthorizationManager.getPrivilegeLevelForProject user_id, project_id, token, (error, privilegeLevel) -> 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? return callback(error) if error?
if !privilegeLevel? or privilegeLevel == PrivilegeLevels.NONE logger.log {project_id, user_id, memberCount: members.length, inviteCount: invites.length, privilegeLevel}, "returning project model view"
logger.log {project_id, user_id, privilegeLevel}, "not an acceptable privilege level, returning null" callback(null,
return callback null, null, false ProjectEditorHandler.buildProjectModelView(project, members, invites),
CollaboratorsInviteHandler.getAllInvites project_id, (error, invites) -> privilegeLevel
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
)
_nameIsAcceptableLength: (name)-> _nameIsAcceptableLength: (name)->
return name? and name.length < 150 and name.length != 0 return name? and name.length < 150 and name.length != 0
@@ -40,6 +40,23 @@ The #{settings.appName} Team - #{settings.siteUrl}
templates = {} 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({ templates.registered = CTAEmailTemplate({
subject: () -> "Activate your #{settings.appName} Account" subject: () -> "Activate your #{settings.appName} Account"
message: (opts) -> """ message: (opts) -> """
@@ -31,56 +31,56 @@ UnsupportedFileTypeError = (message) ->
error.name = "UnsupportedFileTypeError" error.name = "UnsupportedFileTypeError"
error.__proto__ = UnsupportedFileTypeError.prototype error.__proto__ = UnsupportedFileTypeError.prototype
return error return error
UnsupportedFileTypeError.prototype.__proto___ = Error.prototype UnsupportedFileTypeError.prototype.__proto__ = Error.prototype
UnsupportedBrandError = (message) -> UnsupportedBrandError = (message) ->
error = new Error(message) error = new Error(message)
error.name = "UnsupportedBrandError" error.name = "UnsupportedBrandError"
error.__proto__ = UnsupportedBrandError.prototype error.__proto__ = UnsupportedBrandError.prototype
return error return error
UnsupportedBrandError.prototype.__proto___ = Error.prototype UnsupportedBrandError.prototype.__proto__ = Error.prototype
UnsupportedExportRecordsError = (message) -> UnsupportedExportRecordsError = (message) ->
error = new Error(message) error = new Error(message)
error.name = "UnsupportedExportRecordsError" error.name = "UnsupportedExportRecordsError"
error.__proto__ = UnsupportedExportRecordsError.prototype error.__proto__ = UnsupportedExportRecordsError.prototype
return error return error
UnsupportedExportRecordsError.prototype.__proto___ = Error.prototype UnsupportedExportRecordsError.prototype.__proto__ = Error.prototype
V1HistoryNotSyncedError = (message) -> V1HistoryNotSyncedError = (message) ->
error = new Error(message) error = new Error(message)
error.name = "V1HistoryNotSyncedError" error.name = "V1HistoryNotSyncedError"
error.__proto__ = V1HistoryNotSyncedError.prototype error.__proto__ = V1HistoryNotSyncedError.prototype
return error return error
V1HistoryNotSyncedError.prototype.__proto___ = Error.prototype V1HistoryNotSyncedError.prototype.__proto__ = Error.prototype
ProjectHistoryDisabledError = (message) -> ProjectHistoryDisabledError = (message) ->
error = new Error(message) error = new Error(message)
error.name = "ProjectHistoryDisabledError" error.name = "ProjectHistoryDisabledError"
error.__proto__ = ProjectHistoryDisabledError.prototype error.__proto__ = ProjectHistoryDisabledError.prototype
return error return error
ProjectHistoryDisabledError.prototype.__proto___ = Error.prototype ProjectHistoryDisabledError.prototype.__proto__ = Error.prototype
V1ConnectionError = (message) -> V1ConnectionError = (message) ->
error = new Error(message) error = new Error(message)
error.name = "V1ConnectionError" error.name = "V1ConnectionError"
error.__proto__ = V1ConnectionError.prototype error.__proto__ = V1ConnectionError.prototype
return error return error
V1ConnectionError.prototype.__proto___ = Error.prototype V1ConnectionError.prototype.__proto__ = Error.prototype
UnconfirmedEmailError = (message) -> UnconfirmedEmailError = (message) ->
error = new Error(message) error = new Error(message)
error.name = "UnconfirmedEmailError" error.name = "UnconfirmedEmailError"
error.__proto__ = UnconfirmedEmailError.prototype error.__proto__ = UnconfirmedEmailError.prototype
return error return error
UnconfirmedEmailError.prototype.__proto___ = Error.prototype UnconfirmedEmailError.prototype.__proto__ = Error.prototype
EmailExistsError = (message) -> EmailExistsError = (message) ->
error = new Error(message) error = new Error(message)
error.name = "EmailExistsError" error.name = "EmailExistsError"
error.__proto__ = EmailExistsError.prototype error.__proto__ = EmailExistsError.prototype
return error return error
EmailExistsError.prototype.__proto___ = Error.prototype EmailExistsError.prototype.__proto__ = Error.prototype
module.exports = Errors = module.exports = Errors =
NotFoundError: NotFoundError NotFoundError: NotFoundError
@@ -13,12 +13,18 @@ module.exports =
user_id: user_id user_id: user_id
} }
if req.body && req.body.firstName && req.body.lastName if req.body
export_params.first_name = req.body.firstName.trim() export_params.first_name = req.body.firstName.trim() if req.body.firstName
export_params.last_name = req.body.lastName.trim() 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) -> ExportsHandler.exportProject export_params, (err, export_data) ->
return next(err) if err? return err if err?
logger.log logger.log
user_id:user_id user_id:user_id
project_id: project_id project_id: project_id
@@ -30,11 +36,13 @@ module.exports =
exportStatus: (req, res) -> exportStatus: (req, res) ->
{export_id} = req.params {export_id} = req.params
ExportsHandler.fetchExport export_id, (err, export_json) -> ExportsHandler.fetchExport export_id, (err, export_json) ->
return next(err) if err? return err if err?
parsed_export = JSON.parse(export_json) parsed_export = JSON.parse(export_json)
json = { json = {
status_summary: parsed_export.status_summary, 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 res.send export_json: json
@@ -20,9 +20,7 @@ module.exports = ExportsHandler = self =
callback null, export_data callback null, export_data
_buildExport: (export_params, callback=(err, export_data) ->) -> _buildExport: (export_params, callback=(err, export_data) ->) ->
project_id = export_params.project_id {project_id, user_id, brand_variation_id, title, description, author, license, show_source} = export_params
user_id = export_params.user_id
brand_variation_id = export_params.brand_variation_id
jobs = jobs =
project: (cb) -> project: (cb) ->
ProjectGetter.getProject project_id, cb ProjectGetter.getProject project_id, cb
@@ -60,6 +58,11 @@ module.exports = ExportsHandler = self =
metadata: metadata:
compiler: project.compiler compiler: project.compiler
imageName: project.imageName imageName: project.imageName
title: title
description: description
author: author
license: license
show_source: show_source
user: user:
id: user_id id: user_id
firstName: user.first_name firstName: user.first_name
@@ -1,5 +1,7 @@
logger = require("logger-sharelatex") logger = require("logger-sharelatex")
NotificationsHandler = require("./NotificationsHandler") NotificationsHandler = require("./NotificationsHandler")
request = require "request"
settings = require "settings-sharelatex"
module.exports = module.exports =
@@ -29,3 +31,29 @@ module.exports =
NotificationsHandler.createNotification user._id, @key, "notification_project_invite", messageOpts, invite.expires, callback NotificationsHandler.createNotification user._id, @key, "notification_project_invite", messageOpts, invite.expires, callback
read: (callback=()->) -> read: (callback=()->) ->
NotificationsHandler.markAsReadByKeyOnly @key, 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 = [] unreadNotifications = []
callback(null, 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 = { payload = {
key:key key:key
messageOpts:messageOpts messageOpts:messageOpts
templateKey:templateKey templateKey:templateKey
forceCreate: true forceCreate:forceCreate
} }
if expiryDateTime? if expiryDateTime?
payload.expires = expiryDateTime payload.expires = expiryDateTime
@@ -26,6 +26,8 @@ TokenAccessHandler = require '../TokenAccess/TokenAccessHandler'
CollaboratorsHandler = require '../Collaborators/CollaboratorsHandler' CollaboratorsHandler = require '../Collaborators/CollaboratorsHandler'
Modules = require '../../infrastructure/Modules' Modules = require '../../infrastructure/Modules'
ProjectEntityHandler = require './ProjectEntityHandler' ProjectEntityHandler = require './ProjectEntityHandler'
UserGetter = require("../User/UserGetter")
NotificationsBuilder = require("../Notifications/NotificationsBuilder")
crypto = require 'crypto' crypto = require 'crypto'
{ V1ConnectionError } = require '../Errors/Errors' { V1ConnectionError } = require '../Errors/Errors'
Features = require('../../infrastructure/Features') Features = require('../../infrastructure/Features')
@@ -209,6 +211,11 @@ module.exports = ProjectController =
user = results.user user = results.user
warnings = ProjectController._buildWarningsList results.v1Projects 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) -> ProjectController._injectProjectOwners projects, (error, projects) ->
return next(error) if error? return next(error) if error?
@@ -9,7 +9,8 @@ fs = require "fs"
ErrorController = require "../Errors/ErrorController" ErrorController = require "../Errors/ErrorController"
AuthenticationController = require('../Authentication/AuthenticationController') 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 = module.exports = HomeController =
index : (req,res)-> index : (req,res)->
@@ -21,11 +22,13 @@ module.exports = HomeController =
else else
HomeController.home(req, res) HomeController.home(req, res)
home: (req, res)-> home: (req, res, next)->
if Features.hasFeature('homepage') and homepageExists if Features.hasFeature('homepage') and !Settings.overleaf and slHomepageExists
res.render 'external/home' res.render 'external/home/sl'
else if Features.hasFeature('homepage') and Settings.overleaf and v2HomepageExists
res.render 'external/home/v2'
else else
res.redirect "/login" res.redirect '/login'
externalPage: (page, title) -> externalPage: (page, title) ->
return (req, res, next = (error) ->) -> return (req, res, next = (error) ->) ->
@@ -33,7 +33,7 @@ module.exports =
student: true student: true
} }
{ {
feature: 'hundreds_templates' feature: 'thousands_templates'
value: 'bool' value: 'bool'
info: 'hundreds_templates_info' info: 'hundreds_templates_info'
plans: { plans: {
@@ -3,6 +3,7 @@ AuthenticationController = require '../Authentication/AuthenticationController'
TokenAccessHandler = require './TokenAccessHandler' TokenAccessHandler = require './TokenAccessHandler'
Errors = require '../Errors/Errors' Errors = require '../Errors/Errors'
logger = require 'logger-sharelatex' logger = require 'logger-sharelatex'
settings = require 'settings-sharelatex'
module.exports = TokenAccessController = module.exports = TokenAccessController =
@@ -11,11 +12,16 @@ module.exports = TokenAccessController =
return ProjectController.loadEditor(req, res, next) return ProjectController.loadEditor(req, res, next)
_tryHigherAccess: (token, userId, req, res, next) -> _tryHigherAccess: (token, userId, req, res, next) ->
TokenAccessHandler.findProjectWithHigherAccess token, userId, (err, project) -> TokenAccessHandler.findProjectWithHigherAccess token, userId, (err, project, projectExists) ->
if err? if err?
logger.err {err, token, userId}, logger.err {err, token, userId},
"[TokenAccess] error finding project with higher access" "[TokenAccess] error finding project with higher access"
return next(err) 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? if !project?
logger.log {token, userId}, logger.log {token, userId},
"[TokenAccess] no project with higher access found for this user and token" "[TokenAccess] no project with higher access found for this user and token"
@@ -22,7 +22,7 @@ module.exports = TokenAccessHandler =
'publicAccesLevel': PublicAccessLevels.TOKEN_BASED 'publicAccesLevel': PublicAccessLevels.TOKEN_BASED
}, {_id: 1, publicAccesLevel: 1, owner_ref: 1}, callback }, {_id: 1, publicAccesLevel: 1, owner_ref: 1}, callback
findProjectWithHigherAccess: (token, userId, callback=(err, project)->) -> findProjectWithHigherAccess: (token, userId, callback=(err, project, projectExists)->) ->
Project.findOne { Project.findOne {
$or: [ $or: [
{'tokens.readAndWrite': token}, {'tokens.readAndWrite': token},
@@ -32,12 +32,16 @@ module.exports = TokenAccessHandler =
if err? if err?
return callback(err) return callback(err)
if !project? if !project?
return callback(null, null) return callback(null, null, false) # Project doesn't exist, so we handle differently
projectId = project._id projectId = project._id
CollaboratorsHandler.isUserInvitedMemberOfProject userId, projectId, (err, isMember) -> CollaboratorsHandler.isUserInvitedMemberOfProject userId, projectId, (err, isMember) ->
if err? if err?
return callback(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)->) -> addReadOnlyUserToProject: (userId, projectId, callback=(err)->) ->
userId = ObjectId(userId.toString()) userId = ObjectId(userId.toString())
@@ -8,6 +8,7 @@ Errors = require("../Errors/Errors")
module.exports = UserGetter = module.exports = UserGetter =
getUser: (query, projection, callback = (error, user) ->) -> getUser: (query, projection, callback = (error, user) ->) ->
return callback(new Error("no query provided")) unless query?
if query?.email? if query?.email?
return callback(new Error("Don't use getUser to find user by email"), null) return callback(new Error("Don't use getUser to find user by email"), null)
if arguments.length == 2 if arguments.length == 2
@@ -34,7 +35,7 @@ module.exports = UserGetter =
getUserAffiliations userId, (error, affiliationsData) -> getUserAffiliations userId, (error, affiliationsData) ->
return callback error if error? 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) ->) -> getUserByMainEmail: (email, projection, callback = (error, user) ->) ->
email = email.trim() email = email.trim()
@@ -1,20 +1,33 @@
settings = require("settings-sharelatex") settings = require("settings-sharelatex")
logger = require("logger-sharelatex") logger = require("logger-sharelatex")
module.exports = (req, res, next)-> module.exports = RedirectManager =
apply: (webRouter) ->
for redirectUrl, target of settings.redirects
for method in (target.methods or ['get'])
webRouter[method] redirectUrl, RedirectManager.createRedirect(target)
requestedUrl = req.url createRedirect: (target) ->
(req, res, next) ->
redirectUrl = settings.redirects[requestedUrl] code = 302
if typeof target is 'string'
#remove starting slash url = target
if !redirectUrl? and requestedUrl[requestedUrl.length-1] == "/" else
requestedUrl = requestedUrl.substring(0, requestedUrl.length - 1) if req.method == "POST"
redirectUrl = settings.redirects[requestedUrl] code = 307
if typeof target.url == "function"
if redirectUrl? url = target.url(req.params)
logger.log redirectUrl:redirectUrl, reqUrl:req.url, "redirecting to new path" if !url
res.redirect 301, "#{redirectUrl}" return next()
else else
next() 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 methodOverride()
app.use metrics.http.monitor(logger) app.use metrics.http.monitor(logger)
app.use RedirectManager RedirectManager.apply(webRouter)
ProxyManager.apply(publicApiRouter) ProxyManager.apply(publicApiRouter)
@@ -22,6 +22,7 @@ UserSchema = new Schema
confirmed : {type : Boolean, default : false} confirmed : {type : Boolean, default : false}
signUpDate : {type : Date, default: () -> new Date() } signUpDate : {type : Date, default: () -> new Date() }
lastLoggedIn : {type : Date} lastLoggedIn : {type : Date}
lastLoginIp : {type : String, default : ''}
loginCount : {type : Number, default: 0} loginCount : {type : Number, default: 0}
holdingAccount : {type : Boolean, default: false} holdingAccount : {type : Boolean, default: false}
ace : { ace : {
+16
View File
@@ -68,6 +68,7 @@ module.exports = class Router
webRouter.get '/logout', UserController.logout webRouter.get '/logout', UserController.logout
webRouter.get '/restricted', AuthorizationMiddlewear.restricted webRouter.get '/restricted', AuthorizationMiddlewear.restricted
if Features.hasFeature('registration') if Features.hasFeature('registration')
webRouter.get '/register', UserPagesController.registerPage webRouter.get '/register', UserPagesController.registerPage
AuthenticationController.addEndpointToLoginWhitelist '/register' AuthenticationController.addEndpointToLoginWhitelist '/register'
@@ -329,6 +330,21 @@ module.exports = class Router
AuthenticationController.httpAuth, AuthenticationController.httpAuth,
CompileController.getFileFromClsiWithoutUser 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 #Admin Stuff
webRouter.get '/admin', AuthorizationMiddlewear.ensureUserIsSiteAdmin, AdminController.index 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 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'} | #{linkText ? linkText : 'advisor programme'}
mixin linkBenefits(linkText, linkClass) mixin linkBenefits(linkText, linkClass)
//- To Do: verify path a(href="/for/authors" class=linkClass ? linkClass : '')
a(href="/benefits" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'benefits'} | #{linkText ? linkText : 'benefits'}
mixin linkBlog(linkText, linkClass, slug) mixin linkBlog(linkText, linkClass, slug)
@@ -36,8 +35,7 @@ mixin linkDash(linkText, linkClass)
| #{linkText ? linkText : 'project dashboard'} | #{linkText ? linkText : 'project dashboard'}
mixin linkEducation(linkText, linkClass) mixin linkEducation(linkText, linkClass)
//- To Do: verify path a(href="/for/edu" class=linkClass ? linkClass : '')
a(href="/plans" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'teaching toolkit'} | #{linkText ? linkText : 'teaching toolkit'}
mixin linkEmail(linkText, linkClass, email) mixin linkEmail(linkText, linkClass, email)
@@ -66,8 +64,7 @@ mixin linkInvite(linkText, linkClass, track)
| #{linkText ? linkText : 'invite your friends'} | #{linkText ? linkText : 'invite your friends'}
mixin linkPlansAndPricing(linkText, linkClass) mixin linkPlansAndPricing(linkText, linkClass)
//- To Do: verify path a(href="/user/subscription/plans" class=linkClass ? linkClass : '')
a(href="/plans" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'plans and pricing'} | #{linkText ? linkText : 'plans and pricing'}
mixin linkPrintNewTab(linkText, linkClass, icon, track) mixin linkPrintNewTab(linkText, linkClass, icon, track)
@@ -121,6 +118,5 @@ mixin linkTweet(linkText, linkClass, tweetText, track)
) #{linkText ? linkText : 'tweet'} ) #{linkText ? linkText : 'tweet'}
mixin linkUniversities(linkText, linkClass) mixin linkUniversities(linkText, linkClass)
//- To Do: verify path a(href="/for/universities" class=linkClass ? linkClass : '')
a(href="/universities" class=linkClass ? linkClass : '')
| #{linkText ? linkText : 'universities'} | #{linkText ? linkText : 'universities'}
+1 -1
View File
@@ -2,7 +2,7 @@ doctype html
html.full-height(itemscope, itemtype='http://schema.org/Product') html.full-height(itemscope, itemtype='http://schema.org/Product')
head head
title Something went wrong title Something went wrong
link(rel="icon", href="/favicon.ico") link(rel="icon", href="/" + settings.brandPrefix + "favicon.ico")
if buildCssPath if buildCssPath
link(rel="stylesheet", href=buildCssPath("/" + settings.brandPrefix + "style.css")) 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") 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 // loop over header_extras
each item in nav.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 if item.dropdown
li.dropdown(class=item.class, dropdown) li.dropdown(class=item.class, dropdown)
a.dropdown-toggle(href, dropdown-toggle) a.dropdown-toggle(href, dropdown-toggle)
@@ -103,12 +103,8 @@ header.toolbar.toolbar-header.toolbar-with-labels(
a.btn.btn-full-height( a.btn.btn-full-height(
href href
ng-class="{ 'btn-full-height-disabled' : !permissions.admin }"
ng-click="openShareProjectModal(permissions.admin);" ng-click="openShareProjectModal(permissions.admin);"
ng-controller="ShareController" 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 i.fa.fa-fw.fa-group
p.toolbar-label #{translate("share")} p.toolbar-label #{translate("share")}
@@ -8,9 +8,8 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
h3 #{translate("share_project")} h3 #{translate("share_project")}
.modal-body.modal-body-share .modal-body.modal-body-share
.container-fluid .container-fluid
//- Private (with token-access available) //- 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 .col-xs-12.text-center
| #{translate('link_sharing_is_off')} | #{translate('link_sharing_is_off')}
| &nbsp;&nbsp; | &nbsp;&nbsp;
@@ -28,7 +27,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
) )
//- Token-based access //- 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 .col-xs-12.text-center
strong strong
| #{translate('link_sharing_is_on')}. | #{translate('link_sharing_is_on')}.
@@ -57,7 +56,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
pre.access-token(ng-hide="readOnlyTokenLink") #{translate('loading')}... pre.access-token(ng-hide="readOnlyTokenLink") #{translate('loading')}...
//- legacy public-access //- 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 .col-xs-12.text-center
strong(ng-if="project.publicAccesLevel == 'readAndWrite'") #{translate("this_project_is_public")} strong(ng-if="project.publicAccesLevel == 'readAndWrite'") #{translate("this_project_is_public")}
strong(ng-if="project.publicAccesLevel == 'readOnly'") #{translate("this_project_is_public_read_only")} 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 .col-xs-3.text-left
span(ng-show="member.privileges == 'readAndWrite'") #{translate("can_edit")} span(ng-show="member.privileges == 'readAndWrite'") #{translate("can_edit")}
span(ng-show="member.privileges == 'readOnly'") #{translate("read_only")} span(ng-show="member.privileges == 'readOnly'") #{translate("read_only")}
.col-xs-1 .col-xs-1(ng-show="isAdmin")
a( a(
href href
tooltip=translate('remove_collaborator') tooltip=translate('remove_collaborator')
@@ -102,7 +101,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
ng-click="revokeInvite(invite)" ng-click="revokeInvite(invite)"
) )
i.fa.fa-times i.fa.fa-times
.row.invite-controls .row.invite-controls(ng-show="isAdmin")
form(ng-show="canAddCollaborators") form(ng-show="canAddCollaborators")
.small #{translate("share_with_your_collabs")} .small #{translate("share_with_your_collabs")}
.form-group .form-group
@@ -177,8 +176,9 @@ script(type='text/ng-template', id='shareProjectModalTemplate')
) #{translate("start_free_trial")} ) #{translate("start_free_trial")}
p.small(ng-show="startedFreeTrial") 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.modal-footer-share
.modal-footer-left .modal-footer-left
i.fa.fa-refresh.fa-spin(ng-show="state.inflight") i.fa.fa-refresh.fa-spin(ng-show="state.inflight")
+4 -1
View File
@@ -1,5 +1,8 @@
extends ../layout extends ../layout
block vars
- var suppressNavContentLinks = true
block content block content
//- We need to do .replace(/\//g, '\\/') do that '</script>' -> '<\/script>' //- We need to do .replace(/\//g, '\\/') do that '</script>' -> '<\/script>'
//- and doesn't prematurely end the script tag. //- 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. | To tag or rename your v1 projects, please go back to Overleaf v1.
div(ng-show="visible") div(ng-show="visible")
a.project-list-sidebar-v1-link( 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 ) Go back to v1
if userIsFromSL(user) if userIsFromSL(user)
div(ng-show="visible") div(ng-show="visible")
@@ -356,7 +356,7 @@ script(type="text/ng-template", id="v1ImportModalTemplate")
form-messages(for="v1ImportForm") form-messages(for="v1ImportForm")
if settings.overleaf && settings.overleaf.host if settings.overleaf && settings.overleaf.host
a.btn.btn-primary.v1-import-btn( 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}" ng-class="{disabled: v1ImportForm.inflight || v1ImportForm.response.success}"
) No thanks, open in v1 ) No thanks, open in v1
input.btn.btn-primary.v1-import-btn( input.btn.btn-primary.v1-import-btn(
@@ -60,6 +60,21 @@ span(ng-controller="NotificationsController").userNotifications
button(ng-click="dismiss(notification)").close.pull-right button(ng-click="dismiss(notification)").close.pull-right
span(aria-hidden="true") &times; span(aria-hidden="true") &times;
span.sr-only #{translate("close")} 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) .alert.alert-info(ng-switch-default)
div.notification_inner div.notification_inner
span(ng-bind-html="notification.html").notification_body span(ng-bind-html="notification.html").notification_body
@@ -115,19 +115,14 @@
span(ng-controller="LeftHandMenuPromoController", ng-cloak) span(ng-controller="LeftHandMenuPromoController", ng-cloak)
.row-spaced#userProfileInformation(ng-if="hasProjects") .row-spaced#userProfileInformation(ng-if="hasProjects")
div(ng-controller="UserProfileController") div(ng-show="userEmails.length > 0 && userAffiliations.length == 0", ng-cloak)
hr(ng-show="percentComplete < 100") hr
.text-centered.user-profile(ng-show="percentComplete < 100") .text-centered.user-profile
.progress p Are you affiliated with an institution?
.progress-bar.progress-bar-info(ng-style="{'width' : (percentComplete+'%')}")
p.small #{translate("profile_complete_percentage", {percentval:"{{percentComplete}}"})}
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 .row-spaced(ng-if="hasProjects && userHasNoSubscription", ng-cloak).text-centered
hr hr
@@ -13,7 +13,7 @@
ng-show="project.accessLevel == 'owner'" ng-show="project.accessLevel == 'owner'"
) {{project.name}} ) {{project.name}}
a.projectName( a.projectName(
href=settings.overleaf.host + "/{{project.id}}" href='/sign_in_to_v1?return_to=/{{project.id}}'
target="_blank" target="_blank"
ng-hide="project.accessLevel == 'owner'" ng-hide="project.accessLevel == 'owner'"
) {{project.name}} ) {{project.name}}
@@ -2,7 +2,7 @@ script(type="text/ng-template", id="groupPlanModalTemplate")
.modal-header .modal-header
h3 #{translate("group_plan_enquiry")} h3 #{translate("group_plan_enquiry")}
.modal-body .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") span(ng-show="sent == false && error == false")
.form-group .form-group
label#title9(for='Field9') label#title9(for='Field9')
@@ -122,7 +122,7 @@ block content
p p
| You are subscribed to Overleaf through Overleaf v1 | You are subscribed to Overleaf through Overleaf v1
p 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 hr
if settings.overleaf && v1Subscriptions && v1Subscriptions.teams && v1Subscriptions.teams.length > 0 if settings.overleaf && v1Subscriptions && v1Subscriptions.teams && v1Subscriptions.teams.length > 0
@@ -130,7 +130,7 @@ block content
p p
| You are a member of the Overleaf v1 team: <strong>#{team.name}</strong> | You are a member of the Overleaf v1 team: <strong>#{team.name}</strong>
p 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 hr
.card(ng-if="view == 'cancelation'") .card(ng-if="view == 'cancelation'")
+2 -2
View File
@@ -197,7 +197,7 @@ block content
if settings.createV1AccountOnLogin && settings.overleaf if settings.createV1AccountOnLogin && settings.overleaf
p p
strong 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 | 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 | 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. | 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 div.alert.alert-info
| If you can't remember your password, or if you are using Single-Sign-On with another provider | 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 | 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. | and try again.
.modal-footer .modal-footer
button.btn.btn-default( button.btn.btn-default(
+3 -1
View File
@@ -491,7 +491,7 @@ module.exports = settings =
modules: modules:
sanitize: sanitize:
options: options:
allowedTags: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'iframe', 'img', 'figure', 'figcaption' ] allowedTags: [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote', 'p', 'a', 'ul', 'ol', 'nl', 'li', 'b', 'i', 'strong', 'em', 'strike', 'code', 'hr', 'br', 'div', 'table', 'thead', 'caption', 'tbody', 'tr', 'th', 'td', 'pre', 'iframe', 'img', 'figure', 'figcaption', 'source', 'video' ]
allowedAttributes: allowedAttributes:
'a': [ 'href', 'name', 'target', 'class' ] 'a': [ 'href', 'name', 'target', 'class' ]
'div': [ 'class', 'id', 'style' ] 'div': [ 'class', 'id', 'style' ]
@@ -505,3 +505,5 @@ module.exports = settings =
'figcaption': [ 'class', 'id', 'style'] 'figcaption': [ 'class', 'id', 'style']
'iframe': [ 'allowfullscreen', 'frameborder', 'height', 'src', 'width' ] 'iframe': [ 'allowfullscreen', 'frameborder', 'height', 'src', 'width' ]
'img': [ 'alt', 'class', 'src', 'style' ] 'img': [ 'alt', 'class', 'src', 'style' ]
'source': [ 'src', 'type' ]
'video': [ 'alt', 'class', 'controls', 'height', 'width' ]
+120 -5
View File
@@ -1545,6 +1545,11 @@
} }
} }
}, },
"chownr": {
"version": "1.1.1",
"from": "chownr@>=1.0.1 <2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.1.tgz"
},
"ci-info": { "ci-info": {
"version": "1.1.3", "version": "1.1.3",
"from": "ci-info@>=1.0.0 <2.0.0", "from": "ci-info@>=1.0.0 <2.0.0",
@@ -3482,6 +3487,12 @@
"resolved": "https://registry.npmjs.org/flatiron/-/flatiron-0.4.3.tgz", "resolved": "https://registry.npmjs.org/flatiron/-/flatiron-0.4.3.tgz",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"minimist": {
"version": "0.0.10",
"from": "minimist@>=0.0.1 <0.1.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
"dev": true
},
"optimist": { "optimist": {
"version": "0.6.0", "version": "0.6.0",
"from": "optimist@0.6.0", "from": "optimist@0.6.0",
@@ -3603,6 +3614,11 @@
"from": "fs-extra@>=4.0.2 <5.0.0", "from": "fs-extra@>=4.0.2 <5.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz" "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-4.0.3.tgz"
}, },
"fs-minipass": {
"version": "1.2.5",
"from": "fs-minipass@>=1.2.5 <2.0.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz"
},
"fs.realpath": { "fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"from": "fs.realpath@>=1.0.0 <2.0.0", "from": "fs.realpath@>=1.0.0 <2.0.0",
@@ -6696,6 +6712,38 @@
"from": "lynx@0.1.1", "from": "lynx@0.1.1",
"resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz" "resolved": "https://registry.npmjs.org/lynx/-/lynx-0.1.1.tgz"
}, },
"mailchimp-api-v3": {
"version": "1.12.0",
"from": "mailchimp-api-v3@>=1.12.0 <2.0.0",
"resolved": "https://registry.npmjs.org/mailchimp-api-v3/-/mailchimp-api-v3-1.12.0.tgz",
"dependencies": {
"bluebird": {
"version": "3.5.2",
"from": "bluebird@>=3.4.0 <4.0.0",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.2.tgz"
},
"lodash": {
"version": "4.17.11",
"from": "lodash@>=4.17.10 <5.0.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz"
},
"safe-buffer": {
"version": "5.1.2",
"from": "safe-buffer@>=5.1.2 <6.0.0",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
},
"tar": {
"version": "4.4.6",
"from": "tar@>=4.0.2 <5.0.0",
"resolved": "https://registry.npmjs.org/tar/-/tar-4.4.6.tgz"
},
"yallist": {
"version": "3.0.2",
"from": "yallist@>=3.0.2 <4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz"
}
}
},
"mailcomposer": { "mailcomposer": {
"version": "3.3.2", "version": "3.3.2",
"from": "mailcomposer@3.3.2", "from": "mailcomposer@3.3.2",
@@ -6981,9 +7029,31 @@
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz" "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz"
}, },
"minimist": { "minimist": {
"version": "0.0.8", "version": "1.2.0",
"from": "minimist@0.0.8", "from": "minimist@1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz"
},
"minipass": {
"version": "2.3.4",
"from": "minipass@>=2.3.3 <3.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-2.3.4.tgz",
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"from": "safe-buffer@^5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
},
"yallist": {
"version": "3.0.2",
"from": "yallist@^3.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz"
}
}
},
"minizlib": {
"version": "1.1.0",
"from": "minizlib@>=1.1.0 <2.0.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.1.0.tgz"
}, },
"mixin-deep": { "mixin-deep": {
"version": "1.3.1", "version": "1.3.1",
@@ -7002,7 +7072,14 @@
"mkdirp": { "mkdirp": {
"version": "0.5.1", "version": "0.5.1",
"from": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "from": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz" "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"dependencies": {
"minimist": {
"version": "0.0.8",
"from": "minimist@0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz"
}
}
}, },
"mocha": { "mocha": {
"version": "5.0.1", "version": "5.0.1",
@@ -7312,6 +7389,12 @@
"resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz", "resolved": "https://registry.npmjs.org/async/-/async-0.2.9.tgz",
"dev": true "dev": true
}, },
"minimist": {
"version": "0.0.10",
"from": "minimist@>=0.0.1 <0.1.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz",
"dev": true
},
"optimist": { "optimist": {
"version": "0.6.0", "version": "0.6.0",
"from": "optimist@0.6.0", "from": "optimist@0.6.0",
@@ -8145,7 +8228,14 @@
"optimist": { "optimist": {
"version": "0.6.1", "version": "0.6.1",
"from": "optimist@0.6.1", "from": "optimist@0.6.1",
"resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz" "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
"dependencies": {
"minimist": {
"version": "0.0.10",
"from": "minimist@>=0.0.1 <0.1.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz"
}
}
}, },
"optionator": { "optionator": {
"version": "0.8.2", "version": "0.8.2",
@@ -8318,6 +8408,11 @@
"from": "passport@>=0.3.2 <0.4.0", "from": "passport@>=0.3.2 <0.4.0",
"resolved": "https://registry.npmjs.org/passport/-/passport-0.3.2.tgz" "resolved": "https://registry.npmjs.org/passport/-/passport-0.3.2.tgz"
}, },
"passport-google-oauth20": {
"version": "1.0.0",
"from": "passport-google-oauth20@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/passport-google-oauth20/-/passport-google-oauth20-1.0.0.tgz"
},
"passport-ldapauth": { "passport-ldapauth": {
"version": "0.6.0", "version": "0.6.0",
"from": "passport-ldapauth@>=0.6.0 <0.7.0", "from": "passport-ldapauth@>=0.6.0 <0.7.0",
@@ -8328,6 +8423,11 @@
"from": "passport-local@>=1.0.0 <2.0.0", "from": "passport-local@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz" "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz"
}, },
"passport-oauth1": {
"version": "1.1.0",
"from": "passport-oauth1@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/passport-oauth1/-/passport-oauth1-1.1.0.tgz"
},
"passport-oauth2": { "passport-oauth2": {
"version": "1.4.0", "version": "1.4.0",
"from": "passport-oauth2@>=1.4.0 <2.0.0", "from": "passport-oauth2@>=1.4.0 <2.0.0",
@@ -8338,6 +8438,11 @@
"from": "passport-oauth2-refresh@>=1.0.0 <2.0.0", "from": "passport-oauth2-refresh@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/passport-oauth2-refresh/-/passport-oauth2-refresh-1.0.0.tgz" "resolved": "https://registry.npmjs.org/passport-oauth2-refresh/-/passport-oauth2-refresh-1.0.0.tgz"
}, },
"passport-orcid": {
"version": "0.0.3",
"from": "https://registry.npmjs.org/passport-orcid/-/passport-orcid-0.0.3.tgz",
"resolved": "https://registry.npmjs.org/passport-orcid/-/passport-orcid-0.0.3.tgz"
},
"passport-saml": { "passport-saml": {
"version": "0.15.0", "version": "0.15.0",
"from": "passport-saml@>=0.15.0 <0.16.0", "from": "passport-saml@>=0.15.0 <0.16.0",
@@ -8372,6 +8477,11 @@
"from": "passport-strategy@>=1.0.0 <2.0.0", "from": "passport-strategy@>=1.0.0 <2.0.0",
"resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz" "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz"
}, },
"passport-twitter": {
"version": "1.0.4",
"from": "https://registry.npmjs.org/passport-twitter/-/passport-twitter-1.0.4.tgz",
"resolved": "https://registry.npmjs.org/passport-twitter/-/passport-twitter-1.0.4.tgz"
},
"path-browserify": { "path-browserify": {
"version": "0.0.0", "version": "0.0.0",
"from": "path-browserify@0.0.0", "from": "path-browserify@0.0.0",
@@ -12502,6 +12612,11 @@
"from": "xtend@>=4.0.1 <5.0.0", "from": "xtend@>=4.0.1 <5.0.0",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz"
}, },
"xtraverse": {
"version": "0.1.0",
"from": "xtraverse@>=0.1.0 <0.2.0",
"resolved": "https://registry.npmjs.org/xtraverse/-/xtraverse-0.1.0.tgz"
},
"y18n": { "y18n": {
"version": "3.2.1", "version": "3.2.1",
"from": "y18n@>=3.2.1 <4.0.0", "from": "y18n@>=3.2.1 <4.0.0",
+5 -2
View File
@@ -63,6 +63,7 @@
"marked": "^0.3.5", "marked": "^0.3.5",
"method-override": "^2.3.3", "method-override": "^2.3.3",
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.7.1", "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.7.1",
"minimist": "1.2.0",
"mocha": "^5.0.1", "mocha": "^5.0.1",
"mongojs": "2.4.0", "mongojs": "2.4.0",
"mongoose": "4.11.4", "mongoose": "4.11.4",
@@ -75,11 +76,14 @@
"nvd3": "^1.8.6", "nvd3": "^1.8.6",
"optimist": "0.6.1", "optimist": "0.6.1",
"passport": "^0.3.2", "passport": "^0.3.2",
"passport-google-oauth20": "^1.0.0",
"passport-ldapauth": "^0.6.0", "passport-ldapauth": "^0.6.0",
"passport-local": "^1.0.0", "passport-local": "^1.0.0",
"passport-oauth2": "^1.4.0", "passport-oauth2": "^1.4.0",
"passport-oauth2-refresh": "^1.0.0", "passport-oauth2-refresh": "^1.0.0",
"passport-orcid": "0.0.3",
"passport-saml": "^0.15.0", "passport-saml": "^0.15.0",
"passport-twitter": "^1.0.4",
"pug": "^2.0.0-beta6", "pug": "^2.0.0-beta6",
"react": "^15.4.2", "react": "^15.4.2",
"react-dom": "^15.4.2", "react-dom": "^15.4.2",
@@ -99,8 +103,7 @@
"v8-profiler": "^5.2.3", "v8-profiler": "^5.2.3",
"valid-url": "^1.0.9", "valid-url": "^1.0.9",
"xml2js": "0.2.0", "xml2js": "0.2.0",
"yauzl": "^2.8.0", "yauzl": "^2.8.0"
"minimist": "1.2.0"
}, },
"devDependencies": { "devDependencies": {
"autoprefixer": "^6.6.1", "autoprefixer": "^6.6.1",
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,15 @@
<html>
<body>
<h1>Instructions</h1>
<h2>What is this file?</h2>
This file is used by Atlassian Cloud to verify ownership of your domain name.
<br>
Generated at 2017-02-02 11:42:45 UTC.
<h1>Domain Verification Token</h1>
This token must be found in the page at: <code>https://example.com/atlassian-domain-verification.html</code>
<hr>
<code id="domain-verification-token">6YyZjsahYa6Y5nyQDaNakzRPBJ6wVMQwN+2CDx-2wQtG/97OvOjc3gh4NCQ6Sgkv</code>
</body>
</html>
@@ -516,6 +516,7 @@ define [
scope.$on '$destroy', () -> scope.$on '$destroy', () ->
if scope.sharejsDoc? if scope.sharejsDoc?
scope.$broadcast('changeEditor')
tearDownSpellCheck() tearDownSpellCheck()
tearDownCursorPosition() tearDownCursorPosition()
detachFromAce(scope.sharejsDoc) detachFromAce(scope.sharejsDoc)
@@ -3,12 +3,12 @@ define [], () ->
constructor: (@$scope, @adapter, @localStorage) -> constructor: (@$scope, @adapter, @localStorage) ->
@$scope.$on 'editorInit', @jumpToPositionInNewDoc @$scope.$on 'editorInit', @jumpToPositionInNewDoc
@$scope.$on 'beforeChangeDocument', () => @$scope.$on 'beforeChangeDocument', @storePositionAndLine
@storeCursorPosition()
@storeFirstVisibleLine()
@$scope.$on 'afterChangeDocument', @jumpToPositionInNewDoc @$scope.$on 'afterChangeDocument', @jumpToPositionInNewDoc
@$scope.$on 'changeEditor', @storePositionAndLine
@$scope.$on "#{@$scope.name}:gotoLine", (e, line, column) => @$scope.$on "#{@$scope.name}:gotoLine", (e, line, column) =>
if line? if line?
setTimeout () => setTimeout () =>
@@ -24,6 +24,10 @@ define [], () ->
@$scope.$on "#{@$scope.name}:clearSelection", (e) => @$scope.$on "#{@$scope.name}:clearSelection", (e) =>
@adapter.clearSelection() @adapter.clearSelection()
storePositionAndLine: () =>
@storeCursorPosition()
@storeFirstVisibleLine()
jumpToPositionInNewDoc: () => jumpToPositionInNewDoc: () =>
@doc_id = @$scope.sharejsDoc?.doc_id @doc_id = @$scope.sharejsDoc?.doc_id
setTimeout () => setTimeout () =>
@@ -4,9 +4,7 @@ define [
App.controller "ShareController", ["$scope", "$modal", "ide", "projectInvites", "projectMembers", "event_tracking", App.controller "ShareController", ["$scope", "$modal", "ide", "projectInvites", "projectMembers", "event_tracking",
($scope, $modal, ide, projectInvites, projectMembers, event_tracking) -> ($scope, $modal, ide, projectInvites, projectMembers, event_tracking) ->
$scope.openShareProjectModal = (isAdmin) -> $scope.openShareProjectModal = (isAdmin) ->
if !isAdmin $scope.isAdmin = isAdmin;
return
event_tracking.sendMBOnce "ide-open-share-modal-once" event_tracking.sendMBOnce "ide-open-share-modal-once"
$modal.open( $modal.open(
-1
View File
@@ -24,7 +24,6 @@ define [
"main/affiliations/controllers/UserAffiliationsController" "main/affiliations/controllers/UserAffiliationsController"
"main/affiliations/factories/UserAffiliationsDataService" "main/affiliations/factories/UserAffiliationsDataService"
"main/keys" "main/keys"
"main/account-merge-checker"
"main/cms/index" "main/cms/index"
"analytics/AbTestingManager" "analytics/AbTestingManager"
"directives/asyncForm" "directives/asyncForm"
@@ -1,5 +0,0 @@
define [
"base"
], (App) ->
App.controller "AccountMergeCheckerController", ($scope) ->
$scope.hasOlAccount = null
@@ -2,7 +2,7 @@ define [
"base" "base"
"libs/platform" "libs/platform"
], (App, platform) -> ], (App, platform) ->
App.controller 'UniverstiesContactController', ($scope, $modal, $http) -> App.controller 'GroupPlanContactController', ($scope, $modal, $http) ->
$scope.form = {} $scope.form = {}
$scope.sent = false $scope.sent = false
@@ -20,7 +20,7 @@ define [
email: $scope.form.email email: $scope.form.email
labels: "#{$scope.form.source} accounts" labels: "#{$scope.form.source} accounts"
message: "Please contact me with more details" message: "Please contact me with more details"
subject: "#{$scope.form.name} - General Enquiry - #{$scope.form.position} - #{$scope.form.university}" subject: "#{$scope.form.name} - Group Enquiry - #{$scope.form.position} - #{$scope.form.university}"
inbox: "accounts" inbox: "accounts"
request = $http.post "/support", data request = $http.post "/support", data
@@ -2,8 +2,18 @@ define [
"base" "base"
], (App) -> ], (App) ->
App.controller 'LeftHandMenuPromoController', ($scope) -> App.controller 'LeftHandMenuPromoController', ($scope, UserAffiliationsDataService) ->
$scope.hasProjects = window.data.projects.length > 0 $scope.hasProjects = window.data.projects.length > 0
$scope.userHasNoSubscription = window.userHasNoSubscription $scope.userHasNoSubscription = window.userHasNoSubscription
_userHasNoAffiliation = () ->
$scope.userEmails = []
$scope.userAffiliations = []
UserAffiliationsDataService.getUserEmails().then (emails) ->
$scope.userEmails = emails
for email in emails
if email.affiliation
$scope.userAffiliations.push email.affiliation
_userHasNoAffiliation()
@@ -0,0 +1 @@
google-site-verification: google4f15e48c48709a75.html
@@ -0,0 +1 @@
google-site-verification: googleef256f97939bd9b7.html
Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 510.2"><title>Shape-Copy</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path id="Shape-Copy" d="M254.85,303.57v-94.9H493.62C497.19,224.74,500,239.8,500,261c0,145.66-97.7,249.23-244.9,249.23C114.29,510.2,0,395.92,0,255.1S114.29,0,255.1,0C324,0,381.63,25.26,425.77,66.58L353.32,137c-18.37-17.35-50.51-37.76-98.21-37.76-84.44,0-153.32,70.15-153.32,156.12S170.66,411.48,255.1,411.48c97.7,0,133.67-67.6,140.31-107.65H254.85Z" fill="#fff"/></g></g></svg>

After

Width:  |  Height:  |  Size: 540 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 510.2"><title>Shape-Copy</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path id="Shape-Copy" d="M254.85,303.57v-94.9H493.62C497.19,224.74,500,239.8,500,261c0,145.66-97.7,249.23-244.9,249.23C114.29,510.2,0,395.92,0,255.1S114.29,0,255.1,0C324,0,381.63,25.26,425.77,66.58L353.32,137c-18.37-17.35-50.51-37.76-98.21-37.76-84.44,0-153.32,70.15-153.32,156.12S170.66,411.48,255.1,411.48c97.7,0,133.67-67.6,140.31-107.65H254.85Z" fill="#4885ED"/></g></g></svg>

After

Width:  |  Height:  |  Size: 543 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><title>Asset 2</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path d="M490.59,233.83C472.22,217,415.33,179.95,367.7,132.31,320.44,85,282.75,27.78,266.17,9.41c-12.55-12.55-19.79-12.55-32.35,0C217.23,27.78,179.56,85,132.3,132.31,84.67,179.95,27.78,218.5,9.41,233.83c-12.55,12.55-12.55,19.79,0,32.35C27.78,282.53,85,320.44,132.31,367.7c47.64,47.64,84.94,104.52,101.52,122.89,12.55,12.55,19.79,12.55,32.35,0,16.36-18.37,53.88-75.25,101.52-122.89,47.26-47.26,104.9-86.41,122.89-101.52C503.14,253.62,503.14,246.38,490.59,233.83Zm-56.89,29.12c-5.49,7.32-40.07,53.66-80.71,94.12l0,0C312.49,397.74,271.34,428.48,264,434l-.13.08c-7.59,6.29-20.19,6-27.94,0-7.32-5.49-50.39-38.15-90.85-78.79l0,0c-40.65-40.46-73.31-83.53-78.79-90.85h0c-6.29-7.59-6-20.19,0-27.93,5.49-7.32,38.15-50.39,78.79-90.85l0,0c40.46-40.65,83.53-73.31,90.85-78.79,7.74-6,20.35-6.29,27.93,0h0c7.32,5.49,50.39,38.15,90.85,78.79l0,0c40.65,40.46,73.39,84.8,78.88,92.12v0C440,245.32,439.71,255.21,433.69,262.95Z" fill="#fff"/><path d="M420.46,238.64c-5.09-6.79-35.48-47.94-73.2-85.49l0,0c-37.55-37.72-77.52-68-84.31-73.12h0l-.14-.13c-7-5.84-18.6-5.45-25.79.14-6.79,5.09-46.76,35.4-84.31,73.12l0,0c-37.72,37.55-68,77.52-73.12,84.31-5.59,7.18-5.84,18.88,0,25.92h0c5.09,6.79,35.4,46.76,73.12,84.31l0,0c37.55,37.72,77.52,68,84.31,73.12,7.18,5.59,18.88,5.84,25.92,0l.12-.07a787.78,787.78,0,0,0,82.53-71.34l0,0c37.72-37.55,69.81-80.56,74.9-87.35,5.59-7.18,5.84-16.36,0-23.4ZM249.6,118l27.6,79.64H258.79l3.43,75.83c-9.18,1.59-15.89,1.27-24.9-.14l3.27-75.69h-18.8Zm-16,253.83,3.37-77.95c5.31.89,21.4.77,26.72-.06l3.54,78C258.34,374.22,242,373.81,233.55,371.86Zm11.64-82.5c-57.38,0-103.78-18.47-103.89-41.25-.08-15.63,21.78-29.23,54.16-36.22l24.65,15.87c-25.23,4.53-42.51,13.77-42.51,24.43,0,15.13,34.84,27.4,77.81,27.4,36.91,0,78.35-12.27,78.35-27.4-.19-9.53-11.23-15.05-21.94-20.39l-14.7,3.36-7.75-21.39,47.72,11.66-10.77,2.74s18.21,9.43,22.09,20C356,271,306.22,289.35,245.18,289.35Z" fill="#fff"/><path d="M335.42,444.59c-9.3,0-17.58,6.44-17.58,17.49s8.27,17.54,17.58,17.54,17.53-6.47,17.53-17.54S344.66,444.59,335.42,444.59Zm0,31v-.05c-6.94.05-12.54-5.34-12.54-13.43s5.6-13.41,12.54-13.41c6.78,0,12.55,5.36,12.55,13.41S342.19,475.56,335.42,475.56Zm7.83-17.22c0-4.32-2.85-5.68-7.73-5.68h-7v18.85h4v-7.92h1.92l4.34,7.92h4.81l-4.86-8.27C341.24,463,343.25,461.79,343.25,458.34Zm-7.15,2H332.5v-4.55h2.84c1.54,0,3.62.15,3.62,2.11S337.9,460.38,336.1,460.38Z" fill="#fff" fill-rule="evenodd"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><title>Asset 1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path d="M289.06,181.64H242.77V336.72h47.85c68.16,0,83.79-51.76,83.79-77.54C374.41,217.19,347.66,181.64,289.06,181.64Z" fill="#fff"/><path d="M250,0C111.91,0,0,111.91,0,250S111.91,500,250,500,500,388.09,500,250,388.09,0,250,0ZM168.55,249V363.67H138.48V154.49h30.08Zm-15-118.36a19.73,19.73,0,1,1,19.73-19.73A19.8,19.8,0,0,1,153.52,130.66Zm140.82,233.2H212.7V154.49h81.25c77.34,0,111.33,55.27,111.33,104.69C405.27,312.89,363.28,363.87,294.34,363.87Z" fill="#fff"/></g></g></svg>

After

Width:  |  Height:  |  Size: 634 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500"><title>Asset 1</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path d="M289.06,181.64H242.77V336.72h47.85c68.16,0,83.79-51.76,83.79-77.54C374.41,217.19,347.66,181.64,289.06,181.64Z" fill="#95ba2e"/><path d="M250,0C111.91,0,0,111.91,0,250S111.91,500,250,500,500,388.09,500,250,388.09,0,250,0ZM168.55,249V363.67H138.48V154.49h30.08Zm-15-118.36a19.73,19.73,0,1,1,19.73-19.73A19.8,19.8,0,0,1,153.52,130.66Zm140.82,233.2H212.7V154.49h81.25c77.34,0,111.33,55.27,111.33,104.69C405.27,312.89,363.28,363.87,294.34,363.87Z" fill="#95ba2e"/></g></g></svg>

After

Width:  |  Height:  |  Size: 640 B

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 430.79"><title>Asset 4</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><circle cx="291.76" cy="172.38" r="11.72" fill="#fff"/><path d="M307.11,310.48a4.71,4.71,0,0,0-5.89-3.08c-12.93,4-29.57,6.22-46.52,6.56V257c0-.14-.07-.25-.08-.39,14.13-4.8,43.14-26.44,37.62-37.14-2.48-4.81-21.73-5.28-42.35-5.41-20.43-.13-39.65.8-42.06,5.61-5.81,11.62,22.39,32.84,37.49,37.13V314c-17-.35-33.6-2.52-46.52-6.58a4.72,4.72,0,0,0-2.82,9h0c15.16,4.76,34.61,7.13,54.07,7.13s38.86-2.37,54-7.11a4.71,4.71,0,0,0,3.06-5.92Z" fill="#fff"/><circle cx="208.27" cy="172.38" r="11.72" fill="#fff"/><path d="M249.29,404.89c2.88,9.34,5.7,19.73,7.43,25.28,37.9-.39,72.72-.26,109.1,0-.09-14.67-.93-23.15-1-35.49l19.91,21.41c18.71-36.35,41.83-62.9,48.36-103.75,26.95-17,36.71-51.27,55.46-77.28-9.25-4.64-18.86-5.69-27.55-10,11.42-3.81,27-7,39-11-16.71-26.89-30-52.75-45.65-78.11-7.54-14.6-22.8-18.82-37.65-7.17.35-10.22,15.18-29.16,10.76-37.39-12.87-24-26.86-50.76-41-74.1L357,41.14,368.25.65C340.84,1.34,312,3.08,287,2.16c-19.65-.73-37.79,6.58-41.27,29.92-1.86-10.37-5.19-19.41-18.85-24.18C196.14-2.76,138.19,0,123.25,1.29c5.28,13,6.07,27.18,11.07,39.46L107.79,28.32C96.56,48.9,84,68.33,71.62,86.9c-8.47,16.32-12,31.15,13.72,45-14.26-.49-25-4.33-34.37-4.65C34.13,157.48,15.5,184.43,0,214.7c16.88-.43,33.29,7.54,43.2,7.29h0c-8,5.5-10.43,16.17-22.73,24.61,22.64,19,17.76,60.78,48.78,67.77-.73,5.22-4.71,14.07-2.2,19,14.17,28.1,32.86,51.37,48.65,79.92,9.39-13.15,19.17-17.67,26.48-27.21L137.57,429c17.87.61,35,1.42,52.21,1.74C211.09,431.11,242.32,431.14,249.29,404.89ZM348.57,86.16c4.24-6.39,25.13,3.51,28.49,9.37,2.82,4.91.82,29.09-6.37,29.42-2.86.13-8.14-8.58-13.63-18S347,88.53,348.57,86.16ZM122.66,95.53c3.36-5.85,24.25-15.75,28.49-9.37,1.57,2.37-3.06,11.43-8.5,20.77s-10.77,18.14-13.65,18C121.84,124.62,119.84,100.44,122.66,95.53Zm20,290Zm20.9-86.71,6.21-10.75-43.25-74.92,61.74-106.94H311.73l61.75,106.94-43.26,74.92,6.21,10.76-43.22,74.86H206.78Z" fill="#fff"/></g></g></svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 406.28"><title>Asset 3</title><g id="Layer_2" data-name="Layer 2"><g id="Layer_1-2" data-name="Layer 1"><path d="M157.24,406.28c188.68,0,291.88-156.32,291.88-291.88,0-4.44,0-8.86-.3-13.26A208.72,208.72,0,0,0,500,48a204.76,204.76,0,0,1-58.92,16.14,102.94,102.94,0,0,0,45.1-56.74A205.58,205.58,0,0,1,421,32.34,102.68,102.68,0,0,0,246.22,125.9,291.24,291.24,0,0,1,34.8,18.72,102.66,102.66,0,0,0,66.56,155.66,101.82,101.82,0,0,1,20,142.82v1.3a102.62,102.62,0,0,0,82.3,100.56A102.42,102.42,0,0,1,56,246.44a102.7,102.7,0,0,0,95.84,71.24,205.84,205.84,0,0,1-127.4,44A208.82,208.82,0,0,1,0,360.2a290.42,290.42,0,0,0,157.24,46" fill="#fff"/></g></g></svg>

After

Width:  |  Height:  |  Size: 703 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

File diff suppressed because one or more lines are too long
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

@@ -2,3 +2,4 @@
@import "app/front-chat-widget.less"; @import "app/front-chat-widget.less";
@import "app/ol-chat.less"; @import "app/ol-chat.less";
@import "app/templates-v2.less"; @import "app/templates-v2.less";
@import "app/login-register.less";
@@ -32,7 +32,7 @@
li { li {
display: inline-block; display: inline-block;
margin: 0; margin: 0;
padding: 0 @margin-sm 0 0; padding: 0 @padding-sm @padding-sm 0;
} }
a { a {
font-size: small; font-size: small;
@@ -44,4 +44,16 @@
border-radius: @border-radius-base; border-radius: @border-radius-base;
padding: @padding-sm; padding: @padding-sm;
} }
.figure {
background-color: #ffffff;
border: 1px solid @ol-blue-gray-2;
display: inline-block;
margin: 0 auto @margin-sm 0;
padding: @padding-sm;
.figure-caption {
padding-top: @padding-sm;
font-size: small;
}
}
} }
@@ -4,6 +4,10 @@
including About and Blog including About and Blog
*/ */
.cms-page { .cms-page {
img {
height: auto;
max-width: 100%;
}
.btn-description { .btn-description {
margin-right: @margin-sm; margin-right: @margin-sm;
} }
@@ -85,4 +89,57 @@
margin-bottom: @margin-sm; margin-bottom: @margin-sm;
} }
} }
/*
universities page
*/
#universities-container {
display: table;
padding: @padding-md;
width: 100%;
.row {
display: table-row;
div {
border-bottom: 1px solid @gray-lightest;
display: table-cell;
float: none;
padding: @padding-md;
vertical-align: middle;
}
}
.row:first-child {
// parent container contains padding
div {
padding-top: 0;
}
}
.row:last-child {
// parent container contains padding
div {
border: 0;
padding-bottom: 0;
}
}
p {
margin: 0 auto;
width: 100%;
}
// Logos
.uni-logo {
margin: 0 auto;
max-height: 55px;
min-width: 55px;
}
.university-claim-btn {
text-align: center;
}
}
/*
videos
*/
video {
height: auto;
max-width: 100%;
}
} }
@@ -0,0 +1,9 @@
/*
Styling for content pages
Including: about, home, blog, /for/__, legal, contact, portals
*/
.content-page {
a:not(.btn) {
color: @link-color-alt;
}
}
@@ -30,6 +30,13 @@
} }
} }
.public-access-level.public-access-level--notice {
background-color: @gray-lightest;
border-bottom: none;
margin-top: @margin-md;
padding-top: @margin-md;
}
.project-member, .project-invite { .project-member, .project-invite {
&:hover { &:hover {
background-color: @gray-lightest; background-color: @gray-lightest;
@@ -72,17 +72,6 @@
background-color: @toolbar-btn-active-bg-color; background-color: @toolbar-btn-active-bg-color;
box-shadow: @toolbar-btn-active-shadow; box-shadow: @toolbar-btn-active-shadow;
} }
&.btn-full-height-disabled {
opacity: 0.65;
&:hover,
&.active,
&:active {
text-shadow: none;
background-color: transparent;
color: @toolbar-btn-color;
box-shadow: none;
}
}
.label { .label {
top: 4px; top: 4px;
right: 4px; right: 4px;
@@ -13,9 +13,6 @@
h3 { h3 {
margin: 0; margin: 0;
} }
p {
color: @gray
}
i { i {
color: lighten(@blue, 15%); color: lighten(@blue, 15%);
} }
@@ -1,3 +1,5 @@
@register-v-spacing: 20px;
.deprecated-sl-masthead { .deprecated-sl-masthead {
display: inline-block; display: inline-block;
margin-top: @header-height; margin-top: @header-height;
@@ -38,15 +40,12 @@
} }
.masthead { .masthead {
display: inline-block; background-image: -webkit-linear-gradient(to left, rgba(79,156,69,1.0), rgba(28,91,38,1.0));
width: 100%; background-image: linear-gradient(to left, rgba(79,156,69,1.0), rgba(28,91,38,1.0));
background-color: rgba(0,0,0,0.85);
background-image: -webkit-linear-gradient(left,rgba(153,93,179,0.6),rgba(44,155,219,0.6));
background-image: linear-gradient(to right,rgba(153,93,179,0.6),rgba(44,155,219,0.6));
position: relative; position: relative;
text-align: center; text-align: center;
overflow: hidden; overflow: hidden;
margin-top: @header-height; padding-top: @header-height;
h1, p, label { h1, p, label {
color: white; color: white;
text-align: center; text-align: center;
@@ -70,10 +69,13 @@
text-rendering: auto; text-rendering: auto;
margin-bottom: @line-height-computed; margin-bottom: @line-height-computed;
} }
label {
display: block;
}
.register-banner { .register-banner {
background-image: -webkit-linear-gradient(top,rgba(0,0,0,0.7),rgba(0,0,0,0.9)); background-image: -webkit-linear-gradient(top,rgba(0,0,0,0.7),rgba(0,0,0,0.9));
background-image: linear-gradient(to bottom,rgba(0,0,0,0.7),rgba(0,0,0,0.9)); background-image: linear-gradient(to bottom,rgba(0,0,0,0.7),rgba(0,0,0,0.9));
padding: @line-height-computed 0; padding: @register-v-spacing 0;
position: absolute; position: absolute;
bottom: 0; bottom: 0;
width: 100%; width: 100%;
@@ -83,13 +85,17 @@
font-family: @font-family-sans-serif; font-family: @font-family-sans-serif;
font-weight: 500; font-weight: 500;
letter-spacing: 1px; letter-spacing: 1px;
margin-bottom: @register-v-spacing;
} }
.form-group { .form-group {
margin-left: @line-height-computed / 2; margin-left: @line-height-computed / 2;
} }
.input-lg {
border-radius: 9999px;
}
} }
.screenshot { .screenshot {
height: 550px; height: 600px;
margin: auto; margin: auto;
margin-bottom: -50px; margin-bottom: -50px;
overflow-y: hidden; overflow-y: hidden;
@@ -97,7 +103,7 @@
max-width: 960px; max-width: 960px;
.img { .img {
max-width: 960px; max-width: 960px;
background-image: url('/img/screen.png'); background-image: url('/img/homepage.png');
background-size: 100%; background-size: 100%;
background-repeat: no-repeat; background-repeat: no-repeat;
margin: auto; margin: auto;
@@ -111,38 +117,75 @@
only screen and ( min-resolution: 192dpi), only screen and ( min-resolution: 192dpi),
only screen and ( min-resolution: 2dppx) { only screen and ( min-resolution: 2dppx) {
.img { .img {
background-image: url('/img/screen@2x.png'); background-image: url('/img/homepage@2x.png');
}
}
}
.btn-hero {
background-color: transparent;
border-color: white;
border-width: 3px;
color: white;
margin-right: @line-height-computed / 2;
&:hover {
background-color: white;
color: @gray-dark;
}
&.btn-primary {
border-color: lighten(@link-color, 10%);
color: lighten(@link-color, 10%);
&:hover {
background-color: lighten(@link-color, 10%);
color: white;
} }
} }
} }
} }
.hp-register-external-separator {
margin: 0 0 (@register-v-spacing / 2);
color: #FFF;
}
.hp-register-external-separator-or {
vertical-align: middle;
&::before,
&::after {
content: "";
display: inline-block;
vertical-align: middle;
width: 5em;
height: 1px;
background-color: rgba(255, 255, 255, .3);
}
&::before {
margin-right: 1.25em;
}
&::after {
margin-left: 1.25em;
}
}
.hp-register-newsletter-checkbox {
font-size: 90%;
& > .checkbox-newsletter {
text-align: left;
@media only screen and (min-width: @screen-sm-min) {
text-align: center;
}
}
}
.hp-login-btn {
min-width: 220px;
}
.hp-register-form-email-pwd {
position: relative;
}
.hp-register-form-email-pwd-btn-container.form-group {
display: block;
margin-top: (@register-v-spacing / 2);
@media only screen and (min-width: @screen-md-min) {
position: absolute;
display: inline-block;
height: 100%;
top: -(@register-v-spacing / 2);
& > .btn-hero {
height: 100%;
}
}
}
.universities { .universities {
padding-bottom: @line-height-computed; border-bottom: 1px solid @gray-lighter;
text-align: center; text-align: center;
margin: 0 auto; .uni-logo {
img { display: inline-block;
max-width: 100%; padding: 0 @padding-md;
max-height: 60px; width: 20%;
} }
} }
@@ -152,6 +195,12 @@
border-bottom: 1px solid @gray-lightest; border-bottom: 1px solid @gray-lightest;
} }
.pattern-grid {
background: url('/img/grid.png') repeat @content-alt-bg-color;
border-top: 1px solid @gray-lighter;
border-bottom: 1px solid @gray-lighter;
}
.real-time-example { .real-time-example {
.cursor { .cursor {
background-color: hsl(200, 70%, 70%); background-color: hsl(200, 70%, 70%);
@@ -212,3 +261,9 @@
} }
box-shadow: 0 3px 5px rgba(0,0,0,.3); box-shadow: 0 3px 5px rgba(0,0,0,.3);
} }
@media only screen and (max-width: @screen-sm-max) {
.doc-history-example {
margin-bottom: @margin-md;
}
}
@@ -0,0 +1,195 @@
@brand-ieee-color : #00629B;
@brand-google-color : #4885ED;
@brand-twitter-color : #1DA1F2;
@brand-orcid-color : #A6CE39;
@brand-sharelatex-color : #A93529;
.make-login-register-brand-btn(@bg-color) {
background-color: @bg-color;
text-indent: -10px;
padding-left: 0;
padding-right: 0;
&:focus,
&:hover {
background-color: darken(@bg-color, 8%);
}
&:active {
background-color: darken(@bg-color, 16%);
}
}
.make-login-register-brand-btn-alt(@color) {
background-color: #FFF;
color: @color;
text-indent: -10px;
padding-left: 0;
padding-right: 0;
&:focus,
&:hover {
background-color: #FFF;
color: darken(@color, 8%);
}
&:active {
background-color: #FFF;
color: darken(@color, 16%);
}
}
.login-register-container {
max-width: 400px;
margin: 0 auto;
}
.login-register-header {
padding-top: @line-height-computed;
padding-bottom: @line-height-computed - 5;
border-bottom: solid 1px @hr-border;
}
.login-register-header-heading {
margin: 0;
color: @text-color;
}
.login-register-card {
padding-top: 0;
padding-bottom: 0;
text-align: center;
}
.login-register-form,
.login-register-sharelatex {
padding: @line-height-computed;
border-bottom: solid 1px @hr-border;
&:last-child {
border-bottom-width: 0;
}
}
.login-register-other-links {
padding: @line-height-computed;
}
.login-register-text,
.login-register-hr-text-container {
margin: 0;
line-height: 1;
font-size: 90%;
}
.login-register-text {
padding-bottom: 25px;
&:last-child {
padding-bottom: 0;
}
}
.login-register-hr-text-container {
position: relative;
padding: @line-height-computed 0;
&::before {
content: '';
position: absolute;
height: 1px;
background-color: @hr-border;
top: 50%;
left: 0;
right: 0;
}
}
.login-register-hr-text {
position: relative;
background-color: #FFF;
padding: 0 (@line-height-computed / 2);
}
.login-register-sharelatex-tooltip {
display: inline-block;
font-size: 135%;
position: relative;
top: 2px;
margin-left: 3px;
color: @link-color;
cursor: pointer;
}
.login-register-newsletter {
font-size: 90%;
margin-top: @line-height-computed;
text-align: left;
& > .checkbox {
margin-bottom: 0;
}
}
.login-btn-ieee {
.make-login-register-brand-btn(@brand-ieee-color);
}
.login-btn-google {
.make-login-register-brand-btn(@brand-google-color);
}
.login-btn-google-alt {
.make-login-register-brand-btn-alt(@brand-google-color);
}
.login-btn-twitter {
.make-login-register-brand-btn(@brand-twitter-color);
}
.login-btn-orcid {
.make-login-register-brand-btn(@brand-orcid-color);
}
.login-btn-orcid-alt {
.make-login-register-brand-btn-alt(darken(@brand-orcid-color, 6%));
}
.login-btn-sharelatex {
.make-login-register-brand-btn(@brand-sharelatex-color);
}
.login-btn-icon {
display: inline-block;
float: left;
margin-left: 5px;
background: url(/img/brand/lion.svg) center/contain no-repeat;
width: 24px;
&::before {
content: '\00a0'; // Non-breakable space. A non-breakable character here makes this icon work like font-awesome.
}
}
.login-btn-icon-ieee {
background-image: url(/img/other-brands/logo_ieee.svg);
}
.login-btn-icon-google {
background-image: url(/img/other-brands/logo_google.svg);
}
.login-btn-icon-google-alt {
background-image: url(/img/other-brands/logo_google_alt.svg);
}
.login-btn-icon-twitter {
background-image: url(/img/other-brands/logo_twitter.svg);
}
.login-btn-icon-orcid {
background-image: url(/img/other-brands/logo_orcid.svg);
}
.login-btn-icon-orcid-alt {
background-image: url(/img/other-brands/logo_orcid_alt.svg);
}
.login-btn-icon-sharelatex {
background-image: url(/img/other-brands/logo_sharelatex.svg);
}
.login-register-header-heading {
margin: 0;
color: @text-color;
}
.registration-message-heading {
color: @text-color;
}
.registration-message-details {
font-size: 90%;
}
@@ -77,17 +77,7 @@
} }
} }
.circle-img { .circle-img {
border-radius: 50%;
float: right; float: right;
height: 100px;
overflow: hidden;
position: relative;
width: 100px;
img {
display: inline;
margin: 0 auto;
width: 100%;
}
} }
.faq { .faq {
p { p {
@@ -100,52 +100,10 @@
} }
// End Print // End Print
/*
Begin Tabs
*/
.nav-tabs { .nav-tabs {
// Overrides for nav.less
background-color: @ol-blue-gray-0; background-color: @ol-blue-gray-0;
border: 0!important;
margin-bottom: @margin-md;
margin-top: -@line-height-computed; //- adjusted for portal-name
padding: @padding-lg 0 @padding-md;
text-align: center;
a {
color: @link-color;
&:hover {
background-color: transparent!important;
border: 0!important;
color: @link-hover-color!important;
}
}
li {
display: inline-block;
float: none;
a {
border: 0;
}
}
li.active > a {
background-color: transparent!important;
border: 0;
border-bottom: 1px solid @accent-color-secondary!important;
color: @accent-color-secondary;
&:hover {
color: @accent-color-secondary!important;
}
}
} }
.tab-content:extend(.container) {
background-color: transparent!important;
border: none!important;
}
// End Tabs
@media (max-width: @screen-size-sm-max) { @media (max-width: @screen-size-sm-max) {
.content-pull { .content-pull {
padding: 0; padding: 0;
@@ -16,100 +16,81 @@
} }
.cta-links { .cta-links {
margin-top: @margin-lg; margin-bottom: @margin-md;
.cta-link.btn { .cta-link.btn {
margin: 0 @margin-sm; margin-right: @margin-sm;
} }
} }
.tag-link { .tag-link {
margin-right: @margin-xs; margin: 0 @margin-xs @margin-sm 0;
}
.templates-container {
column-count: 3;
column-gap: 1em;
} }
.template-thumbnail { .template-thumbnail {
&.template-thumbnail__container {
display: inline-block;
margin: 0 0 1em;
width: 100%;
}
.thumbnail { .thumbnail {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
-webkit-box-shadow:0 2px 4px rgba(0, 0, 0, 0.1);
height: 465px;
margin: 5% 0; margin: 5% 0;
&.thumbnail-tag {
height: 100px;
}
img {
width: 100%;
height: 100%;
}
}
a {
padding:0px; padding:0px;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
h3 { h3 {
color:@link-color; color:@link-color;
margin: 10px 0px 10px 20px; margin: 10px 0px 10px 20px;
} }
display: flex; &.thumbnail-tag {
justify-content: center; height: 100px;
align-items: center; }
} }
.caption { .caption {
// Override ShareLatex template styles // Override ShareLatex template styles
background: none; background: none;
border: none; border: none;
text-align: center; text-align: center;
}
.description { .caption__description {
font-style: italic; font-style: italic;
padding: 5px 0; padding: 5px 0;
text-overflow: ellipsis; .text-overflow();
overflow: hidden; }
white-space: nowrap;
}
.caption__title {
display: inline-block;
max-width: 100%;
.text-overflow();
} }
/* Media Queries */ /* Media Queries */
@media (max-width: @screen-md-min) { @media (max-width: @screen-md-min) {
.thumbnail { .thumbnail {
height: 400px;
width: 300px;
margin: 5% auto; margin: 5% auto;
} }
img {
width: 90%;
}
.caption .description { .caption .description {
padding: 5px 50px; padding: 5px 50px;
} }
} }
} }
.section-preview {
margin-top: @margin-xl;
.abstract {
padding-right:10%;
padding-bottom: 10%;
@media (max-width: @screen-md-min) {
padding: 5%;
}
}
}
.section-tags { .section-tags {
margin-bottom: @margin-xl; margin-bottom: @margin-xl;
margin-top: @margin-xl; margin-top: @margin-md;
} }
.template-large-pdf-preview { .template-large-pdf-preview {
@@ -15,6 +15,7 @@
} }
img { img {
height: auto;
max-width: 100%; max-width: 100%;
} }
@@ -65,6 +65,9 @@
.alert-danger { .alert-danger {
.alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text); .alert-variant(@alert-danger-bg; @alert-danger-border; @alert-danger-text);
} }
.alert-alt {
.alert-variant(@alert-alt-bg; @alert-alt-border; @alert-alt-text);
}
.alert when (@is-overleaf = true) { .alert when (@is-overleaf = true) {
a { a {
@@ -122,6 +122,10 @@
// Button Sizes // Button Sizes
// -------------------------------------------------- // --------------------------------------------------
.btn-xl {
.button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-h2; @line-height-large; @btn-border-radius-large);
}
.btn-lg { .btn-lg {
// line-height: ensure even-numbered height of button next to large input // line-height: ensure even-numbered height of button next to large input
.button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @btn-border-radius-large); .button-size(@padding-large-vertical; @padding-large-horizontal; @font-size-large; @line-height-large; @btn-border-radius-large);
@@ -1,6 +1,6 @@
// Colors // Colors
.icon-accent { .icon-accent {
color: @accent-color-secondary; color: @accent-color-secondary!important;
} }
// Sizes // Sizes
@@ -0,0 +1,17 @@
.circle-img {
border-radius: 50%;
float: left;
// float: right;
height: 100px;
overflow: hidden;
position: relative;
max-width: 100px;
img {
display: inline;
margin: 0 auto;
width: 100%;
}
}
.blockquote-with-img {
margin-left: 115px;
}
@@ -0,0 +1,44 @@
.ol-tabs {
// Overrides for nav.less
.nav-tabs {
border: 0!important;
margin-bottom: @margin-md;
margin-top: -@line-height-computed; //- adjusted for portal-name
padding: @padding-lg 0 @padding-md;
text-align: center;
a {
color: @link-color;
&:hover {
background-color: transparent!important;
border: 0!important;
color: @link-hover-color!important;
}
}
}
li {
display: inline-block;
float: none;
a {
border: 0;
}
}
li.active > a {
background-color: transparent!important;
border: 0!important;
border-bottom: 1px solid @accent-color-secondary!important;
color: @accent-color-secondary!important;
&:hover {
border-bottom: 1px solid @accent-color-secondary!important;
color: @accent-color-secondary!important;
}
}
.tab-content:extend(.container) {
background-color: transparent!important;
border: none!important;
}
}
@@ -17,7 +17,6 @@
//** Link hover color set via `darken()` function. //** Link hover color set via `darken()` function.
@link-hover-color: darken(@link-color, 15%); @link-hover-color: darken(@link-color, 15%);
//== Typography //== Typography
// //
//## Font, line-height, and color for body text, headings, and more. //## Font, line-height, and color for body text, headings, and more.
@@ -82,6 +81,11 @@
@padding-xs-vertical: 1px; @padding-xs-vertical: 1px;
@padding-xs-horizontal: 5px; @padding-xs-horizontal: 5px;
@padding-sm: 10px;
@padding-md: 20px;
@padding-lg: 30px;
@padding-xl: 40px;
@line-height-large: 1.33; @line-height-large: 1.33;
@line-height-small: 1.5; @line-height-small: 1.5;
@@ -586,6 +590,10 @@
@alert-danger-text: @state-danger-text; @alert-danger-text: @state-danger-text;
@alert-danger-border: @state-danger-border; @alert-danger-border: @state-danger-border;
@alert-alt-bg: @state-info-bg;
@alert-alt-text: @state-info-text;
@alert-alt-border: @state-info-border;
//== Progress bars //== Progress bars
// //
@@ -49,8 +49,10 @@
@text-small-color : @ol-type-color; @text-small-color : @ol-type-color;
@text-color : @ol-type-color; @text-color : @ol-type-color;
@link-color : @ol-blue; @link-color : @ol-blue;
@link-color-alt : @ol-green;
@link-active-color : @ol-dark-green; @link-active-color : @ol-dark-green;
@link-hover-color : @ol-dark-blue; @link-hover-color : @ol-dark-blue;
@hr-border : @ol-blue-gray-1;
// Button colors and sizing // Button colors and sizing
@btn-border-width : 0; @btn-border-width : 0;
@@ -82,11 +84,6 @@
// Padding // Padding
@padding-xs-horizontal : 8px; @padding-xs-horizontal : 8px;
@padding-sm: 10px;
@padding-md: 20px;
@padding-lg: 30px;
@padding-xl: 40px;
// Alerts // Alerts
@alert-padding : 15px; @alert-padding : 15px;
@alert-border-radius : @border-radius-base; @alert-border-radius : @border-radius-base;
@@ -108,6 +105,10 @@
@alert-danger-text : #FFF; @alert-danger-text : #FFF;
@alert-danger-border : transparent; @alert-danger-border : transparent;
@alert-alt-bg : @ol-blue-gray-1;
@alert-alt-text : @ol-type-color;
@alert-alt-border: transparent;
// Tags // Tags
@tag-border-radius : 9999px; @tag-border-radius : 9999px;
@@ -8,12 +8,15 @@
@import "_ol_style_includes.less"; @import "_ol_style_includes.less";
@import "components/embed-responsive.less"; @import "components/embed-responsive.less";
@import "components/icons.less"; @import "components/icons.less";
@import "components/images.less";
@import "components/navs-ol.less"; @import "components/navs-ol.less";
@import "components/pagination.less"; @import "components/pagination.less";
@import "components/tabs.less";
// Pages // Pages
@import "app/about.less"; @import "app/about.less";
@import "app/blog-posts.less"; @import "app/blog-posts.less";
@import "app/cms-page.less"; @import "app/cms-page.less";
@import "app/content_page.less";
@import "app/plans-ol.less"; @import "app/plans-ol.less";
@import "app/portals.less"; @import "app/portals.less";
@@ -30,7 +30,13 @@ describe 'Exports', ->
@owner.request { @owner.request {
method: 'POST', method: 'POST',
url: "/project/#{@project_id}/export/#{@brand_variation_id}", url: "/project/#{@project_id}/export/#{@brand_variation_id}",
json: {}, json: true,
body:
title: 'title'
description: 'description'
author: 'author'
license: 'other'
show_source: true
}, (error, response, body) => }, (error, response, body) =>
throw error if error? throw error if error?
expect(response.statusCode).to.equal 200 expect(response.statusCode).to.equal 200
@@ -42,6 +48,12 @@ describe 'Exports', ->
# project details should match # project details should match
expect(project.id).to.equal @project_id expect(project.id).to.equal @project_id
expect(project.rootDocPath).to.equal '/main.tex' expect(project.rootDocPath).to.equal '/main.tex'
# gallery details should match
expect(project.metadata.title).to.equal 'title'
expect(project.metadata.description).to.equal 'description'
expect(project.metadata.author).to.equal 'author'
expect(project.metadata.license).to.equal 'other'
expect(project.metadata.show_source).to.equal true
# version should match what was retrieved from project-history # version should match what was retrieved from project-history
expect(project.historyVersion).to.equal @version expect(project.historyVersion).to.equal @version
# user details should match # user details should match
@@ -0,0 +1,34 @@
should = require('chai').should()
assert = require('chai').assert
async = require("async")
request = require "./helpers/request"
MockV1Api = require "./helpers/MockV1Api"
assertRedirect = (method, path, expectedStatusCode, destination, cb) ->
request[method] path, (error, response) ->
should.not.exist error
response.statusCode.should.equal expectedStatusCode
response.headers.location.should.equal destination
cb()
describe "RedirectUrls", ->
before ->
@timeout(1000)
it 'proxy static URLs', (done) ->
assertRedirect 'get', '/redirect/one', 302, '/destination/one', done
it 'proxy dynamic URLs', (done) ->
assertRedirect 'get', '/redirect/params/42', 302, '/destination/42/params', done
it 'proxy URLs with baseUrl', (done) ->
assertRedirect 'get', '/redirect/base_url', 302, 'https://example.com/destination/base_url', done
it 'proxy URLs with POST with a 307', (done) ->
assertRedirect 'post', '/redirect/get_and_post', 307, '/destination/get_and_post', done
it 'proxy URLs with multiple support methods', (done) ->
assertRedirect 'get', '/redirect/get_and_post', 302, '/destination/get_and_post', done
it 'redirects with query params', (done) ->
assertRedirect 'get', '/redirect/qs?foo=bar&baz[]=qux1&baz[]=qux2', 302, '/destination/qs?foo=bar&baz[]=qux1&baz[]=qux2', done
@@ -415,3 +415,13 @@ describe 'TokenAccess', ->
try_content_access(@other2, @project_id, (response, body) => try_content_access(@other2, @project_id, (response, body) =>
expect(body.privilegeLevel).to.equal false expect(body.privilegeLevel).to.equal false
, done) , done)
describe 'unimported v1 project', ->
it 'should redirect to v1', (done) ->
unimportedV1Token = '123abc'
try_read_and_write_token_access(@owner, unimportedV1Token, (response, body) =>
expect(response.statusCode).to.equal 302
expect(response.headers.location).to.equal(
'http://overleaf.test:5000/123abc'
)
, done)
@@ -58,7 +58,7 @@ class User
@getCsrfToken (error) => @getCsrfToken (error) =>
return callback(error) if error? return callback(error) if error?
@request.post { @request.post {
url: "/login" url: if settings.enableLegacyLogin then "/login/legacy" else "/login"
json: { email, @password } json: { email, @password }
}, callback }, callback
@@ -3,6 +3,7 @@ v1Api =
module.exports = module.exports =
enableSubscriptions: true enableSubscriptions: true
enableLegacyRegistration: true
features: features = features: features =
v1_free: v1_free:
@@ -105,3 +106,25 @@ module.exports =
path: (params) -> "/universities/list/#{params.id}" path: (params) -> "/universities/list/#{params.id}"
'/institutions/domains': { baseUrl: v1Api.url, path: '/university/domains' } '/institutions/domains': { baseUrl: v1Api.url, path: '/university/domains' }
'/proxy/missing/baseUrl': path: '/foo/bar' '/proxy/missing/baseUrl': path: '/foo/bar'
'/proxy/get_and_post': {
methods: ['get', 'post'],
path: '/destination/get_and_post'
}
overleaf:
host: "http://overleaf.test:5000"
redirects:
'/redirect/one': '/destination/one',
'/redirect/get_and_post': {
methods: ['get', 'post'],
url: '/destination/get_and_post'
},
'/redirect/base_url': {
baseUrl: 'https://example.com'
url: '/destination/base_url'
},
'/redirect/params/:id': {
url: (params) -> "/destination/#{params.id}/params"
},
'/redirect/qs': '/destination/qs'
@@ -15,7 +15,7 @@ describe "AuthenticationController", ->
tk.freeze(Date.now()) tk.freeze(Date.now())
@AuthenticationController = SandboxedModule.require modulePath, requires: @AuthenticationController = SandboxedModule.require modulePath, requires:
"./AuthenticationManager": @AuthenticationManager = {} "./AuthenticationManager": @AuthenticationManager = {}
"../User/UserUpdater" : @UserUpdater = {} "../User/UserUpdater" : @UserUpdater = {updateUser:sinon.stub()}
"metrics-sharelatex": @Metrics = { inc: sinon.stub() } "metrics-sharelatex": @Metrics = { inc: sinon.stub() }
"../Security/LoginRateLimiter": @LoginRateLimiter = { processLoginRequest:sinon.stub(), recordSuccessfulLogin:sinon.stub() } "../Security/LoginRateLimiter": @LoginRateLimiter = { processLoginRequest:sinon.stub(), recordSuccessfulLogin:sinon.stub() }
"../User/UserHandler": @UserHandler = {setupLoginData:sinon.stub()} "../User/UserHandler": @UserHandler = {setupLoginData:sinon.stub()}
@@ -137,11 +137,6 @@ describe "EditorHttpController", ->
.calledWith(@project_id) .calledWith(@project_id)
.should.equal true .should.equal true
it "should look up the user", ->
@UserGetter.getUser
.calledWith(@user_id, { isAdmin: true })
.should.equal true
it "should check the privilege level", -> it "should check the privilege level", ->
@AuthorizationManager.getPrivilegeLevelForProject @AuthorizationManager.getPrivilegeLevelForProject
.calledWith(@user_id, @project_id, @token) .calledWith(@user_id, @project_id, @token)

Some files were not shown because too many files have changed in this diff Show More