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:
James Allen 2016-10-31 16:42:55 +00:00
commit 8368577867
215 changed files with 1658 additions and 815 deletions

View file

@ -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 )

View file

@ -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

View file

@ -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)

View file

@ -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) ->

View file

@ -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

View file

@ -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'

View file

@ -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"]

View file

@ -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 {

View file

@ -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

View file

@ -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.

View file

@ -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"]
} }

View file

@ -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",

View file

@ -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 &nbsp; )
| #{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 &nbsp; li
| #{translate("collabs_per_proj", {collabcount:'Multiple'})} i.fa.fa-check &nbsp;
| #{translate("unlimited_projects")}
li
i.fa.fa-check &nbsp; li
| #{translate("full_doc_history")} i.fa.fa-check &nbsp;
| #{translate("collabs_per_proj", {collabcount:'Multiple'})}
li
i.fa.fa-check &nbsp; li
| #{translate("sync_to_dropbox")} i.fa.fa-check &nbsp;
| #{translate("full_doc_history")}
li
i.fa.fa-check &nbsp;
| #{translate("sync_to_dropbox")}
li li
i.fa.fa-check &nbsp; i.fa.fa-check &nbsp;
| #{translate("sync_to_github")} | #{translate("sync_to_github")}
li li
i.fa.fa-check &nbsp; i.fa.fa-check &nbsp;
|#{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 &nbsp;
| Catch up with your collaborators changes
li
i.fa.fa-check &nbsp;
| See changes over any time period
li
i.fa.fa-check &nbsp;
| Revert your documents to previous versions
li
i.fa.fa-check &nbsp;
| 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")}

View file

@ -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
| &nbsp; | &nbsp;
| #{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

View file

@ -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 &nbsp; li #{translate("one_collaborator")}
li &nbsp; li &nbsp;
li &nbsp; li &nbsp;
li li &nbsp;
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)}}&currency={{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}}&currency={{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 &nbsp;
li &nbsp;
li &nbsp;
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 }}&currency={{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 }}&currency={{currencyCode}}",
ng-click="signUpNowClicked('student')"
) #{translate("buy_now")}
ng-href="#{baseUrl}/user/subscription/new?planCode=collaborator{{ ui.view == 'annual' && '-annual' || planQueryString}}&currency={{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}}&currency={{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 &nbsp;
li &nbsp;
li &nbsp;
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}}&currency={{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&currency={{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}}'})}

View file

@ -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)

View file

@ -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()"

View file

@ -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",

View file

@ -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

View file

@ -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()

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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) ->

View file

@ -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

View file

@ -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) ->

View file

@ -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

View file

@ -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')

View file

@ -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()

View file

@ -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&currency=#{$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}&currency=#{$scope.currencyCode}&cc=#{$scope.data.coupon}"
event_tracking.sendMB "subscription-form", { plan : window.plan_code } event_tracking.sendMB "subscription-form", { plan : window.plan_code }

View file

@ -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"

View file

@ -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"

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

View file

@ -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

View file

@ -0,0 +1 @@
* binary

View file

@ -0,0 +1,3 @@
àRCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSEáCNS2-H

View file

@ -0,0 +1,3 @@
àRCopyright 1990-2009 Adobe Systems Incorporated.
All rights reserved.
See ./LICENSEá ETen-B5-H` ^

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