mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge branch 'master' into ja-review-panel
Conflicts: app/views/project/editor/editor.jade public/coffee/ide.coffee public/coffee/ide/editor/directives/aceEditor.coffee
This commit is contained in:
commit
8368577867
215 changed files with 1658 additions and 815 deletions
|
@ -11,27 +11,6 @@ module.exports = CollaboratorsEmailHandler =
|
||||||
"user_first_name=#{encodeURIComponent(project.owner_ref.first_name)}"
|
"user_first_name=#{encodeURIComponent(project.owner_ref.first_name)}"
|
||||||
].join("&")
|
].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)->
|
notifyUserOfProjectInvite: (project_id, email, invite, callback)->
|
||||||
Project
|
Project
|
||||||
.findOne(_id: project_id )
|
.findOne(_id: project_id )
|
||||||
|
|
|
@ -7,10 +7,18 @@ settings = require("settings-sharelatex")
|
||||||
|
|
||||||
templates = {}
|
templates = {}
|
||||||
|
|
||||||
|
|
||||||
templates.registered =
|
templates.registered =
|
||||||
subject: _.template "Activate your #{settings.appName} Account"
|
subject: _.template "Activate your #{settings.appName} Account"
|
||||||
layout: PersonalEmailLayout
|
layout: PersonalEmailLayout
|
||||||
type: "notification"
|
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 """
|
compiledTemplate: _.template """
|
||||||
<p>Congratulations, you've just had an account created for you on #{settings.appName} with the email address "<%= to %>".</p>
|
<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>
|
<p>If you have any questions or problems, please contact <a href="mailto:#{settings.adminEmail}">#{settings.adminEmail}</a>.</p>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
templates.canceledSubscription =
|
templates.canceledSubscription =
|
||||||
subject: _.template "ShareLaTeX thoughts"
|
subject: _.template "ShareLaTeX thoughts"
|
||||||
layout: PersonalEmailLayout
|
layout: PersonalEmailLayout
|
||||||
type:"lifecycle"
|
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 '''
|
compiledTemplate: _.template '''
|
||||||
<p>Hi <%= first_name %>,</p>
|
<p>Hi <%= first_name %>,</p>
|
||||||
|
|
||||||
|
@ -36,10 +58,26 @@ ShareLaTeX Co-founder
|
||||||
</p>
|
</p>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
|
|
||||||
templates.passwordResetRequested =
|
templates.passwordResetRequested =
|
||||||
subject: _.template "Password Reset - #{settings.appName}"
|
subject: _.template "Password Reset - #{settings.appName}"
|
||||||
layout: NotificationEmailLayout
|
layout: NotificationEmailLayout
|
||||||
type:"notification"
|
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 """
|
compiledTemplate: _.template """
|
||||||
<h2>Password Reset</h2>
|
<h2>Password Reset</h2>
|
||||||
<p>
|
<p>
|
||||||
|
@ -66,26 +104,6 @@ If you didn't request a password reset, let us know.
|
||||||
<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
|
<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 =
|
templates.projectInvite =
|
||||||
subject: _.template "<%= project.name %> - shared by <%= owner.email %>"
|
subject: _.template "<%= project.name %> - shared by <%= owner.email %>"
|
||||||
|
@ -113,10 +131,20 @@ Thank you
|
||||||
<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
|
<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
templates.completeJoinGroupAccount =
|
templates.completeJoinGroupAccount =
|
||||||
subject: _.template "Verify Email to join <%= group_name %> group"
|
subject: _.template "Verify Email to join <%= group_name %> group"
|
||||||
layout: NotificationEmailLayout
|
layout: NotificationEmailLayout
|
||||||
type:"notification"
|
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 """
|
compiledTemplate: _.template """
|
||||||
<p>Hi, please verify your email to join the <%= group_name %> and get your free premium account</p>
|
<p>Hi, please verify your email to join the <%= group_name %> and get your free premium account</p>
|
||||||
<center>
|
<center>
|
||||||
|
@ -134,6 +162,7 @@ templates.completeJoinGroupAccount =
|
||||||
<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
|
<p> <a href="<%= siteUrl %>">#{settings.appName}</a></p>
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
module.exports =
|
module.exports =
|
||||||
templates: templates
|
templates: templates
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,7 @@ Reporter = (res) ->
|
||||||
|
|
||||||
res.contentType("application/json")
|
res.contentType("application/json")
|
||||||
if failures.length > 0
|
if failures.length > 0
|
||||||
|
logger.err failures:failures, "health check failed"
|
||||||
res.send 500, JSON.stringify(results, null, 2)
|
res.send 500, JSON.stringify(results, null, 2)
|
||||||
else
|
else
|
||||||
res.send 200, JSON.stringify(results, null, 2)
|
res.send 200, JSON.stringify(results, null, 2)
|
||||||
|
|
|
@ -8,6 +8,7 @@ Settings = require 'settings-sharelatex'
|
||||||
logger = require('logger-sharelatex')
|
logger = require('logger-sharelatex')
|
||||||
GeoIpLookup = require("../../infrastructure/GeoIpLookup")
|
GeoIpLookup = require("../../infrastructure/GeoIpLookup")
|
||||||
SubscriptionDomainHandler = require("./SubscriptionDomainHandler")
|
SubscriptionDomainHandler = require("./SubscriptionDomainHandler")
|
||||||
|
UserGetter = require "../User/UserGetter"
|
||||||
|
|
||||||
module.exports = SubscriptionController =
|
module.exports = SubscriptionController =
|
||||||
|
|
||||||
|
@ -21,14 +22,25 @@ module.exports = SubscriptionController =
|
||||||
if req.query.v?
|
if req.query.v?
|
||||||
viewName = "#{viewName}_#{req.query.v}"
|
viewName = "#{viewName}_#{req.query.v}"
|
||||||
logger.log viewName:viewName, "showing plans page"
|
logger.log viewName:viewName, "showing plans page"
|
||||||
|
currentUser = null
|
||||||
GeoIpLookup.getCurrencyCode req.query?.ip || req.ip, (err, recomendedCurrency)->
|
GeoIpLookup.getCurrencyCode req.query?.ip || req.ip, (err, recomendedCurrency)->
|
||||||
return next(err) if err?
|
return next(err) if err?
|
||||||
res.render viewName,
|
render = () ->
|
||||||
title: "plans_and_pricing"
|
res.render viewName,
|
||||||
plans: plans
|
title: "plans_and_pricing"
|
||||||
baseUrl: baseUrl
|
plans: plans
|
||||||
gaExperiments: Settings.gaExperiments.plansPage
|
baseUrl: baseUrl
|
||||||
recomendedCurrency:recomendedCurrency
|
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
|
#get to show the recurly.js page
|
||||||
paymentPage: (req, res, next) ->
|
paymentPage: (req, res, next) ->
|
||||||
|
|
|
@ -44,7 +44,7 @@ module.exports =
|
||||||
allPlans = {}
|
allPlans = {}
|
||||||
plans.forEach (plan)->
|
plans.forEach (plan)->
|
||||||
allPlans[plan.planCode] = plan
|
allPlans[plan.planCode] = plan
|
||||||
|
|
||||||
result =
|
result =
|
||||||
allPlans: allPlans
|
allPlans: allPlans
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ module.exports =
|
||||||
|
|
||||||
result.studentAccounts = _.filter plans, (plan)->
|
result.studentAccounts = _.filter plans, (plan)->
|
||||||
plan.planCode.indexOf("student") != -1
|
plan.planCode.indexOf("student") != -1
|
||||||
|
|
||||||
result.groupMonthlyPlans = _.filter plans, (plan)->
|
result.groupMonthlyPlans = _.filter plans, (plan)->
|
||||||
plan.groupPlan and !plan.annual
|
plan.groupPlan and !plan.annual
|
||||||
|
|
||||||
|
@ -68,4 +68,3 @@ module.exports =
|
||||||
!plan.groupPlan and plan.annual and plan.planCode.indexOf("student") == -1
|
!plan.groupPlan and plan.annual and plan.planCode.indexOf("student") == -1
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
|
@ -15,12 +15,26 @@ settings = require "settings-sharelatex"
|
||||||
|
|
||||||
module.exports = UserController =
|
module.exports = UserController =
|
||||||
|
|
||||||
deleteUser: (req, res)->
|
tryDeleteUser: (req, res, next) ->
|
||||||
user_id = AuthenticationController.getLoggedInUserId(req)
|
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||||
UserDeleter.deleteUser user_id, (err)->
|
password = req.body.password
|
||||||
if !err?
|
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()
|
req.session?.destroy()
|
||||||
res.sendStatus(200)
|
res.sendStatus(200)
|
||||||
|
|
||||||
unsubscribe: (req, res)->
|
unsubscribe: (req, res)->
|
||||||
user_id = AuthenticationController.getLoggedInUserId(req)
|
user_id = AuthenticationController.getLoggedInUserId(req)
|
||||||
|
@ -143,7 +157,7 @@ module.exports = UserController =
|
||||||
type:'success'
|
type:'success'
|
||||||
text:'Your password has been changed'
|
text:'Your password has been changed'
|
||||||
else
|
else
|
||||||
logger.log user: user, "current password wrong"
|
logger.log user_id: user_id, "current password wrong"
|
||||||
res.send
|
res.send
|
||||||
message:
|
message:
|
||||||
type:'error'
|
type:'error'
|
||||||
|
|
|
@ -39,7 +39,7 @@ pathList = [
|
||||||
["#{jsPath}ide.js"]
|
["#{jsPath}ide.js"]
|
||||||
["#{jsPath}main.js"]
|
["#{jsPath}main.js"]
|
||||||
["#{jsPath}libs.js"]
|
["#{jsPath}libs.js"]
|
||||||
["#{jsPath}#{ace}/ace.js","#{jsPath}#{ace}/mode-latex.js", "#{jsPath}#{ace}/snippets/latex.js"]
|
["#{jsPath}#{ace}/ace.js","#{jsPath}#{ace}/mode-latex.js","#{jsPath}#{ace}/worker-latex.js","#{jsPath}#{ace}/snippets/latex.js"]
|
||||||
["#{jsPath}libs/#{pdfjs}/pdf.js"]
|
["#{jsPath}libs/#{pdfjs}/pdf.js"]
|
||||||
["#{jsPath}libs/#{pdfjs}/pdf.worker.js"]
|
["#{jsPath}libs/#{pdfjs}/pdf.worker.js"]
|
||||||
["#{jsPath}libs/#{pdfjs}/compatibility.js"]
|
["#{jsPath}libs/#{pdfjs}/compatibility.js"]
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
module.exports =
|
module.exports =
|
||||||
user: (user) ->
|
user: (user) ->
|
||||||
|
if !user?
|
||||||
|
return null
|
||||||
if !user._id?
|
if !user._id?
|
||||||
user = {_id : user}
|
user = {_id : user}
|
||||||
return {
|
return {
|
||||||
|
@ -10,6 +12,8 @@ module.exports =
|
||||||
}
|
}
|
||||||
|
|
||||||
project: (project) ->
|
project: (project) ->
|
||||||
|
if !project?
|
||||||
|
return null
|
||||||
if !project._id?
|
if !project._id?
|
||||||
project = {_id: project}
|
project = {_id: project}
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -92,7 +92,7 @@ module.exports = class Router
|
||||||
webRouter.post '/user/sessions/clear', AuthenticationController.requireLogin(), UserController.clearSessions
|
webRouter.post '/user/sessions/clear', AuthenticationController.requireLogin(), UserController.clearSessions
|
||||||
|
|
||||||
webRouter.delete '/user/newsletter/unsubscribe', AuthenticationController.requireLogin(), UserController.unsubscribe
|
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
|
webRouter.get '/user/personal_info', AuthenticationController.requireLogin(), UserInfoController.getLoggedInUsersPersonalInfo
|
||||||
apiRouter.get '/user/:user_id/personal_info', AuthenticationController.httpAuth, UserInfoController.getPersonalInfo
|
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").
|
script(type="text/javascript").
|
||||||
window.csrfToken = "#{csrfToken}";
|
window.csrfToken = "#{csrfToken}";
|
||||||
|
|
||||||
block scripts
|
|
||||||
|
|
||||||
script(src=buildJsPath("libs/jquery-1.11.1.min.js", {fingerprint:false}))
|
script(src=buildJsPath("libs/jquery-1.11.1.min.js", {fingerprint:false}))
|
||||||
script(type="text/javascript").
|
script(type="text/javascript").
|
||||||
var noCdnKey = "nocdn=true"
|
var noCdnKey = "nocdn=true"
|
||||||
|
@ -61,6 +59,9 @@ html(itemscope, itemtype='http://schema.org/Product')
|
||||||
if (cdnBlocked && !noCdnAlreadyInUrl && navigator.userAgent.indexOf("Googlebot") == -1) {
|
if (cdnBlocked && !noCdnAlreadyInUrl && navigator.userAgent.indexOf("Googlebot") == -1) {
|
||||||
window.location.search += '&'+noCdnKey;
|
window.location.search += '&'+noCdnKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
block scripts
|
||||||
|
|
||||||
script(src=buildJsPath("libs/angular-1.3.15.min.js", {fingerprint:false}))
|
script(src=buildJsPath("libs/angular-1.3.15.min.js", {fingerprint:false}))
|
||||||
|
|
||||||
script.
|
script.
|
||||||
|
|
|
@ -118,6 +118,9 @@ block requirejs
|
||||||
"ace/ext-searchbox": {
|
"ace/ext-searchbox": {
|
||||||
"deps": ["ace/ace"]
|
"deps": ["ace/ace"]
|
||||||
},
|
},
|
||||||
|
"ace/ext-modelist": {
|
||||||
|
"deps": ["ace/ace"]
|
||||||
|
},
|
||||||
"ace/ext-language_tools": {
|
"ace/ext-language_tools": {
|
||||||
"deps": ["ace/ace"]
|
"deps": ["ace/ace"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ div.full-size(
|
||||||
resize-on="layout:main:resize,layout:pdf:resize,layout:review:resize,reviewPanel:toggle",
|
resize-on="layout:main:resize,layout:pdf:resize,layout:review:resize,reviewPanel:toggle",
|
||||||
annotations="pdf.logEntryAnnotations[editor.open_doc_id]",
|
annotations="pdf.logEntryAnnotations[editor.open_doc_id]",
|
||||||
read-only="!permissions.write",
|
read-only="!permissions.write",
|
||||||
|
file-name="editor.open_doc_name",
|
||||||
on-ctrl-enter="recompileViaKey",
|
on-ctrl-enter="recompileViaKey",
|
||||||
syntax-validation="settings.syntaxValidation",
|
syntax-validation="settings.syntaxValidation",
|
||||||
review-panel="reviewPanel",
|
review-panel="reviewPanel",
|
||||||
|
|
|
@ -1,41 +1,85 @@
|
||||||
div#history(ng-show="ui.view == 'history'")
|
div#history(ng-show="ui.view == 'history'")
|
||||||
span(ng-controller="HistoryPremiumPopup")
|
span(ng-controller="HistoryPremiumPopup")
|
||||||
.upgrade-prompt(ng-show="!project.features.versioning")
|
.upgrade-prompt(ng-if="project.features.versioning === false && ui.view === 'history'")
|
||||||
.message(ng-show="project.owner._id == user.id")
|
|
||||||
p.text-center: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})}
|
div(sixpack-switch="teaser-history")
|
||||||
p.text-center.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")}
|
.message(
|
||||||
ul.list-unstyled
|
sixpack-default
|
||||||
li
|
ng-show="project.owner._id == user.id"
|
||||||
i.fa.fa-check
|
)
|
||||||
| #{translate("unlimited_projects")}
|
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")}
|
||||||
li
|
ul.list-unstyled
|
||||||
i.fa.fa-check
|
li
|
||||||
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
i.fa.fa-check
|
||||||
|
| #{translate("unlimited_projects")}
|
||||||
li
|
|
||||||
i.fa.fa-check
|
li
|
||||||
| #{translate("full_doc_history")}
|
i.fa.fa-check
|
||||||
|
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
|
||||||
li
|
|
||||||
i.fa.fa-check
|
li
|
||||||
| #{translate("sync_to_dropbox")}
|
i.fa.fa-check
|
||||||
|
| #{translate("full_doc_history")}
|
||||||
|
|
||||||
|
li
|
||||||
|
i.fa.fa-check
|
||||||
|
| #{translate("sync_to_dropbox")}
|
||||||
|
|
||||||
li
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
| #{translate("sync_to_github")}
|
| #{translate("sync_to_github")}
|
||||||
|
|
||||||
li
|
li
|
||||||
i.fa.fa-check
|
i.fa.fa-check
|
||||||
|#{translate("compile_larger_projects")}
|
|#{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")
|
.message.message-wider(
|
||||||
a.btn.btn-success(
|
sixpack-when="focused"
|
||||||
href
|
ng-show="project.owner._id == user.id"
|
||||||
ng-class="buttonClass"
|
)
|
||||||
ng-click="startFreeTrial('history')"
|
header.message-header
|
||||||
) #{translate("start_free_trial")}
|
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")
|
.message(ng-show="project.owner._id != user.id")
|
||||||
p #{translate("ask_proj_owner_to_upgrade_for_history")}
|
p #{translate("ask_proj_owner_to_upgrade_for_history")}
|
||||||
|
|
|
@ -12,8 +12,8 @@ block scripts
|
||||||
|
|
||||||
mixin printPlan(plan)
|
mixin printPlan(plan)
|
||||||
-if (!plan.hideFromUsers)
|
-if (!plan.hideFromUsers)
|
||||||
tr(ng-controller="ChangePlanFormController")
|
tr(ng-controller="ChangePlanFormController", ng-init="plan=#{JSON.stringify(plan)}", ng-show="shouldShowPlan(plan.planCode)")
|
||||||
td(ng-init="plan=#{JSON.stringify(plan)}")
|
td
|
||||||
strong #{plan.name}
|
strong #{plan.name}
|
||||||
td {{refreshPrice(plan.planCode)}}
|
td {{refreshPrice(plan.planCode)}}
|
||||||
-if (plan.annual)
|
-if (plan.annual)
|
||||||
|
@ -46,8 +46,8 @@ block content
|
||||||
|
|
|
|
||||||
| #{translate("your_billing_details_were_saved")}
|
| #{translate("your_billing_details_were_saved")}
|
||||||
.card(ng-if="view == 'overview'")
|
.card(ng-if="view == 'overview'")
|
||||||
.page-header
|
.page-header(x-current-plan="#{subscription.planCode}")
|
||||||
h1 #{translate("your_subscription")}
|
h1 #{translate("your_subscription")}
|
||||||
|
|
||||||
- if (subscription && user._id+'' == subscription.admin_id+'')
|
- if (subscription && user._id+'' == subscription.admin_id+'')
|
||||||
case subscription.state
|
case subscription.state
|
||||||
|
@ -56,7 +56,8 @@ block content
|
||||||
|
|
||||||
when "active"
|
when "active"
|
||||||
p !{translate("currently_subscribed_to_plan", {planName:"<strong>" + subscription.name + "</strong>"})}
|
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 !{translate("next_payment_of_x_collectected_on_y", {paymentAmmount:"<strong>" + subscription.price + "</strong>", collectionDate:"<strong>" + subscription.nextPaymentDueAt + "</strong>"})}
|
||||||
p.pull-right
|
p.pull-right
|
||||||
p
|
p
|
||||||
|
|
|
@ -3,6 +3,7 @@ block scripts
|
||||||
script(type='text/javascript').
|
script(type='text/javascript').
|
||||||
window.recomendedCurrency = '#{recomendedCurrency}'
|
window.recomendedCurrency = '#{recomendedCurrency}'
|
||||||
window.abCurrencyFlag = '#{abCurrencyFlag}'
|
window.abCurrencyFlag = '#{abCurrencyFlag}'
|
||||||
|
window.shouldABTestPlans = #{shouldABTestPlans || false}
|
||||||
|
|
||||||
script(type='text/javascript').
|
script(type='text/javascript').
|
||||||
(function() {var s=document.createElement('script'); s.type='text/javascript';s.async=true;
|
(function() {var s=document.createElement('script'); s.type='text/javascript';s.async=true;
|
||||||
|
@ -56,133 +57,144 @@ block content
|
||||||
ng-click="changeCurreny(currency)"
|
ng-click="changeCurreny(currency)"
|
||||||
) {{currency}} ({{value['symbol']}})
|
) {{currency}} ({{value['symbol']}})
|
||||||
|
|
||||||
.row(ng-cloak)
|
div(ng-show="showPlans")
|
||||||
.col-md-10.col-md-offset-1
|
.row(ng-cloak)
|
||||||
.row
|
.col-md-10.col-md-offset-1
|
||||||
.card-group.text-centered(ng-if="ui.view == 'monthly' || ui.view == 'annual'")
|
.row
|
||||||
.col-md-4
|
.card-group.text-centered(ng-if="ui.view == 'monthly' || ui.view == 'annual'")
|
||||||
.card.card-first
|
.col-md-4
|
||||||
.card-header
|
.card.card-first
|
||||||
h2 #{translate("personal")}
|
.card-header
|
||||||
.circle #{translate("free")}
|
h2 #{translate("personal")}
|
||||||
ul.list-unstyled
|
.circle #{translate("free")}
|
||||||
li #{translate("one_collaborator")}
|
ul.list-unstyled
|
||||||
li
|
li #{translate("one_collaborator")}
|
||||||
li
|
li
|
||||||
li
|
li
|
||||||
li
|
li
|
||||||
br
|
li
|
||||||
a.btn.btn-info(
|
br
|
||||||
href="/register"
|
a.btn.btn-info(
|
||||||
style=(getLoggedInUserId() === undefined ? "" : "visibility: hidden")
|
href="/register"
|
||||||
) #{translate("sign_up_now")}
|
style=(getLoggedInUserId() === undefined ? "" : "visibility: hidden")
|
||||||
.col-md-4
|
) #{translate("sign_up_now")}
|
||||||
.card.card-highlighted
|
.col-md-4
|
||||||
.card-header
|
.card.card-highlighted
|
||||||
h2 #{translate("collaborator")}
|
.card-header
|
||||||
.circle
|
h2 #{translate("collaborator")}
|
||||||
span(ng-if="ui.view == 'monthly'")
|
.circle
|
||||||
| {{plans[currencyCode]['collaborator']['monthly']}}
|
span(ng-if="ui.view == 'monthly'")
|
||||||
span.small /mo
|
| {{plans[currencyCode]['collaborator']['monthly']}}
|
||||||
span(ng-if="ui.view == 'annual'")
|
span.small /mo
|
||||||
| {{plans[currencyCode]['collaborator']['annual']}}
|
span(ng-if="ui.view == 'annual'")
|
||||||
span.small /yr
|
| {{plans[currencyCode]['collaborator']['annual']}}
|
||||||
ul.list-unstyled
|
span.small /yr
|
||||||
li
|
ul.list-unstyled
|
||||||
strong #{translate("collabs_per_proj", {collabcount:10})}
|
li
|
||||||
li #{translate("full_doc_history")}
|
strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:10})}
|
||||||
li #{translate("sync_to_dropbox")}
|
strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:8})}
|
||||||
li #{translate("sync_to_github")}
|
strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:12})}
|
||||||
li
|
li #{translate("full_doc_history")}
|
||||||
br
|
li #{translate("sync_to_dropbox")}
|
||||||
a.btn.btn-info(
|
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)
|
.row.row-spaced(ng-cloak)
|
||||||
p.text-centered #{translate("choose_plan_works_for_you", {len:'{{trial_len}}'})}
|
p.text-centered #{translate("choose_plan_works_for_you", {len:'{{trial_len}}'})}
|
||||||
|
|
|
@ -2,7 +2,7 @@ extends ../layout
|
||||||
|
|
||||||
block content
|
block content
|
||||||
.content.content-alt
|
.content.content-alt
|
||||||
.container
|
.container(ng-controller="SuccessfulSubscriptionController")
|
||||||
.row
|
.row
|
||||||
.col-md-8.col-md-offset-2
|
.col-md-8.col-md-offset-2
|
||||||
.card(ng-cloak)
|
.card(ng-cloak)
|
||||||
|
|
|
@ -150,16 +150,32 @@ block content
|
||||||
script(type='text/ng-template', id='deleteAccountModalTemplate')
|
script(type='text/ng-template', id='deleteAccountModalTemplate')
|
||||||
.modal-header
|
.modal-header
|
||||||
h3 #{translate("delete_account")}
|
h3 #{translate("delete_account")}
|
||||||
.modal-body
|
div.modal-body#delete-account-modal
|
||||||
p !{translate("delete_account_warning_message_2")}
|
p !{translate("delete_account_warning_message_3")}
|
||||||
form(novalidate, name="deleteAccountForm")
|
form(novalidate, name="deleteAccountForm")
|
||||||
|
label #{translate('email')}
|
||||||
input.form-control(
|
input.form-control(
|
||||||
type="text",
|
type="text",
|
||||||
|
autocomplete="off",
|
||||||
placeholder="",
|
placeholder="",
|
||||||
ng-model="state.deleteText",
|
ng-model="state.deleteText",
|
||||||
focus-on="open",
|
focus-on="open",
|
||||||
ng-keyup="checkValidation()"
|
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
|
.modal-footer
|
||||||
button.btn.btn-default(
|
button.btn.btn-default(
|
||||||
ng-click="cancel()"
|
ng-click="cancel()"
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
"lynx": "0.1.1",
|
"lynx": "0.1.1",
|
||||||
"marked": "^0.3.5",
|
"marked": "^0.3.5",
|
||||||
"method-override": "^2.3.3",
|
"method-override": "^2.3.3",
|
||||||
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.3.0",
|
"metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.6.0",
|
||||||
"mimelib": "0.2.14",
|
"mimelib": "0.2.14",
|
||||||
"mocha": "1.17.1",
|
"mocha": "1.17.1",
|
||||||
"mongojs": "0.18.2",
|
"mongojs": "0.18.2",
|
||||||
|
|
|
@ -69,6 +69,12 @@ define [
|
||||||
reviewPanelOpen: false
|
reviewPanelOpen: false
|
||||||
}
|
}
|
||||||
$scope.user = window.user
|
$scope.user = window.user
|
||||||
|
|
||||||
|
|
||||||
|
$scope.shouldABTestPlans = false
|
||||||
|
if $scope.user.signUpDate >= '2016-10-27'
|
||||||
|
$scope.shouldABTestPlans = true
|
||||||
|
|
||||||
$scope.settings = window.userSettings
|
$scope.settings = window.userSettings
|
||||||
$scope.anonymous = window.anonymous
|
$scope.anonymous = window.anonymous
|
||||||
|
|
||||||
|
@ -81,7 +87,7 @@ define [
|
||||||
# Only run the header AB test for newly registered users.
|
# Only run the header AB test for newly registered users.
|
||||||
_abTestStartDate = new Date(Date.UTC(2016, 8, 28))
|
_abTestStartDate = new Date(Date.UTC(2016, 8, 28))
|
||||||
_userSignUpDate = new Date(window.user.signUpDate)
|
_userSignUpDate = new Date(window.user.signUpDate)
|
||||||
|
|
||||||
$scope.shouldABTestHeaderLabels = _userSignUpDate > _abTestStartDate
|
$scope.shouldABTestHeaderLabels = _userSignUpDate > _abTestStartDate
|
||||||
$scope.headerLabelsABVariant = ""
|
$scope.headerLabelsABVariant = ""
|
||||||
|
|
||||||
|
@ -98,7 +104,7 @@ define [
|
||||||
# Tracking code.
|
# Tracking code.
|
||||||
$scope.$watch "ui.view", (newView, oldView) ->
|
$scope.$watch "ui.view", (newView, oldView) ->
|
||||||
if newView? and newView != "editor" and newView != "pdf"
|
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) ->
|
$scope.$watch "ui.chatOpen", (isOpen) ->
|
||||||
event_tracking.sendMBOnce "ide-open-chat-once" if isOpen
|
event_tracking.sendMBOnce "ide-open-chat-once" if isOpen
|
||||||
|
@ -111,7 +117,7 @@ define [
|
||||||
# End of tracking code.
|
# End of tracking code.
|
||||||
|
|
||||||
window._ide = ide
|
window._ide = ide
|
||||||
|
|
||||||
ide.validFileRegex = '^[^\*\/]*$' # Don't allow * and /
|
ide.validFileRegex = '^[^\*\/]*$' # Don't allow * and /
|
||||||
|
|
||||||
ide.project_id = $scope.project_id = window.project_id
|
ide.project_id = $scope.project_id = window.project_id
|
||||||
|
|
|
@ -27,6 +27,7 @@ define [], () ->
|
||||||
|
|
||||||
@connected = false
|
@connected = false
|
||||||
@userIsInactive = false
|
@userIsInactive = false
|
||||||
|
@gracefullyReconnecting = false
|
||||||
|
|
||||||
@$scope.connection =
|
@$scope.connection =
|
||||||
reconnecting: false
|
reconnecting: false
|
||||||
|
@ -54,6 +55,7 @@ define [], () ->
|
||||||
@ide.socket.on "connect", () =>
|
@ide.socket.on "connect", () =>
|
||||||
sl_console.log "[socket.io connect] Connected"
|
sl_console.log "[socket.io connect] Connected"
|
||||||
@connected = true
|
@connected = true
|
||||||
|
@gracefullyReconnecting = false
|
||||||
@ide.pushEvent("connected")
|
@ide.pushEvent("connected")
|
||||||
|
|
||||||
@$scope.$apply () =>
|
@$scope.$apply () =>
|
||||||
|
@ -81,7 +83,7 @@ define [], () ->
|
||||||
@$scope.$apply () =>
|
@$scope.$apply () =>
|
||||||
@$scope.connection.reconnecting = false
|
@$scope.connection.reconnecting = false
|
||||||
|
|
||||||
if !$scope.connection.forced_disconnect and !@userIsInactive
|
if !$scope.connection.forced_disconnect and !@userIsInactive and !@gracefullyReconnecting
|
||||||
@startAutoReconnectCountdown()
|
@startAutoReconnectCountdown()
|
||||||
|
|
||||||
@ide.socket.on 'forceDisconnect', (message) =>
|
@ide.socket.on 'forceDisconnect', (message) =>
|
||||||
|
@ -97,7 +99,11 @@ define [], () ->
|
||||||
setTimeout () ->
|
setTimeout () ->
|
||||||
location.reload()
|
location.reload()
|
||||||
, 10 * 1000
|
, 10 * 1000
|
||||||
|
|
||||||
|
@ide.socket.on "reconnectGracefully", () =>
|
||||||
|
sl_console.log "Reconnect gracefully"
|
||||||
|
@reconnectGracefully()
|
||||||
|
|
||||||
joinProject: () ->
|
joinProject: () ->
|
||||||
sl_console.log "[joinProject] joining..."
|
sl_console.log "[joinProject] joining..."
|
||||||
@ide.socket.emit 'joinProject', {
|
@ide.socket.emit 'joinProject', {
|
||||||
|
@ -180,3 +186,24 @@ define [], () ->
|
||||||
@$scope.$apply () =>
|
@$scope.$apply () =>
|
||||||
@$scope.connection.inactive_disconnect = true
|
@$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()
|
|
@ -8,11 +8,12 @@ define [
|
||||||
@$scope.editor = {
|
@$scope.editor = {
|
||||||
sharejs_doc: null
|
sharejs_doc: null
|
||||||
open_doc_id: null
|
open_doc_id: null
|
||||||
|
open_doc_name: null
|
||||||
opening: true
|
opening: true
|
||||||
}
|
}
|
||||||
|
|
||||||
@$scope.$on "entity:selected", (event, entity) =>
|
@$scope.$on "entity:selected", (event, entity) =>
|
||||||
if (@$scope.ui.view != "track-changes" and entity.type == "doc")
|
if (@$scope.ui.view != "history" and entity.type == "doc")
|
||||||
@openDoc(entity)
|
@openDoc(entity)
|
||||||
|
|
||||||
@$scope.$on "entity:deleted", (event, entity) =>
|
@$scope.$on "entity:deleted", (event, entity) =>
|
||||||
|
@ -59,6 +60,7 @@ define [
|
||||||
return
|
return
|
||||||
|
|
||||||
@$scope.editor.open_doc_id = doc.id
|
@$scope.editor.open_doc_id = doc.id
|
||||||
|
@$scope.editor.open_doc_name = doc.name
|
||||||
|
|
||||||
@ide.localStorage "doc.open_id.#{@$scope.project_id}", doc.id
|
@ide.localStorage "doc.open_id.#{@$scope.project_id}", doc.id
|
||||||
@ide.fileTreeManager.selectEntity(doc)
|
@ide.fileTreeManager.selectEntity(doc)
|
||||||
|
|
|
@ -2,20 +2,26 @@ define [
|
||||||
"base"
|
"base"
|
||||||
"ace/ace"
|
"ace/ace"
|
||||||
"ace/ext-searchbox"
|
"ace/ext-searchbox"
|
||||||
|
"ace/ext-modelist"
|
||||||
"ide/editor/directives/aceEditor/undo/UndoManager"
|
"ide/editor/directives/aceEditor/undo/UndoManager"
|
||||||
"ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager"
|
"ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager"
|
||||||
"ide/editor/directives/aceEditor/spell-check/SpellCheckManager"
|
"ide/editor/directives/aceEditor/spell-check/SpellCheckManager"
|
||||||
"ide/editor/directives/aceEditor/highlights/HighlightsManager"
|
"ide/editor/directives/aceEditor/highlights/HighlightsManager"
|
||||||
"ide/editor/directives/aceEditor/cursor-position/CursorPositionManager"
|
"ide/editor/directives/aceEditor/cursor-position/CursorPositionManager"
|
||||||
"ide/editor/directives/aceEditor/track-changes/TrackChangesManager"
|
"ide/editor/directives/aceEditor/track-changes/TrackChangesManager"
|
||||||
], (App, Ace, SearchBox, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager) ->
|
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager) ->
|
||||||
EditSession = ace.require('ace/edit_session').EditSession
|
EditSession = ace.require('ace/edit_session').EditSession
|
||||||
|
ModeList = ace.require('ace/ext/modelist')
|
||||||
|
|
||||||
# set the path for ace workers if using a CDN (from editor.jade)
|
# set the path for ace workers if using a CDN (from editor.jade)
|
||||||
if window.aceWorkerPath != ""
|
if window.aceWorkerPath != ""
|
||||||
|
syntaxValidationEnabled = true
|
||||||
ace.config.set('workerPath', "#{window.aceWorkerPath}")
|
ace.config.set('workerPath', "#{window.aceWorkerPath}")
|
||||||
else
|
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
|
# Ace loads its script itself, so we need to hook in to be able to clear
|
||||||
# the cache.
|
# the cache.
|
||||||
|
@ -43,6 +49,7 @@ define [
|
||||||
readOnly: "="
|
readOnly: "="
|
||||||
annotations: "="
|
annotations: "="
|
||||||
navigateHighlights: "="
|
navigateHighlights: "="
|
||||||
|
fileName: "="
|
||||||
onCtrlEnter: "="
|
onCtrlEnter: "="
|
||||||
syntaxValidation: "="
|
syntaxValidation: "="
|
||||||
reviewPanel: "="
|
reviewPanel: "="
|
||||||
|
@ -62,6 +69,11 @@ define [
|
||||||
|
|
||||||
editor = ace.edit(element.find(".ace-editor-body")[0])
|
editor = ace.edit(element.find(".ace-editor-body")[0])
|
||||||
editor.$blockScrolling = Infinity
|
editor.$blockScrolling = Infinity
|
||||||
|
|
||||||
|
# disable auto insertion of brackets and quotes
|
||||||
|
editor.setOption('behavioursEnabled', false)
|
||||||
|
editor.setOption('wrapBehavioursEnabled', false)
|
||||||
|
|
||||||
window.editors ||= []
|
window.editors ||= []
|
||||||
window.editors.push editor
|
window.editors.push editor
|
||||||
|
|
||||||
|
@ -198,16 +210,15 @@ define [
|
||||||
editor.setReadOnly !!value
|
editor.setReadOnly !!value
|
||||||
|
|
||||||
scope.$watch "syntaxValidation", (value) ->
|
scope.$watch "syntaxValidation", (value) ->
|
||||||
session = editor.getSession()
|
# ignore undefined settings here
|
||||||
session.setOption("useWorker", value);
|
# 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)
|
editor.setOption("scrollPastEnd", true)
|
||||||
|
|
||||||
resetSession = () ->
|
|
||||||
session = editor.getSession()
|
|
||||||
session.setUseWrapMode(true)
|
|
||||||
session.setMode("ace/mode/latex")
|
|
||||||
|
|
||||||
updateCount = 0
|
updateCount = 0
|
||||||
onChange = () ->
|
onChange = () ->
|
||||||
updateCount++
|
updateCount++
|
||||||
|
@ -229,9 +240,36 @@ define [
|
||||||
session = editor.getSession()
|
session = editor.getSession()
|
||||||
if session?
|
if session?
|
||||||
session.destroy()
|
session.destroy()
|
||||||
editor.setSession(new EditSession(lines, "ace/mode/latex"))
|
|
||||||
resetSession()
|
# see if we can lookup a suitable mode from ace
|
||||||
session = editor.getSession()
|
# but fall back to text by default
|
||||||
|
try
|
||||||
|
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/plain_text"
|
||||||
|
|
||||||
|
# create our new session
|
||||||
|
session = new EditSession(lines, mode)
|
||||||
|
|
||||||
|
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 = session.getDocument()
|
||||||
doc.on "change", onChange
|
doc.on "change", onChange
|
||||||
|
|
|
@ -233,8 +233,16 @@ define [
|
||||||
start = aceDelta.start
|
start = aceDelta.start
|
||||||
if !start?
|
if !start?
|
||||||
error = new Error("aceDelta had no start event.")
|
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, {
|
Raven?.captureException(error, {
|
||||||
aceDelta: JSON.stringify(aceDelta)
|
aceDelta: JSONstringifyWithCycles(aceDelta)
|
||||||
})
|
})
|
||||||
throw error
|
throw error
|
||||||
linesBefore = docLines.slice(0, start.row)
|
linesBefore = docLines.slice(0, start.row)
|
||||||
|
|
|
@ -29,12 +29,12 @@ define [
|
||||||
|
|
||||||
$scope.$watch "shouldShowLogs", (shouldShow) ->
|
$scope.$watch "shouldShowLogs", (shouldShow) ->
|
||||||
if shouldShow
|
if shouldShow
|
||||||
$scope.$applyAsync () ->
|
$scope.$applyAsync () ->
|
||||||
$scope.shouldDropUp = getFilesDropdownTopCoordAsRatio() > 0.65
|
$scope.shouldDropUp = getFilesDropdownTopCoordAsRatio() > 0.65
|
||||||
|
|
||||||
# log hints tracking
|
# log hints tracking
|
||||||
$scope.logHintsNegFeedbackValues = logHintsFeedback.feedbackOpts
|
$scope.logHintsNegFeedbackValues = logHintsFeedback.feedbackOpts
|
||||||
|
|
||||||
$scope.trackLogHintsLearnMore = () ->
|
$scope.trackLogHintsLearnMore = () ->
|
||||||
event_tracking.sendMB "logs-hints-learn-more"
|
event_tracking.sendMB "logs-hints-learn-more"
|
||||||
|
|
||||||
|
@ -108,7 +108,7 @@ define [
|
||||||
_csrf: window.csrfToken
|
_csrf: window.csrfToken
|
||||||
}, {params: params}
|
}, {params: params}
|
||||||
|
|
||||||
parseCompileResponse = (response) ->
|
parseCompileResponse = (response) ->
|
||||||
|
|
||||||
# keep last url
|
# keep last url
|
||||||
last_pdf_url = $scope.pdf.url
|
last_pdf_url = $scope.pdf.url
|
||||||
|
@ -469,7 +469,7 @@ define [
|
||||||
|
|
||||||
event_tracking.sendMB "subscription-start-trial", { source }
|
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
|
$scope.startedFreeTrial = true
|
||||||
|
|
||||||
App.factory "synctex", ["ide", "$http", "$q", (ide, $http, $q) ->
|
App.factory "synctex", ["ide", "$http", "$q", (ide, $http, $q) ->
|
||||||
|
|
|
@ -30,6 +30,10 @@ define [
|
||||||
TEXTLAYER_TIMEOUT: 100
|
TEXTLAYER_TIMEOUT: 100
|
||||||
|
|
||||||
constructor: (@url, @options) ->
|
constructor: (@url, @options) ->
|
||||||
|
# set up external character mappings - needed for Japanese etc
|
||||||
|
window.PDFJS.cMapUrl = './bcmaps/'
|
||||||
|
window.PDFJS.cMapPacked = true
|
||||||
|
|
||||||
if window.location?.search?.indexOf("disable-font-face=true") >= 0
|
if window.location?.search?.indexOf("disable-font-face=true") >= 0
|
||||||
window.PDFJS.disableFontFace = true
|
window.PDFJS.disableFontFace = true
|
||||||
else
|
else
|
||||||
|
|
|
@ -17,8 +17,13 @@ define [
|
||||||
# When we join the project:
|
# When we join the project:
|
||||||
# index all references files
|
# index all references files
|
||||||
# and don't broadcast to all clients
|
# and don't broadcast to all clients
|
||||||
|
@inited = false
|
||||||
@$scope.$on 'project:joined', (e) =>
|
@$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(
|
setTimeout(
|
||||||
(self) ->
|
(self) ->
|
||||||
|
|
|
@ -30,7 +30,7 @@ define [
|
||||||
|
|
||||||
$scope.$watch "settings.syntaxValidation", (syntaxValidation, oldSyntaxValidation) =>
|
$scope.$watch "settings.syntaxValidation", (syntaxValidation, oldSyntaxValidation) =>
|
||||||
if syntaxValidation != oldSyntaxValidation
|
if syntaxValidation != oldSyntaxValidation
|
||||||
settings.saveProjectSettings({syntaxValidation: syntaxValidation})
|
settings.saveSettings({syntaxValidation: syntaxValidation})
|
||||||
|
|
||||||
$scope.$watch "project.spellCheckLanguage", (language, oldLanguage) =>
|
$scope.$watch "project.spellCheckLanguage", (language, oldLanguage) =>
|
||||||
return if @ignoreUpdates
|
return if @ignoreUpdates
|
||||||
|
|
|
@ -29,10 +29,13 @@ define [
|
||||||
App.controller "DeleteAccountModalController", [
|
App.controller "DeleteAccountModalController", [
|
||||||
"$scope", "$modalInstance", "$timeout", "$http",
|
"$scope", "$modalInstance", "$timeout", "$http",
|
||||||
($scope, $modalInstance, $timeout, $http) ->
|
($scope, $modalInstance, $timeout, $http) ->
|
||||||
$scope.state =
|
$scope.state =
|
||||||
isValid : false
|
isValid : false
|
||||||
deleteText: ""
|
deleteText: ""
|
||||||
|
password: ""
|
||||||
inflight: false
|
inflight: false
|
||||||
|
error: false
|
||||||
|
invalidCredentials: false
|
||||||
|
|
||||||
$modalInstance.opened.then () ->
|
$modalInstance.opened.then () ->
|
||||||
$timeout () ->
|
$timeout () ->
|
||||||
|
@ -40,20 +43,33 @@ define [
|
||||||
, 700
|
, 700
|
||||||
|
|
||||||
$scope.checkValidation = ->
|
$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.delete = () ->
|
||||||
$scope.state.inflight = true
|
$scope.state.inflight = true
|
||||||
|
$scope.state.error = false
|
||||||
|
$scope.state.invalidCredentials = false
|
||||||
$http({
|
$http({
|
||||||
method: "DELETE"
|
method: "POST"
|
||||||
url: "/user"
|
url: "/user/delete"
|
||||||
headers:
|
headers:
|
||||||
"X-CSRF-Token": window.csrfToken
|
"X-CSRF-Token": window.csrfToken
|
||||||
|
"Content-Type": 'application/json'
|
||||||
|
data:
|
||||||
|
password: $scope.state.password
|
||||||
})
|
})
|
||||||
.success () ->
|
.success () ->
|
||||||
$modalInstance.close()
|
$modalInstance.close()
|
||||||
|
$scope.state.inflight = false
|
||||||
|
$scope.state.error = false
|
||||||
|
$scope.state.invalidCredentials = false
|
||||||
window.location = "/"
|
window.location = "/"
|
||||||
|
.error (data, status) ->
|
||||||
|
$scope.state.inflight = false
|
||||||
|
if status == 403
|
||||||
|
$scope.state.invalidCredentials = true
|
||||||
|
else
|
||||||
|
$scope.state.error = true
|
||||||
|
|
||||||
$scope.cancel = () ->
|
$scope.cancel = () ->
|
||||||
$modalInstance.dismiss('cancel')
|
$modalInstance.dismiss('cancel')
|
||||||
|
|
|
@ -6,14 +6,34 @@ define [
|
||||||
$scope.buttonClass = "btn-primary"
|
$scope.buttonClass = "btn-primary"
|
||||||
|
|
||||||
$scope.startFreeTrial = (source, couponCode) ->
|
$scope.startFreeTrial = (source, couponCode) ->
|
||||||
event_tracking.sendMB "subscription-start-trial", { source }
|
plan = 'collaborator_free_trial_7_days'
|
||||||
|
|
||||||
w = window.open()
|
w = window.open()
|
||||||
sixpack.convert "track-changes-discount", ->
|
go = () ->
|
||||||
sixpack.participate 'in-editor-free-trial-plan', ['student', 'collaborator'], (planName, rawResponse)->
|
ga?('send', 'event', 'subscription-funnel', 'upgraded-free-trial', source)
|
||||||
ga?('send', 'event', 'subscription-funnel', 'upgraded-free-trial', source)
|
url = "/user/subscription/new?planCode=#{plan}&ssp=true"
|
||||||
url = "/user/subscription/new?planCode=#{planName}_free_trial_7_days&ssp=#{planName == 'collaborator'}"
|
if couponCode?
|
||||||
if couponCode?
|
url = "#{url}&cc=#{couponCode}"
|
||||||
url = "#{url}&cc=#{couponCode}"
|
$scope.startedFreeTrial = true
|
||||||
$scope.startedFreeTrial = true
|
|
||||||
w.location = url
|
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)->
|
App.controller "NewSubscriptionController", ($scope, MultiCurrencyPricing, abTestManager, $http, sixpack, event_tracking, ccUtils)->
|
||||||
throw new Error("Recurly API Library Missing.") if typeof recurly is "undefined"
|
throw new Error("Recurly API Library Missing.") if typeof recurly is "undefined"
|
||||||
|
|
||||||
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
||||||
$scope.plans = MultiCurrencyPricing.plans
|
$scope.plans = MultiCurrencyPricing.plans
|
||||||
|
|
||||||
$scope.switchToStudent = ()->
|
$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 }
|
event_tracking.sendMB "subscription-form", { plan : window.plan_code }
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,171 @@ define [
|
||||||
|
|
||||||
|
|
||||||
App.factory "MultiCurrencyPricing", () ->
|
App.factory "MultiCurrencyPricing", () ->
|
||||||
|
|
||||||
currencyCode = window.recomendedCurrency
|
currencyCode = window.recomendedCurrency
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currencyCode:currencyCode
|
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:
|
USD:
|
||||||
symbol: "$"
|
symbol: "$"
|
||||||
student:
|
student:
|
||||||
|
@ -23,7 +182,7 @@ define [
|
||||||
monthly: "$30"
|
monthly: "$30"
|
||||||
annual: "$360"
|
annual: "$360"
|
||||||
|
|
||||||
EUR:
|
EUR:
|
||||||
symbol: "€"
|
symbol: "€"
|
||||||
student:
|
student:
|
||||||
monthly: "€7"
|
monthly: "€7"
|
||||||
|
@ -34,7 +193,7 @@ define [
|
||||||
professional:
|
professional:
|
||||||
monthly: "€28"
|
monthly: "€28"
|
||||||
annual: "€336"
|
annual: "€336"
|
||||||
|
|
||||||
GBP:
|
GBP:
|
||||||
symbol: "£"
|
symbol: "£"
|
||||||
student:
|
student:
|
||||||
|
@ -117,7 +276,7 @@ define [
|
||||||
professional:
|
professional:
|
||||||
monthly: "$35"
|
monthly: "$35"
|
||||||
annual: "$420"
|
annual: "$420"
|
||||||
|
|
||||||
CHF:
|
CHF:
|
||||||
symbol: "Fr"
|
symbol: "Fr"
|
||||||
student:
|
student:
|
||||||
|
@ -141,36 +300,54 @@ define [
|
||||||
professional:
|
professional:
|
||||||
monthly: "$40"
|
monthly: "$40"
|
||||||
annual: "$480"
|
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.plans = MultiCurrencyPricing.plans
|
||||||
|
|
||||||
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
||||||
|
|
||||||
$scope.trial_len = 7
|
$scope.trial_len = 7
|
||||||
|
|
||||||
$scope.planQueryString = '_free_trial_7_days'
|
$scope.planQueryString = '_free_trial_7_days'
|
||||||
|
|
||||||
$scope.ui =
|
$scope.ui =
|
||||||
view: "monthly"
|
view: "monthly"
|
||||||
|
|
||||||
|
|
||||||
$scope.changeCurreny = (newCurrency)->
|
$scope.changeCurreny = (newCurrency)->
|
||||||
$scope.currencyCode = newCurrency
|
$scope.currencyCode = newCurrency
|
||||||
|
|
||||||
$scope.signUpNowClicked = (plan, annual)->
|
$scope.signUpNowClicked = (plan, annual)->
|
||||||
|
event_tracking.sendMB 'plans-page-start-trial', {plan}
|
||||||
if $scope.ui.view == "annual"
|
if $scope.ui.view == "annual"
|
||||||
plan = "#{plan}_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.switchToMonthly = ->
|
||||||
$scope.ui.view = "monthly"
|
$scope.ui.view = "monthly"
|
||||||
event_tracking.send 'subscription-funnel', 'plans-page', 'monthly-prices'
|
event_tracking.send 'subscription-funnel', 'plans-page', 'monthly-prices'
|
||||||
|
|
||||||
$scope.switchToStudent = ->
|
$scope.switchToStudent = ->
|
||||||
$scope.ui.view = "student"
|
$scope.ui.view = "student"
|
||||||
event_tracking.send 'subscription-funnel', 'plans-page', 'student-prices'
|
event_tracking.send 'subscription-funnel', 'plans-page', 'student-prices'
|
||||||
|
@ -178,7 +355,7 @@ define [
|
||||||
$scope.switchToAnnual = ->
|
$scope.switchToAnnual = ->
|
||||||
$scope.ui.view = "annual"
|
$scope.ui.view = "annual"
|
||||||
event_tracking.send 'subscription-funnel', 'plans-page', 'student-prices'
|
event_tracking.send 'subscription-funnel', 'plans-page', 'student-prices'
|
||||||
|
|
||||||
$scope.openGroupPlanModal = () ->
|
$scope.openGroupPlanModal = () ->
|
||||||
$modal.open {
|
$modal.open {
|
||||||
templateUrl: "groupPlanModalTemplate"
|
templateUrl: "groupPlanModalTemplate"
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
define [
|
define [
|
||||||
"base"
|
"base"
|
||||||
], (App)->
|
], (App)->
|
||||||
|
|
||||||
|
App.controller 'SuccessfulSubscriptionController', ($scope, sixpack) ->
|
||||||
|
sixpack.convert 'plans-1610', () ->
|
||||||
|
|
||||||
|
|
||||||
SUBSCRIPTION_URL = "/user/subscription/update"
|
SUBSCRIPTION_URL = "/user/subscription/update"
|
||||||
|
|
||||||
setupReturly = _.once ->
|
setupReturly = _.once ->
|
||||||
recurly?.configure window.recurlyApiKey
|
recurly?.configure window.recurlyApiKey
|
||||||
PRICES = {}
|
PRICES = {}
|
||||||
|
|
||||||
|
|
||||||
App.controller "CurrenyDropdownController", ($scope, MultiCurrencyPricing, $q)->
|
App.controller "CurrenyDropdownController", ($scope, MultiCurrencyPricing, $q)->
|
||||||
|
|
||||||
$scope.plans = MultiCurrencyPricing.plans
|
# $scope.plans = MultiCurrencyPricing.plans
|
||||||
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
||||||
|
|
||||||
$scope.changeCurrency = (newCurrency)->
|
$scope.changeCurrency = (newCurrency)->
|
||||||
|
@ -31,7 +37,7 @@ define [
|
||||||
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
||||||
|
|
||||||
$scope.pricing = MultiCurrencyPricing
|
$scope.pricing = MultiCurrencyPricing
|
||||||
$scope.plans = MultiCurrencyPricing.plans
|
# $scope.plans = MultiCurrencyPricing.plans
|
||||||
$scope.currencySymbol = MultiCurrencyPricing.plans[MultiCurrencyPricing.currencyCode].symbol
|
$scope.currencySymbol = MultiCurrencyPricing.plans[MultiCurrencyPricing.currencyCode].symbol
|
||||||
|
|
||||||
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
$scope.currencyCode = MultiCurrencyPricing.currencyCode
|
||||||
|
@ -53,9 +59,9 @@ define [
|
||||||
price = ""
|
price = ""
|
||||||
|
|
||||||
App.controller "ConfirmChangePlanController", ($scope, $modalInstance, $http)->
|
App.controller "ConfirmChangePlanController", ($scope, $modalInstance, $http)->
|
||||||
|
|
||||||
$scope.confirmChangePlan = ->
|
$scope.confirmChangePlan = ->
|
||||||
body =
|
body =
|
||||||
plan_code: $scope.plan.planCode
|
plan_code: $scope.plan.planCode
|
||||||
_csrf : window.csrfToken
|
_csrf : window.csrfToken
|
||||||
|
|
||||||
|
@ -74,7 +80,7 @@ define [
|
||||||
$scope.confirmLeaveGroup = ->
|
$scope.confirmLeaveGroup = ->
|
||||||
$scope.inflight = true
|
$scope.inflight = true
|
||||||
$http({
|
$http({
|
||||||
url: "/subscription/group/user",
|
url: "/subscription/group/user",
|
||||||
method: "DELETE",
|
method: "DELETE",
|
||||||
params: {admin_user_id: $scope.admin_id, _csrf: window.csrfToken}
|
params: {admin_user_id: $scope.admin_id, _csrf: window.csrfToken}
|
||||||
}).success ->
|
}).success ->
|
||||||
|
@ -87,6 +93,8 @@ define [
|
||||||
|
|
||||||
|
|
||||||
App.controller "UserSubscriptionController", ($scope, MultiCurrencyPricing, $http, sixpack, $modal) ->
|
App.controller "UserSubscriptionController", ($scope, MultiCurrencyPricing, $http, sixpack, $modal) ->
|
||||||
|
$scope.plans = MultiCurrencyPricing.plans
|
||||||
|
|
||||||
freeTrialEndDate = new Date(subscription?.trial_ends_at)
|
freeTrialEndDate = new Date(subscription?.trial_ends_at)
|
||||||
|
|
||||||
sevenDaysTime = new Date()
|
sevenDaysTime = new Date()
|
||||||
|
@ -96,6 +104,16 @@ define [
|
||||||
freeTrialExpiresUnderSevenDays = freeTrialEndDate < sevenDaysTime
|
freeTrialExpiresUnderSevenDays = freeTrialEndDate < sevenDaysTime
|
||||||
|
|
||||||
$scope.view = 'overview'
|
$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
|
isMonthlyCollab = subscription?.planCode?.indexOf("collaborator") != -1 and subscription?.planCode?.indexOf("ann") == -1
|
||||||
stillInFreeTrial = freeTrialInFuture and freeTrialExpiresUnderSevenDays
|
stillInFreeTrial = freeTrialInFuture and freeTrialExpiresUnderSevenDays
|
||||||
|
|
||||||
|
@ -118,7 +136,7 @@ define [
|
||||||
$scope.studentPrice = $scope.currencySymbol + (totalPriceExTax + taxAmmount)
|
$scope.studentPrice = $scope.currencySymbol + (totalPriceExTax + taxAmmount)
|
||||||
|
|
||||||
$scope.downgradeToStudent = ->
|
$scope.downgradeToStudent = ->
|
||||||
body =
|
body =
|
||||||
plan_code: 'student'
|
plan_code: 'student'
|
||||||
_csrf : window.csrfToken
|
_csrf : window.csrfToken
|
||||||
$scope.inflight = true
|
$scope.inflight = true
|
||||||
|
@ -129,7 +147,7 @@ define [
|
||||||
console.log "something went wrong changing plan"
|
console.log "something went wrong changing plan"
|
||||||
|
|
||||||
$scope.cancelSubscription = ->
|
$scope.cancelSubscription = ->
|
||||||
body =
|
body =
|
||||||
_csrf : window.csrfToken
|
_csrf : window.csrfToken
|
||||||
|
|
||||||
$scope.inflight = true
|
$scope.inflight = true
|
||||||
|
@ -158,7 +176,7 @@ define [
|
||||||
|
|
||||||
|
|
||||||
$scope.exendTrial = ->
|
$scope.exendTrial = ->
|
||||||
body =
|
body =
|
||||||
_csrf : window.csrfToken
|
_csrf : window.csrfToken
|
||||||
$scope.inflight = true
|
$scope.inflight = true
|
||||||
$http.put("/user/subscription/extend", body)
|
$http.put("/user/subscription/extend", body)
|
||||||
|
@ -166,6 +184,3 @@ define [
|
||||||
location.reload()
|
location.reload()
|
||||||
.error ->
|
.error ->
|
||||||
console.log "something went wrong changing plan"
|
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 |
|
@ -205,128 +205,157 @@ var LatexFoldMode = require("./folding/latex").FoldMode;
|
||||||
var Range = require("../range").Range;
|
var Range = require("../range").Range;
|
||||||
var WorkerClient = require("ace/worker/worker_client").WorkerClient;
|
var WorkerClient = require("ace/worker/worker_client").WorkerClient;
|
||||||
|
|
||||||
|
var createLatexWorker = function (session) {
|
||||||
|
var doc = session.getDocument();
|
||||||
|
var selection = session.getSelection();
|
||||||
|
var cursorAnchor = selection.lead;
|
||||||
|
|
||||||
|
var savedRange = {};
|
||||||
|
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);
|
||||||
|
var docChangeHandler = doc.on("change", function () {
|
||||||
|
docChangePending = true;
|
||||||
|
if(changeHandler) {
|
||||||
|
clearTimeout(changeHandler);
|
||||||
|
changeHandler = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var cursorHandler = selection.on("changeCursor", function () {
|
||||||
|
if (docChangePending) { return; } ;
|
||||||
|
changeHandler = setTimeout(function () {
|
||||||
|
updateMarkers({cursorMoveOnly:true});
|
||||||
|
suppressions = [];
|
||||||
|
changeHandler = null;
|
||||||
|
}, 100);
|
||||||
|
});
|
||||||
|
|
||||||
|
var updateMarkers = function (options) {
|
||||||
|
if (!options) { options = {};};
|
||||||
|
var cursorMoveOnly = options.cursorMoveOnly;
|
||||||
|
var annotations = [];
|
||||||
|
var newRange = {};
|
||||||
|
var cursor = selection.getCursor();
|
||||||
|
suppressions = [];
|
||||||
|
|
||||||
|
for (var i = 0, len = hints.length; i<len; i++) {
|
||||||
|
var hint = hints[i];
|
||||||
|
|
||||||
|
var suppressedChanges = 0;
|
||||||
|
var hintRange = new Range(hint.start_row, hint.start_col, hint.end_row, hint.end_col);
|
||||||
|
|
||||||
|
var cursorInRange = hintRange.insideStart(cursor.row, cursor.column);
|
||||||
|
var cursorAtStart = hintRange.isStart(cursor.row, cursor.column);
|
||||||
|
var cursorAtEnd = hintRange.isEnd(cursor.row, cursor.column);
|
||||||
|
if (hint.suppressIfEditing && (cursorAtStart || cursorAtEnd)) {
|
||||||
|
suppressions.push(hintRange);
|
||||||
|
if (!hint.suppressed) { suppressedChanges++; };
|
||||||
|
hint.suppressed = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var isCascadeError = false;
|
||||||
|
for (var j = 0, suplen = suppressions.length; j < suplen; j++) {
|
||||||
|
var badRange = suppressions[j];
|
||||||
|
if (badRange.intersects(hintRange)) {
|
||||||
|
isCascadeError = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(isCascadeError) {
|
||||||
|
if (!hint.suppressed) { suppressedChanges++; };
|
||||||
|
hint.suppressed = true;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (hint.suppressed) { suppressedChanges++; };
|
||||||
|
hint.suppressed = false;
|
||||||
|
|
||||||
|
annotations.push(hint);
|
||||||
|
if (hint.type === "info") {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
var key = hintRange.toString() + (cursorInRange ? "+cursor" : "");
|
||||||
|
newRange[key] = {hint: hint, cursorInRange: cursorInRange, range: hintRange};
|
||||||
|
}
|
||||||
|
for (key in newRange) {
|
||||||
|
if (!savedRange[key]) { // doesn't exist in already displayed errors
|
||||||
|
var new_range = newRange[key].range;
|
||||||
|
cursorInRange = newRange[key].cursorInRange;
|
||||||
|
hint = newRange[key].hint;
|
||||||
|
var errorAtStart = (hint.row === hint.start_row && hint.column === hint.start_col);
|
||||||
|
var a = (cursorInRange && !errorAtStart) ? cursorAnchor : doc.createAnchor(new_range.start);
|
||||||
|
var b = (cursorInRange && errorAtStart) ? cursorAnchor : doc.createAnchor(new_range.end);
|
||||||
|
var range = new Range();
|
||||||
|
range.start = a;
|
||||||
|
range.end = b;
|
||||||
|
var cssClass = "ace_error-marker";
|
||||||
|
if (hint.type === "warning") { cssClass = "ace_highlight-marker"; };
|
||||||
|
range.id = session.addMarker(range, cssClass, "text");
|
||||||
|
savedRange[key] = range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (key in savedRange) {
|
||||||
|
if (!newRange[key]) { // no longer present in list of errors to display
|
||||||
|
range = savedRange[key];
|
||||||
|
if (range.start !== cursorAnchor) { range.start.detach(); }
|
||||||
|
if (range.end !== cursorAnchor) { range.end.detach(); }
|
||||||
|
session.removeMarker(range.id);
|
||||||
|
delete savedRange[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!cursorMoveOnly || suppressedChanges) {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
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];
|
||||||
|
if (range.start !== cursorAnchor) { range.start.detach(); }
|
||||||
|
if (range.end !== cursorAnchor) { range.end.detach(); }
|
||||||
|
session.removeMarker(range.id);
|
||||||
|
}
|
||||||
|
savedRange = {};
|
||||||
|
hints = [];
|
||||||
|
suppressions = [];
|
||||||
|
session.clearAnnotations();
|
||||||
|
});
|
||||||
|
|
||||||
|
return worker;
|
||||||
|
};
|
||||||
|
|
||||||
var Mode = function() {
|
var Mode = function() {
|
||||||
this.HighlightRules = LatexHighlightRules;
|
this.HighlightRules = LatexHighlightRules;
|
||||||
this.foldingRules = new LatexFoldMode();
|
this.foldingRules = new LatexFoldMode();
|
||||||
this.createWorker = function(session) {
|
this.createWorker = createLatexWorker;
|
||||||
var doc = session.getDocument();
|
|
||||||
var selection = session.getSelection();
|
|
||||||
|
|
||||||
var savedRange = {};
|
|
||||||
var suppressions = [];
|
|
||||||
var hints = [];
|
|
||||||
var changeHandler = null;
|
|
||||||
|
|
||||||
var worker = new WorkerClient(["ace"], "ace/mode/latex_worker", "LatexWorker");
|
|
||||||
worker.attachToDocument(doc);
|
|
||||||
|
|
||||||
doc.on("change", function () {
|
|
||||||
if(changeHandler) {
|
|
||||||
clearTimeout(changeHandler);
|
|
||||||
changeHandler = null;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
selection.on("changeCursor", function () {
|
|
||||||
if(suppressions.length > 0) {
|
|
||||||
changeHandler = setTimeout(function () {
|
|
||||||
updateMarkers();
|
|
||||||
suppressions = [];
|
|
||||||
changeHandler = null;
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
var updateMarkers = function () {
|
|
||||||
var annotations = [];
|
|
||||||
var newRange = {};
|
|
||||||
var cursor = selection.getCursor();
|
|
||||||
suppressions = [];
|
|
||||||
|
|
||||||
for (var i = 0; i<hints.length; i++) {
|
|
||||||
var data = hints[i];
|
|
||||||
var start_row = data.start_row;
|
|
||||||
var start_col = data.start_col;
|
|
||||||
var end_row = data.end_row;
|
|
||||||
var end_col = data.end_col;
|
|
||||||
if (data.suppressIfEditing &&
|
|
||||||
((cursor.row === start_row && cursor.column == start_col+1)
|
|
||||||
|| (cursor.row === end_row && (cursor.column+1) == end_col))) {
|
|
||||||
suppressions.push([start_row, start_col, end_row, end_col]);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
var suppress = false;
|
|
||||||
for (var j = 0; j < suppressions.length; j++) {
|
|
||||||
var e=suppressions[j];
|
|
||||||
var fromRow=e[0], fromCol=e[1], toRow=e[2], toCol=e[3];
|
|
||||||
if (start_row == fromRow && start_col >= fromCol && start_row === toRow && start_col <= toCol) {
|
|
||||||
suppress = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(suppress) { continue; };
|
|
||||||
|
|
||||||
var key = "(" + start_row + "," + start_col + ")" + ":" + "(" + end_row + "," + end_col + ")";
|
|
||||||
newRange[key] = data;
|
|
||||||
annotations.push(data);
|
|
||||||
}
|
|
||||||
|
|
||||||
var newKeys = Object.keys(newRange);
|
|
||||||
var oldKeys = Object.keys(savedRange);
|
|
||||||
var changes = 0;
|
|
||||||
for (i = 0; i < newKeys.length; i++) {
|
|
||||||
key = newKeys[i];
|
|
||||||
if (!savedRange[key]) {
|
|
||||||
var new_range = newRange[key];
|
|
||||||
var a = doc.createAnchor(new_range.start_row, new_range.start_col);
|
|
||||||
var b = doc.createAnchor(new_range.end_row, new_range.end_col);
|
|
||||||
var range = new Range();
|
|
||||||
range.start = a;
|
|
||||||
range.end = b;
|
|
||||||
range.id = session.addMarker(range, "ace_error-marker", "text");
|
|
||||||
savedRange[key] = range;
|
|
||||||
changes++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (i = 0; i < oldKeys.length; i++) {
|
|
||||||
key = oldKeys[i];
|
|
||||||
if (!newRange[key]) {
|
|
||||||
range = savedRange[key];
|
|
||||||
range.start.detach();
|
|
||||||
range.end.detach();
|
|
||||||
session.removeMarker(range.id);
|
|
||||||
delete savedRange[key];
|
|
||||||
changes++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changes>0) {
|
|
||||||
session.setAnnotations(annotations);
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
worker.on("lint", function(results) {
|
|
||||||
hints = results.data;
|
|
||||||
if (hints.length > 100) {
|
|
||||||
hints = hints.slice(0, 100); // limit to 100 errors
|
|
||||||
};
|
|
||||||
updateMarkers();
|
|
||||||
});
|
|
||||||
|
|
||||||
worker.on("terminate", function() {
|
|
||||||
var oldKeys = Object.keys(savedRange);
|
|
||||||
for (var i = 0; i < oldKeys.length; i++) {
|
|
||||||
var key = oldKeys[i];
|
|
||||||
var range = savedRange[key];
|
|
||||||
session.removeMarker(range.id);
|
|
||||||
delete savedRange[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
return worker;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
oop.inherits(Mode, TextMode);
|
oop.inherits(Mode, TextMode);
|
||||||
|
|
||||||
|
|
File diff suppressed because it is too large
Load diff
1
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/.gitattributes
vendored
Normal file
1
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
* binary
|
Binary file not shown.
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/78-H.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/78-H.bcmap
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/78-V.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/78-V.bcmap
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/Add-H.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/Add-H.bcmap
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/Add-V.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/Add-V.bcmap
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/B5-H.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/B5-H.bcmap
Normal file
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/B5-V.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/B5-V.bcmap
Normal file
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/B5pc-H.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/B5pc-H.bcmap
Normal file
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/B5pc-V.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/B5pc-V.bcmap
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/CNS1-H.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/CNS1-H.bcmap
Normal file
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/CNS1-V.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/CNS1-V.bcmap
Normal file
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/CNS2-H.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/CNS2-H.bcmap
Normal file
Binary file not shown.
|
@ -0,0 +1,3 @@
|
||||||
|
àRCopyright 1990-2009 Adobe Systems Incorporated.
|
||||||
|
All rights reserved.
|
||||||
|
See ./LICENSEáCNS2-H
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,3 @@
|
||||||
|
àRCopyright 1990-2009 Adobe Systems Incorporated.
|
||||||
|
All rights reserved.
|
||||||
|
See ./LICENSEá ETen-B5-H` ^
|
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/EUC-H.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/EUC-H.bcmap
Normal file
Binary file not shown.
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/EUC-V.bcmap
Normal file
BIN
services/web/public/js/libs/pdfjs-1.6.210p1/bcmaps/EUC-V.bcmap
Normal file
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue