Просмотр исходного кода

Merge branch 'master' of https://github.com/sharelatex/web-sharelatex

Henry Oswald 10 лет назад
Родитель
Сommit
114dbf9f3f

+ 10 - 4
services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee

@@ -29,13 +29,15 @@ module.exports = RecurlyWrapper =
 			RecurlyWrapper.apiRequest({
 				url:    "accounts/#{user._id}"
 				method: "GET"
+				expect404: true
 			}, (error, response, responseBody) ->
 				if error
-					if response.statusCode == 404  # actually not an error in this case, just no existing account
-						cache.userExists = false
-						return next(null, cache)
 					logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while checking account"
 					return next(error)
+				if response.statusCode == 404  # actually not an error in this case, just no existing account
+					logger.log {user_id: user._id, recurly_token_id}, "user does not currently exist in recurly, proceed"
+					cache.userExists = false
+					return next(null, cache)
 				logger.log {user_id: user._id, recurly_token_id}, "user appears to exist in recurly"
 				RecurlyWrapper._parseAccountXml responseBody, (err, account) ->
 					if err
@@ -236,10 +238,14 @@ module.exports = RecurlyWrapper =
 			"Authorization" : "Basic " + new Buffer(Settings.apis.recurly.apiKey).toString("base64")
 			"Accept"        : "application/xml"
 			"Content-Type"  : "application/xml; charset=utf-8"
+		expect404 = options.expect404
+		delete options.expect404
 		request options, (error, response, body) ->
-			unless error? or response.statusCode == 200 or response.statusCode == 201 or response.statusCode == 204
+			unless error? or response.statusCode == 200 or response.statusCode == 201 or response.statusCode == 204 or (response.statusCode == 404 and expect404)
 				logger.err err:error, body:body, options:options, statusCode:response?.statusCode, "error returned from recurly"
 				error = "Recurly API returned with status code: #{response.statusCode}"
+			if response.statusCode == 404 and expect404
+				logger.log {url: options.url, method: options.method}, "got 404 response from recurly, expected as valid response"
 			callback(error, response, body)
 
 	sign : (parameters, callback) ->

+ 40 - 17
services/web/app/coffee/Features/Subscription/SubscriptionController.coffee

@@ -22,6 +22,7 @@ module.exports = SubscriptionController =
 			viewName = "#{viewName}_#{req.query.v}"
 		logger.log viewName:viewName, "showing plans page"
 		GeoIpLookup.getCurrencyCode req.query?.ip || req.ip, (err, recomendedCurrency)->
+			return next(err) if err?
 			res.render viewName,
 				title: "plans_and_pricing"
 				plans: plans
@@ -71,12 +72,13 @@ module.exports = SubscriptionController =
 		AuthenticationController.getLoggedInUser req, (error, user) =>
 			return next(error) if error?
 			LimitationsManager.userHasSubscriptionOrIsGroupMember user, (err, hasSubOrIsGroupMember, subscription)->
+				return next(err) if err?
 				groupLicenceInviteUrl = SubscriptionDomainHandler.getDomainLicencePage(user)
 				if subscription?.customAccount
 					logger.log user: user, "redirecting to custom account page"
 					res.redirect "/user/subscription/custom_account"
 				else if groupLicenceInviteUrl? and !hasSubOrIsGroupMember
-					logger.log user:user, "redirecting to group subscription invite page"		
+					logger.log user:user, "redirecting to group subscription invite page"
 					res.redirect groupLicenceInviteUrl
 				else if !hasSubOrIsGroupMember
 					logger.log user: user, "redirecting to plans"
@@ -95,11 +97,13 @@ module.exports = SubscriptionController =
 							groupSubscriptions: groupSubscriptions
 							subscriptionTabActive: true
 							user:user
-
+							saved_billing_details: req.query.saved_billing_details?
 
 	userCustomSubscriptionPage: (req, res, next)->
 		AuthenticationController.getLoggedInUser req, (error, user) ->
+			return next(error) if error?
 			LimitationsManager.userHasSubscriptionOrIsGroupMember user, (err, hasSubOrIsGroupMember, subscription)->
+				return next(err) if err?
 				if !subscription?
 					err = new Error("subscription null for custom account, user:#{user?._id}")
 					logger.warn err:err, "subscription is null for custom accounts page"
@@ -113,6 +117,7 @@ module.exports = SubscriptionController =
 		AuthenticationController.getLoggedInUser req, (error, user) ->
 			return next(error) if error?
 			LimitationsManager.userHasSubscription user, (err, hasSubscription)->
+				return next(err) if err?
 				if !hasSubscription
 					res.redirect "/user/subscription"
 				else
@@ -126,9 +131,12 @@ module.exports = SubscriptionController =
 								currency: "USD"
 								subdomain: Settings.apis.recurly.subdomain
 							signature  : signature
-							successURL : "#{Settings.siteUrl}/user/subscription/update"
+							successURL : "#{Settings.siteUrl}/user/subscription/billing-details/update"
 							user       :
 								id : user._id
+	
+	updateBillingDetails: (req, res, next) ->
+		res.redirect "/user/subscription?saved_billing_details=true"
 
 	createSubscription: (req, res, next)->
 		AuthenticationController.getLoggedInUser req, (error, user) ->
@@ -142,54 +150,67 @@ module.exports = SubscriptionController =
 					return res.sendStatus 500
 				res.sendStatus 201
 
-	successful_subscription: (req, res)->
+	successful_subscription: (req, res, next)->
 		AuthenticationController.getLoggedInUser req, (error, user) =>
+			return next(error) if error?
 			SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel user, (error, subscription) ->
+				return next(error) if error?
 				res.render "subscriptions/successful_subscription",
 					title: "thank_you"
 					subscription:subscription
 
 	cancelSubscription: (req, res, next) ->
 		AuthenticationController.getLoggedInUser req, (error, user) ->
-			logger.log user_id:user._id, "canceling subscription"
 			return next(error) if error?
+			logger.log user_id:user._id, "canceling subscription"
 			SubscriptionHandler.cancelSubscription user, (err)->
 				if err?
 					logger.err err:err, user_id:user._id, "something went wrong canceling subscription"
+					return next(err)
 				res.redirect "/user/subscription"
- 
-	updateSubscription: (req, res)->
+
+	updateSubscription: (req, res, next)->
+		_origin = req?.query?.origin || null
 		AuthenticationController.getLoggedInUser req, (error, user) ->
 			return next(error) if error?
 			planCode = req.body.plan_code
+			if !planCode?
+				err = new Error('plan_code is not defined')
+				logger.err {user_id: user._id, err, planCode, origin: _origin, body: req.body}, "[Subscription] error in updateSubscription form"
+				return next(err)
 			logger.log planCode: planCode, user_id:user._id, "updating subscription"
 			SubscriptionHandler.updateSubscription user, planCode, null, (err)->
 				if err?
 					logger.err err:err, user_id:user._id, "something went wrong updating subscription"
+					return next(err)
 				res.redirect "/user/subscription"
 
-	reactivateSubscription: (req, res)->
+	reactivateSubscription: (req, res, next)->
 		AuthenticationController.getLoggedInUser req, (error, user) ->
-			logger.log user_id:user._id, "reactivating subscription"
 			return next(error) if error?
+			logger.log user_id:user._id, "reactivating subscription"
 			SubscriptionHandler.reactivateSubscription user, (err)->
 				if err?
 					logger.err err:err, user_id:user._id, "something went wrong reactivating subscription"
+					return next(err)
 				res.redirect "/user/subscription"
 
-	recurlyCallback: (req, res)->
+	recurlyCallback: (req, res, next)->
 		logger.log data: req.body, "received recurly callback"
 		# we only care if a subscription has exipired
 		if req.body? and req.body["expired_subscription_notification"]?
 			recurlySubscription = req.body["expired_subscription_notification"].subscription
-			SubscriptionHandler.recurlyCallback recurlySubscription, ->
+			SubscriptionHandler.recurlyCallback recurlySubscription, (err)->
+				return next(err) if err?
 				res.sendStatus 200
 		else
 			res.sendStatus 200
 
-	renderUpgradeToAnnualPlanPage: (req, res)->
+	renderUpgradeToAnnualPlanPage: (req, res, next)->
 		AuthenticationController.getLoggedInUser req, (error, user) ->
+			return next(error) if error?
 			LimitationsManager.userHasSubscription user, (err, hasSubscription, subscription)->
+				return next(err) if err?
 				planCode = subscription?.planCode.toLowerCase()
 				if planCode?.indexOf("annual") != -1
 					planName = "annual"
@@ -204,8 +225,9 @@ module.exports = SubscriptionController =
 					title: "Upgrade to annual"
 					planName: planName
 
-	processUpgradeToAnnualPlan: (req, res)->
+	processUpgradeToAnnualPlan: (req, res, next)->
 		AuthenticationController.getLoggedInUser req, (error, user) ->
+			return next(error) if error?
 			{planName} = req.body
 			coupon_code = Settings.coupon_codes.upgradeToAnnualPromo[planName]
 			annualPlanName = "#{planName}-annual"
@@ -213,13 +235,14 @@ module.exports = SubscriptionController =
 			SubscriptionHandler.updateSubscription user, annualPlanName, coupon_code, (err)->
 				if err?
 					logger.err err:err, user_id:user._id, "error updating subscription"
-					res.sendStatus 500
-				else
-					res.sendStatus 200
+					return next(err)
+				res.sendStatus 200
 
-	extendTrial: (req, res)->
+	extendTrial: (req, res, next)->
 		AuthenticationController.getLoggedInUser req, (error, user) ->
+			return next(error) if error?
 			LimitationsManager.userHasSubscription user, (err, hasSubscription, subscription)->
+				return next(err) if err?
 				SubscriptionHandler.extendTrial subscription, 14, (err)->
 					if err?
 						res.send 500

+ 1 - 0
services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee

@@ -16,6 +16,7 @@ module.exports =
 		
 		webRouter.get  '/user/subscription/new',        AuthenticationController.requireLogin(), SubscriptionController.paymentPage 
 		webRouter.get  '/user/subscription/billing-details/edit', AuthenticationController.requireLogin(), SubscriptionController.editBillingDetailsPage
+		webRouter.post '/user/subscription/billing-details/update', AuthenticationController.requireLogin(), SubscriptionController.updateBillingDetails
 
 		webRouter.get  '/user/subscription/thank-you', AuthenticationController.requireLogin(), SubscriptionController.successful_subscription
 

+ 6 - 5
services/web/app/views/project/editor/chat.jade

@@ -41,11 +41,12 @@ aside.chat(
 							}"
 						)
 							.arrow(ng-style="{'border-color': 'hsl({{ hue(message.user) }}, 70%, 70%)'}")
-							p(
-								mathjax,
-								ng-repeat="content in message.contents track by $index"
-							)
-								span(ng-bind-html="content | linky:'_blank' | wrapLongWords")
+							.message-content
+								p(
+									mathjax,
+									ng-repeat="content in message.contents track by $index"
+								)
+									span(ng-bind-html="content | linky:'_blank'")
 
 	.new-message
 		textarea(

+ 5 - 0
services/web/app/views/subscriptions/dashboard.jade

@@ -40,6 +40,11 @@ block content
 		.container(ng-controller="UserSubscriptionController")
 			.row
 				.col-md-8.col-md-offset-2
+					if saved_billing_details
+						.alert.alert-success
+							i.fa.fa-check
+							|  
+							| #{translate("your_billing_details_were_saved")}
 					.card(ng-if="view == 'overview'")
 						.page-header
 							h1 #{translate("your_subscription")}

+ 1 - 1
services/web/app/views/subscriptions/edit-billing-details.jade

@@ -19,7 +19,7 @@ block content
 		Recurly.config(!{recurlyConfig})
 		Recurly.buildBillingInfoUpdateForm({
 			target      : "#billingDetailsForm",
-			successURL  : "#{successURL}?_csrf=#{csrfToken}",
+			successURL  : "#{successURL}?_csrf=#{csrfToken}&origin=editBillingDetails",
 			signature   : "!{signature}",
 			accountCode : "#{user.id}"
 		});

+ 15 - 1
services/web/public/coffee/ide/human-readable-logs/HumanReadableLogs.coffee

@@ -11,6 +11,8 @@ define [
 		_getRule = (logMessage) ->
 			return rule for rule in ruleset when rule.regexToMatch.test logMessage
 
+		seenErrorTypes = {} # keep track of types of errors seen
+
 		for entry in parsedLogEntries.all
 			ruleDetails = _getRule entry.message
 
@@ -21,8 +23,20 @@ define [
 					entry.ruleId = 'hint_' + ruleDetails.regexToMatch.toString().replace(/\s/g, '_').slice(1, -1)
 				if ruleDetails.newMessage?
 					entry.message = entry.message.replace ruleDetails.regexToMatch, ruleDetails.newMessage
+				# suppress any entries that are known to cascade from previous error types
+				if ruleDetails.cascadesFrom?
+					for type in ruleDetails.cascadesFrom
+						entry.suppressed = true if seenErrorTypes[type]
+				# record the types of errors seen
+				if ruleDetails.types?
+					for type in ruleDetails.types
+						seenErrorTypes[type] = true
 				
 				entry.humanReadableHint = ruleDetails.humanReadableHint if ruleDetails.humanReadableHint?
 				entry.extraInfoURL = ruleDetails.extraInfoURL if ruleDetails.extraInfoURL?
-	
+
+		# filter out the suppressed errors (from the array entries in parsedLogEntries)
+		for key, errors of parsedLogEntries when typeof errors is 'object' and errors.length > 0
+			parsedLogEntries[key] = (err for err in errors when not err.suppressed)
+
 		return parsedLogEntries

+ 77 - 1
services/web/public/coffee/ide/human-readable-logs/HumanReadableLogsRules.coffee

@@ -90,6 +90,7 @@ define -> [
 		"""
 	,
 		ruleId: "hint_mismatched_environment"
+		types: ['environment']
 		regexToMatch: /Error: `([^']{2,})' expected, found `([^']{2,})'.*/
 		newMessage: "Error: environment does not match \\begin{$1} ... \\end{$2}"
 		humanReadableHint: """
@@ -97,10 +98,85 @@ define -> [
 		"""
 	,
 		ruleId: "hint_mismatched_brackets"
+		types: ['environment']
 		regexToMatch: /Error: `([^a-zA-Z0-9])' expected, found `([^a-zA-Z0-9])'.*/
 		newMessage: "Error: brackets do not match, found '$2' instead of '$1'"
 		humanReadableHint: """
 			You have used an open bracket without a corresponding close bracket.
 		"""
-
+	,
+		regexToMatch: /LaTeX Error: Can be used only in preamble/
+		extraInfoURL: "https://www.sharelatex.com/learn/Errors/LaTeX_Error:_Can_be_used_only_in_preamble"
+		humanReadableHint: """
+			You have used a command in the main body of your document which should be used in the preamble. Make sure that \\documentclass[\u2026]{\u2026} and all \\usepackage{\u2026} commands are written before \\begin{document}.
+		"""
+	,
+		regexToMatch: /Missing \\right inserted/
+		extraInfoURL: "https://www.sharelatex.com/learn/Errors/Missing_%5Cright_insertede"
+		humanReadableHint: """
+			You have started an expression with a \\left command, but have not included a corresponding \\right command. Make sure that your \\left and \\right commands balance everywhere, or else try using \\Biggl and \\Biggr commands instead as shown <a target=\"_blank\" href=\"https://www.sharelatex.com/learn/Errors/Missing_%5Cright_inserted\">here</a>.
+		"""
+	,
+		regexToMatch: /Double superscript/
+		extraInfoURL: "https://www.sharelatex.com/learn/Errors/Double_superscript"
+		humanReadableHint: """
+			You have written a double superscript incorrectly as a^b^c, or else you have written a prime with a superscript. Remember to include { and } when using multiple superscripts. Try a^{b^c} instead.
+		"""
+	,
+		regexToMatch: /Double subscript/
+		extraInfoURL: "https://www.sharelatex.com/learn/Errors/Double_subscript"
+		humanReadableHint: """
+			You have written a double subscript incorrectly as a_b_c. Remember to include { and } when using multiple subscripts. Try a_{b_c} instead.
+		"""
+	,
+		regexToMatch: /No \\author given/
+		extraInfoURL: "https://www.sharelatex.com/learn/Errors/No_%5Cauthor_given"
+		humanReadableHint: """
+			You have used the \\maketitle command, but have not specified any \\author. To fix this, include an author in your preamble using the \\author{\u2026} command.
+		"""
+	,
+		regexToMatch: /LaTeX Error: Environment .+ undefined/
+		extraInfoURL: "https://www.sharelatex.com/learn/Errors%2FLaTeX%20Error%3A%20Environment%20XXX%20undefined"
+		humanReadableHint: """
+			You have created an environment (using \\begin{\u2026} and \\end{\u2026} commands) which is not recognized. Make sure you have included the required package for that environment in your preamble, and that the environment is spelled correctly.
+		"""
+	,
+		regexToMatch: /LaTeX Error: Something's wrong--perhaps a missing \\item/
+		extraInfoURL: "https://www.sharelatex.com/learn/Errors/LaTeX_Error:_Something%27s_wrong--perhaps_a_missing_%5Citem"
+		humanReadableHint: """
+			There are no entries found in a list you have created. Make sure you label list entries using the \\item command, and that you have not used a list inside a table.
+		"""
+	,
+		regexToMatch: /Misplaced \\noalign/
+		extraInfoURL: "https://www.sharelatex.com/learn/Errors/Misplaced_%5Cnoalign"
+		humanReadableHint: """
+			You have used a \\hline command in the wrong place, probably outside a table. If the \\hline command is written inside a table, try including \\\ before it.
+		"""
+	,
+		ruleId: "hint_mismatched_environment2"
+		types: ['environment']
+		cascadesFrom: ['environment']
+		regexToMatch: /Error: `\\end\{([^\}]+)\}' expected but found `\\end\{([^\}]+)\}'.*/
+		newMessage: "Error: environments do not match: \\begin{$1} ... \\end{$2}"
+		humanReadableHint: """
+			You have used \\begin{} without a corresponding \\end{}.
+		"""
+	,
+		ruleId: "hint_mismatched_environment3"
+		types: ['environment']
+		cascadesFrom: ['environment']
+		regexToMatch: /Warning: No matching \\end found for `\\begin\{([^\}]+)\}'.*/
+		newMessage: "Warning: No matching \\end found for \\begin{$1}"
+		humanReadableHint: """
+			You have used \\begin{} without a corresponding \\end{}.
+		"""
+	,
+		ruleId: "hint_mismatched_environment4"
+		types: ['environment']
+		cascadesFrom: ['environment']
+		regexToMatch: /Error: Found `\\end\{([^\}]+)\}' without corresponding \\begin.*/
+		newMessage: "Error: found \\end{$1} without a corresponding \\begin{$1}"
+		humanReadableHint: """
+			You have used \\begin{} without a corresponding \\end{}.
+		"""
 ]

+ 2 - 3
services/web/public/coffee/main/subscription-dashboard.coffee

@@ -62,8 +62,7 @@ define [
 
 			$scope.inflight = true
 
-
-			$http.post(SUBSCRIPTION_URL, body)
+			$http.post("#{SUBSCRIPTION_URL}?origin=confirmChangePlan", body)
 				.success ->
 					location.reload()
 				.error ->
@@ -124,7 +123,7 @@ define [
 				plan_code: 'student'
 				_csrf : window.csrfToken
 			$scope.inflight = true
-			$http.post(SUBSCRIPTION_URL, body)
+			$http.post("#{SUBSCRIPTION_URL}?origin=downgradeToStudent", body)
 				.success ->
 					location.reload()
 				.error ->

+ 8 - 2
services/web/public/stylesheets/app/editor/chat.less

@@ -47,6 +47,7 @@
 			}
 			.message-wrapper {
 				margin-left: 50px + @line-height-computed/2;
+
 				.name {
 					font-size: 12px;
 					color: @gray-light;
@@ -54,12 +55,17 @@
 					min-height: 16px;
 				}
 				.message {
-					padding: @line-height-computed / 2;
 					border-left: 3px solid transparent;
-					position: relative;
 					font-size: 14px;
 					box-shadow: -1px 2px 3px #ddd;
 					border-raduis: @border-radius-base;
+					position: relative;
+					
+					.message-content {
+						padding: @line-height-computed / 2;
+						overflow-x: auto;
+					}
+
 					.arrow {
 						right: 100%;
 						top: @line-height-computed / 4;

+ 1 - 1
services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee

@@ -728,7 +728,7 @@ describe "RecurlyWrapper", ->
 			describe 'when the account does not exist', ->
 
 				beforeEach ->
-					@apiRequest.callsArgWith(1, new Error('not found'), {statusCode: 404}, '')
+					@apiRequest.callsArgWith(1, null, {statusCode: 404}, '')
 
 				it 'should not produce an error', (done) ->
 					@call (err, result) =>

+ 1 - 1
services/web/test/UnitTests/coffee/Subscription/SubscriptionControllerTests.coffee

@@ -109,7 +109,7 @@ describe "SubscriptionController sanboxed", ->
 
 			it "should set the correct variables for the template", ->
 				should.exist @res.renderedVariables.signature
-				@res.renderedVariables.successURL.should.equal "#{@settings.siteUrl}/user/subscription/update"
+				@res.renderedVariables.successURL.should.equal "#{@settings.siteUrl}/user/subscription/billing-details/update"
 				@res.renderedVariables.user.id.should.equal @user._id
 
 		describe "with a user without subscription", ->