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

This commit is contained in:
Alasdair Smith 2017-12-13 14:44:31 +00:00
commit 4a9c9c563a
29 changed files with 747 additions and 525 deletions

View file

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

View file

@ -11,6 +11,7 @@ NotificationsBuilder = require("../Notifications/NotificationsBuilder")
AnalyticsManger = require("../Analytics/AnalyticsManager") AnalyticsManger = require("../Analytics/AnalyticsManager")
AuthenticationController = require("../Authentication/AuthenticationController") AuthenticationController = require("../Authentication/AuthenticationController")
rateLimiter = require("../../infrastructure/RateLimiter") rateLimiter = require("../../infrastructure/RateLimiter")
request = require 'request'
module.exports = CollaboratorsInviteController = module.exports = CollaboratorsInviteController =

View file

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

View file

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

View file

@ -26,8 +26,16 @@ InvalidNameError = (message) ->
return error return error
InvalidNameError.prototype.__proto__ = Error.prototype 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 = module.exports = Errors =
NotFoundError: NotFoundError NotFoundError: NotFoundError
ServiceNotConfiguredError: ServiceNotConfiguredError ServiceNotConfiguredError: ServiceNotConfiguredError
TooManyRequestsError: TooManyRequestsError TooManyRequestsError: TooManyRequestsError
InvalidNameError: InvalidNameError InvalidNameError: InvalidNameError
UnsupportedFileTypeError: UnsupportedFileTypeError

View file

@ -292,3 +292,14 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)->
res.locals.moduleIncludes = Modules.moduleIncludes res.locals.moduleIncludes = Modules.moduleIncludes
res.locals.moduleIncludesAvailable = Modules.moduleIncludesAvailable res.locals.moduleIncludesAvailable = Modules.moduleIncludesAvailable
next() 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()

View file

@ -97,6 +97,15 @@ html(itemscope, itemtype='http://schema.org/Product')
} }
body 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") - if(typeof(suppressSystemMessages) == "undefined")
.system-messages( .system-messages(

View file

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

View file

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

View file

@ -63,6 +63,12 @@ block content
aside.project-list-sidebar.col-md-2.col-xs-3 aside.project-list-sidebar.col-md-2.col-xs-3
include ./list/side-bar 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 .project-list-main.col-md-10.col-xs-9
include ./list/notifications include ./list/notifications
include ./list/project-list include ./list/project-list

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -14,6 +14,9 @@
@import "./editor/review-panel.less"; @import "./editor/review-panel.less";
@import "./editor/feature-onboarding.less"; @import "./editor/feature-onboarding.less";
@ui-layout-toggler-def-height: 50px;
@ui-resizer-extra-hit-area: 8px;
@keyframes blink { @keyframes blink {
0% { 0% {
opacity: 0.2; opacity: 0.2;
@ -261,9 +264,9 @@
} }
} }
.ui-layout-resizer { .ui-layout-resizer when (@is-overleaf = false) {
width: 6px; width: 6px;
background-color: #f4f4f4; background-color: @editor-resizer-bg-color;
border-left: 1px solid @editor-border-color; border-left: 1px solid @editor-border-color;
border-right: 1px solid @editor-border-color; border-right: 1px solid @editor-border-color;
.ui-layout-toggler { .ui-layout-toggler {
@ -276,28 +279,117 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
font-size: 16px !important; font-size: 16px !important;
line-height: 50px; line-height: 50px;
background-color: @editor-toggler-bg-color;
&:hover { &:hover {
background-color: #ddd; background-color: @editor-toggler-hover-bg-color;
color: #333; 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 { .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 { &:before {
content: "\f104" content: "\f104"
} }
} }
} }
.ui-layout-resizer-east.ui-layout-resizer-open, .ui-layout-resizer-west.ui-layout-resizer-closed { .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 { &:before {
content: "\f105" 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 { .ui-layout-resizer-dragging {
background-color: #ddd; background-color: @editor-resizer-bg-color-dragging;
} }
.context-menu { .context-menu {

View file

@ -229,14 +229,69 @@
} }
.synctex-controls { .synctex-controls {
position: absolute;
z-index: @synctex-controls-z-index;
padding: @synctex-controls-padding;
top: 68px; top: 68px;
padding: 0px 2px;
.btn-xs {
line-height: 1.3;
padding: 0 2px 0;
}
} }
.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;
}
.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 { .editor-dark {
.pdf-logs { .pdf-logs {
background-color: lighten(@editor-dark-background-color, 10%); background-color: lighten(@editor-dark-background-color, 10%);

View file

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

View file

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

View file

@ -917,6 +917,14 @@
@file-tree-item-selected-bg : transparent; @file-tree-item-selected-bg : transparent;
@file-tree-multiselect-bg : lighten(@brand-info, 40%); @file-tree-multiselect-bg : lighten(@brand-info, 40%);
@file-tree-multiselect-hover-bg : lighten(@brand-info, 30%); @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 // Tags
@tag-border-radius : 0.25em; @tag-border-radius : 0.25em;
@tag-bg-color : @label-default-bg; @tag-bg-color : @label-default-bg;

View file

@ -110,6 +110,9 @@
@sidebar-active-bg : @ol-blue-gray-6; @sidebar-active-bg : @ol-blue-gray-6;
@sidebar-hover-bg : @ol-blue-gray-4; @sidebar-hover-bg : @ol-blue-gray-4;
@sidebar-hover-text-decoration : none; @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-margin : 0 -(@grid-gutter-width / 2);
@folders-menu-line-height : @structured-list-line-height; @folders-menu-line-height : @structured-list-line-height;
@ -171,8 +174,8 @@
@toolbar-icon-btn-color : #FFF; @toolbar-icon-btn-color : #FFF;
@toolbar-icon-btn-hover-color : #FFF; @toolbar-icon-btn-hover-color : #FFF;
@toolbar-icon-btn-hover-shadow : none; @toolbar-icon-btn-hover-shadow : none;
@toolbar-icon-btn-hover-boxshadow : none;
@toolbar-border-bottom : 1px solid @toolbar-border-color; @toolbar-border-bottom : 1px solid @toolbar-border-color;
@toolbar-icon-btn-hover-boxshadow : none;
// Editor file-tree // Editor file-tree
@file-tree-bg : @ol-blue-gray-4; @file-tree-bg : @ol-blue-gray-4;
@file-tree-line-height : 2.05; @file-tree-line-height : 2.05;
@ -186,8 +189,13 @@
@file-tree-multiselect-bg : @ol-blue; @file-tree-multiselect-bg : @ol-blue;
@file-tree-multiselect-hover-bg : @ol-dark-blue; @file-tree-multiselect-hover-bg : @ol-dark-blue;
@file-tree-droppable-bg-color : tint(@ol-green, 5%); @file-tree-droppable-bg-color : tint(@ol-green, 5%);
//== Colors // 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 and brand colors for use across Bootstrap.
@gray-darker: #252525; @gray-darker: #252525;
@gray-dark: #505050; @gray-dark: #505050;

View file

@ -2,3 +2,4 @@
@import "core/ol-variables.less"; @import "core/ol-variables.less";
@import "app/ol-style-guide.less"; @import "app/ol-style-guide.less";
@import "_style_includes.less"; @import "_style_includes.less";
@import "_ol_style_includes.less";

View file

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