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

Merge branch 'master' into dcl-olsl760b

Douglas Lovell 8 лет назад
Родитель
Сommit
404e768d24
22 измененных файлов с 233 добавлено и 102 удалено
  1. 5 1
      services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee
  2. 2 2
      services/web/app/coffee/Features/Subscription/DomainLicenceController.coffee
  3. 26 23
      services/web/app/coffee/Features/Subscription/SubscriptionController.coffee
  4. 1 5
      services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee
  5. 2 1
      services/web/app/coffee/Features/User/UserUpdater.coffee
  6. 14 4
      services/web/app/views/subscriptions/_plans_faq.pug
  7. 10 5
      services/web/app/views/subscriptions/_plans_page_mixins.pug
  8. 25 0
      services/web/app/views/subscriptions/_plans_quotes.pug
  9. 8 7
      services/web/app/views/subscriptions/new.pug
  10. 16 28
      services/web/app/views/subscriptions/plans.pug
  11. 6 0
      services/web/app/views/user/settings/user-affiliations.pug
  12. 12 0
      services/web/public/coffee/main/affiliations/components/affiliationForm.coffee
  13. 14 0
      services/web/public/stylesheets/app/plans-ol.less
  14. 34 19
      services/web/public/stylesheets/app/plans.less
  15. 5 5
      services/web/public/stylesheets/app/subscription.less
  16. 4 0
      services/web/public/stylesheets/components/card.less
  17. 7 0
      services/web/public/stylesheets/components/navs-ol.less
  18. 9 2
      services/web/public/stylesheets/components/navs.less
  19. 14 0
      services/web/public/stylesheets/core/_common-variables.less
  20. 16 0
      services/web/public/stylesheets/core/ol-variables.less
  21. 1 0
      services/web/public/stylesheets/core/variables.less
  22. 2 0
      services/web/public/stylesheets/ol-style.less

+ 5 - 1
services/web/app/coffee/Features/Newsletter/NewsletterManager.coffee

@@ -46,11 +46,15 @@ module.exports =
 			else if err? and err?.message?.indexOf("could not be validated") != -1
 			else if err? and err?.message?.indexOf("could not be validated") != -1
 				logger.log {oldEmail, newEmail}, 
 				logger.log {oldEmail, newEmail}, 
 					"unable to change email in newsletter, user has previously unsubscribed or new email already exist on list"
 					"unable to change email in newsletter, user has previously unsubscribed or new email already exist on list"
-				return callback(err)
+				return callback()
 			else if err? and err.message.indexOf("is already a list member") != -1
 			else if err? and err.message.indexOf("is already a list member") != -1
 				logger.log {oldEmail, newEmail},
 				logger.log {oldEmail, newEmail},
 					"unable to change email in newsletter, new email is already on mailing list"
 					"unable to change email in newsletter, new email is already on mailing list"
 				return callback()
 				return callback()
+			else if err? and err?.message?.indexOf("looks fake or invalid") != -1
+				logger.log {oldEmail, newEmail},
+					"unable to change email in newsletter, email looks fake to mailchimp"
+				return callback()
 			else if err?
 			else if err?
 				logger.err {err, oldEmail, newEmail}, "error changing email in newsletter"
 				logger.err {err, oldEmail, newEmail}, "error changing email in newsletter"
 				return callback(err)
 				return callback(err)

+ 2 - 2
services/web/app/coffee/Features/Subscription/DomainLicenceController.coffee

@@ -18,9 +18,9 @@ module.exports =
 
 
 		jobs =
 		jobs =
 			partOfGroup: (cb)->
 			partOfGroup: (cb)->
-				SubscriptionGroupHandler.isUserPartOfGroup user.id, licence.group_subscription_id, cb
+				SubscriptionGroupHandler.isUserPartOfGroup user._id, licence.group_subscription_id, cb
 			subscription: (cb)->
 			subscription: (cb)->
-				SubscriptionLocator.getUsersSubscription user.id, cb
+				SubscriptionLocator.getUsersSubscription user._id, cb
 
 
 		async.series jobs, (err, results)->
 		async.series jobs, (err, results)->
 			{ partOfGroup, subscription } = results
 			{ partOfGroup, subscription } = results

+ 26 - 23
services/web/app/coffee/Features/Subscription/SubscriptionController.coffee

@@ -15,30 +15,33 @@ planFeatures = require './planFeatures'
 module.exports = SubscriptionController =
 module.exports = SubscriptionController =
 
 
 	plansPage: (req, res, next) ->
 	plansPage: (req, res, next) ->
-		plans = SubscriptionViewModelBuilder.buildViewModel()
-		viewName = "subscriptions/plans"
-		if req.query.v?
-			viewName = "#{viewName}_#{req.query.v}"
-		logger.log viewName:viewName, "showing plans page"
-		currentUser = null
-
-		GeoIpLookup.getCurrencyCode req.query?.ip || req.ip, (err, recomendedCurrency)->
-			return next(err) if err?
-			render = () ->
-				res.render viewName,
-					title: "plans_and_pricing"
-					plans: plans
-					gaExperiments: Settings.gaExperiments.plansPage
-					recomendedCurrency:recomendedCurrency
-					planFeatures: planFeatures
-			user_id = AuthenticationController.getLoggedInUserId(req)
-			if user_id?
-				UserGetter.getUser user_id, {signUpDate: 1}, (err, user) ->
-					return next(err) if err?
-					currentUser = user
+		if Settings.overleaf? && !req.query.plns
+			res.redirect "#{Settings.overleaf.host}/plans"
+		else
+			plans = SubscriptionViewModelBuilder.buildViewModel()
+			viewName = "subscriptions/plans"
+			if req.query.v?
+				viewName = "#{viewName}_#{req.query.v}"
+			logger.log viewName:viewName, "showing plans page"
+			currentUser = null
+
+			GeoIpLookup.getCurrencyCode req.query?.ip || req.ip, (err, recomendedCurrency)->
+				return next(err) if err?
+				render = () ->
+					res.render viewName,
+						title: "plans_and_pricing"
+						plans: plans
+						gaExperiments: Settings.gaExperiments.plansPage
+						recomendedCurrency:recomendedCurrency
+						planFeatures: planFeatures
+				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()
 					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) ->

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

@@ -9,11 +9,7 @@ module.exports =
 	apply: (webRouter, privateApiRouter, publicApiRouter) ->
 	apply: (webRouter, privateApiRouter, publicApiRouter) ->
 		return unless Settings.enableSubscriptions
 		return unless Settings.enableSubscriptions
 
 
-		if Settings.overleaf?
-			webRouter.get  '/user/subscription/plans', (req, res) ->
-				res.redirect "#{Settings.overleaf.host}/plans"
-		else
-			webRouter.get  '/user/subscription/plans', SubscriptionController.plansPage
+		webRouter.get  '/user/subscription/plans', SubscriptionController.plansPage
 
 
 		webRouter.get  '/user/subscription',            AuthenticationController.requireLogin(), SubscriptionController.userSubscriptionPage
 		webRouter.get  '/user/subscription',            AuthenticationController.requireLogin(), SubscriptionController.userSubscriptionPage
 
 

+ 2 - 1
services/web/app/coffee/Features/User/UserUpdater.coffee

@@ -112,7 +112,8 @@ module.exports = UserUpdater =
 				else if res.n == 0 # TODO: Check n or nMatched?
 				else if res.n == 0 # TODO: Check n or nMatched?
 					return callback(new Error('Default email does not belong to user'))
 					return callback(new Error('Default email does not belong to user'))
 				else
 				else
-					NewsletterManager.changeEmail oldEmail, email, callback
+					NewsletterManager.changeEmail oldEmail, email, ->
+					callback()
 
 
 
 
 
 

+ 14 - 4
services/web/app/views/subscriptions/_plans_faq.pug

@@ -6,7 +6,7 @@
 	.row
 	.row
 		.col-md-6
 		.col-md-6
 			h3 #{translate("faq_how_free_trial_works_question")}
 			h3 #{translate("faq_how_free_trial_works_question")}
-			p #{translate('faq_how_free_trial_works_answer', { len:'{{trial_len}}' })}
+			p #{translate('faq_how_does_free_trial_works_answer', { appName:'{{settings.appName}}', len:'{{trial_len}}' })}
 		.col-md-6
 		.col-md-6
 			h3 #{translate('faq_change_plans_question')}
 			h3 #{translate('faq_change_plans_question')}
 			p #{translate('faq_change_plans_answer')}
 			p #{translate('faq_change_plans_answer')}
@@ -16,11 +16,18 @@
 			p #{translate('faq_do_collab_need_premium_answer')}
 			p #{translate('faq_do_collab_need_premium_answer')}
 		.col-md-6
 		.col-md-6
 			h3 #{translate('faq_need_more_collab_question')}
 			h3 #{translate('faq_need_more_collab_question')}
-			p !{translate('faq_need_more_collab_answer', { referFriendsLink: '<a href="/user/bonus">' + translate('referring_your_friends') + '</a>'})}
+			if settings.overleaf
+				p !{translate('faq_need_more_collab_answer', { referFriendsLink: translate('referring_your_friends') })}
+			else
+				p !{translate('faq_need_more_collab_answer', { referFriendsLink: '<a href="/user/bonus">' + translate('referring_your_friends') + '</a>'})}
 	.row
 	.row
 		.col-md-6
 		.col-md-6
 			h3 #{translate('faq_purchase_more_licenses_question')}
 			h3 #{translate('faq_purchase_more_licenses_question')}
-			p !{translate('faq_purchase_more_licenses_answer', { groupLink: '<a href="/i/university/groups">' + translate('discounted_group_accounts') + '</a>' })}
+			if settings.overleaf
+				p !{translate('faq_purchase_more_licenses_answer', { groupLink: translate('discounted_group_accounts') })}&nbsp;
+					a(href, ng-click="openGroupPlanModal()") #{translate("get_in_touch_for_details")}
+			else
+				p !{translate('faq_purchase_more_licenses_answer', { groupLink: '<a href="/i/university/groups">' + translate('discounted_group_accounts') + '</a>' })}
 		.col-md-6
 		.col-md-6
 			h3 #{translate('faq_monthly_or_annual_question')}
 			h3 #{translate('faq_monthly_or_annual_question')}
 			p #{translate('faq_monthly_or_annual_answer')}
 			p #{translate('faq_monthly_or_annual_answer')}
@@ -30,4 +37,7 @@
 			p #{translate('faq_how_to_pay_answer')}
 			p #{translate('faq_how_to_pay_answer')}
 		.col-md-6
 		.col-md-6
 			h3 #{translate('faq_pay_by_invoice_question')}
 			h3 #{translate('faq_pay_by_invoice_question')}
-			p !{translate('faq_pay_by_invoice_answer', { groupLink: '<a href="/i/university/groups">' + translate('discounted_group_accounts') + '</a>' })}
+			if settings.overleaf
+				p !{translate('faq_pay_by_invoice_answer', { groupLink: translate('discounted_group_accounts') })}
+			else
+				p !{translate('faq_pay_by_invoice_answer', { groupLink: '<a href="/i/university/groups">' + translate('discounted_group_accounts') + '</a>' })}

+ 10 - 5
services/web/app/views/subscriptions/_plans_page_mixins.pug

@@ -1,20 +1,23 @@
 //- Buy Buttons
 //- Buy Buttons
 mixin btn_buy_collaborator(location)
 mixin btn_buy_collaborator(location)
-	a.btn.btn-info(
+	a.btn(
+		class=settings.overleaf ? 'btn-primary' : 'btn-info'
 		ng-href="/user/subscription/new?planCode={{ getCollaboratorPlanCode() }}&currency={{currencyCode}}",
 		ng-href="/user/subscription/new?planCode={{ getCollaboratorPlanCode() }}&currency={{currencyCode}}",
 		ng-click="signUpNowClicked('collaborator','" + location + "')"
 		ng-click="signUpNowClicked('collaborator','" + location + "')"
 	)
 	)
 		span(ng-show="ui.view != 'annual'") #{translate("start_free_trial")}
 		span(ng-show="ui.view != 'annual'") #{translate("start_free_trial")}
 		span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
 		span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
 mixin btn_buy_free(location)
 mixin btn_buy_free(location)
-	a.btn.btn-info(
+	a.btn(
+		class=settings.overleaf ? 'btn-primary' : 'btn-info'
 		href="/register"
 		href="/register"
 		style=(getLoggedInUserId() === null ? "" : "visibility: hidden")
 		style=(getLoggedInUserId() === null ? "" : "visibility: hidden")
 		ng-click="signUpNowClicked('free','" + location + "')"
 		ng-click="signUpNowClicked('free','" + location + "')"
 	)
 	)
 		span.text-capitalize #{translate('get_started_now')}
 		span.text-capitalize #{translate('get_started_now')}
 mixin btn_buy_professional(location)
 mixin btn_buy_professional(location)
-	a.btn.btn-info(
+	a.btn(
+		class=settings.overleaf ? 'btn-primary' : 'btn-info'
 		ng-href="/user/subscription/new?planCode=professional{{ ui.view == 'annual' && '-annual' || planQueryString}}&currency={{currencyCode}}"
 		ng-href="/user/subscription/new?planCode=professional{{ ui.view == 'annual' && '-annual' || planQueryString}}&currency={{currencyCode}}"
 		ng-click="signUpNowClicked('professional','" + location + "')"
 		ng-click="signUpNowClicked('professional','" + location + "')"
 	)
 	)
@@ -22,13 +25,15 @@ mixin btn_buy_professional(location)
 		span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
 		span(ng-show="ui.view == 'annual'") #{translate("buy_now")}
 mixin btn_buy_student(location, plan)
 mixin btn_buy_student(location, plan)
 	if plan == 'annual'
 	if plan == 'annual'
-		a.btn.btn-info(
+		a.btn(
+			class=settings.overleaf ? 'btn-primary' : 'btn-info'
 			ng-href="/user/subscription/new?planCode=student-annual&currency={{currencyCode}}",
 			ng-href="/user/subscription/new?planCode=student-annual&currency={{currencyCode}}",
 			ng-click="signUpNowClicked('student-annual','" + location + "')"
 			ng-click="signUpNowClicked('student-annual','" + location + "')"
 		) #{translate("buy_now")}
 		) #{translate("buy_now")}
 	else
 	else
 		//- planQueryString will contain _free_trial_7_days
 		//- planQueryString will contain _free_trial_7_days
-		a.btn.btn-info(
+		a.btn(
+			class=settings.overleaf ? 'btn-primary' : 'btn-info'
 			ng-href="/user/subscription/new?planCode=student{{planQueryString}}&currency={{currencyCode}}",
 			ng-href="/user/subscription/new?planCode=student{{planQueryString}}&currency={{currencyCode}}",
 			ng-click="signUpNowClicked('student-monthly','" + location + "')"
 			ng-click="signUpNowClicked('student-monthly','" + location + "')"
 		) #{translate("start_free_trial")}
 		) #{translate("start_free_trial")}

+ 25 - 0
services/web/app/views/subscriptions/_plans_quotes.pug

@@ -0,0 +1,25 @@
+.row.row-spaced-large
+	.col-md-12
+		.page-header.plans-header.plans-subheader.text-centered
+			h2 #{translate('in_good_company')}
+.row
+	.col-md-6
+		div
+			.row
+				.col-md-3
+					.circle-img
+						img(src=buildImgPath('advocates/erdogmus.jpg') alt="Professor Erdogmus")
+				.col-md-9
+					blockquote
+						p The ability to track changes and the real-time collaborative nature is what sets ShareLaTeX apart.
+						footer Professor Erdogmus, Northeastern University
+	.col-md-6
+		div
+			.row
+				.col-md-3
+					.circle-img
+						img(src=buildImgPath('advocates/henderson.jpg') alt="Rob Henderson")
+				.col-md-9
+					blockquote
+						p ShareLaTeX has proven to be a powerful and robust collaboration tool that is widely used in our School.
+						footer Rob Henderson, School Of Informatics And Computing - Indiana University

+ 8 - 7
services/web/app/views/subscriptions/new.pug

@@ -14,7 +14,7 @@ block content
 		.container(ng-controller="NewSubscriptionController" ng-cloak)
 		.container(ng-controller="NewSubscriptionController" ng-cloak)
 			.row.card-group
 			.row.card-group
 				.col-md-5.col-md-push-4
 				.col-md-5.col-md-push-4
-					.card.card-highlighted
+					.card.card-highlighted(class=settings.overleaf ? 'card-border' : '')
 						.page-header
 						.page-header
 							.row
 							.row
 								.col-xs-9
 								.col-xs-9
@@ -250,12 +250,13 @@ block content
 							hr
 							hr
 								
 								
 							p.small.text-center We're confident that you'll love #{settings.appName}, but if not you can cancel anytime. We'll give you your money back, no questions asked, if you let us know within 30 days.
 							p.small.text-center We're confident that you'll love #{settings.appName}, but if not you can cancel anytime. We'll give you your money back, no questions asked, if you let us know within 30 days.
-							hr
-							span &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;
-							a(href="https://www.positivessl.com" style="font-family: arial; font-size: 10px; color: #212121; text-decoration: none;")
-								img(src="https://www.positivessl.com/images-new/PositiveSSL_tl_trans.png" alt="SSL Certificate" title="SSL Certificate" border="0")
-								div(style="font-family: arial;font-weight:bold;font-size:15px;color:#86BEE0;")
-									a(href="https://www.positivessl.com" style="color:#86BEE0; text-decoration: none;")
+							if !settings.overleaf
+								hr
+								span &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;  &nbsp;
+								a(href="https://www.positivessl.com" style="font-family: arial; font-size: 10px; color: #212121; text-decoration: none;")
+									img(src="https://www.positivessl.com/images-new/PositiveSSL_tl_trans.png" alt="SSL Certificate" title="SSL Certificate" border="0")
+									div(style="font-family: arial;font-weight:bold;font-size:15px;color:#86BEE0;")
+										a(href="https://www.positivessl.com" style="color:#86BEE0; text-decoration: none;")
 
 
 
 
 	script(type="text/javascript").
 	script(type="text/javascript").

+ 16 - 28
services/web/app/views/subscriptions/plans.pug

@@ -15,7 +15,7 @@ block content
 				.row
 				.row
 					.col-md-12
 					.col-md-12
 						.page-header.centered.plans-header.text-centered
 						.page-header.centered.plans-header.text-centered
-							h1.text-capitalize #{translate('instant_access')}
+							h1.text-capitalize #{translate('get_instant_access_to')} #{settings.appName}
 				.row
 				.row
 					.col-md-8.col-md-offset-2
 					.col-md-8.col-md-offset-2
 						p.text-centered #{translate("sl_benefits_plans")}
 						p.text-centered #{translate("sl_benefits_plans")}
@@ -89,7 +89,13 @@ block content
 							span #{translate('reduce_costs_group_licenses')}
 							span #{translate('reduce_costs_group_licenses')}
 							br
 							br
 							br
 							br
-							a.btn.btn-info(href="/i/university/groups") #{translate('find_out_more')}
+							if settings.overleaf
+								a.btn.btn-default(
+									href
+									ng-click="openGroupPlanModal()"
+								) #{translate('find_out_more')}
+							else
+								a.btn.btn-info(href="/i/university/groups") #{translate('find_out_more')}
 
 
 				div
 				div
 					.row.row-spaced-large
 					.row.row-spaced-large
@@ -107,31 +113,8 @@ block content
 						.col-sm-12(ng-if="ui.view == 'student'")
 						.col-sm-12(ng-if="ui.view == 'student'")
 							+table_student
 							+table_student
 
 
-					.row.row-spaced-large
-						.col-md-12
-							.page-header.plans-header.plans-subheader.text-centered
-								h2 #{translate('in_good_company')}
-					.row
-						.col-md-6
-							div
-								.row
-									.col-md-3
-										.circle-img
-											img(src=buildImgPath('advocates/erdogmus.jpg') alt="Professor Erdogmus")
-									.col-md-9
-										blockquote
-											p The ability to track changes and the real-time collaborative nature is what sets ShareLaTeX apart.
-											footer Professor Erdogmus, Northeastern University
-						.col-md-6
-							div
-								.row
-									.col-md-3
-										.circle-img
-											img(src=buildImgPath('advocates/henderson.jpg') alt="Rob Henderson")
-									.col-md-9
-										blockquote
-											p ShareLaTeX has proven to be a powerful and robust collaboration tool that is widely used in our School.
-											footer Rob Henderson, School Of Informatics And Computing - Indiana University
+					if !settings.overleaf
+						include _plans_quotes
 
 
 					include _plans_faq
 					include _plans_faq
 
 
@@ -139,8 +122,13 @@ block content
 						.col-md-12
 						.col-md-12
 							.plans-header.plans-subheader.text-centered
 							.plans-header.plans-subheader.text-centered
 								h2 #{translate('still_have_questions')}
 								h2 #{translate('still_have_questions')}
-									button.btn.btn-info.btn-header.text-capitalize(ng-controller="ContactGeneralModal" ng-click="openModal()") #{translate('get_in_touch')}
+									button.btn.btn-header.text-capitalize(
+										class=settings.overleaf ? 'btn-default' : 'btn-info'
+										ng-controller="ContactGeneralModal"
+										ng-click="openModal()"
+									) #{translate('get_in_touch')}
 									!= moduleIncludes("contactModalGeneral", locals)
 									!= moduleIncludes("contactModalGeneral", locals)
 
 
 					.row.row-spaced
 					.row.row-spaced
 
 
+	include _modal_group_inquiry

+ 6 - 0
services/web/app/views/user/settings/user-affiliations.pug

@@ -202,6 +202,8 @@ script(type="text/ng-template", id="affiliationFormTpl")
 			) {{ $select.selected.name }}
 			) {{ $select.selected.name }}
 			ui-select-choices(
 			ui-select-choices(
 				repeat="university in $ctrl.universities | filter: $select.search"
 				repeat="university in $ctrl.universities | filter: $select.search"
+				refresh="$ctrl.handleFreeformInputChange($select, 'name');"
+				refresh-delay="10"
 			)
 			)
 				span(
 				span(
 					ng-bind="university.name"
 					ng-bind="university.name"
@@ -219,6 +221,8 @@ script(type="text/ng-template", id="affiliationFormTpl")
 			) {{ $select.selected }}
 			) {{ $select.selected }}
 			ui-select-choices(
 			ui-select-choices(
 				repeat="role in $ctrl.roles | filter: $select.search"
 				repeat="role in $ctrl.roles | filter: $select.search"
+				refresh="$ctrl.handleFreeformInputChange($select);"
+				refresh-delay="10"
 			)
 			)
 				span(
 				span(
 					ng-bind="role"
 					ng-bind="role"
@@ -237,6 +241,8 @@ script(type="text/ng-template", id="affiliationFormTpl")
 			) {{ $select.selected }}
 			) {{ $select.selected }}
 			ui-select-choices(
 			ui-select-choices(
 				repeat="department in $ctrl.departments | filter: $select.search"
 				repeat="department in $ctrl.departments | filter: $select.search"
+				refresh="$ctrl.handleFreeformInputChange($select);"
+				refresh-delay="10"
 			)
 			)
 				span(
 				span(
 					ng-bind="department"
 					ng-bind="department"

+ 12 - 0
services/web/public/coffee/main/affiliations/components/affiliationForm.coffee

@@ -11,6 +11,18 @@ define [
 
 
 		ctrl.addUniversityToSelection = (universityName) -> 
 		ctrl.addUniversityToSelection = (universityName) -> 
 			{ name: universityName, isUserSuggested: true }
 			{ name: universityName, isUserSuggested: true }
+		ctrl.handleFreeformInputChange = ($select, propertyToMatch) ->
+			if !$select.search? or $select.search == ""
+				return
+			resultingItem = $select.search
+			if $select.tagging?.fct?
+				resultingItem = $select.tagging.fct $select.search
+			if propertyToMatch?
+				matchingItem = _.find $select.items, (item) -> item[propertyToMatch] == $select.search
+				if matchingItem?
+					resultingItem = matchingItem
+			$select.searchInput.scope().$broadcast "uis:select", resultingItem
+
 		# Populates the countries dropdown
 		# Populates the countries dropdown
 		UserAffiliationsDataService
 		UserAffiliationsDataService
 			.getCountries()
 			.getCountries()

+ 14 - 0
services/web/public/stylesheets/app/plans-ol.less

@@ -0,0 +1,14 @@
+
+.plans {
+	blockquote {
+		footer{
+			/* accessibility fix */
+			color: @ol-blue-gray-3;
+		}
+	}
+	.plans-header{
+		h1, h2 {
+			color: @gray-dark;
+		}
+	}
+}

+ 34 - 19
services/web/public/stylesheets/app/plans.less

@@ -4,8 +4,9 @@
 		margin-bottom: @line-height-computed;
 		margin-bottom: @line-height-computed;
 	}
 	}
 	.best-value {
 	.best-value {
-		color: @red;
+		color: @brand-secondary;
 		line-height: @line-height-computed;
 		line-height: @line-height-computed;
+		text-transform: capitalize;
 	}
 	}
 	blockquote {
 	blockquote {
 		footer{
 		footer{
@@ -20,6 +21,22 @@
 		text-shadow: 0 0 0;
 		text-shadow: 0 0 0;
 	}
 	}
 	.card .btn { white-space:normal; }
 	.card .btn { white-space:normal; }
+	.card.features {
+		margin-top: @line-height-computed;
+		i {
+			color: @red;
+		}
+		p {
+			margin: 0;
+		}
+	}
+	.btn-header {
+		font-family: @font-family-sans-serif;
+		margin-left: 10px;
+		margin-top: -10px;
+		text-shadow: 0 0 0;
+	}
+	.card .btn { white-space:normal; }
 	.card-group {
 	.card-group {
 		.card-highlighted {
 		.card-highlighted {
 			padding-top: @line-height-computed * 2;
 			padding-top: @line-height-computed * 2;
@@ -27,10 +44,10 @@
 		}
 		}
 	}
 	}
 	.card-first, .card-last {
 	.card-first, .card-last {
-		background: @white-med;
+		background: @plans-non-highlighted;
 	}
 	}
 	.card-highlighted {
 	.card-highlighted {
-		border: @highlight-border solid @gray-lighter;
+		border: @border-width-base solid @border-color-base;
 		padding-top: 10px!important;
 		padding-top: 10px!important;
 		.best-value {
 		.best-value {
 			margin-bottom: 15px;
 			margin-bottom: 15px;
@@ -51,7 +68,7 @@
 		width: 120px;
 		width: 120px;
 		height: 120px;
 		height: 120px;
 		border-radius: 50%;
 		border-radius: 50%;
-		background-color: @red;
+		background-color: @brand-secondary;
 		color: white;
 		color: white;
 		white-space: nowrap;
 		white-space: nowrap;
 		span.small {
 		span.small {
@@ -148,10 +165,8 @@
 	Plans Test
 	Plans Test
 */
 */
 @best-val-height: 35px;
 @best-val-height: 35px;
-@highlight-border: 3px;
-@highlight-color: #d3584b;
+@border-width-base: 3px;
 @gray-med: #6d6d6d;
 @gray-med: #6d6d6d;
-@white-med: #fdfdfd;
 
 
 /* Media Queries */
 /* Media Queries */
 @media (max-width: @screen-sm-max) {
 @media (max-width: @screen-sm-max) {
@@ -192,7 +207,7 @@
 */
 */
 .plans-table {
 .plans-table {
 	border: 1px solid @gray-lighter;
 	border: 1px solid @gray-lighter;
-	background-color: @white-med;
+	background-color: @plans-non-highlighted;
 	margin: @best-val-height 0 15px 0;
 	margin: @best-val-height 0 15px 0;
 	table-layout: fixed;
 	table-layout: fixed;
 	width: 100%;
 	width: 100%;
@@ -215,8 +230,8 @@
 		border-top: 0;
 		border-top: 0;
 		font-family: @headings-font-family;
 		font-family: @headings-font-family;
 		font-size: @font-size-h2;
 		font-size: @font-size-h2;
-	  font-weight: @headings-font-weight;
-	  line-height: @headings-line-height;
+		font-weight: @headings-font-weight;
+		line-height: @headings-line-height;
 		padding: 18px;
 		padding: 18px;
 	}
 	}
 
 
@@ -246,13 +261,13 @@
 			border-bottom: 0;
 			border-bottom: 0;
 			padding: 18px;
 			padding: 18px;
 		}
 		}
-			/* highlighted column */
+		/* highlighted column */
 		td:nth-child(3) {
 		td:nth-child(3) {
 			position: relative;
 			position: relative;
 			/* keep here position here, otherwise messes up border on safari when there is a bg color */
 			/* keep here position here, otherwise messes up border on safari when there is a bg color */
 			&:before {
 			&:before {
 				/* needed for safafi */
 				/* needed for safafi */
-				border-top: 1px solid @gray-lighter;
+				border-top: 1px solid @border-color-base;
 				content: '';
 				content: '';
 				left: 0;
 				left: 0;
 				position: absolute;
 				position: absolute;
@@ -277,17 +292,17 @@
 	/* highlighted column */
 	/* highlighted column */
 	td:nth-child(3), th:nth-child(3) {
 	td:nth-child(3), th:nth-child(3) {
 		background-color: white;
 		background-color: white;
-		border-left: @highlight-border solid @gray-lighter;
-		border-right: @highlight-border solid @gray-lighter;
+		border-left: @border-width-base solid @border-color-base;
+		border-right: @border-width-base solid @border-color-base;
 	}
 	}
 	.outer {
 	.outer {
-		left: -@highlight-border;
-		right: -@highlight-border;
+		left: -@border-width-base;
+		right: -@border-width-base;
 		position: absolute;
 		position: absolute;
 
 
 		.outer-content {
 		.outer-content {
 			background: white;
 			background: white;
-			border: @highlight-border solid @gray-lighter;
+			border: @border-width-base solid @border-color-base;
 			border-radius: @border-radius-base;
 			border-radius: @border-radius-base;
 			font-size: @font-size-base;
 			font-size: @font-size-base;
 			font-family: @font-family-sans-serif;
 			font-family: @font-family-sans-serif;
@@ -317,7 +332,7 @@
 	/* highlight rows on hover */
 	/* highlight rows on hover */
 	tr:hover {
 	tr:hover {
 		td {
 		td {
-			background-color: @gray-lightest;
+			background-color: @table-hover-bg;
 		}
 		}
 	}
 	}
 	tr:first-child:hover {
 	tr:first-child:hover {
@@ -332,7 +347,7 @@
 
 
 	/* tooltip */
 	/* tooltip */
 	sup {
 	sup {
-		color: @red;
+		color: @brand-secondary;
 		cursor: pointer;
 		cursor: pointer;
 		margin-left: 5px;
 		margin-left: 5px;
 	}
 	}

+ 5 - 5
services/web/public/stylesheets/app/subscription.less

@@ -44,16 +44,16 @@
 		border: solid 1px @gray-lighter;
 		border: solid 1px @gray-lighter;
 		border-radius: @border-radius-large 0 0 @border-radius-large;
 		border-radius: @border-radius-large 0 0 @border-radius-large;
 		padding: (@line-height-computed / 2);
 		padding: (@line-height-computed / 2);
-		color: @gray;
+		color: @btn-switch-color;
 
 
 		&:hover,
 		&:hover,
 		&:focus {
 		&:focus {
-			color: @gray;
+			color: @btn-switch-color;
 			text-decoration: none;
 			text-decoration: none;
 		}
 		}
 
 
 		&:hover {
 		&:hover {
-			color: @gray-dark;
+			color: @btn-switch-hover-color;
 		}
 		}
 
 
 		& + & {
 		& + & {
@@ -62,12 +62,12 @@
 		}
 		}
 
 
 		&-selected {
 		&-selected {
-			color: @link-color;
+			color: @link-active-color;
 			box-shadow: inset 0 -2px 0 0;
 			box-shadow: inset 0 -2px 0 0;
 
 
 			&:hover,
 			&:hover,
 			&:focus {
 			&:focus {
-				color: @link-color;
+				color: @link-active-color;
 			}
 			}
 		}
 		}
 	}
 	}

+ 4 - 0
services/web/public/stylesheets/components/card.less

@@ -50,4 +50,8 @@
             border-bottom-right-radius: @border-radius-base;
             border-bottom-right-radius: @border-radius-base;
         }
         }
     }
     }
+}
+
+.card-border {
+  border: @border-width-base solid @border-color-base;
 }
 }

+ 7 - 0
services/web/public/stylesheets/components/navs-ol.less

@@ -0,0 +1,7 @@
+.nav-pills {
+  > li {
+    > a {
+      border-radius: @btn-border-radius-base;
+    }
+  }
+}

+ 9 - 2
services/web/public/stylesheets/components/navs.less

@@ -123,8 +123,14 @@
     // Links rendered as pills
     // Links rendered as pills
     > a {
     > a {
       border-radius: @nav-pills-border-radius;
       border-radius: @nav-pills-border-radius;
-      border: 2px solid @link-color;
+      border: 2px solid @nav-pills-link-color;
+      color: @nav-pills-link-color;
       padding: 8px 13px;
       padding: 8px 13px;
+      &:hover,
+      &:focus {
+        background-color: @nav-pills-link-hover-bg;
+        border: 2px solid @nav-pills-link-hover-bg;
+      }
     }
     }
     + li {
     + li {
       margin-left: 2px;
       margin-left: 2px;
@@ -136,6 +142,7 @@
       &:hover,
       &:hover,
       &:focus {
       &:focus {
         color: @nav-pills-active-link-hover-color;
         color: @nav-pills-active-link-hover-color;
+        border: 2px solid @nav-pills-active-link-hover-bg;
         background-color: @nav-pills-active-link-hover-bg;
         background-color: @nav-pills-active-link-hover-bg;
       }
       }
     }
     }
@@ -247,4 +254,4 @@
   margin-top: -1px;
   margin-top: -1px;
   // Remove the top rounded corners here since there is a hard edge above the menu
   // Remove the top rounded corners here since there is a hard edge above the menu
   .border-top-radius(0);
   .border-top-radius(0);
-}
+}

+ 14 - 0
services/web/public/stylesheets/core/_common-variables.less

@@ -13,6 +13,7 @@
 
 
 //** Global textual link color.
 //** Global textual link color.
 @link-color:            @brand-primary;
 @link-color:            @brand-primary;
+@link-active-color:     @link-color;
 //** Link hover color set via `darken()` function.
 //** Link hover color set via `darken()` function.
 @link-hover-color:      darken(@link-color, 15%);
 @link-hover-color:      darken(@link-color, 15%);
 
 
@@ -87,6 +88,12 @@
 @border-radius-base:        3px;
 @border-radius-base:        3px;
 @border-radius-large:       5px;
 @border-radius-large:       5px;
 @border-radius-small:       2px;
 @border-radius-small:       2px;
+@border-width-base:         3px;
+@border-color-base:         @gray-lighter;
+
+@btn-switch-color:               @gray;
+@btn-switch-hover-color:         @gray-dark;
+
 //** Global color for active items (e.g., navs or dropdowns).
 //** Global color for active items (e.g., navs or dropdowns).
 @component-active-color:    #fff;
 @component-active-color:    #fff;
 //** Global background color for active items (e.g., navs or dropdowns).
 //** Global background color for active items (e.g., navs or dropdowns).
@@ -370,6 +377,9 @@
 @nav-tabs-justified-active-link-border-color:     @body-bg;
 @nav-tabs-justified-active-link-border-color:     @body-bg;
 
 
 //== Pills
 //== Pills
+@nav-pills-link-color:                      @link-color;
+@nav-pills-link-hover-color:                @nav-link-hover-bg;
+@nav-pills-link-hover-bg:                   @nav-link-hover-bg;
 @nav-pills-border-radius:                   @border-radius-base;
 @nav-pills-border-radius:                   @border-radius-base;
 @nav-pills-active-link-hover-bg:            @component-active-bg;
 @nav-pills-active-link-hover-bg:            @component-active-bg;
 @nav-pills-active-link-hover-color:         @component-active-color;
 @nav-pills-active-link-hover-color:         @component-active-color;
@@ -412,6 +422,10 @@
 @pager-disabled-color:                 @pagination-disabled-color;
 @pager-disabled-color:                 @pagination-disabled-color;
 
 
 
 
+// Plans
+@table-hover-bg:                       @gray-lightest;
+@plans-non-highlighted:                #fdfdfd;
+
 //== Jumbotron
 //== Jumbotron
 //
 //
 //##
 //##

+ 16 - 0
services/web/public/stylesheets/core/ol-variables.less

@@ -34,14 +34,22 @@
 @navbar-default-border    : transparent;
 @navbar-default-border    : transparent;
 @navbar-brand-image-url   : url(/img/ol-brand/overleaf-white.svg);
 @navbar-brand-image-url   : url(/img/ol-brand/overleaf-white.svg);
 @navbar-default-link-bg   : transparent;
 @navbar-default-link-bg   : transparent;
+@nav-pills-active-link-hover-bg: @ol-dark-green;
+@nav-pills-link-color     : @btn-default-bg;
+@nav-pills-link-hover-bg  : darken(@ol-blue-gray-4, 8%); // match button-variant mixin
+
 // Backgrounds
 // Backgrounds
 @body-bg                : #FFF;
 @body-bg                : #FFF;
 @content-alt-bg-color   : @ol-blue-gray-1;
 @content-alt-bg-color   : @ol-blue-gray-1;
 
 
+// Border
+@border-color-base:         @ol-blue-gray-2;
+
 // Typography
 // Typography
 @text-small-color       : @ol-type-color;
 @text-small-color       : @ol-type-color;
 @text-color             : @ol-type-color;
 @text-color             : @ol-type-color;
 @link-color             : @ol-blue;
 @link-color             : @ol-blue;
+@link-active-color      : @ol-dark-green;
 @link-hover-color       : @ol-dark-blue;
 @link-hover-color       : @ol-dark-blue;
 
 
 // Button colors and sizing
 // Button colors and sizing
@@ -68,6 +76,9 @@
 @btn-info-bg             : @ol-blue;
 @btn-info-bg             : @ol-blue;
 @btn-info-border         : transparent;
 @btn-info-border         : transparent;
 
 
+@btn-switch-color        : @ol-blue-gray-4;
+@btn-switch-hover-color  : darken(@ol-blue-gray-4, 8%);
+
 // Padding
 // Padding
 @padding-xs-horizontal   : 8px;
 @padding-xs-horizontal   : 8px;
 
 
@@ -308,6 +319,10 @@
 @log-line-no-color              : #FFF;
 @log-line-no-color              : #FFF;
 @log-hints-color                : @ol-blue-gray-4;
 @log-hints-color                : @ol-blue-gray-4;
 
 
+// Plans
+@table-hover-bg                 : @ol-blue-gray-0;
+@plans-non-highlighted          : white;
+
 // Portals
 // Portals
 @black-alpha-strong             : rgba(0,0,0,0.8);
 @black-alpha-strong             : rgba(0,0,0,0.8);
 
 
@@ -360,6 +375,7 @@
 @purple:                #7a43b6;
 @purple:                #7a43b6;
 
 
 @brand-primary:         @ol-green;
 @brand-primary:         @ol-green;
+@brand-secondary:       @ol-dark-green;
 @brand-success:         @green;
 @brand-success:         @green;
 @brand-info:            @ol-blue;
 @brand-info:            @ol-blue;
 @brand-warning:         @orange;
 @brand-warning:         @orange;

+ 1 - 0
services/web/public/stylesheets/core/variables.less

@@ -20,6 +20,7 @@
 @purple:                #7a43b6;
 @purple:                #7a43b6;
 
 
 @brand-primary:         @red;
 @brand-primary:         @red;
+@brand-secondary:       @red;
 @brand-success:         @green;
 @brand-success:         @green;
 @brand-info:            @blue;
 @brand-info:            @blue;
 @brand-warning:         @orange;
 @brand-warning:         @orange;

+ 2 - 0
services/web/public/stylesheets/ol-style.less

@@ -8,6 +8,7 @@
 @import "_ol_style_includes.less";
 @import "_ol_style_includes.less";
 @import "components/embed-responsive.less";
 @import "components/embed-responsive.less";
 @import "components/icons.less";
 @import "components/icons.less";
+@import "components/navs-ol.less";
 @import "components/pagination.less";
 @import "components/pagination.less";
 @import "components/publish-modal.less";
 @import "components/publish-modal.less";
 
 
@@ -15,4 +16,5 @@
 @import "app/about.less";
 @import "app/about.less";
 @import "app/blog-posts.less";
 @import "app/blog-posts.less";
 @import "app/cms-page.less";
 @import "app/cms-page.less";
+@import "app/plans-ol.less";
 @import "app/portals.less";
 @import "app/portals.less";