Prechádzať zdrojové kódy

Merge branch 'master' into as-autocompile-rollout-60

Alasdair Smith 8 rokov pred
rodič
commit
4a9c9c563a
29 zmenil súbory, kde vykonal 645 pridanie a 506 odobranie
  1. 21 0
      services/web/app/coffee/Features/Captcha/CaptchaMiddleware.coffee
  2. 2 1
      services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee
  3. 2 0
      services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee
  4. 6 5
      services/web/app/coffee/Features/Email/EmailSender.coffee
  5. 8 0
      services/web/app/coffee/Features/Errors/Errors.coffee
  6. 11 0
      services/web/app/coffee/infrastructure/ExpressLocals.coffee
  7. 9 0
      services/web/app/views/layout.pug
  8. 3 2
      services/web/app/views/project/editor.pug
  9. 4 5
      services/web/app/views/project/editor/editor.pug
  10. 6 0
      services/web/app/views/project/list.pug
  11. 258 430
      services/web/npm-shrinkwrap.json
  12. 1 0
      services/web/package.json
  13. 13 1
      services/web/public/coffee/directives/asyncForm.coffee
  14. 3 0
      services/web/public/coffee/ide.coffee
  15. 9 5
      services/web/public/coffee/ide/directives/layout.coffee
  16. 8 4
      services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee
  17. 2 1
      services/web/public/coffee/ide/share/services/projectInvites.coffee
  18. 1 0
      services/web/public/coffee/main.coffee
  19. 24 0
      services/web/public/coffee/services/validateCaptcha.coffee
  20. 1 0
      services/web/public/stylesheets/_ol_style_includes.less
  21. 4 0
      services/web/public/stylesheets/app/base.less
  22. 98 6
      services/web/public/stylesheets/app/editor.less
  23. 59 4
      services/web/public/stylesheets/app/editor/pdf.less
  24. 0 1
      services/web/public/stylesheets/app/project-list.less
  25. 32 0
      services/web/public/stylesheets/app/sidebar-v1-dash-link.less
  26. 33 25
      services/web/public/stylesheets/core/_common-variables.less
  27. 23 15
      services/web/public/stylesheets/core/ol-variables.less
  28. 2 1
      services/web/public/stylesheets/ol-style.less
  29. 2 0
      services/web/test/unit/coffee/Email/EmailSenderTests.coffee

+ 21 - 0
services/web/app/coffee/Features/Captcha/CaptchaMiddleware.coffee

@@ -0,0 +1,21 @@
+request = require 'request'
+logger = require 'logger-sharelatex'
+Settings = require 'settings-sharelatex'
+
+module.exports = CaptchaMiddleware =
+	validateCaptcha: (req, res, next) ->
+		if !Settings.recaptcha?
+			return next()
+		response = req.body['g-recaptcha-response']
+		options =
+			form:
+				secret: Settings.recaptcha.secretKey
+				response: response
+			json: true
+		request.post "https://www.google.com/recaptcha/api/siteverify", options, (error, response, body) ->
+			return next(error) if error?
+			if !body?.success
+				logger.warn {statusCode: response.statusCode, body: body}, 'failed recaptcha siteverify request'
+				return res.sendStatus 400
+			else
+				return next()

+ 2 - 1
services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee

@@ -11,6 +11,7 @@ NotificationsBuilder = require("../Notifications/NotificationsBuilder")
 AnalyticsManger = require("../Analytics/AnalyticsManager")
 AuthenticationController = require("../Authentication/AuthenticationController")
 rateLimiter = require("../../infrastructure/RateLimiter")
+request = require 'request'
 
 module.exports = CollaboratorsInviteController =
 
@@ -32,7 +33,7 @@ module.exports = CollaboratorsInviteController =
 				callback(null, userExists)
 		else
 			callback(null, true)
-	
+
 	_checkRateLimit: (user_id, callback = (error) ->) ->
 		LimitationsManager.allowedNumberOfCollaboratorsForUser user_id, (err, collabLimit = 1)->
 			return callback(err) if err?

+ 2 - 0
services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee

@@ -3,6 +3,7 @@ AuthenticationController = require('../Authentication/AuthenticationController')
 AuthorizationMiddlewear = require('../Authorization/AuthorizationMiddlewear')
 CollaboratorsInviteController = require('./CollaboratorsInviteController')
 RateLimiterMiddlewear = require('../Security/RateLimiterMiddlewear')
+CaptchaMiddleware = require '../Captcha/CaptchaMiddleware'
 
 module.exports =
 	apply: (webRouter, apiRouter) ->
@@ -32,6 +33,7 @@ module.exports =
 				maxRequests: 100
 				timeInterval: 60 * 10
 			}),
+			CaptchaMiddleware.validateCaptcha,
 			AuthenticationController.requireLogin(),
 			AuthorizationMiddlewear.ensureUserCanAdminProject,
 			CollaboratorsInviteController.inviteToProject

+ 6 - 5
services/web/app/coffee/Features/Email/EmailSender.coffee

@@ -4,6 +4,7 @@ Settings = require('settings-sharelatex')
 nodemailer = require("nodemailer")
 sesTransport = require('nodemailer-ses-transport')
 sgTransport = require('nodemailer-sendgrid-transport')
+mandrillTransport = require('nodemailer-mandrill-transport')
 rateLimiter = require('../../infrastructure/RateLimiter')
 _ = require("underscore")
 
@@ -17,22 +18,22 @@ client =
 	sendMail: (options, callback = (err,status) ->) ->
 		logger.log options:options, "Would send email if enabled."
 		callback()
-
 if Settings?.email?.parameters?.AWSAccessKeyID? or Settings?.email?.driver == 'ses'
 	logger.log "using aws ses for email"
 	nm_client = nodemailer.createTransport(sesTransport(Settings.email.parameters))
 else if Settings?.email?.parameters?.sendgridApiKey?
 	logger.log "using sendgrid for email"
 	nm_client = nodemailer.createTransport(sgTransport({auth:{api_key:Settings?.email?.parameters?.sendgridApiKey}}))
+else if Settings?.email?.parameters?.MandrillApiKey?
+	logger.log "using mandril for email"
+	nm_client = nodemailer.createTransport(mandrillTransport({auth:{apiKey:Settings?.email?.parameters?.MandrillApiKey}}))
 else if Settings?.email?.parameters?
-	smtp = _.pick(Settings?.email?.parameters, "host", "port", "secure", "auth", "ignoreTLS")
-
-
 	logger.log "using smtp for email"
+	smtp = _.pick(Settings?.email?.parameters, "host", "port", "secure", "auth", "ignoreTLS")
 	nm_client = nodemailer.createTransport(smtp)
 else
-	nm_client = client
 	logger.warn "Email transport and/or parameters not defined. No emails will be sent."
+	nm_client = client
 
 if nm_client?
 		client = nm_client

+ 8 - 0
services/web/app/coffee/Features/Errors/Errors.coffee

@@ -26,8 +26,16 @@ InvalidNameError = (message) ->
 	return error
 InvalidNameError.prototype.__proto__ = Error.prototype
 
+UnsupportedFileTypeError = (message) ->
+	error = new Error(message)
+	error.name = "UnsupportedFileTypeError"
+	error.__proto__ = UnsupportedFileTypeError.prototype
+	return error
+UnsupportedFileTypeError.prototype.__proto___ = Error.prototype
+
 module.exports = Errors =
 	NotFoundError: NotFoundError
 	ServiceNotConfiguredError: ServiceNotConfiguredError
 	TooManyRequestsError: TooManyRequestsError
 	InvalidNameError: InvalidNameError
+	UnsupportedFileTypeError: UnsupportedFileTypeError

+ 11 - 0
services/web/app/coffee/infrastructure/ExpressLocals.coffee

@@ -292,3 +292,14 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)->
 		res.locals.moduleIncludes = Modules.moduleIncludes
 		res.locals.moduleIncludesAvailable = Modules.moduleIncludesAvailable
 		next()
+
+	webRouter.use (req, res, next) ->
+		isOl = (Settings.brandPrefix == 'ol-')
+		res.locals.uiConfig = 
+			defaultResizerSizeOpen   : if isOl then 2 else 24
+			defaultResizerSizeClosed : if isOl then 2 else 24
+			eastResizerCursor        : if isOl then "ew-resize" else null
+			westResizerCursor        : if isOl then "ew-resize" else null
+			chatResizerSizeOpen      : if isOl then 2 else 12
+			chatResizerSizeClosed    : 0
+		next()

+ 9 - 0
services/web/app/views/layout.pug

@@ -97,6 +97,15 @@ html(itemscope, itemtype='http://schema.org/Product')
 				}
 				
 	body
+		if(settings.recaptcha)
+			script(src="https://www.google.com/recaptcha/api.js?render=explicit")
+			div(
+				id="recaptcha"
+				class="g-recaptcha"
+				data-sitekey=settings.recaptcha.siteKey
+				data-size="invisible"
+				data-badge="inline"
+			)
 
 		- if(typeof(suppressSystemMessages) == "undefined")
 			.system-messages(

+ 3 - 2
services/web/app/views/project/editor.pug

@@ -43,8 +43,8 @@ block content
 
 		#chat-wrapper.full-size(
 			layout="chat",
-			spacing-open="12",
-			spacing-closed="0",
+			spacing-open="{{ui.chatResizerSizeOpen}}",
+			spacing-closed="{{ui.chatResizerSizeClosed}}",
 			initial-size-east="250",
 			init-closed-east="true",
 			open-east="ui.chatOpen",
@@ -158,6 +158,7 @@ block requirejs
 		window.aceFingerprint = "#{fingerprint(jsPath + lib('ace') + '/ace.js')}"
 		window.aceWorkerPath = "#{aceWorkerPath}";
 		window.pdfCMapsPath = "#{pdfCMapsPath}"
+		window.uiConfig = JSON.parse('!{JSON.stringify(uiConfig).replace(/\//g, "\\/")}');
 
 	script(
 		data-main=buildJsPath("ide.js", {fingerprint:false}),

+ 4 - 5
services/web/app/views/project/editor/editor.pug

@@ -73,21 +73,20 @@ div.full-size(
 		ng-show="!!pdf.url && settings.pdfViewer == 'pdfjs'"
 		ng-controller="PdfSynctexController"
 	)
-		a.btn.btn-default.btn-xs(
+		a.btn.btn-default.btn-xs.synctex-control.synctex-control-goto-pdf(
 			tooltip=translate('go_to_code_location_in_pdf')
 			tooltip-placement="right"
 			tooltip-append-to-body="true"
 			ng-click="syncToPdf()"
 		)
-			i.fa.fa-long-arrow-right
-		br
-		a.btn.btn-default.btn-xs(
+			i.synctex-control-icon
+		a.btn.btn-default.btn-xs.synctex-control.synctex-control-goto-code(
 			tooltip-html="'"+translate('go_to_pdf_location_in_code', {}, true)+"'"
 			tooltip-placement="right"
 			tooltip-append-to-body="true"
 			ng-click="syncToCode()"
 		)
-			i.fa.fa-long-arrow-left
+			i.synctex-control-icon
 			
 div.full-size(
 	ng-if="ui.pdfLayout == 'flat'"

+ 6 - 0
services/web/app/views/project/list.pug

@@ -63,6 +63,12 @@ block content
 					aside.project-list-sidebar.col-md-2.col-xs-3
 						include ./list/side-bar
 
+					if isShowingV1Projects && settings.overleaf && settings.overleaf.host
+						.project-list-sidebar-v1-link.col-md-2.col-xs-3
+							span Want to go back to the V1 dashboard?
+							a.btn.btn-default(href=settings.overleaf.host + "/dash?prefer-v1-dash=1")
+								| Go back to V1
+
 					.project-list-main.col-md-10.col-xs-9
 						include ./list/notifications
 						include ./list/project-list

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 258 - 430
services/web/npm-shrinkwrap.json


+ 1 - 0
services/web/package.json

@@ -56,6 +56,7 @@
     "multer": "^0.1.8",
     "node-html-encoder": "0.0.2",
     "nodemailer": "2.1.0",
+    "nodemailer-mandrill-transport": "^1.2.0",
     "nodemailer-sendgrid-transport": "^0.2.0",
     "nodemailer-ses-transport": "^1.3.0",
     "optimist": "0.6.1",

+ 13 - 1
services/web/public/coffee/directives/asyncForm.coffee

@@ -2,7 +2,7 @@ define [
 	"base"
 	"libs/passfield"
 ], (App) ->
-	App.directive "asyncForm", ($http) ->
+	App.directive "asyncForm", ($http, validateCaptcha) ->
 		return {
 			controller: ['$scope', ($scope) ->
 				@getEmail = () ->
@@ -17,11 +17,23 @@ define [
 
 				element.on "submit", (e) ->
 					e.preventDefault()
+					validateCaptchaIfEnabled (response) ->
+						submitRequest response
 
+				validateCaptchaIfEnabled = (callback = (response) ->) ->
+					if attrs.captcha?
+						validateCaptcha callback
+					else
+						callback()
+
+				submitRequest = (grecaptchaResponse) ->
 					formData = {}
 					for data in element.serializeArray()
 						formData[data.name] = data.value
 
+					if grecaptchaResponse?
+						formData['g-recaptcha-response'] = grecaptchaResponse
+
 					scope[attrs.name].inflight = true
 
 					# for asyncForm prevent automatic redirect to /login if

+ 3 - 0
services/web/public/coffee/ide.coffee

@@ -33,6 +33,7 @@ define [
 	"directives/expandableTextArea"
 	"directives/videoPlayState"
 	"services/queued-http"
+	"services/validateCaptcha"
 	"filters/formatDate"
 	"main/event"
 	"main/account-upgrade"
@@ -76,6 +77,8 @@ define [
 			pdfWidth: 0
 			reviewPanelOpen: localStorage("ui.reviewPanelOpen.#{window.project_id}")
 			miniReviewPanelVisible: false
+			chatResizerSizeOpen: window.uiConfig.chatResizerSizeOpen
+			chatResizerSizeClosed: window.uiConfig.chatResizerSizeClosed
 		}
 		$scope.onboarding = {
 			autoCompile: if window.showAutoCompileOnboarding then 'unseen' else 'dismissed'

+ 9 - 5
services/web/public/coffee/ide/directives/layout.coffee

@@ -11,12 +11,12 @@ define [
 					if attrs.spacingOpen?
 						spacingOpen = parseInt(attrs.spacingOpen, 10)
 					else
-						spacingOpen = 24
+						spacingOpen = window.uiConfig.defaultResizerSizeOpen
 
 					if attrs.spacingClosed?
 						spacingClosed = parseInt(attrs.spacingClosed, 10)
 					else
-						spacingClosed = 24
+						spacingClosed = window.uiConfig.defaultResizerSizeClosed
 
 					options =
 						spacing_open: spacingOpen
@@ -44,6 +44,12 @@ define [
 							if !attrs.minimumRestoreSizeWest? or (state.west.size >= attrs.minimumRestoreSizeWest and !state.west.initClosed)
 								options.west = state.west
 
+					if window.uiConfig.eastResizerCursor?
+						options.east.resizerCursor = window.uiConfig.eastResizerCursor
+
+					if window.uiConfig.westResizerCursor?
+						options.west.resizerCursor = window.uiConfig.westResizerCursor
+						
 					repositionControls = () ->
 						state = element.layout().readState()
 						if state.east?
@@ -53,9 +59,7 @@ define [
 							else
 								controls.show()
 								controls.css({
-									position: "absolute"
 									right: state.east.size
-									"z-index": 3
 								})
 
 					resetOpenStates = () ->
@@ -112,7 +116,7 @@ define [
 						# Set the panel as overflowing (gives it higher z-index and sets overflow rules)
 						layoutObj.allowOverflow overflowPane
 						# Read the given z-index value and increment it, so that it's higher than synctex controls.
-						overflowPaneZVal = overflowPaneEl.css "z-index"
+						overflowPaneZVal = overflowPaneEl.zIndex()
 						overflowPaneEl.css "z-index", overflowPaneZVal + 1
 
 					resetOpenStates()

+ 8 - 4
services/web/public/coffee/ide/share/controllers/ShareProjectModalController.coffee

@@ -1,7 +1,7 @@
 define [
 	"base"
 ], (App) ->
-	App.controller "ShareProjectModalController", ($scope, $modalInstance, $timeout, projectMembers, projectInvites, $modal, $http, ide) ->
+	App.controller "ShareProjectModalController", ($scope, $modalInstance, $timeout, projectMembers, projectInvites, $modal, $http, ide, validateCaptcha) ->
 		$scope.inputs = {
 			privileges: "readAndWrite"
 			contacts: []
@@ -100,7 +100,7 @@ define [
 					if email in currentInviteEmails and inviteId = _.find(($scope.project.invites || []), (invite) -> invite.email == email)?._id
 						request = projectInvites.resendInvite(inviteId)
 					else
-						request = projectInvites.sendInvite(email, $scope.inputs.privileges)
+						request = projectInvites.sendInvite(email, $scope.inputs.privileges, $scope.grecaptchaResponse)
 
 					request
 						.then (response) ->
@@ -135,7 +135,9 @@ define [
 							else
 								$scope.state.errorReason = null
 
-			$timeout addMembers, 50 # Give email list a chance to update
+			validateCaptcha (response) ->
+				$scope.grecaptchaResponse = response
+				$timeout addMembers, 50 # Give email list a chance to update
 
 		$scope.removeMember = (member) ->
 			$scope.state.error = null
@@ -210,6 +212,8 @@ define [
 		$scope.cancel = () ->
 			$modalInstance.dismiss()
 
+
+
 	App.controller "MakePublicModalController", ["$scope", "$modalInstance", "settings", ($scope, $modalInstance, settings) ->
 		$scope.inputs = {
 			privileges: "readAndWrite"
@@ -244,4 +248,4 @@ define [
 
 		$scope.cancel = () ->
 			$modalInstance.dismiss()
-	]
+	]

+ 2 - 1
services/web/public/coffee/ide/share/services/projectInvites.coffee

@@ -4,11 +4,12 @@ define [
 	App.factory "projectInvites", ["ide", "$http", (ide, $http) ->
 		return {
 
-			sendInvite: (email, privileges) ->
+			sendInvite: (email, privileges, grecaptchaResponse) ->
 				$http.post("/project/#{ide.project_id}/invite", {
 					email: email
 					privileges: privileges
 					_csrf: window.csrfToken
+					'g-recaptcha-response': grecaptchaResponse
 				})
 
 			revokeInvite: (inviteId) ->

+ 1 - 0
services/web/public/coffee/main.coffee

@@ -30,6 +30,7 @@ define [
 	"directives/maxHeight"
 	"directives/creditCards"
 	"services/queued-http"
+	"services/validateCaptcha"
 	"filters/formatDate"
 	"__MAIN_CLIENTSIDE_INCLUDES__"
 ], () ->

+ 24 - 0
services/web/public/coffee/services/validateCaptcha.coffee

@@ -0,0 +1,24 @@
+define [
+	"base"
+], (App) ->
+	App.factory "validateCaptcha", () ->
+		_recaptchaCallbacks = []
+		onRecaptchaSubmit = (token) ->
+			for cb in _recaptchaCallbacks
+				cb(token)
+			_recaptchaCallbacks = []
+
+		recaptchaId = null
+		validateCaptcha = (callback = (response) ->) =>
+			if !grecaptcha?
+				return callback()
+			reset = () ->
+				grecaptcha.reset()
+			_recaptchaCallbacks.push callback
+			_recaptchaCallbacks.push reset
+			if !recaptchaId?
+				el = $('#recaptcha')[0]
+				recaptchaId = grecaptcha.render(el, {callback: onRecaptchaSubmit})
+			grecaptcha.execute(recaptchaId)
+
+		return validateCaptcha

+ 1 - 0
services/web/public/stylesheets/_ol_style_includes.less

@@ -0,0 +1 @@
+@import "app/sidebar-v1-dash-link.less";

+ 4 - 0
services/web/public/stylesheets/app/base.less

@@ -104,3 +104,7 @@
   -ms-transform-origin: center bottom;
   transform-origin: center bottom;
 }
+
+.grecaptcha-badge {
+  display: none;
+}

+ 98 - 6
services/web/public/stylesheets/app/editor.less

@@ -14,6 +14,9 @@
 @import "./editor/review-panel.less";
 @import "./editor/feature-onboarding.less";
 
+@ui-layout-toggler-def-height: 50px;
+@ui-resizer-extra-hit-area: 8px;
+
 @keyframes blink {
 	0% { 
 		opacity: 0.2;
@@ -261,9 +264,9 @@
 	}
 }
 
-.ui-layout-resizer {
+.ui-layout-resizer when (@is-overleaf = false) {
 	width: 6px;
-	background-color: #f4f4f4;
+	background-color: @editor-resizer-bg-color;
 	border-left: 1px solid @editor-border-color;
 	border-right: 1px solid @editor-border-color;
 	.ui-layout-toggler {
@@ -276,28 +279,117 @@
 		-moz-osx-font-smoothing: grayscale;
 		font-size: 16px !important;
 		line-height: 50px;
+		background-color: @editor-toggler-bg-color;
 		&:hover {
-			background-color: #ddd;
+			background-color: @editor-toggler-hover-bg-color;
 			color: #333;
 		}
 	}
 }
-.ui-layout-resizer-west.ui-layout-resizer-open, .ui-layout-resizer-east.ui-layout-resizer-closed {
+
+.ui-layout-resizer when (@is-overleaf = true) {
+	margin-left: -(@ui-resizer-extra-hit-area) !important;
+	margin-right: -(@ui-resizer-extra-hit-area - 1px) !important;
+	padding-left: @ui-resizer-extra-hit-area !important;
+	padding-right: @ui-resizer-extra-hit-area !important;
+	z-index: 5 !important;
+	box-sizing: content-box;
+	background-image: linear-gradient(90deg, 
+		transparent,
+		transparent (@ui-resizer-extra-hit-area - 1px),
+		@editor-resizer-bg-color (@ui-resizer-extra-hit-area - 1px),
+		@editor-resizer-bg-color (@ui-resizer-extra-hit-area + 1px),
+		transparent (@ui-resizer-extra-hit-area + 1px),
+		transparent);
+
 	.ui-layout-toggler {
+		padding: 0 @ui-resizer-extra-hit-area !important;
+		background-image: linear-gradient(90deg, 
+			transparent,
+			transparent (@ui-resizer-extra-hit-area - 1px),
+			@editor-toggler-bg-color (@ui-resizer-extra-hit-area - 1px),
+			@editor-toggler-bg-color (@ui-resizer-extra-hit-area + 1px),
+			transparent (@ui-resizer-extra-hit-area + 1px),
+			transparent);
+
+		&:hover {
+			background-image: linear-gradient(90deg, 
+				transparent,
+				transparent (@ui-resizer-extra-hit-area - 2px),
+				@editor-toggler-hover-bg-color (@ui-resizer-extra-hit-area - 2px),
+				@editor-toggler-hover-bg-color (@ui-resizer-extra-hit-area + 2px),
+				transparent (@ui-resizer-extra-hit-area + 2px),
+				transparent);
+		}
+	}
+}
+
+.ui-layout-resizer-west.ui-layout-resizer-open, .ui-layout-resizer-east.ui-layout-resizer-closed {
+	.ui-layout-toggler when (@is-overleaf = false) {
 		&:before {
 			content: "\f104"
 		}
 	}
 }
 .ui-layout-resizer-east.ui-layout-resizer-open, .ui-layout-resizer-west.ui-layout-resizer-closed {
-	.ui-layout-toggler {
+	.ui-layout-toggler when (@is-overleaf = false) {
 		&:before {
 			content: "\f105"
 		}
 	}
 }
+
+.ui-layout-toggler.ui-layout-toggler-closed when (@is-overleaf = true) {
+	background-color: @editor-resizer-bg-color;
+	background-image: none;
+	line-height: @ui-layout-toggler-def-height;
+	
+	&::before {
+		content: "\22EE"; // Vertical ellipsis
+		display: block;
+		color: #FFF;
+		font-weight: 700;
+		font-size: @font-size-h2;
+		width: @ui-resizer-extra-hit-area / 2;
+	}
+	&:hover {
+		background-color: @editor-toggler-hover-bg-color;
+		background-image: none;
+	}
+	.ui-layout-resizer-west > & {
+		border-radius: 0 @border-radius-base @border-radius-base 0;
+		&::before {
+			margin-left: -2px;
+		}
+	}
+	.ui-layout-resizer-east > & {
+		border-radius: @border-radius-base 0 0 @border-radius-base;
+		&::before {
+			margin-left: (-1 - @ui-resizer-extra-hit-area);
+		}
+	}
+}
+
+.ui-layout-toggler-east when (@is-overleaf = true) {
+	&.ui-layout-toggler-open {
+		cursor: e-resize !important
+	}
+	&.ui-layout-toggler-closed {
+		cursor: w-resize !important
+	}
+}
+
+.ui-layout-toggler-west when (@is-overleaf = true) {
+	&.ui-layout-toggler-open {
+		cursor: w-resize !important
+	}
+	&.ui-layout-toggler-closed {
+		cursor: e-resize !important
+	}
+}
+
 .ui-layout-resizer-dragging {
-	background-color: #ddd;
+	background-color: @editor-resizer-bg-color-dragging;
 }
 
 .context-menu {

+ 59 - 4
services/web/public/stylesheets/app/editor/pdf.less

@@ -229,13 +229,68 @@
 }
 
 .synctex-controls {
+	position: absolute;
+	z-index: @synctex-controls-z-index;
+	padding: @synctex-controls-padding;
 	top: 68px;
-	padding: 0px 2px;
-	.btn-xs {
+}
+
+.synctex-controls when (@is-overleaf = true) {
+	margin-right: -11px;
+}
+	.synctex-control {
+		display: block;
+		margin-bottom: 3px;
+
+		> .synctex-control-icon {
+			display: inline-block;
+			font: normal normal normal 14px/1 FontAwesome;
+			font-size: inherit;
+			text-rendering: auto;
+			-webkit-font-smoothing: antialiased;
+			-moz-osx-font-smoothing: grayscale;
+		}
+	}
+
+	.synctex-control when (@is-overleaf = true) {
+		@ol-synctex-control-size: 24px;
+		display: flex;
+		align-items: center;
+		justify-content: center;
+		font-size: 1em;
+		width: @ol-synctex-control-size;
+		height: @ol-synctex-control-size;
+		border-radius: @ol-synctex-control-size / 2;
+		padding: 0 0 2px;
+		background-color: fade(@btn-default-bg, 80%);
+		transition: background 0.15s ease;
+		margin-bottom: @ol-synctex-control-size / 2;
+	}
+
+	.synctex-control when (@is-overleaf = false) {
 		line-height: 1.3;
-		padding: 0 2px 0;
+		padding: 0 2px;
+	}
+	
+	.synctex-control-goto-pdf > .synctex-control-icon when (@is-overleaf = true) {
+		text-indent: 1px; // "Optical" adjustment.
+		&::before {
+			content: "\f061";		
+		}
+	}
+	.synctex-control-goto-code > .synctex-control-icon when (@is-overleaf = true) {
+		text-indent: -1px; // "Optical" adjustment.
+		&::before {
+			content: "\f060";		
+		}
+	}
+	
+	.synctex-control-goto-pdf > .synctex-control-icon::before when (@is-overleaf = false) {
+		content: "\f178";		
+	}
+	.synctex-control-goto-code > .synctex-control-icon::before when (@is-overleaf = false) {
+		content: "\f177";		
 	}
-}
 
 .editor-dark {
 	.pdf-logs {

+ 0 - 1
services/web/public/stylesheets/app/project-list.less

@@ -57,7 +57,6 @@
 		} 
 
 		.project-list-sidebar when (@is-overleaf) {
-			height: 100%;
 			overflow-x: hidden;
 			overflow-y: auto;
 			-ms-overflow-style: -ms-autohiding-scrollbar;

+ 32 - 0
services/web/public/stylesheets/app/sidebar-v1-dash-link.less

@@ -0,0 +1,32 @@
+@v1-dash-link-height: 130px;
+
+.project-list-sidebar {
+	height: calc(~"100% -" @v1-dash-link-height);
+}
+
+.project-list-sidebar-v1-link {
+	position: absolute;
+	bottom: 0;
+	height: @v1-dash-link-height;
+	background-color: @v1-dash-link-bg;
+	text-align: center;
+	display: flex;
+	flex-direction: column;
+	justify-content: center;
+	color: white;
+}
+
+	.project-list-sidebar-v1-link a {
+		display: block;
+		margin-left: auto;
+		margin-right: auto;
+		margin-top: 12.5px;
+		padding-top: 0;
+		padding-bottom: 0;
+		background-color: @v1-dash-link-btn-bg;
+		color: #fff;
+	}
+
+	.project-list-sidebar-v1-link a:hover {
+		background-color: @v1-dash-link-btn-hover-bg;
+	}

+ 33 - 25
services/web/public/stylesheets/core/_common-variables.less

@@ -889,34 +889,42 @@
 @footer-padding                : 2em;
 
 // Editor header
-@toolbar-header-bg-color          : transparent;
-@toolbar-header-shadow            : 0 0 2px #ccc;
-@toolbar-btn-color                : @link-color;
-@toolbar-btn-hover-color          : @link-hover-color;
-@toolbar-btn-hover-bg-color       : darken(white, 10%);
-@toolbar-btn-hover-text-shadow    : 0 1px 0 rgba(0, 0, 0, 0.15);
-@toolbar-btn-active-color         : white;
-@toolbar-btn-active-bg-color      : @link-color;
-@toolbar-btn-active-shadow        : inset 0 3px 5px rgba(0, 0, 0, 0.225);
-@toolbar-alt-bg-color             : #fafafa;
-@toolbar-icon-btn-color           : @gray-light;
-@toolbar-icon-btn-hover-color     : @gray-dark;
-@toolbar-icon-btn-hover-shadow    : 0 1px 0 rgba(0, 0, 0, 0.25);
+@toolbar-header-bg-color        : transparent;
+@toolbar-header-shadow          : 0 0 2px #ccc;
+@toolbar-btn-color              : @link-color;
+@toolbar-btn-hover-color        : @link-hover-color;
+@toolbar-btn-hover-bg-color     : darken(white, 10%);
+@toolbar-btn-hover-text-shadow  : 0 1px 0 rgba(0, 0, 0, 0.15);
+@toolbar-btn-active-color       : white;
+@toolbar-btn-active-bg-color    : @link-color;
+@toolbar-btn-active-shadow      : inset 0 3px 5px rgba(0, 0, 0, 0.225);
+@toolbar-alt-bg-color           : #fafafa;
+@toolbar-icon-btn-color         : @gray-light;
+@toolbar-icon-btn-hover-color   : @gray-dark;
+@toolbar-icon-btn-hover-shadow  : 0 1px 0 rgba(0, 0, 0, 0.25);
 @toolbar-icon-btn-hover-boxshadow : inset 0 3px 5px rgba(0, 0, 0, 0.225);
-@toolbar-border-bottom            : 1px solid @toolbar-border-color;
+@toolbar-border-bottom          : 1px solid @toolbar-border-color;
 
 // Editor file-tree
-@file-tree-bg                    : transparent;
-@file-tree-line-height           : 2.6;
-@file-tree-item-color            : @gray-darker;
-@file-tree-item-toggle-color     : @gray;
-@file-tree-item-icon-color       : @gray-light;
-@file-tree-item-input-color      : inherit;
-@file-tree-item-folder-color     : lighten(desaturate(@link-color, 10%), 5%);
-@file-tree-item-hover-bg         : @gray-lightest;
-@file-tree-item-selected-bg      : transparent;
-@file-tree-multiselect-bg        : lighten(@brand-info, 40%);
-@file-tree-multiselect-hover-bg  : lighten(@brand-info, 30%);
+@file-tree-bg                   : transparent;
+@file-tree-line-height          : 2.6;
+@file-tree-item-color           : @gray-darker;
+@file-tree-item-toggle-color    : @gray;
+@file-tree-item-icon-color      : @gray-light;
+@file-tree-item-input-color     : inherit;
+@file-tree-item-folder-color    : lighten(desaturate(@link-color, 10%), 5%);
+@file-tree-item-hover-bg        : @gray-lightest;
+@file-tree-item-selected-bg     : transparent;
+@file-tree-multiselect-bg       : lighten(@brand-info, 40%);
+@file-tree-multiselect-hover-bg : lighten(@brand-info, 30%);
+
+// Editor resizers
+@editor-resizer-bg-color          : #F4F4F4;
+@editor-resizer-bg-color-dragging : #ddd;
+@editor-toggler-bg-color          : transparent;
+@editor-toggler-hover-bg-color    : #DDD;
+@synctex-controls-z-index         : 3;
+@synctex-controls-padding         : 0 2px;
 // Tags
 @tag-border-radius  : 0.25em;
 @tag-bg-color       : @label-default-bg;

+ 23 - 15
services/web/public/stylesheets/core/ol-variables.less

@@ -110,6 +110,9 @@
 @sidebar-active-bg              : @ol-blue-gray-6;
 @sidebar-hover-bg               : @ol-blue-gray-4;
 @sidebar-hover-text-decoration  : none;
+@v1-dash-link-bg                : @ol-blue-gray-4;
+@v1-dash-link-btn-bg            : @ol-blue-gray-5;
+@v1-dash-link-btn-hover-bg      : @ol-blue-gray-6;
 
 @folders-menu-margin            : 0 -(@grid-gutter-width / 2);
 @folders-menu-line-height       : @structured-list-line-height;
@@ -171,23 +174,28 @@
 @toolbar-icon-btn-color           : #FFF;
 @toolbar-icon-btn-hover-color     : #FFF;
 @toolbar-icon-btn-hover-shadow    : none;
-@toolbar-icon-btn-hover-boxshadow : none;
 @toolbar-border-bottom            : 1px solid @toolbar-border-color;
+@toolbar-icon-btn-hover-boxshadow : none;
 // Editor file-tree
-@file-tree-bg                   : @ol-blue-gray-4;
-@file-tree-line-height          : 2.05;
-@file-tree-item-color           : #FFF;
-@file-tree-item-input-color     : @ol-blue-gray-5;
-@file-tree-item-toggle-color    : @ol-blue-gray-2;
-@file-tree-item-icon-color      : @ol-blue-gray-2;
-@file-tree-item-folder-color    : @ol-blue-gray-2;
-@file-tree-item-hover-bg        : @ol-blue-gray-5;
-@file-tree-item-selected-bg     : @ol-green;
-@file-tree-multiselect-bg       : @ol-blue;
-@file-tree-multiselect-hover-bg : @ol-dark-blue;
-@file-tree-droppable-bg-color   : tint(@ol-green, 5%);
-//== Colors
-//
+@file-tree-bg                     : @ol-blue-gray-4;
+@file-tree-line-height            : 2.05;
+@file-tree-item-color             : #FFF;
+@file-tree-item-input-color       : @ol-blue-gray-5;
+@file-tree-item-toggle-color      : @ol-blue-gray-2;
+@file-tree-item-icon-color        : @ol-blue-gray-2;
+@file-tree-item-folder-color      : @ol-blue-gray-2;
+@file-tree-item-hover-bg          : @ol-blue-gray-5;
+@file-tree-item-selected-bg       : @ol-green;
+@file-tree-multiselect-bg         : @ol-blue;
+@file-tree-multiselect-hover-bg   : @ol-dark-blue;
+@file-tree-droppable-bg-color     : tint(@ol-green, 5%);
+// Editor resizers
+@editor-resizer-bg-color          : @ol-blue-gray-6;
+@editor-resizer-bg-color-dragging : transparent;
+@editor-toggler-bg-color          : @ol-blue-gray-2;
+@editor-toggler-hover-bg-color    : @ol-green;
+@synctex-controls-z-index         : 6;
+@synctex-controls-padding         : 0;
 //## Gray and brand colors for use across Bootstrap.
 @gray-darker:           #252525;
 @gray-dark:             #505050;

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

@@ -1,4 +1,5 @@
 // Core variables and mixins
 @import "core/ol-variables.less";
 @import "app/ol-style-guide.less";
-@import "_style_includes.less";
+@import "_style_includes.less";
+@import "_ol_style_includes.less";

+ 2 - 0
services/web/test/unit/coffee/Email/EmailSenderTests.coffee

@@ -31,6 +31,8 @@ describe "EmailSender", ->
 
 		@sender = SandboxedModule.require modulePath, requires:
 			'nodemailer': @ses
+			"nodemailer-mandrill-transport":{}
+			"nodemailer-sendgrid-transport":{}
 			"settings-sharelatex":@settings
 			'../../infrastructure/RateLimiter':@RateLimiter
 			"logger-sharelatex":

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov