mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-09 04:16:02 +00:00
Merge branch 'master' into pr-latex-linter-onboarding
This commit is contained in:
commit
97b92928dc
33 changed files with 840 additions and 321 deletions
|
@ -11,27 +11,6 @@ module.exports = CollaboratorsEmailHandler =
|
|||
"user_first_name=#{encodeURIComponent(project.owner_ref.first_name)}"
|
||||
].join("&")
|
||||
|
||||
notifyUserOfProjectShare: (project_id, email, callback)->
|
||||
Project
|
||||
.findOne(_id: project_id )
|
||||
.select("name owner_ref")
|
||||
.populate('owner_ref')
|
||||
.exec (err, project)->
|
||||
emailOptions =
|
||||
to: email
|
||||
replyTo: project.owner_ref.email
|
||||
project:
|
||||
name: project.name
|
||||
url: "#{Settings.siteUrl}/project/#{project._id}?" + [
|
||||
"project_name=#{encodeURIComponent(project.name)}"
|
||||
"user_first_name=#{encodeURIComponent(project.owner_ref.first_name)}"
|
||||
"new_email=#{encodeURIComponent(email)}"
|
||||
"r=#{project.owner_ref.referal_id}" # Referal
|
||||
"rs=ci" # referral source = collaborator invite
|
||||
].join("&")
|
||||
owner: project.owner_ref
|
||||
EmailHandler.sendEmail "projectSharedWithYou", emailOptions, callback
|
||||
|
||||
notifyUserOfProjectInvite: (project_id, email, invite, callback)->
|
||||
Project
|
||||
.findOne(_id: project_id )
|
||||
|
|
|
@ -7,10 +7,18 @@ settings = require("settings-sharelatex")
|
|||
|
||||
templates = {}
|
||||
|
||||
|
||||
templates.registered =
|
||||
subject: _.template "Activate your #{settings.appName} Account"
|
||||
layout: PersonalEmailLayout
|
||||
type: "notification"
|
||||
plainTextTemplate: _.template """
|
||||
Congratulations, you've just had an account created for you on #{settings.appName} with the email address "<%= to %>".
|
||||
|
||||
Click here to set your password and log in: <%= setNewPasswordUrl %>
|
||||
|
||||
If you have any questions or problems, please contact #{settings.adminEmail}
|
||||
"""
|
||||
compiledTemplate: _.template """
|
||||
<p>Congratulations, you've just had an account created for you on #{settings.appName} with the email address "<%= to %>".</p>
|
||||
|
||||
|
@ -19,10 +27,24 @@ templates.registered =
|
|||
<p>If you have any questions or problems, please contact <a href="mailto:#{settings.adminEmail}">#{settings.adminEmail}</a>.</p>
|
||||
"""
|
||||
|
||||
|
||||
templates.canceledSubscription =
|
||||
subject: _.template "ShareLaTeX thoughts"
|
||||
layout: PersonalEmailLayout
|
||||
type:"lifecycle"
|
||||
plainTextTemplate: _.template """
|
||||
Hi <%= first_name %>,
|
||||
|
||||
I'm sorry to see you cancelled your ShareLaTeX premium account. Would you mind giving me some advice on what the site is lacking at the moment via this survey?:
|
||||
|
||||
https://sharelatex.typeform.com/to/f5lBiZ
|
||||
|
||||
Thank you in advance.
|
||||
|
||||
Henry
|
||||
|
||||
ShareLaTeX Co-founder
|
||||
"""
|
||||
compiledTemplate: _.template '''
|
||||
<p>Hi <%= first_name %>,</p>
|
||||
|
||||
|
@ -36,10 +58,26 @@ ShareLaTeX Co-founder
|
|||
</p>
|
||||
'''
|
||||
|
||||
|
||||
templates.passwordResetRequested =
|
||||
subject: _.template "Password Reset - #{settings.appName}"
|
||||
layout: NotificationEmailLayout
|
||||
type:"notification"
|
||||
plainTextTemplate: _.template """
|
||||
Password Reset
|
||||
|
||||
We got a request to reset your #{settings.appName} password.
|
||||
|
||||
Click this link to reset your password: <%= setNewPasswordUrl %>
|
||||
|
||||
If you ignore this message, your password won't be changed.
|
||||
|
||||
If you didn't request a password reset, let us know.
|
||||
|
||||
Thank you
|
||||
|
||||
#{settings.appName} - <%= siteUrl %>
|
||||
"""
|
||||
compiledTemplate: _.template """
|
||||
<h2>Password Reset</h2>
|
||||
<p>
|
||||
|
@ -66,26 +104,6 @@ If you didn't request a password reset, let us know.
|
|||
<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
|
||||
"""
|
||||
|
||||
templates.projectSharedWithYou =
|
||||
subject: _.template "<%= owner.email %> wants to share <%= project.name %> with you"
|
||||
layout: NotificationEmailLayout
|
||||
type:"notification"
|
||||
compiledTemplate: _.template """
|
||||
<p>Hi, <%= owner.email %> wants to share <a href="<%= project.url %>">'<%= project.name %>'</a> with you</p>
|
||||
<center>
|
||||
<div style="width:200px;background-color:#a93629;border:1px solid #e24b3b;border-radius:3px;padding:15px; margin:24px;">
|
||||
<div style="padding-right:10px;padding-left:10px">
|
||||
<a href="<%= project.url %>" style="text-decoration:none" target="_blank">
|
||||
<span style= "font-size:16px;font-family:Helvetica,Arial;font-weight:400;color:#fff;white-space:nowrap;display:block; text-align:center">
|
||||
View Project
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</center>
|
||||
<p> Thank you</p>
|
||||
<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
|
||||
"""
|
||||
|
||||
templates.projectInvite =
|
||||
subject: _.template "<%= project.name %> - shared by <%= owner.email %>"
|
||||
|
@ -113,10 +131,20 @@ Thank you
|
|||
<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
|
||||
"""
|
||||
|
||||
|
||||
templates.completeJoinGroupAccount =
|
||||
subject: _.template "Verify Email to join <%= group_name %> group"
|
||||
layout: NotificationEmailLayout
|
||||
type:"notification"
|
||||
plainTextTemplate: _.template """
|
||||
Hi, please verify your email to join the <%= group_name %> and get your free premium account
|
||||
|
||||
Click this link to verify now: <%= completeJoinUrl %>
|
||||
|
||||
Thank You
|
||||
|
||||
#{settings.appName} - <%= siteUrl %>
|
||||
"""
|
||||
compiledTemplate: _.template """
|
||||
<p>Hi, please verify your email to join the <%= group_name %> and get your free premium account</p>
|
||||
<center>
|
||||
|
@ -134,6 +162,7 @@ templates.completeJoinGroupAccount =
|
|||
<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
|
||||
"""
|
||||
|
||||
|
||||
module.exports =
|
||||
templates: templates
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ Settings = require 'settings-sharelatex'
|
|||
logger = require('logger-sharelatex')
|
||||
GeoIpLookup = require("../../infrastructure/GeoIpLookup")
|
||||
SubscriptionDomainHandler = require("./SubscriptionDomainHandler")
|
||||
UserGetter = require "../User/UserGetter"
|
||||
|
||||
module.exports = SubscriptionController =
|
||||
|
||||
|
@ -21,14 +22,25 @@ module.exports = SubscriptionController =
|
|||
if req.query.v?
|
||||
viewName = "#{viewName}_#{req.query.v}"
|
||||
logger.log viewName:viewName, "showing plans page"
|
||||
currentUser = null
|
||||
GeoIpLookup.getCurrencyCode req.query?.ip || req.ip, (err, recomendedCurrency)->
|
||||
return next(err) if err?
|
||||
res.render viewName,
|
||||
title: "plans_and_pricing"
|
||||
plans: plans
|
||||
baseUrl: baseUrl
|
||||
gaExperiments: Settings.gaExperiments.plansPage
|
||||
recomendedCurrency:recomendedCurrency
|
||||
render = () ->
|
||||
res.render viewName,
|
||||
title: "plans_and_pricing"
|
||||
plans: plans
|
||||
baseUrl: baseUrl
|
||||
gaExperiments: Settings.gaExperiments.plansPage
|
||||
recomendedCurrency:recomendedCurrency
|
||||
shouldABTestPlans: currentUser == null or (currentUser?.signUpDate? and currentUser.signUpDate >= (new Date('2016-10-27')))
|
||||
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||
if user_id?
|
||||
UserGetter.getUser user_id, {signUpDate: 1}, (err, user) ->
|
||||
return next(err) if err?
|
||||
currentUser = user
|
||||
render()
|
||||
else
|
||||
render()
|
||||
|
||||
#get to show the recurly.js page
|
||||
paymentPage: (req, res, next) ->
|
||||
|
|
|
@ -44,7 +44,7 @@ module.exports =
|
|||
allPlans = {}
|
||||
plans.forEach (plan)->
|
||||
allPlans[plan.planCode] = plan
|
||||
|
||||
|
||||
result =
|
||||
allPlans: allPlans
|
||||
|
||||
|
@ -54,7 +54,7 @@ module.exports =
|
|||
|
||||
result.studentAccounts = _.filter plans, (plan)->
|
||||
plan.planCode.indexOf("student") != -1
|
||||
|
||||
|
||||
result.groupMonthlyPlans = _.filter plans, (plan)->
|
||||
plan.groupPlan and !plan.annual
|
||||
|
||||
|
@ -68,4 +68,3 @@ module.exports =
|
|||
!plan.groupPlan and plan.annual and plan.planCode.indexOf("student") == -1
|
||||
|
||||
return result
|
||||
|
||||
|
|
|
@ -15,12 +15,26 @@ settings = require "settings-sharelatex"
|
|||
|
||||
module.exports = UserController =
|
||||
|
||||
deleteUser: (req, res)->
|
||||
tryDeleteUser: (req, res, next) ->
|
||||
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||
UserDeleter.deleteUser user_id, (err)->
|
||||
if !err?
|
||||
password = req.body.password
|
||||
logger.log {user_id}, "trying to delete user account"
|
||||
if !password? or password == ''
|
||||
logger.err {user_id}, 'no password supplied for attempt to delete account'
|
||||
return res.sendStatus(403)
|
||||
AuthenticationManager.authenticate {_id: user_id}, password, (err, user) ->
|
||||
if err?
|
||||
logger.err {user_id}, 'error authenticating during attempt to delete account'
|
||||
return next(err)
|
||||
if !user
|
||||
logger.err {user_id}, 'auth failed during attempt to delete account'
|
||||
return res.sendStatus(403)
|
||||
UserDeleter.deleteUser user_id, (err) ->
|
||||
if err?
|
||||
logger.err {user_id}, "error while deleting user account"
|
||||
return next(err)
|
||||
req.session?.destroy()
|
||||
res.sendStatus(200)
|
||||
res.sendStatus(200)
|
||||
|
||||
unsubscribe: (req, res)->
|
||||
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||
|
@ -143,7 +157,7 @@ module.exports = UserController =
|
|||
type:'success'
|
||||
text:'Your password has been changed'
|
||||
else
|
||||
logger.log user: user, "current password wrong"
|
||||
logger.log user_id: user_id, "current password wrong"
|
||||
res.send
|
||||
message:
|
||||
type:'error'
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
module.exports =
|
||||
user: (user) ->
|
||||
if !user?
|
||||
return null
|
||||
if !user._id?
|
||||
user = {_id : user}
|
||||
return {
|
||||
|
@ -10,6 +12,8 @@ module.exports =
|
|||
}
|
||||
|
||||
project: (project) ->
|
||||
if !project?
|
||||
return null
|
||||
if !project._id?
|
||||
project = {_id: project}
|
||||
return {
|
||||
|
|
|
@ -92,7 +92,7 @@ module.exports = class Router
|
|||
webRouter.post '/user/sessions/clear', AuthenticationController.requireLogin(), UserController.clearSessions
|
||||
|
||||
webRouter.delete '/user/newsletter/unsubscribe', AuthenticationController.requireLogin(), UserController.unsubscribe
|
||||
webRouter.delete '/user', AuthenticationController.requireLogin(), UserController.deleteUser
|
||||
webRouter.post '/user/delete', AuthenticationController.requireLogin(), UserController.tryDeleteUser
|
||||
|
||||
webRouter.get '/user/personal_info', AuthenticationController.requireLogin(), UserInfoController.getLoggedInUsersPersonalInfo
|
||||
apiRouter.get '/user/:user_id/personal_info', AuthenticationController.httpAuth, UserInfoController.getPersonalInfo
|
||||
|
|
|
@ -51,8 +51,6 @@ html(itemscope, itemtype='http://schema.org/Product')
|
|||
script(type="text/javascript").
|
||||
window.csrfToken = "#{csrfToken}";
|
||||
|
||||
block scripts
|
||||
|
||||
script(src=buildJsPath("libs/jquery-1.11.1.min.js", {fingerprint:false}))
|
||||
script(type="text/javascript").
|
||||
var noCdnKey = "nocdn=true"
|
||||
|
@ -61,6 +59,9 @@ html(itemscope, itemtype='http://schema.org/Product')
|
|||
if (cdnBlocked && !noCdnAlreadyInUrl && navigator.userAgent.indexOf("Googlebot") == -1) {
|
||||
window.location.search += '&'+noCdnKey;
|
||||
}
|
||||
|
||||
block scripts
|
||||
|
||||
script(src=buildJsPath("libs/angular-1.3.15.min.js", {fingerprint:false}))
|
||||
|
||||
script.
|
||||
|
|
|
@ -1,41 +1,85 @@
|
|||
div#history(ng-show="ui.view == 'history'")
|
||||
span(ng-controller="HistoryPremiumPopup")
|
||||
.upgrade-prompt(ng-show="!project.features.versioning")
|
||||
.message(ng-show="project.owner._id == user.id")
|
||||
p.text-center: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})}
|
||||
p.text-center.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")}
|
||||
ul.list-unstyled
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("unlimited_projects")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("full_doc_history")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("sync_to_dropbox")}
|
||||
.upgrade-prompt(ng-if="project.features.versioning === false && ui.view === 'history'")
|
||||
|
||||
div(sixpack-switch="teaser-history")
|
||||
.message(
|
||||
sixpack-default
|
||||
ng-show="project.owner._id == user.id"
|
||||
)
|
||||
p.text-center: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})}
|
||||
p.text-center.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")}
|
||||
ul.list-unstyled
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("unlimited_projects")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("full_doc_history")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("sync_to_dropbox")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("sync_to_github")}
|
||||
li
|
||||
i.fa.fa-check
|
||||
| #{translate("sync_to_github")}
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
|#{translate("compile_larger_projects")}
|
||||
li
|
||||
i.fa.fa-check
|
||||
|#{translate("compile_larger_projects")}
|
||||
p.text-center(ng-controller="FreeTrialModalController")
|
||||
a.btn.btn-success(
|
||||
href
|
||||
ng-class="buttonClass"
|
||||
ng-click="startFreeTrial('history')"
|
||||
sixpack-convert="teaser-history"
|
||||
) #{translate("start_free_trial")}
|
||||
|
||||
p.text-center(ng-controller="FreeTrialModalController")
|
||||
a.btn.btn-success(
|
||||
href
|
||||
ng-class="buttonClass"
|
||||
ng-click="startFreeTrial('history')"
|
||||
) #{translate("start_free_trial")}
|
||||
.message.message-wider(
|
||||
sixpack-when="focused"
|
||||
ng-show="project.owner._id == user.id"
|
||||
)
|
||||
header.message-header
|
||||
h3 History
|
||||
|
||||
.message-body
|
||||
h4.teaser-title See who changed what. Go back to previous versions.
|
||||
img.teaser-img(
|
||||
src="/img/teasers/history/teaser-history.png"
|
||||
alt="History"
|
||||
)
|
||||
p.text-center.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")}
|
||||
.row
|
||||
.col-md-8.col-md-offset-2
|
||||
ul.list-unstyled
|
||||
li
|
||||
i.fa.fa-check
|
||||
| Catch up with your collaborators changes
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| See changes over any time period
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| Revert your documents to previous versions
|
||||
|
||||
li
|
||||
i.fa.fa-check
|
||||
| Restore deleted files
|
||||
p.text-center(ng-controller="FreeTrialModalController")
|
||||
a.btn.btn-success(
|
||||
href
|
||||
ng-class="buttonClass"
|
||||
ng-click="startFreeTrial('history')"
|
||||
sixpack-convert="teaser-history"
|
||||
) Try it for free
|
||||
|
||||
.message(ng-show="project.owner._id != user.id")
|
||||
p #{translate("ask_proj_owner_to_upgrade_for_history")}
|
||||
|
|
|
@ -12,8 +12,8 @@ block scripts
|
|||
|
||||
mixin printPlan(plan)
|
||||
-if (!plan.hideFromUsers)
|
||||
tr(ng-controller="ChangePlanFormController")
|
||||
td(ng-init="plan=#{JSON.stringify(plan)}")
|
||||
tr(ng-controller="ChangePlanFormController", ng-init="plan=#{JSON.stringify(plan)}", ng-show="shouldShowPlan(plan.planCode)")
|
||||
td
|
||||
strong #{plan.name}
|
||||
td {{refreshPrice(plan.planCode)}}
|
||||
-if (plan.annual)
|
||||
|
@ -46,8 +46,8 @@ block content
|
|||
|
|
||||
| #{translate("your_billing_details_were_saved")}
|
||||
.card(ng-if="view == 'overview'")
|
||||
.page-header
|
||||
h1 #{translate("your_subscription")}
|
||||
.page-header(x-current-plan="#{subscription.planCode}")
|
||||
h1 #{translate("your_subscription")}
|
||||
|
||||
- if (subscription && user._id+'' == subscription.admin_id+'')
|
||||
case subscription.state
|
||||
|
@ -56,7 +56,8 @@ block content
|
|||
|
||||
when "active"
|
||||
p !{translate("currently_subscribed_to_plan", {planName:"<strong>" + subscription.name + "</strong>"})}
|
||||
a(href, ng-click="changePlan = true") !{translate("change_plan")}.
|
||||
span(ng-show="!isNextGenPlan")
|
||||
a(href, ng-click="changePlan = true") !{translate("change_plan")}.
|
||||
p !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"<strong>" + subscription.price + "</strong>", collectionDate:"<strong>" + subscription.nextPaymentDueAt + "</strong>"})}
|
||||
p.pull-right
|
||||
p
|
||||
|
|
|
@ -3,6 +3,7 @@ block scripts
|
|||
script(type='text/javascript').
|
||||
window.recomendedCurrency = '#{recomendedCurrency}'
|
||||
window.abCurrencyFlag = '#{abCurrencyFlag}'
|
||||
window.shouldABTestPlans = #{shouldABTestPlans || false}
|
||||
|
||||
script(type='text/javascript').
|
||||
(function() {var s=document.createElement('script'); s.type='text/javascript';s.async=true;
|
||||
|
@ -56,133 +57,144 @@ block content
|
|||
ng-click="changeCurreny(currency)"
|
||||
) {{currency}} ({{value['symbol']}})
|
||||
|
||||
.row(ng-cloak)
|
||||
.col-md-10.col-md-offset-1
|
||||
.row
|
||||
.card-group.text-centered(ng-if="ui.view == 'monthly' || ui.view == 'annual'")
|
||||
.col-md-4
|
||||
.card.card-first
|
||||
.card-header
|
||||
h2 #{translate("personal")}
|
||||
.circle #{translate("free")}
|
||||
ul.list-unstyled
|
||||
li #{translate("one_collaborator")}
|
||||
li
|
||||
li
|
||||
li
|
||||
li
|
||||
br
|
||||
a.btn.btn-info(
|
||||
href="/register"
|
||||
style=(getLoggedInUserId() === undefined ? "" : "visibility: hidden")
|
||||
) #{translate("sign_up_now")}
|
||||
.col-md-4
|
||||
.card.card-highlighted
|
||||
.card-header
|
||||
h2 #{translate("collaborator")}
|
||||
.circle
|
||||
span(ng-if="ui.view == 'monthly'")
|
||||
| {{plans[currencyCode]['collaborator']['monthly']}}
|
||||
span.small /mo
|
||||
span(ng-if="ui.view == 'annual'")
|
||||
| {{plans[currencyCode]['collaborator']['annual']}}
|
||||
span.small /yr
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong #{translate("collabs_per_proj", {collabcount:10})}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
li
|
||||
br
|
||||
a.btn.btn-info(
|
||||
div(ng-show="showPlans")
|
||||
.row(ng-cloak)
|
||||
.col-md-10.col-md-offset-1
|
||||
.row
|
||||
.card-group.text-centered(ng-if="ui.view == 'monthly' || ui.view == 'annual'")
|
||||
.col-md-4
|
||||
.card.card-first
|
||||
.card-header
|
||||
h2 #{translate("personal")}
|
||||
.circle #{translate("free")}
|
||||
ul.list-unstyled
|
||||
li #{translate("one_collaborator")}
|
||||
li
|
||||
li
|
||||
li
|
||||
li
|
||||
br
|
||||
a.btn.btn-info(
|
||||
href="/register"
|
||||
style=(getLoggedInUserId() === undefined ? "" : "visibility: hidden")
|
||||
) #{translate("sign_up_now")}
|
||||
.col-md-4
|
||||
.card.card-highlighted
|
||||
.card-header
|
||||
h2 #{translate("collaborator")}
|
||||
.circle
|
||||
span(ng-if="ui.view == 'monthly'")
|
||||
| {{plans[currencyCode]['collaborator']['monthly']}}
|
||||
span.small /mo
|
||||
span(ng-if="ui.view == 'annual'")
|
||||
| {{plans[currencyCode]['collaborator']['annual']}}
|
||||
span.small /yr
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:10})}
|
||||
strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:8})}
|
||||
strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:12})}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
li
|
||||
br
|
||||
a.btn.btn-info(
|
||||
|
||||
ng-href="#{baseUrl}/user/subscription/new?planCode=collaborator{{ (ui.view == 'annual' ? '-annual' : '') + (plansVariant == 'default' ? planQueryString : '_'+plansVariant)}}¤cy={{currencyCode}}", ng-click="signUpNowClicked('collaborator')"
|
||||
)
|
||||
span(ng-show="ui.view != 'annual'") #{translate("start_free_trial")}
|
||||
span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
|
||||
.col-md-4
|
||||
.card.card-last
|
||||
.card-header
|
||||
h2 #{translate("professional")}
|
||||
.circle
|
||||
span(ng-if="ui.view == 'monthly'")
|
||||
| {{plans[currencyCode]['professional']['monthly']}}
|
||||
span.small /mo
|
||||
span(ng-if="ui.view == 'annual'")
|
||||
| {{plans[currencyCode]['professional']['annual']}}
|
||||
span.small /yr
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong #{translate("unlimited_collabs")}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
li
|
||||
br
|
||||
a.btn.btn-info(
|
||||
ng-href="#{baseUrl}/user/subscription/new?planCode=professional{{ ui.view == 'annual' && '-annual' || planQueryString}}¤cy={{currencyCode}}", ng-click="signUpNowClicked('professional')"
|
||||
)
|
||||
span(ng-show="ui.view != 'annual'") #{translate("start_free_trial")}
|
||||
span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
|
||||
|
||||
.card-group.text-centered(ng-if="ui.view == 'student'")
|
||||
.col-md-4
|
||||
.card.card-first
|
||||
.card-header
|
||||
h2 #{translate("personal")}
|
||||
.circle #{translate("free")}
|
||||
ul.list-unstyled
|
||||
li #{translate("one_collaborator")}
|
||||
li
|
||||
li
|
||||
li
|
||||
li
|
||||
br
|
||||
a.btn.btn-info(
|
||||
href="/register"
|
||||
style=(getLoggedInUserId() === undefined ? "" : "visibility: hidden")
|
||||
) #{translate("sign_up_now")}
|
||||
|
||||
.col-md-4
|
||||
.card.card-highlighted
|
||||
.card-header
|
||||
h2 #{translate("student")}
|
||||
.circle
|
||||
span
|
||||
| {{plans[currencyCode]['student']['monthly']}}
|
||||
span.small /mo
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:6})}
|
||||
strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:4})}
|
||||
strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:8})}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
li
|
||||
br
|
||||
a.btn.btn-info(
|
||||
ng-href="#{baseUrl}/user/subscription/new?planCode=student{{ plansVariant == 'default' ? planQueryString : '_'+plansVariant }}¤cy={{currencyCode}}",
|
||||
ng-click="signUpNowClicked('student')"
|
||||
) #{translate("start_free_trial")}
|
||||
|
||||
.col-md-4
|
||||
.card.card-last
|
||||
.card-header
|
||||
h2 #{translate("student")} (#{translate("annual")})
|
||||
.circle
|
||||
span
|
||||
| {{plans[currencyCode]['student']['annual']}}
|
||||
span.small /yr
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:6})}
|
||||
strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:4})}
|
||||
strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:8})}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
li
|
||||
br
|
||||
a.btn.btn-info(
|
||||
ng-href="#{baseUrl}/user/subscription/new?planCode=student-annual{{ plansVariant == 'default' ? '' : '_'+plansVariant }}¤cy={{currencyCode}}",
|
||||
ng-click="signUpNowClicked('student')"
|
||||
) #{translate("buy_now")}
|
||||
|
||||
ng-href="#{baseUrl}/user/subscription/new?planCode=collaborator{{ ui.view == 'annual' && '-annual' || planQueryString}}¤cy={{currencyCode}}", ng-click="signUpNowClicked('collaborator')"
|
||||
)
|
||||
span(ng-show="ui.view != 'annual'") #{translate("start_free_trial")}
|
||||
span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
|
||||
.col-md-4
|
||||
.card.card-last
|
||||
.card-header
|
||||
h2 #{translate("professional")}
|
||||
.circle
|
||||
span(ng-if="ui.view == 'monthly'")
|
||||
| {{plans[currencyCode]['professional']['monthly']}}
|
||||
span.small /mo
|
||||
span(ng-if="ui.view == 'annual'")
|
||||
| {{plans[currencyCode]['professional']['annual']}}
|
||||
span.small /yr
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong #{translate("unlimited_collabs")}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
li
|
||||
br
|
||||
a.btn.btn-info(
|
||||
ng-href="#{baseUrl}/user/subscription/new?planCode=professional{{ ui.view == 'annual' && '-annual' || planQueryString}}¤cy={{currencyCode}}", ng-click="signUpNowClicked('professional')"
|
||||
)
|
||||
span(ng-show="ui.view != 'annual'") #{translate("start_free_trial")}
|
||||
span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
|
||||
|
||||
.card-group.text-centered(ng-if="ui.view == 'student'")
|
||||
.col-md-4
|
||||
.card.card-first
|
||||
.card-header
|
||||
h2 #{translate("personal")}
|
||||
.circle #{translate("free")}
|
||||
ul.list-unstyled
|
||||
li #{translate("one_collaborator")}
|
||||
li
|
||||
li
|
||||
li
|
||||
li
|
||||
br
|
||||
a.btn.btn-info(
|
||||
href="/register"
|
||||
style=(getLoggedInUserId() === undefined ? "" : "visibility: hidden")
|
||||
) #{translate("sign_up_now")}
|
||||
|
||||
.col-md-4
|
||||
.card.card-highlighted
|
||||
.card-header
|
||||
h2 #{translate("student")}
|
||||
.circle
|
||||
span
|
||||
| {{plans[currencyCode]['student']['monthly']}}
|
||||
span.small /mo
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong #{translate("collabs_per_proj", {collabcount:6})}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
li
|
||||
br
|
||||
a.btn.btn-info(
|
||||
ng-href="#{baseUrl}/user/subscription/new?planCode=student{{planQueryString}}¤cy={{currencyCode}}", ng-click="signUpNowClicked('student')"
|
||||
) #{translate("start_free_trial")}
|
||||
|
||||
.col-md-4
|
||||
.card.card-last
|
||||
.card-header
|
||||
h2 #{translate("student")} (#{translate("annual")})
|
||||
.circle
|
||||
span
|
||||
| {{plans[currencyCode]['student']['annual']}}
|
||||
span.small /yr
|
||||
ul.list-unstyled
|
||||
li
|
||||
strong #{translate("collabs_per_proj", {collabcount:6})}
|
||||
li #{translate("full_doc_history")}
|
||||
li #{translate("sync_to_dropbox")}
|
||||
li #{translate("sync_to_github")}
|
||||
li
|
||||
br
|
||||
a.btn.btn-info(
|
||||
ng-href="#{baseUrl}/user/subscription/new?planCode=student-annual¤cy={{currencyCode}}", ng-click="signUpNowClicked('student')"
|
||||
) #{translate("buy_now")}
|
||||
|
||||
.row.row-spaced(ng-cloak)
|
||||
p.text-centered #{translate("choose_plan_works_for_you", {len:'{{trial_len}}'})}
|
||||
|
|
|
@ -2,7 +2,7 @@ extends ../layout
|
|||
|
||||
block content
|
||||
.content.content-alt
|
||||
.container
|
||||
.container(ng-controller="SuccessfulSubscriptionController")
|
||||
.row
|
||||
.col-md-8.col-md-offset-2
|
||||
.card(ng-cloak)
|
||||
|
|
|
@ -150,16 +150,32 @@ block content
|
|||
script(type='text/ng-template', id='deleteAccountModalTemplate')
|
||||
.modal-header
|
||||
h3 #{translate("delete_account")}
|
||||
.modal-body
|
||||
p !{translate("delete_account_warning_message_2")}
|
||||
div.modal-body#delete-account-modal
|
||||
p !{translate("delete_account_warning_message_3")}
|
||||
form(novalidate, name="deleteAccountForm")
|
||||
label #{translate('email')}
|
||||
input.form-control(
|
||||
type="text",
|
||||
autocomplete="off",
|
||||
placeholder="",
|
||||
ng-model="state.deleteText",
|
||||
focus-on="open",
|
||||
ng-keyup="checkValidation()"
|
||||
)
|
||||
label #{translate('password')}
|
||||
input.form-control(
|
||||
type="password",
|
||||
autocomplete="off",
|
||||
placeholder="",
|
||||
ng-model="state.password",
|
||||
ng-keyup="checkValidation()"
|
||||
)
|
||||
div(ng-if="state.error")
|
||||
div.alert.alert-danger
|
||||
| #{translate('generic_something_went_wrong')}
|
||||
div(ng-if="state.invalidCredentials")
|
||||
div.alert.alert-danger
|
||||
| #{translate('email_or_password_wrong_try_again')}
|
||||
.modal-footer
|
||||
button.btn.btn-default(
|
||||
ng-click="cancel()"
|
||||
|
|
|
@ -67,16 +67,21 @@ define [
|
|||
pdfLayout: 'sideBySide'
|
||||
}
|
||||
$scope.user = window.user
|
||||
|
||||
|
||||
$scope.shouldABTestPlans = false
|
||||
if $scope.user.signUpDate >= '2016-10-27'
|
||||
$scope.shouldABTestPlans = true
|
||||
|
||||
$scope.settings = window.userSettings
|
||||
$scope.anonymous = window.anonymous
|
||||
|
||||
$scope.chat = {}
|
||||
|
||||
|
||||
# Only run the header AB test for newly registered users.
|
||||
_abTestStartDate = new Date(Date.UTC(2016, 8, 28))
|
||||
_userSignUpDate = new Date(window.user.signUpDate)
|
||||
|
||||
|
||||
$scope.shouldABTestHeaderLabels = _userSignUpDate > _abTestStartDate
|
||||
$scope.headerLabelsABVariant = ""
|
||||
|
||||
|
@ -93,7 +98,7 @@ define [
|
|||
# Tracking code.
|
||||
$scope.$watch "ui.view", (newView, oldView) ->
|
||||
if newView? and newView != "editor" and newView != "pdf"
|
||||
event_tracking.sendMBOnce "ide-open-view-#{ newView }-once"
|
||||
event_tracking.sendMBOnce "ide-open-view-#{ newView }-once"
|
||||
|
||||
$scope.$watch "ui.chatOpen", (isOpen) ->
|
||||
event_tracking.sendMBOnce "ide-open-chat-once" if isOpen
|
||||
|
@ -106,7 +111,7 @@ define [
|
|||
# End of tracking code.
|
||||
|
||||
window._ide = ide
|
||||
|
||||
|
||||
ide.validFileRegex = '^[^\*\/]*$' # Don't allow * and /
|
||||
|
||||
ide.project_id = $scope.project_id = window.project_id
|
||||
|
|
|
@ -27,6 +27,7 @@ define [], () ->
|
|||
|
||||
@connected = false
|
||||
@userIsInactive = false
|
||||
@gracefullyReconnecting = false
|
||||
|
||||
@$scope.connection =
|
||||
reconnecting: false
|
||||
|
@ -54,6 +55,7 @@ define [], () ->
|
|||
@ide.socket.on "connect", () =>
|
||||
sl_console.log "[socket.io connect] Connected"
|
||||
@connected = true
|
||||
@gracefullyReconnecting = false
|
||||
@ide.pushEvent("connected")
|
||||
|
||||
@$scope.$apply () =>
|
||||
|
@ -81,7 +83,7 @@ define [], () ->
|
|||
@$scope.$apply () =>
|
||||
@$scope.connection.reconnecting = false
|
||||
|
||||
if !$scope.connection.forced_disconnect and !@userIsInactive
|
||||
if !$scope.connection.forced_disconnect and !@userIsInactive and !@gracefullyReconnecting
|
||||
@startAutoReconnectCountdown()
|
||||
|
||||
@ide.socket.on 'forceDisconnect', (message) =>
|
||||
|
@ -97,7 +99,11 @@ define [], () ->
|
|||
setTimeout () ->
|
||||
location.reload()
|
||||
, 10 * 1000
|
||||
|
||||
|
||||
@ide.socket.on "reconnectGracefully", () =>
|
||||
sl_console.log "Reconnect gracefully"
|
||||
@reconnectGracefully()
|
||||
|
||||
joinProject: () ->
|
||||
sl_console.log "[joinProject] joining..."
|
||||
@ide.socket.emit 'joinProject', {
|
||||
|
@ -180,3 +186,24 @@ define [], () ->
|
|||
@$scope.$apply () =>
|
||||
@$scope.connection.inactive_disconnect = true
|
||||
|
||||
RECONNECT_GRACEFULLY_RETRY_INTERVAL: 5000 # ms
|
||||
MAX_RECONNECT_GRACEFULLY_INTERVAL: 60 * 5 * 1000 # 5 minutes
|
||||
reconnectGracefully: () ->
|
||||
@reconnectGracefullyStarted ?= new Date()
|
||||
userIsInactive = (new Date() - @lastUserAction) > @RECONNECT_GRACEFULLY_RETRY_INTERVAL
|
||||
maxIntervalReached = (new Date() - @reconnectGracefullyStarted) > @MAX_RECONNECT_GRACEFULLY_INTERVAL
|
||||
if userIsInactive or maxIntervalReached
|
||||
sl_console.log "[reconnectGracefully] User didn't do anything for last 5 seconds, reconnecting"
|
||||
@_reconnectGracefullyNow()
|
||||
else
|
||||
sl_console.log "[reconnectGracefully] User is working, will try again in 5 seconds"
|
||||
setTimeout () =>
|
||||
@reconnectGracefully()
|
||||
, @RECONNECT_GRACEFULLY_RETRY_INTERVAL
|
||||
|
||||
_reconnectGracefullyNow: () ->
|
||||
@gracefullyReconnecting = true
|
||||
@reconnectGracefullyStarted = null
|
||||
# Clear cookie so we don't go to the same backend server
|
||||
$.cookie("SERVERID", "", { expires: -1, path: "/" })
|
||||
@reconnectImmediately()
|
|
@ -15,9 +15,13 @@ define [
|
|||
|
||||
# set the path for ace workers if using a CDN (from editor.jade)
|
||||
if window.aceWorkerPath != ""
|
||||
syntaxValidationEnabled = true
|
||||
ace.config.set('workerPath', "#{window.aceWorkerPath}")
|
||||
else
|
||||
ace.config.setDefaultValue("session", "useWorker", false)
|
||||
syntaxValidationEnabled = false
|
||||
|
||||
# By default, don't use workers - enable them per-session as required
|
||||
ace.config.setDefaultValue("session", "useWorker", false)
|
||||
|
||||
# Ace loads its script itself, so we need to hook in to be able to clear
|
||||
# the cache.
|
||||
|
@ -202,8 +206,12 @@ define [
|
|||
editor.setReadOnly !!value
|
||||
|
||||
scope.$watch "syntaxValidation", (value) ->
|
||||
session = editor.getSession()
|
||||
session.setOption("useWorker", value);
|
||||
# ignore undefined settings here
|
||||
# only instances of ace with an explicit value should set useWorker
|
||||
# the history instance will have syntaxValidation undefined
|
||||
if value? and syntaxValidationEnabled
|
||||
session = editor.getSession()
|
||||
session.setOption("useWorker", value);
|
||||
|
||||
editor.setOption("scrollPastEnd", true)
|
||||
|
||||
|
@ -223,14 +231,32 @@ define [
|
|||
# see if we can lookup a suitable mode from ace
|
||||
# but fall back to text by default
|
||||
try
|
||||
mode = ModeList.getModeForPath(scope.fileName).mode
|
||||
if scope.fileName.match(/\.(Rtex|bbl)$/i)
|
||||
# recognise Rtex and bbl as latex
|
||||
mode = "ace/mode/latex"
|
||||
else if scope.fileName.match(/\.(sty|cls|clo)$/)
|
||||
# recognise some common files as tex
|
||||
mode = "ace/mode/tex"
|
||||
else
|
||||
mode = ModeList.getModeForPath(scope.fileName).mode
|
||||
# we prefer plain_text mode over text mode because ace's
|
||||
# text mode is actually for code and has unwanted
|
||||
# indenting (see wrapMethod in ace edit_session.js)
|
||||
if mode is "ace/mode/text"
|
||||
mode = "ace/mode/plain_text"
|
||||
catch
|
||||
mode = "ace/mode/text"
|
||||
mode = "ace/mode/plain_text"
|
||||
|
||||
editor.setSession(new EditSession(lines, mode))
|
||||
# create our new session
|
||||
session = new EditSession(lines, mode)
|
||||
|
||||
session = editor.getSession()
|
||||
session.setUseWrapMode(true)
|
||||
# use syntax validation only when explicitly set
|
||||
if scope.syntaxValidation? and syntaxValidationEnabled
|
||||
session.setOption("useWorker", scope.syntaxValidation);
|
||||
|
||||
# now attach session to editor
|
||||
editor.setSession(session)
|
||||
|
||||
doc = session.getDocument()
|
||||
doc.on "change", onChange
|
||||
|
|
|
@ -233,8 +233,16 @@ define [
|
|||
start = aceDelta.start
|
||||
if !start?
|
||||
error = new Error("aceDelta had no start event.")
|
||||
JSONstringifyWithCycles = (o) ->
|
||||
seen = []
|
||||
return JSON.stringify o, (k,v) ->
|
||||
if (typeof v == 'object')
|
||||
if ( !seen.indexOf(v) )
|
||||
return '__cycle__'
|
||||
seen.push(v);
|
||||
return v
|
||||
Raven?.captureException(error, {
|
||||
aceDelta: JSON.stringify(aceDelta)
|
||||
aceDelta: JSONstringifyWithCycles(aceDelta)
|
||||
})
|
||||
throw error
|
||||
linesBefore = docLines.slice(0, start.row)
|
||||
|
|
|
@ -29,12 +29,12 @@ define [
|
|||
|
||||
$scope.$watch "shouldShowLogs", (shouldShow) ->
|
||||
if shouldShow
|
||||
$scope.$applyAsync () ->
|
||||
$scope.$applyAsync () ->
|
||||
$scope.shouldDropUp = getFilesDropdownTopCoordAsRatio() > 0.65
|
||||
|
||||
# log hints tracking
|
||||
$scope.logHintsNegFeedbackValues = logHintsFeedback.feedbackOpts
|
||||
|
||||
|
||||
$scope.trackLogHintsLearnMore = () ->
|
||||
event_tracking.sendMB "logs-hints-learn-more"
|
||||
|
||||
|
@ -108,7 +108,7 @@ define [
|
|||
_csrf: window.csrfToken
|
||||
}, {params: params}
|
||||
|
||||
parseCompileResponse = (response) ->
|
||||
parseCompileResponse = (response) ->
|
||||
|
||||
# keep last url
|
||||
last_pdf_url = $scope.pdf.url
|
||||
|
@ -469,7 +469,7 @@ define [
|
|||
|
||||
event_tracking.sendMB "subscription-start-trial", { source }
|
||||
|
||||
window.open("/user/subscription/new?planCode=student_free_trial_7_days")
|
||||
window.open("/user/subscription/new?planCode=#{$scope.startTrialPlanCode}")
|
||||
$scope.startedFreeTrial = true
|
||||
|
||||
App.factory "synctex", ["ide", "$http", "$q", (ide, $http, $q) ->
|
||||
|
|
|
@ -17,8 +17,13 @@ define [
|
|||
# When we join the project:
|
||||
# index all references files
|
||||
# and don't broadcast to all clients
|
||||
@inited = false
|
||||
@$scope.$on 'project:joined', (e) =>
|
||||
@indexAllReferences(false)
|
||||
# We only need to grab the references when the editor first loads,
|
||||
# not on every reconnect
|
||||
if !@inited
|
||||
@inited = true
|
||||
@indexAllReferences(false)
|
||||
|
||||
setTimeout(
|
||||
(self) ->
|
||||
|
|
|
@ -29,10 +29,13 @@ define [
|
|||
App.controller "DeleteAccountModalController", [
|
||||
"$scope", "$modalInstance", "$timeout", "$http",
|
||||
($scope, $modalInstance, $timeout, $http) ->
|
||||
$scope.state =
|
||||
$scope.state =
|
||||
isValid : false
|
||||
deleteText: ""
|
||||
password: ""
|
||||
inflight: false
|
||||
error: false
|
||||
invalidCredentials: false
|
||||
|
||||
$modalInstance.opened.then () ->
|
||||
$timeout () ->
|
||||
|
@ -40,20 +43,33 @@ define [
|
|||
, 700
|
||||
|
||||
$scope.checkValidation = ->
|
||||
$scope.state.isValid = $scope.state.deleteText == $scope.email
|
||||
$scope.state.isValid = $scope.state.deleteText == $scope.email and $scope.state.password.length > 0
|
||||
|
||||
$scope.delete = () ->
|
||||
$scope.state.inflight = true
|
||||
|
||||
$scope.state.error = false
|
||||
$scope.state.invalidCredentials = false
|
||||
$http({
|
||||
method: "DELETE"
|
||||
url: "/user"
|
||||
method: "POST"
|
||||
url: "/user/delete"
|
||||
headers:
|
||||
"X-CSRF-Token": window.csrfToken
|
||||
"Content-Type": 'application/json'
|
||||
data:
|
||||
password: $scope.state.password
|
||||
})
|
||||
.success () ->
|
||||
$modalInstance.close()
|
||||
$scope.state.inflight = false
|
||||
$scope.state.error = false
|
||||
$scope.state.invalidCredentials = false
|
||||
window.location = "/"
|
||||
.error (data, status) ->
|
||||
$scope.state.inflight = false
|
||||
if status == 403
|
||||
$scope.state.invalidCredentials = true
|
||||
else
|
||||
$scope.state.error = true
|
||||
|
||||
$scope.cancel = () ->
|
||||
$modalInstance.dismiss('cancel')
|
||||
|
|
|
@ -6,14 +6,34 @@ define [
|
|||
$scope.buttonClass = "btn-primary"
|
||||
|
||||
$scope.startFreeTrial = (source, couponCode) ->
|
||||
event_tracking.sendMB "subscription-start-trial", { source }
|
||||
plan = 'collaborator_free_trial_7_days'
|
||||
|
||||
w = window.open()
|
||||
sixpack.convert "track-changes-discount", ->
|
||||
sixpack.participate 'in-editor-free-trial-plan', ['student', 'collaborator'], (planName, rawResponse)->
|
||||
ga?('send', 'event', 'subscription-funnel', 'upgraded-free-trial', source)
|
||||
url = "/user/subscription/new?planCode=#{planName}_free_trial_7_days&ssp=#{planName == 'collaborator'}"
|
||||
if couponCode?
|
||||
url = "#{url}&cc=#{couponCode}"
|
||||
$scope.startedFreeTrial = true
|
||||
w.location = url
|
||||
go = () ->
|
||||
ga?('send', 'event', 'subscription-funnel', 'upgraded-free-trial', source)
|
||||
url = "/user/subscription/new?planCode=#{plan}&ssp=true"
|
||||
if couponCode?
|
||||
url = "#{url}&cc=#{couponCode}"
|
||||
$scope.startedFreeTrial = true
|
||||
|
||||
switch source
|
||||
when "dropbox"
|
||||
sixpack.participate 'teaser-dropbox-text', ['default', 'dropbox-focused'], (variant) ->
|
||||
event_tracking.sendMB "subscription-start-trial", { source, plan, variant }
|
||||
|
||||
when "history"
|
||||
sixpack.participate 'teaser-history', ['default', 'focused'], (variant) ->
|
||||
event_tracking.sendMB "subscription-start-trial", { source, plan, variant }
|
||||
|
||||
else
|
||||
event_tracking.sendMB "subscription-start-trial", { source, plan }
|
||||
|
||||
w.location = url
|
||||
|
||||
if $scope.shouldABTestPlans
|
||||
sixpack.participate 'plans-1610', ['default', 'heron', 'ibis'], (chosenVariation, rawResponse)->
|
||||
if chosenVariation in ['heron', 'ibis']
|
||||
plan = "collaborator_#{chosenVariation}"
|
||||
go()
|
||||
else
|
||||
go()
|
||||
|
|
|
@ -5,12 +5,15 @@ define [
|
|||
|
||||
App.controller "NewSubscriptionController", ($scope, MultiCurrencyPricing, abTestManager, $http, sixpack, event_tracking, ccUtils)->
|
||||
throw new Error("Recurly API Library Missing.") if typeof recurly is "undefined"
|
||||
|
||||
|
||||
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
||||
$scope.plans = MultiCurrencyPricing.plans
|
||||
|
||||
$scope.switchToStudent = ()->
|
||||
window.location = "/user/subscription/new?planCode=student_free_trial_7_days¤cy=#{$scope.currencyCode}&cc=#{$scope.data.coupon}"
|
||||
currentPlanCode = window.plan_code
|
||||
planCode = currentPlanCode.replace('collaborator', 'student')
|
||||
event_tracking.sendMB 'subscription-form-switch-to-student', { plan: window.plan_code }
|
||||
window.location = "/user/subscription/new?planCode=#{planCode}¤cy=#{$scope.currencyCode}&cc=#{$scope.data.coupon}"
|
||||
|
||||
event_tracking.sendMB "subscription-form", { plan : window.plan_code }
|
||||
|
||||
|
|
|
@ -5,12 +5,171 @@ define [
|
|||
|
||||
|
||||
App.factory "MultiCurrencyPricing", () ->
|
||||
|
||||
|
||||
currencyCode = window.recomendedCurrency
|
||||
|
||||
return {
|
||||
currencyCode:currencyCode
|
||||
plans:
|
||||
|
||||
heron:
|
||||
USD:
|
||||
student:
|
||||
monthly: "$6"
|
||||
annual: "$60"
|
||||
collaborator:
|
||||
monthly: "$12"
|
||||
annual: "$144"
|
||||
EUR:
|
||||
student:
|
||||
monthly: "€5"
|
||||
annual: "€50"
|
||||
collaborator:
|
||||
monthly: "€11"
|
||||
annual: "€132"
|
||||
GBP:
|
||||
student:
|
||||
monthly: "£5"
|
||||
annual: "£50"
|
||||
collaborator:
|
||||
monthly: "£10"
|
||||
annual: "£120"
|
||||
SEK:
|
||||
student:
|
||||
monthly: "45 kr"
|
||||
annual: "450 kr"
|
||||
collaborator:
|
||||
monthly: "90 kr"
|
||||
annual: "1080 kr"
|
||||
CAD:
|
||||
student:
|
||||
monthly: "$7"
|
||||
annual: "$70"
|
||||
collaborator:
|
||||
monthly: "$14"
|
||||
annual: "$168"
|
||||
NOK:
|
||||
student:
|
||||
monthly: "45 kr"
|
||||
annual: "450 kr"
|
||||
collaborator:
|
||||
monthly: "90 kr"
|
||||
annual: "1080 kr"
|
||||
DKK:
|
||||
student:
|
||||
monthly: "40 kr"
|
||||
annual: "400 kr"
|
||||
collaborator:
|
||||
monthly: "70 kr"
|
||||
annual: "840 kr"
|
||||
AUD:
|
||||
student:
|
||||
monthly: "$8"
|
||||
annual: "$80"
|
||||
collaborator:
|
||||
monthly: "$15"
|
||||
annual: "$180"
|
||||
NZD:
|
||||
student:
|
||||
monthly: "$8"
|
||||
annual: "$80"
|
||||
collaborator:
|
||||
monthly: "$15"
|
||||
annual: "$180"
|
||||
CHF:
|
||||
student:
|
||||
monthly: "Fr 6"
|
||||
annual: "Fr 60"
|
||||
collaborator:
|
||||
monthly: "Fr 12"
|
||||
annual: "Fr 144"
|
||||
SGD:
|
||||
student:
|
||||
monthly: "$8"
|
||||
annual: "$80"
|
||||
collaborator:
|
||||
monthly: "$16"
|
||||
annual: "$192"
|
||||
|
||||
ibis:
|
||||
USD:
|
||||
student:
|
||||
monthly: "$10"
|
||||
annual: "$100"
|
||||
collaborator:
|
||||
monthly: "$18"
|
||||
annual: "$216"
|
||||
EUR:
|
||||
student:
|
||||
monthly: "€9"
|
||||
annual: "€90"
|
||||
collaborator:
|
||||
monthly: "€17"
|
||||
annual: "€204"
|
||||
GBP:
|
||||
student:
|
||||
monthly: "£7"
|
||||
annual: "£70"
|
||||
collaborator:
|
||||
monthly: "£14"
|
||||
annual: "£168"
|
||||
SEK:
|
||||
student:
|
||||
monthly: "75 kr"
|
||||
annual: "750 kr"
|
||||
collaborator:
|
||||
monthly: "140 kr"
|
||||
annual: "1680 kr"
|
||||
CAD:
|
||||
student:
|
||||
monthly: "$12"
|
||||
annual: "$120"
|
||||
collaborator:
|
||||
monthly: "$22"
|
||||
annual: "$264"
|
||||
NOK:
|
||||
student:
|
||||
monthly: "75 kr"
|
||||
annual: "750 kr"
|
||||
collaborator:
|
||||
monthly: "140 kr"
|
||||
annual: "1680 kr"
|
||||
DKK:
|
||||
student:
|
||||
monthly: "68 kr"
|
||||
annual: "680 kr"
|
||||
collaborator:
|
||||
monthly: "110 kr"
|
||||
annual: "1320 kr"
|
||||
AUD:
|
||||
student:
|
||||
monthly: "$13"
|
||||
annual: "$130"
|
||||
collaborator:
|
||||
monthly: "$22"
|
||||
annual: "$264"
|
||||
NZD:
|
||||
student:
|
||||
monthly: "$14"
|
||||
annual: "$140"
|
||||
collaborator:
|
||||
monthly: "$22"
|
||||
annual: "$264"
|
||||
CHF:
|
||||
student:
|
||||
monthly: "Fr 10"
|
||||
annual: "Fr 100"
|
||||
collaborator:
|
||||
monthly: "Fr 18"
|
||||
annual: "Fr 216"
|
||||
SGD:
|
||||
student:
|
||||
monthly: "$14"
|
||||
annual: "$140"
|
||||
collaborator:
|
||||
monthly: "$25"
|
||||
annual: "$300"
|
||||
|
||||
plans:
|
||||
USD:
|
||||
symbol: "$"
|
||||
student:
|
||||
|
@ -23,7 +182,7 @@ define [
|
|||
monthly: "$30"
|
||||
annual: "$360"
|
||||
|
||||
EUR:
|
||||
EUR:
|
||||
symbol: "€"
|
||||
student:
|
||||
monthly: "€7"
|
||||
|
@ -34,7 +193,7 @@ define [
|
|||
professional:
|
||||
monthly: "€28"
|
||||
annual: "€336"
|
||||
|
||||
|
||||
GBP:
|
||||
symbol: "£"
|
||||
student:
|
||||
|
@ -117,7 +276,7 @@ define [
|
|||
professional:
|
||||
monthly: "$35"
|
||||
annual: "$420"
|
||||
|
||||
|
||||
CHF:
|
||||
symbol: "Fr"
|
||||
student:
|
||||
|
@ -141,36 +300,54 @@ define [
|
|||
professional:
|
||||
monthly: "$40"
|
||||
annual: "$480"
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
App.controller "PlansController", ($scope, $modal, event_tracking, abTestManager, MultiCurrencyPricing, $http, sixpack) ->
|
||||
|
||||
App.controller "PlansController", ($scope, $modal, event_tracking, abTestManager, MultiCurrencyPricing, $http) ->
|
||||
$scope.showPlans = false
|
||||
|
||||
$scope.plansVariant = 'default'
|
||||
$scope.shouldABTestPlans = window.shouldABTestPlans
|
||||
|
||||
if $scope.shouldABTestPlans
|
||||
sixpack.participate 'plans-1610', ['default', 'heron', 'ibis'], (chosenVariation, rawResponse)->
|
||||
$scope.plansVariant = chosenVariation
|
||||
event_tracking.sendMB 'plans-page', {plans_variant: chosenVariation}
|
||||
if chosenVariation in ['heron', 'ibis']
|
||||
# overwrite student plans with alternative
|
||||
for currency, _v of $scope.plans
|
||||
$scope.plans[currency]['student'] = MultiCurrencyPricing[chosenVariation][currency]['student']
|
||||
$scope.plans[currency]['collaborator'] = MultiCurrencyPricing[chosenVariation][currency]['collaborator']
|
||||
$scope.showPlans = true
|
||||
else
|
||||
$scope.showPlans = true
|
||||
|
||||
$scope.plans = MultiCurrencyPricing.plans
|
||||
|
||||
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
||||
|
||||
$scope.trial_len = 7
|
||||
|
||||
$scope.planQueryString = '_free_trial_7_days'
|
||||
|
||||
$scope.ui =
|
||||
view: "monthly"
|
||||
|
||||
|
||||
$scope.changeCurreny = (newCurrency)->
|
||||
$scope.currencyCode = newCurrency
|
||||
|
||||
$scope.signUpNowClicked = (plan, annual)->
|
||||
event_tracking.sendMB 'plans-page-start-trial', {plan}
|
||||
if $scope.ui.view == "annual"
|
||||
plan = "#{plan}_annual"
|
||||
|
||||
event_tracking.send 'subscription-funnel', 'sign_up_now_button', plan
|
||||
event_tracking.send 'subscription-funnel', 'sign_up_now_button', plan
|
||||
|
||||
$scope.switchToMonthly = ->
|
||||
$scope.ui.view = "monthly"
|
||||
event_tracking.send 'subscription-funnel', 'plans-page', 'monthly-prices'
|
||||
|
||||
|
||||
$scope.switchToStudent = ->
|
||||
$scope.ui.view = "student"
|
||||
event_tracking.send 'subscription-funnel', 'plans-page', 'student-prices'
|
||||
|
@ -178,7 +355,7 @@ define [
|
|||
$scope.switchToAnnual = ->
|
||||
$scope.ui.view = "annual"
|
||||
event_tracking.send 'subscription-funnel', 'plans-page', 'student-prices'
|
||||
|
||||
|
||||
$scope.openGroupPlanModal = () ->
|
||||
$modal.open {
|
||||
templateUrl: "groupPlanModalTemplate"
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
define [
|
||||
"base"
|
||||
], (App)->
|
||||
|
||||
App.controller 'SuccessfulSubscriptionController', ($scope, sixpack) ->
|
||||
sixpack.convert 'plans-1610', () ->
|
||||
|
||||
|
||||
SUBSCRIPTION_URL = "/user/subscription/update"
|
||||
|
||||
setupReturly = _.once ->
|
||||
recurly?.configure window.recurlyApiKey
|
||||
PRICES = {}
|
||||
|
||||
|
||||
App.controller "CurrenyDropdownController", ($scope, MultiCurrencyPricing, $q)->
|
||||
|
||||
$scope.plans = MultiCurrencyPricing.plans
|
||||
# $scope.plans = MultiCurrencyPricing.plans
|
||||
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
||||
|
||||
$scope.changeCurrency = (newCurrency)->
|
||||
|
@ -31,7 +37,7 @@ define [
|
|||
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
||||
|
||||
$scope.pricing = MultiCurrencyPricing
|
||||
$scope.plans = MultiCurrencyPricing.plans
|
||||
# $scope.plans = MultiCurrencyPricing.plans
|
||||
$scope.currencySymbol = MultiCurrencyPricing.plans[MultiCurrencyPricing.currencyCode].symbol
|
||||
|
||||
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
||||
|
@ -53,9 +59,9 @@ define [
|
|||
price = ""
|
||||
|
||||
App.controller "ConfirmChangePlanController", ($scope, $modalInstance, $http)->
|
||||
|
||||
|
||||
$scope.confirmChangePlan = ->
|
||||
body =
|
||||
body =
|
||||
plan_code: $scope.plan.planCode
|
||||
_csrf : window.csrfToken
|
||||
|
||||
|
@ -74,7 +80,7 @@ define [
|
|||
$scope.confirmLeaveGroup = ->
|
||||
$scope.inflight = true
|
||||
$http({
|
||||
url: "/subscription/group/user",
|
||||
url: "/subscription/group/user",
|
||||
method: "DELETE",
|
||||
params: {admin_user_id: $scope.admin_id, _csrf: window.csrfToken}
|
||||
}).success ->
|
||||
|
@ -87,6 +93,8 @@ define [
|
|||
|
||||
|
||||
App.controller "UserSubscriptionController", ($scope, MultiCurrencyPricing, $http, sixpack, $modal) ->
|
||||
$scope.plans = MultiCurrencyPricing.plans
|
||||
|
||||
freeTrialEndDate = new Date(subscription?.trial_ends_at)
|
||||
|
||||
sevenDaysTime = new Date()
|
||||
|
@ -96,6 +104,16 @@ define [
|
|||
freeTrialExpiresUnderSevenDays = freeTrialEndDate < sevenDaysTime
|
||||
|
||||
$scope.view = 'overview'
|
||||
$scope.getSuffix = (planCode) ->
|
||||
planCode?.match(/(.*?)_(.*)/)?[2] || null
|
||||
$scope.subscriptionSuffix = $scope.getSuffix(window?.subscription?.planCode)
|
||||
if $scope.subscriptionSuffix == 'free_trial_7_days'
|
||||
$scope.subscriptionSuffix = ''
|
||||
$scope.isNextGenPlan = $scope.subscriptionSuffix in ['heron', 'ibis']
|
||||
|
||||
$scope.shouldShowPlan = (planCode) ->
|
||||
$scope.getSuffix(planCode) not in ['heron', 'ibis']
|
||||
|
||||
isMonthlyCollab = subscription?.planCode?.indexOf("collaborator") != -1 and subscription?.planCode?.indexOf("ann") == -1
|
||||
stillInFreeTrial = freeTrialInFuture and freeTrialExpiresUnderSevenDays
|
||||
|
||||
|
@ -118,7 +136,7 @@ define [
|
|||
$scope.studentPrice = $scope.currencySymbol + (totalPriceExTax + taxAmmount)
|
||||
|
||||
$scope.downgradeToStudent = ->
|
||||
body =
|
||||
body =
|
||||
plan_code: 'student'
|
||||
_csrf : window.csrfToken
|
||||
$scope.inflight = true
|
||||
|
@ -129,7 +147,7 @@ define [
|
|||
console.log "something went wrong changing plan"
|
||||
|
||||
$scope.cancelSubscription = ->
|
||||
body =
|
||||
body =
|
||||
_csrf : window.csrfToken
|
||||
|
||||
$scope.inflight = true
|
||||
|
@ -158,7 +176,7 @@ define [
|
|||
|
||||
|
||||
$scope.exendTrial = ->
|
||||
body =
|
||||
body =
|
||||
_csrf : window.csrfToken
|
||||
$scope.inflight = true
|
||||
$http.put("/user/subscription/extend", body)
|
||||
|
@ -166,6 +184,3 @@ define [
|
|||
location.reload()
|
||||
.error ->
|
||||
console.log "something went wrong changing plan"
|
||||
|
||||
|
||||
|
||||
|
|
BIN
services/web/public/img/teasers/history/teaser-history.png
Normal file
BIN
services/web/public/img/teasers/history/teaser-history.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 114 KiB |
|
@ -214,18 +214,21 @@ var createLatexWorker = function (session) {
|
|||
var suppressions = [];
|
||||
var hints = [];
|
||||
var changeHandler = null;
|
||||
var docChangePending = false;
|
||||
var firstPass = true;
|
||||
|
||||
var worker = new WorkerClient(["ace"], "ace/mode/latex_worker", "LatexWorker");
|
||||
worker.attachToDocument(doc);
|
||||
|
||||
doc.on("change", function () {
|
||||
var docChangeHandler = doc.on("change", function () {
|
||||
docChangePending = true;
|
||||
if(changeHandler) {
|
||||
clearTimeout(changeHandler);
|
||||
changeHandler = null;
|
||||
}
|
||||
});
|
||||
|
||||
selection.on("changeCursor", function () {
|
||||
var cursorHandler = selection.on("changeCursor", function () {
|
||||
if (docChangePending) { return; } ;
|
||||
changeHandler = setTimeout(function () {
|
||||
updateMarkers({cursorMoveOnly:true});
|
||||
suppressions = [];
|
||||
|
@ -307,11 +310,20 @@ var createLatexWorker = function (session) {
|
|||
}
|
||||
}
|
||||
if (!cursorMoveOnly || suppressedChanges) {
|
||||
session.setAnnotations(annotations);
|
||||
if (firstPass) {
|
||||
if (annotations.length > 0) {
|
||||
var originalAnnotations = session.getAnnotations();
|
||||
session.setAnnotations(originalAnnotations.concat(annotations));
|
||||
};
|
||||
firstPass = false;
|
||||
} else {
|
||||
session.setAnnotations(annotations);
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
worker.on("lint", function(results) {
|
||||
if(docChangePending) { docChangePending = false; };
|
||||
hints = results.data;
|
||||
if (hints.length > 100) {
|
||||
hints = hints.slice(0, 100); // limit to 100 errors
|
||||
|
@ -319,14 +331,22 @@ var createLatexWorker = function (session) {
|
|||
updateMarkers();
|
||||
});
|
||||
worker.on("terminate", function() {
|
||||
if(changeHandler) {
|
||||
clearTimeout(changeHandler);
|
||||
changeHandler = null;
|
||||
}
|
||||
doc.off("change", docChangeHandler);
|
||||
selection.off("changeCursor", cursorHandler);
|
||||
for (var key in savedRange) {
|
||||
var range = savedRange[key];
|
||||
range.start.detach();
|
||||
range.end.detach();
|
||||
if (range.start !== cursorAnchor) { range.start.detach(); }
|
||||
if (range.end !== cursorAnchor) { range.end.detach(); }
|
||||
session.removeMarker(range.id);
|
||||
delete savedRange[key];
|
||||
}
|
||||
|
||||
savedRange = {};
|
||||
hints = [];
|
||||
suppressions = [];
|
||||
session.clearAnnotations();
|
||||
});
|
||||
|
||||
return worker;
|
||||
|
|
|
@ -1755,7 +1755,7 @@ var InterpretTokens = function (TokeniseResult, ErrorReporter) {
|
|||
};
|
||||
|
||||
if (endToken) {
|
||||
TokenErrorFromTo(token, endToken, "invalid environment command" + text.substring(token[2], endToken[3] || endToken[2]));
|
||||
TokenErrorFromTo(token, endToken, "invalid environment command " + text.substring(token[2], endToken[3] || endToken[2]));
|
||||
} else {
|
||||
TokenError(token, "invalid environment command");
|
||||
};
|
||||
|
|
|
@ -2,4 +2,11 @@
|
|||
.alert {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#delete-account-modal {
|
||||
.alert {
|
||||
margin-top: 25px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -414,11 +414,13 @@
|
|||
}
|
||||
}
|
||||
|
||||
.teaser-title,
|
||||
.dropbox-teaser-title {
|
||||
margin-top: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.teaser-img,
|
||||
.dropbox-teaser-img {
|
||||
.img-responsive;
|
||||
margin-bottom: 5px;
|
||||
|
|
|
@ -25,6 +25,19 @@
|
|||
background-color: white;
|
||||
border-radius: 8px;
|
||||
}
|
||||
.message-wider {
|
||||
width: 650px;
|
||||
margin-top: 60px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.message-header {
|
||||
.modal-header;
|
||||
}
|
||||
|
||||
.message-body {
|
||||
.modal-body;
|
||||
}
|
||||
}
|
||||
|
||||
.diff-panel {
|
||||
|
|
|
@ -18,30 +18,6 @@ describe "EmailBuilder", ->
|
|||
"settings-sharelatex":@settings
|
||||
"logger-sharelatex": log:->
|
||||
|
||||
describe "projectSharedWithYou", ->
|
||||
beforeEach ->
|
||||
@opts =
|
||||
to:"bob@bob.com"
|
||||
first_name:"bob"
|
||||
owner:
|
||||
email:"sally@hally.com"
|
||||
project:
|
||||
url:"http://www.project.com"
|
||||
name:"standard project"
|
||||
@email = @EmailBuilder.buildEmail("projectSharedWithYou", @opts)
|
||||
|
||||
it "should insert the owner email into the template", ->
|
||||
@email.html.indexOf(@opts.owner.email).should.not.equal -1
|
||||
@email.subject.indexOf(@opts.owner.email).should.not.equal -1
|
||||
|
||||
it 'should not have text component', ->
|
||||
expect(@email.html?).to.equal true
|
||||
expect(@email.text?).to.equal false
|
||||
|
||||
it "should not have undefined in it", ->
|
||||
@email.html.indexOf("undefined").should.equal -1
|
||||
@email.subject.indexOf("undefined").should.equal -1
|
||||
|
||||
describe "projectInvite", ->
|
||||
beforeEach ->
|
||||
@opts =
|
||||
|
|
|
@ -20,7 +20,7 @@ mockSubscriptions =
|
|||
describe "SubscriptionController sanboxed", ->
|
||||
|
||||
beforeEach ->
|
||||
@user = {email:"tom@yahoo.com", _id: 'one'}
|
||||
@user = {email:"tom@yahoo.com", _id: 'one', signUpDate: new Date('2000-10-01')}
|
||||
@activeRecurlySubscription = mockSubscriptions["subscription-123-active"]
|
||||
|
||||
@AuthenticationController =
|
||||
|
@ -63,6 +63,8 @@ describe "SubscriptionController sanboxed", ->
|
|||
getCurrencyCode:sinon.stub()
|
||||
@SubscriptionDomainHandler =
|
||||
getDomainLicencePage:sinon.stub()
|
||||
@UserGetter =
|
||||
getUser: sinon.stub().callsArgWith(2, null, @user)
|
||||
@SubscriptionController = SandboxedModule.require modulePath, requires:
|
||||
'../Authentication/AuthenticationController': @AuthenticationController
|
||||
'./SubscriptionHandler': @SubscriptionHandler
|
||||
|
@ -76,6 +78,7 @@ describe "SubscriptionController sanboxed", ->
|
|||
warn:->
|
||||
"settings-sharelatex": @settings
|
||||
"./SubscriptionDomainHandler":@SubscriptionDomainHandler
|
||||
"../User/UserGetter": @UserGetter
|
||||
|
||||
|
||||
@res = new MockResponse()
|
||||
|
@ -92,12 +95,31 @@ describe "SubscriptionController sanboxed", ->
|
|||
@GeoIpLookup.getCurrencyCode.callsArgWith(1, null, @stubbedCurrencyCode)
|
||||
@res.callback = done
|
||||
@SubscriptionController.plansPage(@req, @res)
|
||||
@UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user)
|
||||
|
||||
it "should set the recommended currency from the geoiplookup", (done)->
|
||||
@res.renderedVariables.recomendedCurrency.should.equal(@stubbedCurrencyCode)
|
||||
@GeoIpLookup.getCurrencyCode.calledWith(@req.ip).should.equal true
|
||||
done()
|
||||
|
||||
it 'should fetch the current user', (done) ->
|
||||
@UserGetter.getUser.callCount.should.equal 1
|
||||
done()
|
||||
|
||||
it 'should decide not to AB test the plans', (done) ->
|
||||
@res.renderedVariables.shouldABTestPlans.should.equal false
|
||||
done()
|
||||
|
||||
describe 'when user is not logged in', (done) ->
|
||||
|
||||
beforeEach ->
|
||||
@AuthenticationController.getLoggedInUserId.returns(null)
|
||||
|
||||
it 'should not fetch the current user', (done) ->
|
||||
@UserGetter.getUser.callCount.should.equal 0
|
||||
done()
|
||||
|
||||
|
||||
describe "editBillingDetailsPage", ->
|
||||
describe "with a user with a subscription", ->
|
||||
beforeEach (done) ->
|
||||
|
|
|
@ -84,15 +84,81 @@ describe "UserController", ->
|
|||
sendStatus: sinon.stub()
|
||||
json: sinon.stub()
|
||||
@next = sinon.stub()
|
||||
describe "deleteUser", ->
|
||||
|
||||
it "should delete the user", (done)->
|
||||
describe 'tryDeleteUser', ->
|
||||
|
||||
@res.sendStatus = (code)=>
|
||||
@UserDeleter.deleteUser.calledWith(@user_id)
|
||||
beforeEach ->
|
||||
@req.body.password = 'wat'
|
||||
@AuthenticationController.getLoggedInUserId = sinon.stub().returns(@user._id)
|
||||
@AuthenticationManager.authenticate = sinon.stub().callsArgWith(2, null, @user)
|
||||
@UserDeleter.deleteUser = sinon.stub().callsArgWith(1, null)
|
||||
|
||||
it 'should send 200', (done) ->
|
||||
@res.sendStatus = (code) =>
|
||||
code.should.equal 200
|
||||
done()
|
||||
@UserController.deleteUser @req, @res
|
||||
@UserController.tryDeleteUser @req, @res, @next
|
||||
|
||||
it 'should try to authenticate user', (done) ->
|
||||
@res.sendStatus = (code) =>
|
||||
@AuthenticationManager.authenticate.callCount.should.equal 1
|
||||
@AuthenticationManager.authenticate.calledWith({_id: @user._id}, @req.body.password).should.equal true
|
||||
done()
|
||||
@UserController.tryDeleteUser @req, @res, @next
|
||||
|
||||
it 'should delete the user', (done) ->
|
||||
@res.sendStatus = (code) =>
|
||||
@UserDeleter.deleteUser.callCount.should.equal 1
|
||||
@UserDeleter.deleteUser.calledWith(@user._id).should.equal true
|
||||
done()
|
||||
@UserController.tryDeleteUser @req, @res, @next
|
||||
|
||||
describe 'when no password is supplied', ->
|
||||
|
||||
beforeEach ->
|
||||
@req.body.password = ''
|
||||
|
||||
it 'should return 403', (done) ->
|
||||
@res.sendStatus = (code) =>
|
||||
code.should.equal 403
|
||||
done()
|
||||
@UserController.tryDeleteUser @req, @res, @next
|
||||
|
||||
describe 'when authenticate produces an error', ->
|
||||
|
||||
beforeEach ->
|
||||
@AuthenticationManager.authenticate = sinon.stub().callsArgWith(2, new Error('woops'))
|
||||
|
||||
it 'should call next with an error', (done) ->
|
||||
@next = (err) =>
|
||||
expect(err).to.not.equal null
|
||||
expect(err).to.be.instanceof Error
|
||||
done()
|
||||
@UserController.tryDeleteUser @req, @res, @next
|
||||
|
||||
describe 'when authenticate does not produce a user', ->
|
||||
|
||||
beforeEach ->
|
||||
@AuthenticationManager.authenticate = sinon.stub().callsArgWith(2, null, null)
|
||||
|
||||
it 'should return 403', (done) ->
|
||||
@res.sendStatus = (code) =>
|
||||
code.should.equal 403
|
||||
done()
|
||||
@UserController.tryDeleteUser @req, @res, @next
|
||||
|
||||
describe 'when deleteUser produces an error', ->
|
||||
|
||||
beforeEach ->
|
||||
@UserDeleter.deleteUser = sinon.stub().callsArgWith(1, new Error('woops'))
|
||||
|
||||
it 'should call next with an error', (done) ->
|
||||
@next = (err) =>
|
||||
expect(err).to.not.equal null
|
||||
expect(err).to.be.instanceof Error
|
||||
done()
|
||||
@UserController.tryDeleteUser @req, @res, @next
|
||||
|
||||
|
||||
describe "unsubscribe", ->
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue