diff --git a/services/web/app/coffee/Features/Compile/ClsiManager.coffee b/services/web/app/coffee/Features/Compile/ClsiManager.coffee
index 8d5c5b2baa..b0f7a01fa1 100755
--- a/services/web/app/coffee/Features/Compile/ClsiManager.coffee
+++ b/services/web/app/coffee/Features/Compile/ClsiManager.coffee
@@ -7,8 +7,8 @@ ProjectGetter = require("../Project/ProjectGetter")
ProjectEntityHandler = require("../Project/ProjectEntityHandler")
logger = require "logger-sharelatex"
Url = require("url")
-ClsiCookieManager = require("./ClsiCookieManager")()
-NewBackendCloudClsiCookieManager = require("./ClsiCookieManager")("newBackendcloud")
+ClsiCookieManager = require("./ClsiCookieManager")(Settings.apis.clsi?.backendGroupName)
+NewBackendCloudClsiCookieManager = require("./ClsiCookieManager")(Settings.apis.clsi_new?.backendGroupName)
ClsiStateManager = require("./ClsiStateManager")
_ = require("underscore")
async = require("async")
diff --git a/services/web/app/coffee/Features/Compile/CompileController.coffee b/services/web/app/coffee/Features/Compile/CompileController.coffee
index d79eb5d389..8a3f92ac66 100755
--- a/services/web/app/coffee/Features/Compile/CompileController.coffee
+++ b/services/web/app/coffee/Features/Compile/CompileController.coffee
@@ -9,7 +9,7 @@ Settings = require "settings-sharelatex"
AuthenticationController = require "../Authentication/AuthenticationController"
UserGetter = require "../User/UserGetter"
RateLimiter = require("../../infrastructure/RateLimiter")
-ClsiCookieManager = require("./ClsiCookieManager")()
+ClsiCookieManager = require("./ClsiCookieManager")(Settings.apis.clsi?.backendGroupName)
Path = require("path")
module.exports = CompileController =
diff --git a/services/web/app/coffee/Features/Editor/EditorController.coffee b/services/web/app/coffee/Features/Editor/EditorController.coffee
index 846042312d..7dad02bde9 100644
--- a/services/web/app/coffee/Features/Editor/EditorController.coffee
+++ b/services/web/app/coffee/Features/Editor/EditorController.coffee
@@ -163,6 +163,13 @@ module.exports = EditorController =
EditorRealTimeController.emitToRoom project_id, 'compilerUpdated', compiler
callback()
+ setImageName : (project_id, imageName, callback = (err) ->) ->
+ ProjectOptionsHandler.setImageName project_id, imageName, (err) ->
+ return callback(err) if err?
+ logger.log imageName:imageName, project_id:project_id, "setting imageName"
+ EditorRealTimeController.emitToRoom project_id, 'imageNameUpdated', imageName
+ callback()
+
setSpellCheckLanguage : (project_id, languageCode, callback = (err) ->) ->
ProjectOptionsHandler.setSpellCheckLanguage project_id, languageCode, (err) ->
return callback(err) if err?
diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsFeatures.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsFeatures.coffee
index 5c91058146..4ce233406b 100644
--- a/services/web/app/coffee/Features/Institutions/InstitutionsFeatures.coffee
+++ b/services/web/app/coffee/Features/Institutions/InstitutionsFeatures.coffee
@@ -1,4 +1,4 @@
-UserGetter = require '../User/UserGetter'
+InstitutionsGetter = require './InstitutionsGetter'
PlansLocator = require '../Subscription/PlansLocator'
Settings = require 'settings-sharelatex'
logger = require 'logger-sharelatex'
@@ -13,11 +13,10 @@ module.exports = InstitutionsFeatures =
hasLicence: (userId, callback = (error, hasLicence) ->) ->
- UserGetter.getUserFullEmails userId, (error, emailsData) ->
+ InstitutionsGetter.getConfirmedInstitutions userId, (error, institutions) ->
return callback error if error?
- affiliation = emailsData.find (emailData) ->
- licence = emailData.affiliation?.institution?.licence
- emailData.confirmedAt? and licence? and licence != 'free'
+ hasLicence = institutions.some (institution) ->
+ institution.licence and institution.licence != 'free'
- callback(null, !!affiliation)
+ callback(null, hasLicence)
diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsGetter.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsGetter.coffee
new file mode 100644
index 0000000000..e5941dc777
--- /dev/null
+++ b/services/web/app/coffee/Features/Institutions/InstitutionsGetter.coffee
@@ -0,0 +1,14 @@
+UserGetter = require '../User/UserGetter'
+logger = require 'logger-sharelatex'
+
+module.exports = InstitutionsGetter =
+ getConfirmedInstitutions: (userId, callback = (error, institutions) ->) ->
+ UserGetter.getUserFullEmails userId, (error, emailsData) ->
+ return callback error if error?
+
+ confirmedInstitutions = emailsData.filter (emailData) ->
+ emailData.confirmedAt? and emailData.affiliation?.institution?
+ .map (emailData) ->
+ emailData.affiliation?.institution
+
+ callback(null, confirmedInstitutions)
diff --git a/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee b/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee
index 8d7eee43bf..5fb2e00eb7 100644
--- a/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee
+++ b/services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee
@@ -1,37 +1,68 @@
async = require('async')
-Request = require('request')
logger = require 'logger-sharelatex'
Settings = require 'settings-sharelatex'
+crypto = require('crypto')
+Mailchimp = require('mailchimp-api-v3')
+
+if !Settings.mailchimp?.api_key?
+ logger.info "Using newsletter provider: none"
+ mailchimp =
+ request: (opts, cb)-> cb()
+else
+ logger.info "Using newsletter provider: mailchimp"
+ mailchimp = new Mailchimp(Settings.mailchimp?.api_key)
module.exports =
+
subscribe: (user, callback = () ->)->
- if !Settings.markdownmail?
- logger.warn "No newsletter provider configured so not subscribing user"
- return callback()
- logger.log user:user, email:user.email, "trying to subscribe user to the mailing list"
options = buildOptions(user, true)
- Request.post options, (err, response, body)->
- logger.log body:body, user:user, "finished attempting to subscribe the user to the news letter"
+ logger.log options:options, user:user, email:user.email, "trying to subscribe user to the mailing list"
+ mailchimp.request options, (err)->
+ if err?
+ logger.err err:err, "error subscribing person to newsletter"
+ else
+ logger.log user:user, "finished subscribing user to the newsletter"
callback(err)
unsubscribe: (user, callback = () ->)->
- if !Settings.markdownmail?
- logger.warn "No newsletter provider configured so not unsubscribing user"
- return callback()
logger.log user:user, email:user.email, "trying to unsubscribe user to the mailing list"
options = buildOptions(user, false)
- Request.post options, (err, response, body)->
- logger.log err:err, body:body, email:user.email, "compled newsletter unsubscribe attempt"
+ mailchimp.request options, (err)->
+ if err?
+ logger.err err:err, "error unsubscribing person to newsletter"
+ else
+ logger.log user:user, "finished unsubscribing user to the newsletter"
callback(err)
+ changeEmail: (oldEmail, newEmail, callback = ()->)->
+ options = buildOptions({email:oldEmail})
+ delete options.body.status
+ options.body.email_address = newEmail
+ mailchimp.request options, (err)->
+ # if the user has unsubscribed mailchimp will error on email address change
+ if err? and err?.message.indexOf("could not be validated") == -1
+ logger.err err:err, "error changing email in newsletter"
+ return callback(err)
+ else
+ logger.log "finished changing email in the newsletter"
+ return callback()
+
+hashEmail = (email)->
+ crypto.createHash('md5').update(email.toLowerCase()).digest("hex")
+
buildOptions = (user, is_subscribed)->
- options =
- json:
- secret_token: Settings.markdownmail.secret
- name: "#{user.first_name} #{user.last_name}"
- email: user.email
- subscriber_list_id: Settings.markdownmail.list_id
- is_subscribed: is_subscribed
- url: "https://www.markdownmail.io/lists/subscribe"
- timeout: 30 * 1000
- return options
\ No newline at end of file
+ status = if is_subscribed then "subscribed" else "unsubscribed"
+ subscriber_hash = hashEmail(user.email)
+ opts =
+ method: "PUT"
+ path: "/lists/#{Settings.mailchimp?.list_id}/members/#{subscriber_hash}"
+ body:
+ status_if_new: status
+ status: status
+ email_address:user.email
+ merge_fields:
+ FNAME: user.first_name
+ LNAME: user.last_name
+ MONGO_ID:user._id
+ return opts
+
diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee
index bb16855885..f3d12bf5c5 100644
--- a/services/web/app/coffee/Features/Project/ProjectController.coffee
+++ b/services/web/app/coffee/Features/Project/ProjectController.coffee
@@ -49,6 +49,10 @@ module.exports = ProjectController =
jobs.push (callback) ->
editorController.setCompiler project_id, req.body.compiler, callback
+ if req.body.imageName?
+ jobs.push (callback) ->
+ editorController.setImageName project_id, req.body.imageName, callback
+
if req.body.name?
jobs.push (callback) ->
editorController.renameProject project_id, req.body.name, callback
@@ -347,6 +351,7 @@ module.exports = ProjectController =
useV2History: !!project.overleaf?.history?.display
richTextEnabled: Features.hasFeature('rich-text')
showTestControls: req.query?.tc == 'true' || user.isAdmin
+ allowedImageNames: Settings.allowedImageNames || []
timer.done()
_buildProjectList: (allProjects, v1Projects = [])->
diff --git a/services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee
index 410dbf0351..f81c59c399 100644
--- a/services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee
+++ b/services/web/app/coffee/Features/Project/ProjectEditorHandler.coffee
@@ -1,5 +1,5 @@
_ = require("underscore")
-
+Path = require 'path'
module.exports = ProjectEditorHandler =
trackChangesAvailable: false
@@ -20,6 +20,7 @@ module.exports = ProjectEditorHandler =
members: []
invites: invites
tokens: project.tokens
+ imageName: if project.imageName? then Path.basename(project.imageName) else undefined
if !result.invites?
result.invites = []
diff --git a/services/web/app/coffee/Features/Project/ProjectOptionsHandler.coffee b/services/web/app/coffee/Features/Project/ProjectOptionsHandler.coffee
index 0a0b02e40e..456b164683 100644
--- a/services/web/app/coffee/Features/Project/ProjectOptionsHandler.coffee
+++ b/services/web/app/coffee/Features/Project/ProjectOptionsHandler.coffee
@@ -17,6 +17,16 @@ module.exports =
if callback?
callback()
+ setImageName : (project_id, imageName, callback = ()->)->
+ logger.log project_id:project_id, imageName:imageName, "setting the imageName"
+ imageName = imageName.toLowerCase()
+ if ! _.some(settings.allowedImageNames, (allowed) -> imageName is allowed.imageName)
+ return callback()
+ conditions = {_id:project_id}
+ update = {imageName: settings.imageRoot + '/' + imageName}
+ Project.update conditions, update, {}, (err)->
+ if callback?
+ callback()
setSpellCheckLanguage: (project_id, languageCode, callback = ()->)->
logger.log project_id:project_id, languageCode:languageCode, "setting the spell check language"
diff --git a/services/web/app/coffee/Features/User/UserPagesController.coffee b/services/web/app/coffee/Features/User/UserPagesController.coffee
index 2a7ed62d02..5e6ea7d62b 100644
--- a/services/web/app/coffee/Features/User/UserPagesController.coffee
+++ b/services/web/app/coffee/Features/User/UserPagesController.coffee
@@ -68,7 +68,6 @@ module.exports =
shouldAllowEditingDetails: shouldAllowEditingDetails
languages: Settings.languages,
accountSettingsTabActive: true
- showAffiliationsUI: (req.query?.aff == "true") or false
sessionsPage: (req, res, next) ->
user = AuthenticationController.getSessionUser(req)
diff --git a/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee b/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee
index df7fe93218..1291142dab 100644
--- a/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee
+++ b/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee
@@ -1,4 +1,3 @@
-sanitize = require('sanitizer')
User = require("../../models/User").User
UserCreator = require("./UserCreator")
UserGetter = require("./UserGetter")
@@ -54,7 +53,8 @@ module.exports = UserRegistrationHandler =
(cb)-> User.update {_id: user._id}, {"$set":{holdingAccount:false}}, cb
(cb)-> AuthenticationManager.setUserPassword user._id, userDetails.password, cb
(cb)->
- NewsLetterManager.subscribe user, ->
+ if userDetails.subscribeToNewsletter == "true"
+ NewsLetterManager.subscribe user, ->
cb() #this can be slow, just fire it off
], (err)->
logger.log user: user, "registered"
diff --git a/services/web/app/coffee/Features/User/UserUpdater.coffee b/services/web/app/coffee/Features/User/UserUpdater.coffee
index 8d5e3658f9..9ee81c1ca3 100644
--- a/services/web/app/coffee/Features/User/UserUpdater.coffee
+++ b/services/web/app/coffee/Features/User/UserUpdater.coffee
@@ -11,6 +11,7 @@ EmailHelper = require "../Helpers/EmailHelper"
Errors = require "../Errors/Errors"
Settings = require "settings-sharelatex"
request = require 'request'
+NewsletterManager = require "../Newsletter/NewsletterManager"
module.exports = UserUpdater =
updateUser: (query, update, callback = (error) ->) ->
@@ -99,15 +100,21 @@ module.exports = UserUpdater =
setDefaultEmailAddress: (userId, email, callback) ->
email = EmailHelper.parseEmail(email)
return callback(new Error('invalid email')) if !email?
- query = _id: userId, 'emails.email': email
- update = $set: email: email
- @updateUser query, update, (error, res) ->
- if error?
- logger.err error:error, 'problem setting default emails'
+ UserGetter.getUserEmail userId, (error, oldEmail) =>
+ if err?
return callback(error)
- if res.n == 0 # TODO: Check n or nMatched?
- return callback(new Error('Default email does not belong to user'))
- callback()
+ query = _id: userId, 'emails.email': email
+ update = $set: email: email
+ @updateUser query, update, (error, res) ->
+ if error?
+ logger.err error:error, 'problem setting default emails'
+ return callback(error)
+ else if res.n == 0 # TODO: Check n or nMatched?
+ return callback(new Error('Default email does not belong to user'))
+ else
+ NewsletterManager.changeEmail oldEmail, email, callback
+
+
updateV1AndSetDefaultEmailAddress: (userId, email, callback) ->
@updateEmailAddressInV1 userId, email, (error) =>
@@ -152,7 +159,10 @@ module.exports = UserUpdater =
else
return callback new Error("non-success code from v1: #{response.statusCode}")
- confirmEmail: (userId, email, callback) ->
+ confirmEmail: (userId, email, confirmedAt, callback) ->
+ if arguments.length == 3
+ callback = confirmedAt
+ confirmedAt = new Date()
email = EmailHelper.parseEmail(email)
return callback(new Error('invalid email')) if !email?
logger.log {userId, email}, 'confirming user email'
@@ -166,7 +176,7 @@ module.exports = UserUpdater =
'emails.email': email
update =
$set:
- 'emails.$.confirmedAt': new Date()
+ 'emails.$.confirmedAt': confirmedAt
@updateUser query, update, (error, res) ->
return callback(error) if error?
logger.log {res, userId, email}, "tried to confirm email"
diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee
index f48882a634..b590e58f5d 100644
--- a/services/web/app/coffee/router.coffee
+++ b/services/web/app/coffee/router.coffee
@@ -22,7 +22,7 @@ UserPagesController = require('./Features/User/UserPagesController')
DocumentController = require('./Features/Documents/DocumentController')
CompileManager = require("./Features/Compile/CompileManager")
CompileController = require("./Features/Compile/CompileController")
-ClsiCookieManager = require("./Features/Compile/ClsiCookieManager")()
+ClsiCookieManager = require("./Features/Compile/ClsiCookieManager")(Settings.apis.clsi?.backendGroupName)
HealthCheckController = require("./Features/HealthCheck/HealthCheckController")
ProjectDownloadsController = require "./Features/Downloads/ProjectDownloadsController"
FileStoreController = require("./Features/FileStore/FileStoreController")
diff --git a/services/web/app/views/_mixins_links.pug b/services/web/app/views/_mixins_links.pug
new file mode 100644
index 0000000000..bad56ffc25
--- /dev/null
+++ b/services/web/app/views/_mixins_links.pug
@@ -0,0 +1,122 @@
+mixin linkAdvisors(linkText, linkClass, track)
+ //- To Do: verify path
+ - var gaCategory = track && track.category ? track.category : 'All'
+ - var gaAction = track && track.action ? track.action : null
+ - var gaLabel = track && track.label ? track.label : null
+ - var mb = track && track.mb ? 'true' : null
+ - var mbSegmentation = track && track.segmentation ? track.segmentation : null
+ - var trigger = track && track.trigger ? track.trigger : null
+ a(href="/advisors"
+ class=linkClass ? linkClass : ''
+ event-tracking-ga=gaCategory
+ event-tracking=gaAction
+ event-tracking-label=gaLabel
+ event-tracking-trigger=trigger
+ event-tracking-mb=mb
+ event-segmentation=mbSegmentation
+ )
+ | #{linkText ? linkText : 'advisor programme'}
+
+mixin linkBenefits(linkText, linkClass)
+ //- To Do: verify path
+ a(href="/benefits" class=linkClass ? linkClass : '')
+ | #{linkText ? linkText : 'benefits'}
+
+mixin linkBlog(linkText, linkClass, slug)
+ if slug
+ a(href="/blog/#{slug}" class=linkClass ? linkClass : '')
+ | #{linkText ? linkText : 'blog'}
+
+mixin linkContact(linkText, linkClass)
+ a(href="/contact" class=linkClass ? linkClass : '')
+ | #{linkText ? linkText : 'contact'}
+
+mixin linkEducation(linkText, linkClass)
+ //- To Do: verify path
+ a(href="/plans" class=linkClass ? linkClass : '')
+ | #{linkText ? linkText : 'teaching toolkit'}
+
+mixin linkEmail(linkText, linkClass, email)
+ //- To Do: env var?
+ - var emailDomain = 'overleaf.com'
+ a(href="mailto:#{email ? email : 'contact'}@#{emailDomain}" class=linkClass ? linkClass : '')
+ | #{linkText ? linkText : 'email'}
+
+mixin linkInvite(linkText, linkClass, track)
+ - var gaCategory = track && track.category ? track.category : 'All'
+ - var gaAction = track && track.action ? track.action : null
+ - var gaLabel = track && track.label ? track.label : null
+ - var mb = track && track.mb ? 'true' : null
+ - var mbSegmentation = track && track.segmentation ? track.segmentation : null
+ - var trigger = track && track.trigger ? track.trigger : null
+
+ a(href="/user/bonus"
+ class=linkClass ? linkClass : ''
+ event-tracking-ga=gaCategory
+ event-tracking=gaAction
+ event-tracking-label=gaLabel
+ event-tracking-trigger=trigger
+ event-tracking-mb=mb
+ event-segmentation=mbSegmentation
+ )
+ | #{linkText ? linkText : 'invite your friends'}
+
+mixin linkPlansAndPricing(linkText, linkClass)
+ //- To Do: verify path
+ a(href="/plans" class=linkClass ? linkClass : '')
+ | #{linkText ? linkText : 'plans and pricing'}
+
+mixin linkPrintNewTab(linkText, linkClass, icon, track)
+ - var gaCategory = track && track.category ? track.category : null
+ - var gaAction = track && track.action ? track.action : null
+ - var gaLabel = track && track.label ? track.label : null
+ - var mb = track && track.mb ? 'true' : null
+ - var mbSegmentation = track && track.segmentation ? track.segmentation : null
+ - var trigger = track && track.trigger ? track.trigger : null
+
+ a(href='?media=print'
+ class=linkClass ? linkClass : ''
+ event-tracking-ga=gaCategory
+ event-tracking=gaAction
+ event-tracking-label=gaLabel
+ event-tracking-trigger=trigger
+ event-tracking-mb=mb
+ event-segmentation=mbSegmentation
+ target="_BLANK"
+ )
+ if icon
+ i(class="fa fa-print")
+ |
+ | #{linkText ? linkText : 'print'}
+
+mixin linkSignIn(linkText, linkClass)
+ a(href="/login" class=linkClass ? linkClass : '')
+ | #{linkText ? linkText : 'sign in'}
+
+mixin linkSignUp(linkText, linkClass)
+ a(href="/register" class=linkClass ? linkClass : '')
+ | #{linkText ? linkText : 'sign up'}
+
+mixin linkTweet(linkText, linkClass, tweetText, track)
+ //- twitter-share-button is required by twitter
+ - var gaCategory = track && track.category ? track.category : 'All'
+ - var gaAction = track && track.action ? track.action : null
+ - var gaLabel = track && track.label ? track.label : null
+ - var mb = track && track.mb ? 'true' : null
+ - var mbSegmentation = track && track.segmentation ? track.segmentation : null
+ - var trigger = track && track.trigger ? track.trigger : null
+ a(class="twitter-share-button " + linkClass
+ event-tracking-ga=gaCategory
+ event-tracking=gaAction
+ event-tracking-label=gaLabel
+ event-tracking-trigger=trigger
+ event-tracking-mb=mb
+ event-segmentation=mbSegmentation
+ href="https://twitter.com/intent/tweet?text=" + tweetText
+ target="_BLANK"
+ ) #{linkText ? linkText : 'tweet'}
+
+mixin linkUniversities(linkText, linkClass)
+ //- To Do: verify path
+ a(href="/universities" class=linkClass ? linkClass : '')
+ | #{linkText ? linkText : 'universities'}
diff --git a/services/web/app/views/project/editor/left-menu.pug b/services/web/app/views/project/editor/left-menu.pug
index 2cedc0b37e..bbfa4c480c 100644
--- a/services/web/app/views/project/editor/left-menu.pug
+++ b/services/web/app/views/project/editor/left-menu.pug
@@ -188,6 +188,15 @@ aside#left-menu.full-size(
option(value="pdfjs") #{translate("built_in")}
option(value="native") #{translate("native")}
+ if (getSessionUser() && getSessionUser().isAdmin && typeof(allowedImageNames) !== 'undefined' && allowedImageNames.length > 0)
+ .form-controls(ng-show="permissions.write")
+ label(for="imageName") #{translate("TeXLive")}
+ select(
+ name="imageName"
+ ng-model="project.imageName"
+ )
+ each image in allowedImageNames
+ option(value=image.imageName) #{image.imageDesc}
h4 #{translate("hotkeys")}
ul.list-unstyled.nav
diff --git a/services/web/app/views/project/list/modals.pug b/services/web/app/views/project/list/modals.pug
index 2bbde60e7b..aaacf660a6 100644
--- a/services/web/app/views/project/list/modals.pug
+++ b/services/web/app/views/project/list/modals.pug
@@ -345,13 +345,11 @@ script(type="text/ng-template", id="v1ImportModalTemplate")
i.fa.fa-flask
.v1-import-col
h2.v1-import-title #[strong Warning:] Overleaf v2 is Experimental
- p We are still working hard to bring some Overleaf v1 features to the v2 editor. In v2 there is:
+ p We are still working hard to bring some Overleaf v1 features to the v2 editor. In v2:
ul
- li No Journals and Services menu to submit directly to our partners yet
- li No Rich Text (WYSIWYG) mode yet
- li No linked files (to URLs or to files in other Overleaf projects) yet
- li No Zotero and CiteULike integrations yet
- li No labelled versions yet
+ li You may not be able to access all of your Labelled versions yet
+ li There are no Zotero and CiteULike integrations yet
+ li Some Journals and Services in the Submit menu don't support direct submissions yet
p.row-spaced-small
| If you currently use the Overleaf Git bridge with your v1 project, you can migrate your project to the Overleaf v2 GitHub integration.
|
diff --git a/services/web/app/views/user/settings.pug b/services/web/app/views/user/settings.pug
index a43e81c12c..e4da83c1db 100644
--- a/services/web/app/views/user/settings.pug
+++ b/services/web/app/views/user/settings.pug
@@ -9,7 +9,7 @@ block content
.page-header
h1 #{translate("account_settings")}
.account-settings(ng-controller="AccountSettingsController", ng-cloak)
- if locals.showAffiliationsUI && hasFeature('affiliations')
+ if hasFeature('affiliations')
include settings/user-affiliations
form-messages(for="settingsForm")
@@ -22,7 +22,7 @@ block content
h3 #{translate("update_account_info")}
form(async-form="settings", name="settingsForm", method="POST", action="/user/settings", novalidate)
input(type="hidden", name="_csrf", value=csrfToken)
- if !(locals.showAffiliationsUI && hasFeature('affiliations'))
+ if !hasFeature('affiliations')
if !externalAuthenticationSystemUsed()
.form-group
label(for='email') #{translate("email")}
diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee
index 1306ce91c2..832c3855b5 100644
--- a/services/web/config/settings.defaults.coffee
+++ b/services/web/config/settings.defaults.coffee
@@ -135,6 +135,7 @@ module.exports = settings =
url: "http://#{process.env['FILESTORE_HOST'] or 'localhost'}:3009"
clsi:
url: "http://#{process.env['CLSI_HOST'] or 'localhost'}:3013"
+ backendGroupName: undefined
templates:
url: "http://#{process.env['TEMPLATES_HOST'] or 'localhost'}:3007"
githubSync:
@@ -277,10 +278,10 @@ module.exports = settings =
# Third party services
# --------------------
#
- # ShareLaTeX's regular newsletter is managed by Markdown mail. Add your
+ # ShareLaTeX's regular newsletter is managed by mailchimp. Add your
# credentials here to integrate with this.
- # markdownmail:
- # secret: ""
+ # mailchimp:
+ # api_key: ""
# list_id: ""
#
# Fill in your unique token from various analytics services to enable
@@ -472,3 +473,14 @@ module.exports = settings =
autoCompile:
everyone: 100
standard: 25
+
+ # currentImage: "texlive-full:2017.1"
+ # imageRoot: "" # without any trailing slash
+
+ # allowedImageNames: [
+ # {imageName: 'texlive-full:2017.1', imageDesc: 'TeXLive 2017'}
+ # {imageName: 'wl_texlive:2018.1', imageDesc: 'Legacy OL TeXLive 2015'}
+ # {imageName: 'texlive-full:2016.1', imageDesc: 'Legacy SL TeXLive 2016'}
+ # {imageName: 'texlive-full:2015.1', imageDesc: 'Legacy SL TeXLive 2015'}
+ # {imageName: 'texlive-full:2014.2', imageDesc: 'Legacy SL TeXLive 2014.2'}
+ # ]
\ No newline at end of file
diff --git a/services/web/package.json b/services/web/package.json
index ed0eb06455..8b8879375c 100644
--- a/services/web/package.json
+++ b/services/web/package.json
@@ -59,6 +59,7 @@
"lodash": "^4.13.1",
"logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#master",
"lynx": "0.1.1",
+ "mailchimp-api-v3": "^1.12.0",
"marked": "^0.3.5",
"method-override": "^2.3.3",
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.7.1",
@@ -98,7 +99,8 @@
"v8-profiler": "^5.2.3",
"valid-url": "^1.0.9",
"xml2js": "0.2.0",
- "yauzl": "^2.8.0"
+ "yauzl": "^2.8.0",
+ "minimist": "1.2.0"
},
"devDependencies": {
"autoprefixer": "^6.6.1",
diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
index 3be4369213..8fde8595cd 100644
--- a/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
+++ b/services/web/public/coffee/ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager.coffee
@@ -162,7 +162,8 @@ define [
cursorPosition = @editor.getCursorPosition()
end = change.end
{lineUpToCursor, commandFragment} = Helpers.getContext(@editor, end)
- if (i = lineUpToCursor.indexOf('%') > -1 and lineUpToCursor[i-1] != '\\')
+ if ((i = lineUpToCursor.indexOf('%')) > -1 and lineUpToCursor[i-1] != '\\')
+ console.log lineUpToCursor, i
return
lastCharIsBackslash = lineUpToCursor.slice(-1) == "\\"
lastTwoChars = lineUpToCursor.slice(-2)
diff --git a/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee b/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee
index 9920372aab..2e8c09739e 100644
--- a/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee
+++ b/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee
@@ -67,6 +67,11 @@ define [
if oldCompiler? and compiler != oldCompiler
settings.saveProjectSettings({compiler: compiler})
+ $scope.$watch "project.imageName", (imageName, oldImageName) =>
+ return if @ignoreUpdates
+ if oldImageName? and imageName != oldImageName
+ settings.saveProjectSettings({imageName: imageName})
+
$scope.$watch "project.rootDoc_id", (rootDoc_id, oldRootDoc_id) =>
return if @ignoreUpdates
# don't save on initialisation, Angular passes oldRootDoc_id as
@@ -83,6 +88,12 @@ define [
$scope.project.compiler = compiler
delete @ignoreUpdates
+ ide.socket.on "imageNameUpdated", (imageName) =>
+ @ignoreUpdates = true
+ $scope.$apply () =>
+ $scope.project.imageName = imageName
+ delete @ignoreUpdates
+
ide.socket.on "spellCheckLanguageUpdated", (languageCode) =>
@ignoreUpdates = true
$scope.$apply () =>
diff --git a/services/web/public/stylesheets/app/portals.less b/services/web/public/stylesheets/app/portals.less
new file mode 100644
index 0000000000..ae6fcdf230
--- /dev/null
+++ b/services/web/public/stylesheets/app/portals.less
@@ -0,0 +1,161 @@
+.content-portal {
+ padding-top: @navbar-height!important;
+
+ /*
+ Begin Header
+ */
+ .banner-image {
+ background-size: cover;
+ background-position: 50% 50%;
+ background-repeat: no-repeat;
+ height: 375px;
+ }
+
+ .image-fill {
+ display: inline-block;
+ height: 100%;
+ vertical-align: middle;
+ }
+
+ .institution-logo {
+ left: 50%;
+ margin-left: -100px;
+ padding: 0;
+ position: absolute;
+ div {
+ background-color: @white;
+ box-shadow: 1px 11px 22px -9px @black-alpha-strong;
+ display: inline-block;
+ height: 125px;
+ overflow: hidden;
+ position: absolute;
+ text-align: center;
+ top: -110px;
+ white-space: nowrap;
+ width: @btn-portal-width;
+ }
+ img {
+ max-height: 75px;
+ max-width: 150px;
+ vertical-align: middle;
+ }
+ }
+
+ .portal-name {
+ background-color: @ol-blue-gray-0;
+ padding-bottom: @line-height-computed; //- center header when no tabs
+ padding-top: @padding-md;
+ text-align: center;
+ width: 100%;
+ }
+ // End Header
+
+ /*
+ Begin Layout
+ */
+ .button-pull,
+ .content-pull {
+ float: left;
+ }
+
+ .button-pull {
+ text-align: right;
+ > a.btn {
+ white-space: normal;
+ width: @btn-portal-width;
+ text-align: center;
+ }
+ }
+ .content-pull {
+ padding-right: @padding-sm;
+ width: calc(~"100% - "@btn-portal-width);
+ }
+ // End Layout
+
+ /*
+ Begin Card
+ */
+ .card {
+ margin-bottom: @margin-md;
+ }
+ // End Card
+
+ /*
+ Begin Actions
+ */
+ .portal-actions {
+ i {
+ margin-bottom: @margin-sm;
+ }
+ }
+ // End Actions
+
+ /*
+ Begin Print
+ */
+ .print {
+ .hidden-print {
+ display: none;
+ }
+ }
+ // End Print
+
+ /*
+ Begin Tabs
+ */
+ .nav-tabs {
+ // Overrides for nav.less
+ 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) {
+ .content-pull {
+ padding: 0;
+ width: auto;
+ }
+
+ .button-pull {
+ > a.btn {
+ width: auto;
+ }
+ }
+ }
+}
diff --git a/services/web/public/stylesheets/components/embed-responsive.less b/services/web/public/stylesheets/components/embed-responsive.less
new file mode 100644
index 0000000000..997f100449
--- /dev/null
+++ b/services/web/public/stylesheets/components/embed-responsive.less
@@ -0,0 +1,26 @@
+.embed-responsive {
+ display: block;
+ height: 0;
+ overflow: hidden;
+ padding: 0;
+ position: relative;
+}
+.embed-responsive .embed-responsive-item,
+.embed-responsive iframe,
+.embed-responsive embed,
+.embed-responsive object,
+.embed-responsive video {
+ position: absolute;
+ top: 0;
+ bottom: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ border: 0;
+}
+.embed-responsive-16by9 {
+ padding-bottom: 56.25% !important;
+}
+.embed-responsive-4by3 {
+ padding-bottom: 75% !important;
+}
\ No newline at end of file
diff --git a/services/web/public/stylesheets/components/icons.less b/services/web/public/stylesheets/components/icons.less
new file mode 100644
index 0000000000..fa808ad5ae
--- /dev/null
+++ b/services/web/public/stylesheets/components/icons.less
@@ -0,0 +1,9 @@
+// Colors
+.icon-accent {
+ color: @accent-color-secondary;
+}
+
+// Sizes
+.icon-lg {
+ font-size: @font-size-h1;
+}
\ No newline at end of file
diff --git a/services/web/public/stylesheets/core/_common-variables.less b/services/web/public/stylesheets/core/_common-variables.less
index aef1f2b5a2..9112c1400c 100644
--- a/services/web/public/stylesheets/core/_common-variables.less
+++ b/services/web/public/stylesheets/core/_common-variables.less
@@ -62,9 +62,12 @@
//
//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start).
-@margin-sm: 10px;
-@margin-md: 20px;
-@margin-lg: 30px;
+@margin-xs: 5px;
+@margin-sm: 10px;
+@margin-md: 20px;
+@margin-lg: 30px;
+@margin-xl: 40px;
+@margin-xxl: 50px;
@padding-base-vertical: 5px;
@padding-base-horizontal: 16px;
diff --git a/services/web/public/stylesheets/core/ol-variables.less b/services/web/public/stylesheets/core/ol-variables.less
index b75de6086d..e6a20c6ad8 100644
--- a/services/web/public/stylesheets/core/ol-variables.less
+++ b/services/web/public/stylesheets/core/ol-variables.less
@@ -6,6 +6,7 @@
@footer-height: 50px;
// Styleguide colors
+@ol-blue-gray-0 : #f4f5f8;
@ol-blue-gray-1 : #E4E8EE;
@ol-blue-gray-2 : #9DA7B7;
@ol-blue-gray-3 : #5D6879;
@@ -21,6 +22,8 @@
@ol-dark-red : #A6312B;
@ol-type-color : @ol-blue-gray-3;
+@accent-color-primary: @ol-green;
+@accent-color-secondary: @ol-dark-green;
// Navbar customization
@navbar-title-color : @ol-blue-gray-1;
@@ -65,8 +68,14 @@
@btn-info-bg : @ol-blue;
@btn-info-border : transparent;
+// Padding
@padding-xs-horizontal : 8px;
+@padding-sm: 10px;
+@padding-md: 20px;
+@padding-lg: 30px;
+@padding-xl: 40px;
+
// Alerts
@alert-padding : 15px;
@alert-border-radius : @border-radius-base;
@@ -167,6 +176,9 @@
@folders-tag-menu-hover : rgba(0, 0, 0, .1);
@folders-tag-menu-active-hover : rgba(0, 0, 0, .1);
+// Portal
+@btn-portal-width : 200px;
+
// Project table
@structured-list-line-height : 2.5;
@structured-list-link-color : @ol-blue;
@@ -273,6 +285,9 @@
@log-line-no-color : #FFF;
@log-hints-color : @ol-blue-gray-4;
+// Portals
+@black-alpha-strong : rgba(0,0,0,0.8);
+
// v2 History
@history-base-font-size : @font-size-small;
@@ -288,6 +303,12 @@
@history-toolbar-bg-color : @editor-toolbar-bg;
@history-toolbar-color : #FFF;
+// Screens
+// added -size to not conflict with common_variables
+@screen-size-sm-max : 767px;
+@screen-size-md-min : 768px;
+@screen-size-md-max : 991px;
+
// System messages
@sys-msg-background : @ol-blue;
@sys-msg-color : #FFF;
@@ -304,6 +325,7 @@
@gray-light: #a4a4a4;
@gray-lighter: #cfcfcf;
@gray-lightest: #f0f0f0;
+@white: #ffffff;
@blue: #405ebf;
@blueDark: #040D2D;
diff --git a/services/web/public/stylesheets/ol-style.less b/services/web/public/stylesheets/ol-style.less
index 03e2bb3ed6..774e70a2ab 100644
--- a/services/web/public/stylesheets/ol-style.less
+++ b/services/web/public/stylesheets/ol-style.less
@@ -6,8 +6,11 @@
@import "app/ol-style-guide.less";
@import "_style_includes.less";
@import "_ol_style_includes.less";
+@import "components/embed-responsive.less";
+@import "components/icons.less";
// Pages
@import "app/about.less";
@import "app/blog-posts.less";
-@import "app/cms-page.less";
\ No newline at end of file
+@import "app/cms-page.less";
+@import "app/portals.less";
\ No newline at end of file
diff --git a/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee b/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee
index 703a03d14f..f1bf3792a2 100644
--- a/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee
+++ b/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee
@@ -32,6 +32,7 @@ describe "EditorController", ->
'../Project/ProjectEntityUpdateHandler' : @ProjectEntityUpdateHandler = {}
'../Project/ProjectOptionsHandler' : @ProjectOptionsHandler =
setCompiler: sinon.stub().yields()
+ setImageName: sinon.stub().yields()
setSpellCheckLanguage: sinon.stub().yields()
'../Project/ProjectDetailsHandler': @ProjectDetailsHandler =
setProjectDescription: sinon.stub().yields()
@@ -377,6 +378,19 @@ describe "EditorController", ->
.calledWith(@project_id, "compilerUpdated", @compiler)
.should.equal true
+ describe "setImageName", ->
+ beforeEach ->
+ @imageName = "texlive-1234.5"
+ @EditorController.setImageName @project_id, @imageName, @callback
+
+ it "should send the new imageName and project id to the project options handler", ->
+ @ProjectOptionsHandler.setImageName
+ .calledWith(@project_id, @imageName)
+ .should.equal true
+ @EditorRealTimeController.emitToRoom
+ .calledWith(@project_id, "imageNameUpdated", @imageName)
+ .should.equal true
+
describe "setSpellCheckLanguage", ->
beforeEach ->
@languageCode = "fr"
diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsFeaturesTests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsFeaturesTests.coffee
index 2304f2e5b7..8811f72c35 100644
--- a/services/web/test/unit/coffee/Institutions/InstitutionsFeaturesTests.coffee
+++ b/services/web/test/unit/coffee/Institutions/InstitutionsFeaturesTests.coffee
@@ -8,11 +8,11 @@ modulePath = require('path').join __dirname, '../../../../app/js/Features/Instit
describe 'InstitutionsFeatures', ->
beforeEach ->
- @UserGetter = getUserFullEmails: sinon.stub()
+ @InstitutionsGetter = getConfirmedInstitutions: sinon.stub()
@PlansLocator = findLocalPlanInSettings: sinon.stub()
@institutionPlanCode = 'institution_plan_code'
@InstitutionsFeatures = SandboxedModule.require modulePath, requires:
- '../User/UserGetter': @UserGetter
+ './InstitutionsGetter': @InstitutionsGetter
'../Subscription/PlansLocator': @PlansLocator
'settings-sharelatex': institutionPlanCode: @institutionPlanCode
'logger-sharelatex':
@@ -23,47 +23,37 @@ describe 'InstitutionsFeatures', ->
describe "hasLicence", ->
it 'should handle error', (done)->
- @UserGetter.getUserFullEmails.yields(new Error('Nope'))
+ @InstitutionsGetter.getConfirmedInstitutions.yields(new Error('Nope'))
@InstitutionsFeatures.hasLicence @userId, (error, hasLicence) ->
expect(error).to.exist
done()
- it 'should return false if user has no affiliations', (done) ->
- @UserGetter.getUserFullEmails.yields(null, [])
- @InstitutionsFeatures.hasLicence @userId, (error, hasLicence) ->
- expect(error).to.not.exist
- expect(hasLicence).to.be.false
- done()
-
it 'should return false if user has no confirmed affiliations', (done) ->
- affiliations = [
- { confirmedAt: null, affiliation: institution: { licence: 'pro_plus' } }
- ]
- @UserGetter.getUserFullEmails.yields(null, affiliations)
+ institutions = []
+ @InstitutionsGetter.getConfirmedInstitutions.yields(null, institutions)
@InstitutionsFeatures.hasLicence @userId, (error, hasLicence) ->
expect(error).to.not.exist
expect(hasLicence).to.be.false
done()
it 'should return false if user has no paid affiliations', (done) ->
- affiliations = [
- { confirmedAt: new Date(), affiliation: institution: { licence: 'free' } }
+ institutions = [
+ { licence: 'free' }
]
- @UserGetter.getUserFullEmails.yields(null, affiliations)
+ @InstitutionsGetter.getConfirmedInstitutions.yields(null, institutions)
@InstitutionsFeatures.hasLicence @userId, (error, hasLicence) ->
expect(error).to.not.exist
expect(hasLicence).to.be.false
done()
it 'should return true if user has confirmed paid affiliation', (done)->
- affiliations = [
- { confirmedAt: new Date(), affiliation: institution: { licence: 'pro_plus' } }
- { confirmedAt: new Date(), affiliation: institution: { licence: 'free' } }
- { confirmedAt: null, affiliation: institution: { licence: 'pro' } }
- { confirmedAt: null, affiliation: institution: { licence: null } }
- { confirmedAt: new Date(), affiliation: institution: {} }
+ institutions = [
+ { licence: 'pro_plus' }
+ { licence: 'free' }
+ { licence: 'pro' }
+ { licence: null }
]
- @UserGetter.getUserFullEmails.yields(null, affiliations)
+ @InstitutionsGetter.getConfirmedInstitutions.yields(null, institutions)
@InstitutionsFeatures.hasLicence @userId, (error, hasLicence) ->
expect(error).to.not.exist
expect(hasLicence).to.be.true
diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsGetterTests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsGetterTests.coffee
new file mode 100644
index 0000000000..62c57cdaa9
--- /dev/null
+++ b/services/web/test/unit/coffee/Institutions/InstitutionsGetterTests.coffee
@@ -0,0 +1,44 @@
+SandboxedModule = require('sandboxed-module')
+require('chai').should()
+expect = require('chai').expect
+sinon = require('sinon')
+modulePath = require('path').join __dirname, '../../../../app/js/Features/Institutions/InstitutionsGetter.js'
+
+describe 'InstitutionsGetter', ->
+ beforeEach ->
+ @UserGetter = getUserFullEmails: sinon.stub()
+ @InstitutionsGetter = SandboxedModule.require modulePath, requires:
+ '../User/UserGetter': @UserGetter
+ 'logger-sharelatex':
+ log:-> console.log(arguments)
+ err:->
+
+ @userId = '12345abcde'
+
+ describe "getConfirmedInstitutions", ->
+ it 'filters unconfirmed emails', (done) ->
+ @userEmails = [
+ { confirmedAt: null, affiliation: institution: { id: 123 } }
+ { confirmedAt: new Date(), affiliation: institution: { id: 456 } }
+ { confirmedAt: new Date(), affiliation: null }
+ { confirmedAt: new Date(), affiliation: institution: null }
+ ]
+ @UserGetter.getUserFullEmails.yields(null, @userEmails)
+ @InstitutionsGetter.getConfirmedInstitutions @userId, (error, institutions) ->
+ expect(error).to.not.exist
+ institutions.length.should.equal 1
+ institutions[0].id.should.equal 456
+ done()
+
+ it 'should handle empty response', (done) ->
+ @UserGetter.getUserFullEmails.yields(null, [])
+ @InstitutionsGetter.getConfirmedInstitutions @userId, (error, institutions) ->
+ expect(error).to.not.exist
+ institutions.length.should.equal 0
+ done()
+
+ it 'should handle error', (done) ->
+ @UserGetter.getUserFullEmails.yields(new Error('Nope'))
+ @InstitutionsGetter.getConfirmedInstitutions @userId, (error, institutions) ->
+ expect(error).to.exist
+ done()
diff --git a/services/web/test/unit/coffee/Project/ProjectControllerTests.coffee b/services/web/test/unit/coffee/Project/ProjectControllerTests.coffee
index 1acd1c8128..46e54501c9 100644
--- a/services/web/test/unit/coffee/Project/ProjectControllerTests.coffee
+++ b/services/web/test/unit/coffee/Project/ProjectControllerTests.coffee
@@ -145,6 +145,18 @@ describe "ProjectController", ->
done()
@ProjectController.updateProjectSettings @req, @res
+ it "should update the imageName", (done) ->
+ @EditorController.setImageName = sinon.stub().callsArg(2)
+ @req.body =
+ imageName: @imageName = "texlive-1234.5"
+ @res.sendStatus = (code) =>
+ @EditorController.setImageName
+ .calledWith(@project_id, @imageName)
+ .should.equal true
+ code.should.equal 204
+ done()
+ @ProjectController.updateProjectSettings @req, @res
+
it "should update the spell check language", (done) ->
@EditorController.setSpellCheckLanguage = sinon.stub().callsArg(2)
@req.body =
diff --git a/services/web/test/unit/coffee/Project/ProjectOptionsHandlerTests.coffee b/services/web/test/unit/coffee/Project/ProjectOptionsHandlerTests.coffee
index 825822c236..435dba9dbc 100644
--- a/services/web/test/unit/coffee/Project/ProjectOptionsHandlerTests.coffee
+++ b/services/web/test/unit/coffee/Project/ProjectOptionsHandlerTests.coffee
@@ -19,6 +19,11 @@ describe 'creating a project', ->
{name: "English", code: "en"}
{name: "French", code: "fr"}
]
+ imageRoot: "docker-repo/subdir"
+ allowedImageNames: [
+ {imageName: "texlive-0000.0", imageDesc: "test image 0"}
+ {imageName: "texlive-1234.5", imageDesc: "test image 1"}
+ ]
'logger-sharelatex':
log:->
err:->
@@ -37,6 +42,19 @@ describe 'creating a project', ->
@projectModel.update.called.should.equal false
done()
+ describe 'Setting the imageName', ->
+ it 'should perform and update on mongo', (done)->
+ @handler.setImageName project_id, "texlive-1234.5", (err)=>
+ args = @projectModel.update.args[0]
+ args[0]._id.should.equal project_id
+ args[1].imageName.should.equal "docker-repo/subdir/texlive-1234.5"
+ done()
+ @projectModel.update.args[0][3]()
+
+ it 'should not perform and update on mongo if it is not a reconised compiler', (done)->
+ @handler.setImageName project_id, "something", (err)=>
+ @projectModel.update.called.should.equal false
+ done()
describe "setting the spellCheckLanguage", ->
diff --git a/services/web/test/unit/coffee/User/UserRegistrationHandlerTests.coffee b/services/web/test/unit/coffee/User/UserRegistrationHandlerTests.coffee
index e9a6c568dd..f8bcce30ce 100644
--- a/services/web/test/unit/coffee/User/UserRegistrationHandlerTests.coffee
+++ b/services/web/test/unit/coffee/User/UserRegistrationHandlerTests.coffee
@@ -132,11 +132,17 @@ describe "UserRegistrationHandler", ->
@AuthenticationManager.setUserPassword.calledWith(@user._id, @passingRequest.password).should.equal true
done()
- it "should add the user to the news letter manager", (done)->
+ it "should add the user to the newsletter if accepted terms", (done)->
+ @passingRequest.subscribeToNewsletter = "true"
@handler.registerNewUser @passingRequest, (err)=>
@NewsLetterManager.subscribe.calledWith(@user).should.equal true
done()
+ it "should not add the user to the newsletter if not accepted terms", (done)->
+ @handler.registerNewUser @passingRequest, (err)=>
+ @NewsLetterManager.subscribe.calledWith(@user).should.equal false
+ done()
+
it "should track the registration event", (done)->
@handler.registerNewUser @passingRequest, (err)=>
@AnalyticsManager.recordEvent
diff --git a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee
index f2ac951727..17f691edba 100644
--- a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee
+++ b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee
@@ -18,21 +18,27 @@ describe "UserUpdater", ->
getUserEmail: sinon.stub()
getUserByAnyEmail: sinon.stub()
ensureUniqueEmailAddress: sinon.stub()
- @logger = err: sinon.stub(), log: ->
+ @logger =
+ err: sinon.stub()
+ log: ->
+ warn: ->
@addAffiliation = sinon.stub().yields()
@removeAffiliation = sinon.stub().callsArgWith(2, null)
@refreshFeatures = sinon.stub().yields()
+ @NewsletterManager =
+ changeEmail:sinon.stub()
@UserUpdater = SandboxedModule.require modulePath, requires:
"logger-sharelatex": @logger
+ "../../infrastructure/mongojs":@mongojs
+ "metrics-sharelatex": timeAsyncMethod: sinon.stub()
"./UserGetter": @UserGetter
'../Institutions/InstitutionsAPI':
addAffiliation: @addAffiliation
removeAffiliation: @removeAffiliation
'../Subscription/FeaturesUpdater': refreshFeatures: @refreshFeatures
- "../../infrastructure/mongojs":@mongojs
- "metrics-sharelatex": timeAsyncMethod: sinon.stub()
"settings-sharelatex": @settings = {}
"request": @request = {}
+ "../Newsletter/NewsletterManager": @NewsletterManager
@stubbedUser =
_id: "3131231"
@@ -174,6 +180,10 @@ describe "UserUpdater", ->
done()
describe 'setDefaultEmailAddress', ->
+ beforeEach ->
+ @UserGetter.getUserEmail.callsArgWith(1, null, @stubbedUser.email)
+ @NewsletterManager.changeEmail.callsArgWith(2, null)
+
it 'set default', (done)->
@UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, n: 1)
@@ -185,6 +195,16 @@ describe "UserUpdater", ->
).should.equal true
done()
+ it 'set changed the email in newsletter', (done)->
+ @UserUpdater.updateUser = sinon.stub().callsArgWith(2, null, n: 1)
+
+ @UserUpdater.setDefaultEmailAddress @stubbedUser._id, @newEmail, (err)=>
+ should.not.exist(err)
+ @NewsletterManager.changeEmail.calledWith(
+ @stubbedUser.email, @newEmail
+ ).should.equal true
+ done()
+
it 'handle error', (done)->
@UserUpdater.updateUser = sinon.stub().callsArgWith(2, new Error('nope'))