mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge branch 'master' into pr-style-v2-pdf
This commit is contained in:
commit
d8153f9fdb
31 changed files with 679 additions and 694 deletions
|
@ -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()
|
|
@ -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 =
|
||||||
|
|
||||||
|
@ -32,7 +33,7 @@ module.exports = CollaboratorsInviteController =
|
||||||
callback(null, userExists)
|
callback(null, userExists)
|
||||||
else
|
else
|
||||||
callback(null, true)
|
callback(null, true)
|
||||||
|
|
||||||
_checkRateLimit: (user_id, callback = (error) ->) ->
|
_checkRateLimit: (user_id, callback = (error) ->) ->
|
||||||
LimitationsManager.allowedNumberOfCollaboratorsForUser user_id, (err, collabLimit = 1)->
|
LimitationsManager.allowedNumberOfCollaboratorsForUser user_id, (err, collabLimit = 1)->
|
||||||
return callback(err) if err?
|
return callback(err) if err?
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
EditorRealTimeController = require "../Editor/EditorRealTimeController"
|
|
||||||
LabelsHandler = require './LabelsHandler'
|
|
||||||
logger = require 'logger-sharelatex'
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = LabelsController =
|
|
||||||
|
|
||||||
getAllLabels: (req, res, next) ->
|
|
||||||
project_id = req.params.project_id
|
|
||||||
logger.log {project_id}, "getting all labels for project"
|
|
||||||
LabelsHandler.getAllLabelsForProject project_id, (err, projectLabels) ->
|
|
||||||
if err?
|
|
||||||
logger.err {project_id, err}, "[LabelsController] error getting all labels from project"
|
|
||||||
return next(err)
|
|
||||||
res.json {projectId: project_id, projectLabels: projectLabels}
|
|
||||||
|
|
||||||
broadcastLabelsForDoc: (req, res, next) ->
|
|
||||||
project_id = req.params.project_id
|
|
||||||
doc_id = req.params.doc_id
|
|
||||||
logger.log {project_id, doc_id}, "getting labels for doc"
|
|
||||||
LabelsHandler.getLabelsForDoc project_id, doc_id, (err, docLabels) ->
|
|
||||||
if err?
|
|
||||||
logger.err {project_id, doc_id, err}, "[LabelsController] error getting labels from doc"
|
|
||||||
return next(err)
|
|
||||||
EditorRealTimeController.emitToRoom project_id, 'broadcastDocLabels', {
|
|
||||||
docId: doc_id, labels: docLabels
|
|
||||||
}
|
|
||||||
res.sendStatus(200)
|
|
|
@ -1,43 +0,0 @@
|
||||||
ProjectEntityHandler = require "../Project/ProjectEntityHandler"
|
|
||||||
DocumentUpdaterHandler = require('../DocumentUpdater/DocumentUpdaterHandler')
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = LabelsHandler =
|
|
||||||
|
|
||||||
labelCaptureRegex: () ->
|
|
||||||
/\\label\{([^\}\n\\]{0,80})\}/g
|
|
||||||
|
|
||||||
getAllLabelsForProject: (projectId, callback=(err, projectLabels)->) ->
|
|
||||||
DocumentUpdaterHandler.flushProjectToMongo projectId, (err) ->
|
|
||||||
if err?
|
|
||||||
return callback(err)
|
|
||||||
ProjectEntityHandler.getAllDocs projectId, (err, docs) ->
|
|
||||||
if err?
|
|
||||||
return callback(err)
|
|
||||||
projectLabels = LabelsHandler.extractLabelsFromProjectDocs docs
|
|
||||||
callback(null, projectLabels)
|
|
||||||
|
|
||||||
getLabelsForDoc: (projectId, docId, callback=(err, docLabels)->) ->
|
|
||||||
DocumentUpdaterHandler.flushDocToMongo projectId, docId, (err) ->
|
|
||||||
if err?
|
|
||||||
return callback(err)
|
|
||||||
ProjectEntityHandler.getDoc projectId, docId, (err, lines) ->
|
|
||||||
if err?
|
|
||||||
return callback(err)
|
|
||||||
docLabels = LabelsHandler.extractLabelsFromDoc lines
|
|
||||||
callback(null, docLabels)
|
|
||||||
|
|
||||||
extractLabelsFromDoc: (lines) ->
|
|
||||||
docLabels = []
|
|
||||||
for line in lines
|
|
||||||
re = LabelsHandler.labelCaptureRegex()
|
|
||||||
while (labelMatch = re.exec(line))
|
|
||||||
if labelMatch[1]
|
|
||||||
docLabels.push(labelMatch[1])
|
|
||||||
return docLabels
|
|
||||||
|
|
||||||
extractLabelsFromProjectDocs: (projectDocs) ->
|
|
||||||
projectLabels = {} # docId => List[Label]
|
|
||||||
for _path, doc of projectDocs
|
|
||||||
projectLabels[doc._id] = LabelsHandler.extractLabelsFromDoc(doc.lines)
|
|
||||||
return projectLabels
|
|
|
@ -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()
|
||||||
|
|
|
@ -44,7 +44,6 @@ SudoModeMiddlewear = require('./Features/SudoMode/SudoModeMiddlewear')
|
||||||
AnalyticsRouter = require('./Features/Analytics/AnalyticsRouter')
|
AnalyticsRouter = require('./Features/Analytics/AnalyticsRouter')
|
||||||
AnnouncementsController = require("./Features/Announcements/AnnouncementsController")
|
AnnouncementsController = require("./Features/Announcements/AnnouncementsController")
|
||||||
MetaController = require('./Features/Metadata/MetaController')
|
MetaController = require('./Features/Metadata/MetaController')
|
||||||
LabelsController = require('./Features/Labels/LabelsController')
|
|
||||||
TokenAccessController = require('./Features/TokenAccess/TokenAccessController')
|
TokenAccessController = require('./Features/TokenAccess/TokenAccessController')
|
||||||
Features = require('./infrastructure/Features')
|
Features = require('./infrastructure/Features')
|
||||||
|
|
||||||
|
@ -206,8 +205,6 @@ module.exports = class Router
|
||||||
webRouter.get '/project/:project_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetaController.getMetadata
|
webRouter.get '/project/:project_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetaController.getMetadata
|
||||||
webRouter.post '/project/:project_id/doc/:doc_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetaController.broadcastMetadataForDoc
|
webRouter.post '/project/:project_id/doc/:doc_id/metadata', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), MetaController.broadcastMetadataForDoc
|
||||||
|
|
||||||
webRouter.get '/project/:project_id/labels', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), LabelsController.getAllLabels
|
|
||||||
webRouter.post '/project/:project_id/doc/:doc_id/labels', AuthorizationMiddlewear.ensureUserCanReadProject, AuthenticationController.requireLogin(), LabelsController.broadcastLabelsForDoc
|
|
||||||
|
|
||||||
webRouter.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
|
webRouter.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
|
||||||
webRouter.post '/tag', AuthenticationController.requireLogin(), TagsController.createTag
|
webRouter.post '/tag', AuthenticationController.requireLogin(), TagsController.createTag
|
||||||
|
@ -360,4 +357,3 @@ module.exports = class Router
|
||||||
TokenAccessController.readAndWriteToken
|
TokenAccessController.readAndWriteToken
|
||||||
|
|
||||||
webRouter.get '*', ErrorController.notFound
|
webRouter.get '*', ErrorController.notFound
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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}),
|
||||||
|
|
|
@ -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'"
|
||||||
|
|
807
services/web/npm-shrinkwrap.json
generated
807
services/web/npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load diff
|
@ -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",
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -10,13 +10,11 @@ define [
|
||||||
"ide/editor/directives/aceEditor/cursor-position/CursorPositionManager"
|
"ide/editor/directives/aceEditor/cursor-position/CursorPositionManager"
|
||||||
"ide/editor/directives/aceEditor/track-changes/TrackChangesManager"
|
"ide/editor/directives/aceEditor/track-changes/TrackChangesManager"
|
||||||
"ide/editor/directives/aceEditor/metadata/MetadataManager"
|
"ide/editor/directives/aceEditor/metadata/MetadataManager"
|
||||||
"ide/editor/directives/aceEditor/labels/LabelsManager"
|
|
||||||
"ide/labels/services/labels"
|
|
||||||
"ide/metadata/services/metadata"
|
"ide/metadata/services/metadata"
|
||||||
"ide/graphics/services/graphics"
|
"ide/graphics/services/graphics"
|
||||||
"ide/preamble/services/preamble"
|
"ide/preamble/services/preamble"
|
||||||
"ide/files/services/files"
|
"ide/files/services/files"
|
||||||
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager, LabelsManager) ->
|
], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager) ->
|
||||||
EditSession = ace.require('ace/edit_session').EditSession
|
EditSession = ace.require('ace/edit_session').EditSession
|
||||||
ModeList = ace.require('ace/ext/modelist')
|
ModeList = ace.require('ace/ext/modelist')
|
||||||
|
|
||||||
|
@ -38,7 +36,7 @@ define [
|
||||||
url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}"
|
url = ace.config._moduleUrl(args...) + "?fingerprint=#{window.aceFingerprint}"
|
||||||
return url
|
return url
|
||||||
|
|
||||||
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, labels, metadata, graphics, preamble, files, $http, $q) ->
|
App.directive "aceEditor", ($timeout, $compile, $rootScope, event_tracking, localStorage, $cacheFactory, metadata, graphics, preamble, files, $http, $q) ->
|
||||||
monkeyPatchSearch($rootScope, $compile)
|
monkeyPatchSearch($rootScope, $compile)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
@ -105,9 +103,8 @@ define [
|
||||||
highlightsManager = new HighlightsManager(scope, editor, element)
|
highlightsManager = new HighlightsManager(scope, editor, element)
|
||||||
cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage)
|
cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage)
|
||||||
trackChangesManager = new TrackChangesManager(scope, editor, element)
|
trackChangesManager = new TrackChangesManager(scope, editor, element)
|
||||||
labelsManager = new LabelsManager(scope, editor, element, labels)
|
|
||||||
metadataManager = new MetadataManager(scope, editor, element, metadata)
|
metadataManager = new MetadataManager(scope, editor, element, metadata)
|
||||||
autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, labelsManager, graphics, preamble, files)
|
autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, graphics, preamble, files)
|
||||||
|
|
||||||
|
|
||||||
# Prevert Ctrl|Cmd-S from triggering save dialog
|
# Prevert Ctrl|Cmd-S from triggering save dialog
|
||||||
|
|
|
@ -10,7 +10,7 @@ define [
|
||||||
aceSnippetManager = ace.require('ace/snippets').snippetManager
|
aceSnippetManager = ace.require('ace/snippets').snippetManager
|
||||||
|
|
||||||
class AutoCompleteManager
|
class AutoCompleteManager
|
||||||
constructor: (@$scope, @editor, @element, @metadataManager, @labelsManager, @graphics, @preamble, @files) ->
|
constructor: (@$scope, @editor, @element, @metadataManager, @graphics, @preamble, @files) ->
|
||||||
|
|
||||||
@monkeyPatchAutocomplete()
|
@monkeyPatchAutocomplete()
|
||||||
|
|
||||||
|
|
|
@ -1,58 +0,0 @@
|
||||||
define [
|
|
||||||
"ace/ace"
|
|
||||||
], () ->
|
|
||||||
Range = ace.require("ace/range").Range
|
|
||||||
|
|
||||||
getLastCommandFragment = (lineUpToCursor) ->
|
|
||||||
if m = lineUpToCursor.match(/(\\[^\\]+)$/)
|
|
||||||
return m[1]
|
|
||||||
else
|
|
||||||
return null
|
|
||||||
|
|
||||||
class LabelsManager
|
|
||||||
constructor: (@$scope, @editor, @element, @Labels) ->
|
|
||||||
@debouncer = {} # DocId => Timeout
|
|
||||||
|
|
||||||
onChange = (change) =>
|
|
||||||
if change.remote
|
|
||||||
return
|
|
||||||
if change.action not in ['remove', 'insert']
|
|
||||||
return
|
|
||||||
cursorPosition = @editor.getCursorPosition()
|
|
||||||
end = change.end
|
|
||||||
range = new Range(end.row, 0, end.row, end.column)
|
|
||||||
lineUpToCursor = @editor.getSession().getTextRange(range)
|
|
||||||
commandFragment = getLastCommandFragment(lineUpToCursor)
|
|
||||||
linesContainLabel = _.any(change.lines, (line) -> line.match(/\\label\{[^\}\n\\]{0,80}\}/))
|
|
||||||
lastCommandFragmentIsLabel = commandFragment?.slice(0,7) == '\\label{'
|
|
||||||
if linesContainLabel or lastCommandFragmentIsLabel
|
|
||||||
@scheduleLoadCurrentDocLabelsFromServer()
|
|
||||||
|
|
||||||
@editor.on "changeSession", (e) =>
|
|
||||||
e.oldSession.off "change", onChange
|
|
||||||
e.session.on "change", onChange
|
|
||||||
|
|
||||||
loadCurrentDocLabelsFromServer: () ->
|
|
||||||
currentDocId = @$scope.docId
|
|
||||||
@Labels.loadDocLabelsFromServer(currentDocId)
|
|
||||||
|
|
||||||
loadDocLabelsFromServer: (docId) ->
|
|
||||||
@Labels.loadDocLabelsFromServer(docId)
|
|
||||||
|
|
||||||
scheduleLoadCurrentDocLabelsFromServer: () ->
|
|
||||||
# De-bounce loading labels with a timeout
|
|
||||||
currentDocId = @$scope.docId
|
|
||||||
existingTimeout = @debouncer[currentDocId]
|
|
||||||
if existingTimeout?
|
|
||||||
clearTimeout(existingTimeout)
|
|
||||||
delete @debouncer[currentDocId]
|
|
||||||
@debouncer[currentDocId] = setTimeout(
|
|
||||||
() =>
|
|
||||||
@loadDocLabelsFromServer(currentDocId)
|
|
||||||
delete @debouncer[currentDocId]
|
|
||||||
, 1000
|
|
||||||
, this
|
|
||||||
)
|
|
||||||
|
|
||||||
getAllLabels: () ->
|
|
||||||
@Labels.getAllLabels()
|
|
|
@ -1,13 +0,0 @@
|
||||||
define [], () ->
|
|
||||||
|
|
||||||
class LabelsManager
|
|
||||||
|
|
||||||
constructor: (@ide, @$scope, @labels) ->
|
|
||||||
|
|
||||||
@ide.socket.on 'broadcastDocLabels', (data) =>
|
|
||||||
@labels.onBroadcastDocLabels(data)
|
|
||||||
@$scope.$on 'entity:deleted', @labels.onEntityDeleted
|
|
||||||
@$scope.$on 'file:upload:complete', @labels.fileUploadComplete
|
|
||||||
|
|
||||||
loadProjectLabelsFromServer: () ->
|
|
||||||
@labels.loadProjectLabelsFromServer()
|
|
|
@ -1,44 +0,0 @@
|
||||||
define [
|
|
||||||
"base"
|
|
||||||
], (App) ->
|
|
||||||
|
|
||||||
App.factory 'labels', ($http, ide) ->
|
|
||||||
|
|
||||||
state = {documents: {}}
|
|
||||||
|
|
||||||
labels = {
|
|
||||||
state: state
|
|
||||||
}
|
|
||||||
|
|
||||||
labels.onBroadcastDocLabels = (data) ->
|
|
||||||
if data.docId and data.labels
|
|
||||||
state.documents[data.docId] = data.labels
|
|
||||||
|
|
||||||
labels.onEntityDeleted = (e, entity) ->
|
|
||||||
if entity.type == 'doc'
|
|
||||||
delete state.documents[entity.id]
|
|
||||||
|
|
||||||
labels.onFileUploadComplete = (e, upload) ->
|
|
||||||
if upload.entity_type == 'doc'
|
|
||||||
labels.loadDocLabelsFromServer(upload.entity_id)
|
|
||||||
|
|
||||||
labels.getAllLabels = () ->
|
|
||||||
_.flatten(labels for docId, labels of state.documents)
|
|
||||||
|
|
||||||
labels.loadProjectLabelsFromServer = () ->
|
|
||||||
$http
|
|
||||||
.get("/project/#{window.project_id}/labels")
|
|
||||||
.then (response) ->
|
|
||||||
{ data } = response
|
|
||||||
if data.projectLabels
|
|
||||||
for docId, docLabels of data.projectLabels
|
|
||||||
state.documents[docId] = docLabels
|
|
||||||
|
|
||||||
labels.loadDocLabelsFromServer = (docId) ->
|
|
||||||
$http
|
|
||||||
.post(
|
|
||||||
"/project/#{window.project_id}/doc/#{docId}/labels",
|
|
||||||
{_csrf: window.csrfToken}
|
|
||||||
)
|
|
||||||
|
|
||||||
return labels
|
|
|
@ -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,7 +135,9 @@ define [
|
||||||
else
|
else
|
||||||
$scope.state.errorReason = null
|
$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.removeMember = (member) ->
|
||||||
$scope.state.error = null
|
$scope.state.error = null
|
||||||
|
@ -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"
|
||||||
|
@ -244,4 +248,4 @@ define [
|
||||||
|
|
||||||
$scope.cancel = () ->
|
$scope.cancel = () ->
|
||||||
$modalInstance.dismiss()
|
$modalInstance.dismiss()
|
||||||
]
|
]
|
|
@ -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) ->
|
||||||
|
|
|
@ -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__"
|
||||||
], () ->
|
], () ->
|
||||||
|
|
24
services/web/public/coffee/services/validateCaptcha.coffee
Normal file
24
services/web/public/coffee/services/validateCaptcha.coffee
Normal 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
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -233,14 +233,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%);
|
||||||
|
|
|
@ -889,19 +889,19 @@
|
||||||
@footer-padding : 2em;
|
@footer-padding : 2em;
|
||||||
|
|
||||||
// Editor header
|
// Editor header
|
||||||
@toolbar-header-bg-color : transparent;
|
@toolbar-header-bg-color : transparent;
|
||||||
@toolbar-header-shadow : 0 0 2px #ccc;
|
@toolbar-header-shadow : 0 0 2px #ccc;
|
||||||
@toolbar-btn-color : @link-color;
|
@toolbar-btn-color : @link-color;
|
||||||
@toolbar-btn-hover-color : @link-hover-color;
|
@toolbar-btn-hover-color : @link-hover-color;
|
||||||
@toolbar-btn-hover-bg-color : darken(white, 10%);
|
@toolbar-btn-hover-bg-color : darken(white, 10%);
|
||||||
@toolbar-btn-hover-text-shadow : 0 1px 0 rgba(0, 0, 0, 0.15);
|
@toolbar-btn-hover-text-shadow : 0 1px 0 rgba(0, 0, 0, 0.15);
|
||||||
@toolbar-btn-active-color : white;
|
@toolbar-btn-active-color : white;
|
||||||
@toolbar-btn-active-bg-color : @link-color;
|
@toolbar-btn-active-bg-color : @link-color;
|
||||||
@toolbar-btn-active-shadow : inset 0 3px 5px rgba(0, 0, 0, 0.225);
|
@toolbar-btn-active-shadow : inset 0 3px 5px rgba(0, 0, 0, 0.225);
|
||||||
@toolbar-alt-bg-color : #fafafa;
|
@toolbar-alt-bg-color : #fafafa;
|
||||||
@toolbar-icon-btn-color : @gray-light;
|
@toolbar-icon-btn-color : @gray-light;
|
||||||
@toolbar-icon-btn-hover-color : @gray-dark;
|
@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-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-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;
|
||||||
@toolbar-small-height : 32px;
|
@toolbar-small-height : 32px;
|
||||||
|
@ -920,6 +920,14 @@
|
||||||
@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;
|
||||||
|
|
||||||
// PDF
|
// PDF
|
||||||
@pdf-top-offset : @toolbar-tall-height;
|
@pdf-top-offset : @toolbar-tall-height;
|
||||||
@pdf-bg : transparent;
|
@pdf-bg : transparent;
|
||||||
|
@ -927,6 +935,7 @@
|
||||||
@pdf-page-shadow-color : #000;
|
@pdf-page-shadow-color : #000;
|
||||||
@log-line-no-color : @gray;
|
@log-line-no-color : @gray;
|
||||||
@log-hints-color : @gray-dark;
|
@log-hints-color : @gray-dark;
|
||||||
|
|
||||||
// Tags
|
// Tags
|
||||||
@tag-border-radius : 0.25em;
|
@tag-border-radius : 0.25em;
|
||||||
@tag-bg-color : @label-default-bg;
|
@tag-bg-color : @label-default-bg;
|
||||||
|
|
|
@ -209,8 +209,9 @@
|
||||||
@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;
|
||||||
|
@ -225,6 +226,14 @@
|
||||||
@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%);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
// PDF
|
// PDF
|
||||||
@pdf-top-offset : @toolbar-small-height;
|
@pdf-top-offset : @toolbar-small-height;
|
||||||
@pdf-bg : @ol-blue-gray-1;
|
@pdf-bg : @ol-blue-gray-1;
|
||||||
|
@ -232,6 +241,7 @@
|
||||||
@pdf-page-shadow-color : rgba(0, 0, 0, 0.5);
|
@pdf-page-shadow-color : rgba(0, 0, 0, 0.5);
|
||||||
@log-line-no-color : #FFF;
|
@log-line-no-color : #FFF;
|
||||||
@log-hints-color : @ol-blue-gray-4;
|
@log-hints-color : @ol-blue-gray-4;
|
||||||
|
|
||||||
//== Colors
|
//== Colors
|
||||||
//
|
//
|
||||||
//## Gray and brand colors for use across Bootstrap.
|
//## Gray and brand colors for use across Bootstrap.
|
||||||
|
|
|
@ -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":
|
||||||
|
|
Loading…
Reference in a new issue