mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge branch 'master' into i18n
This commit is contained in:
commit
aa4f748608
28 changed files with 406 additions and 104 deletions
|
@ -18,12 +18,14 @@ module.exports = DocumentUpdaterHandler =
|
|||
queueChange : (project_id, doc_id, change, sl_req_id, callback = ()->)->
|
||||
{callback, sl_req_id} = slReqIdHelper.getCallbackAndReqId(callback, sl_req_id)
|
||||
jsonChange = JSON.stringify change
|
||||
rclient.rpush keys.pendingUpdates(doc_id:doc_id), jsonChange, (error)->
|
||||
doc_key = keys.combineProjectIdAndDocId(project_id, doc_id)
|
||||
multi = rclient.multi()
|
||||
multi.rpush keys.pendingUpdates(doc_id:doc_id), jsonChange
|
||||
multi.sadd keys.docsWithPendingUpdates, doc_key
|
||||
multi.rpush "pending-updates-list", doc_key
|
||||
multi.exec (error) ->
|
||||
return callback(error) if error?
|
||||
doc_key = keys.combineProjectIdAndDocId(project_id, doc_id)
|
||||
rclient.sadd keys.docsWithPendingUpdates, doc_key, (error) ->
|
||||
return callback(error) if error?
|
||||
rclient.publish "pending-updates", doc_key, callback
|
||||
callback()
|
||||
|
||||
flushProjectToMongo: (project_id, sl_req_id, callback = (error) ->)->
|
||||
{callback, sl_req_id} = slReqIdHelper.getCallbackAndReqId(callback, sl_req_id)
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
DropboxUserController = require './DropboxUserController'
|
||||
DropboxWebhookController = require './DropboxWebhookController'
|
||||
|
||||
module.exports =
|
||||
apply: (app) ->
|
||||
app.get '/dropbox/beginAuth', DropboxUserController.redirectUserToDropboxAuth
|
||||
app.get '/dropbox/completeRegistration', DropboxUserController.completeDropboxRegistration
|
||||
app.get '/dropbox/unlink', DropboxUserController.unlinkDropbox
|
||||
|
||||
app.get '/dropbox/webhook', DropboxWebhookController.verify
|
||||
app.post '/dropbox/webhook', DropboxWebhookController.webhook
|
||||
app.ignoreCsrf('post', '/dropbox/webhook')
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
logger = require("logger-sharelatex")
|
||||
DropboxWebhookHandler = require("./DropboxWebhookHandler")
|
||||
|
||||
module.exports = DropboxWebhookController =
|
||||
verify: (req, res, next = (error) ->) ->
|
||||
res.send(req.query.challenge)
|
||||
|
||||
webhook: (req, res, next = (error) ->) ->
|
||||
dropbox_uids = req.body?.delta?.users
|
||||
logger.log dropbox_uids: dropbox_uids, "received webhook request from Dropbox"
|
||||
if !dropbox_uids?
|
||||
return res.send(400) # Bad Request
|
||||
|
||||
# Do this in the background so as not to keep Dropbox waiting
|
||||
DropboxWebhookHandler.pollDropboxUids dropbox_uids, (error) ->
|
||||
if error?
|
||||
logger.error err: error, dropbox_uids: dropbox_uids, "error in webhook"
|
||||
|
||||
res.send(200)
|
|
@ -0,0 +1,49 @@
|
|||
logger = require("logger-sharelatex")
|
||||
settings = require("settings-sharelatex")
|
||||
async = require "async"
|
||||
User = require("../../models/User").User
|
||||
TpdsUpdateSender = require "../ThirdPartyDataStore/TpdsUpdateSender"
|
||||
|
||||
redis = require('redis')
|
||||
rclient = redis.createClient(settings.redis.web.port, settings.redis.web.host)
|
||||
rclient.auth(settings.redis.web.password)
|
||||
|
||||
module.exports = DropboxWebhookHandler =
|
||||
pollDropboxUids: (dropbox_uids, callback = (error) ->) ->
|
||||
jobs = []
|
||||
for uid in dropbox_uids
|
||||
do (uid) ->
|
||||
jobs.push (callback) ->
|
||||
DropboxWebhookHandler.pollDropboxUid uid, callback
|
||||
async.series jobs, callback
|
||||
|
||||
pollDropboxUid: (dropbox_uid, callback = (error) ->) ->
|
||||
DropboxWebhookHandler._delayAndBatchPoll dropbox_uid, (error, shouldPoll) ->
|
||||
return callback(error) if error?
|
||||
return callback() if !shouldPoll
|
||||
User.find {
|
||||
"dropbox.access_token.uid": dropbox_uid.toString()
|
||||
"features.dropbox": true
|
||||
}, (error, users = []) ->
|
||||
return callback(error) if error?
|
||||
user = users[0]
|
||||
if !user?
|
||||
logger.log dropbox_uid: dropbox_uid, "no sharelatex user found"
|
||||
return callback()
|
||||
TpdsUpdateSender.pollDropboxForUser user._id, callback
|
||||
|
||||
POLL_DELAY_IN_MS: 5000 # 5 seconds
|
||||
_delayAndBatchPoll: (dropbox_uid, callback = (error, shouldPoll) ->) ->
|
||||
rclient.set(
|
||||
"dropbox-poll-lock:#{dropbox_uid}", "LOCK",
|
||||
"PX", DropboxWebhookHandler.POLL_DELAY_IN_MS,
|
||||
"NX",
|
||||
(error, gotLock) ->
|
||||
return callback(error) if error?
|
||||
if gotLock
|
||||
setTimeout () ->
|
||||
callback(null, true)
|
||||
, DropboxWebhookHandler.POLL_DELAY_IN_MS
|
||||
else
|
||||
callback(null, false)
|
||||
)
|
|
@ -12,7 +12,7 @@ rclient.auth(Settings.redis.web.password)
|
|||
RecurlyWrapper = require('../Subscription/RecurlyWrapper')
|
||||
SubscriptionHandler = require('../Subscription/SubscriptionHandler')
|
||||
projectEntityHandler = require('../Project/ProjectEntityHandler')
|
||||
TpdsPollingBackgroundTasks = require("../ThirdPartyDataStore/TpdsPollingBackgroundTasks")
|
||||
TpdsUpdateSender = require("../ThirdPartyDataStore/TpdsUpdateSender")
|
||||
EditorRealTimeController = require("../Editor/EditorRealTimeController")
|
||||
SystemMessageManager = require("../SystemMessages/SystemMessageManager")
|
||||
|
||||
|
@ -74,8 +74,9 @@ module.exports = AdminController =
|
|||
projectEntityHandler.flushProjectToThirdPartyDataStore req.body.project_id, (err)->
|
||||
res.send 200
|
||||
|
||||
pollUsersWithDropbox: (req, res)->
|
||||
TpdsPollingBackgroundTasks.pollUsersWithDropbox ->
|
||||
pollDropboxForUser: (req, res)->
|
||||
user_id = req.body.user_id
|
||||
TpdsUpdateSender.pollDropboxForUser user_id, () ->
|
||||
res.send 200
|
||||
|
||||
createMessage: (req, res, next) ->
|
||||
|
|
|
@ -36,7 +36,17 @@ module.exports = TemplatesWebController =
|
|||
|
||||
proxyToTemplatesApi: (req, res)->
|
||||
url = req.url
|
||||
logger.log url:url, "proxying request to templates api"
|
||||
|
||||
name = req.query.name or "Template"
|
||||
if req.query.inline?
|
||||
disposition = "inline"
|
||||
else
|
||||
disposition = "attachment"
|
||||
console.log "HEADER", "#{disposition}; filename=#{name};"
|
||||
res.header({"content-disposition": "#{disposition}; filename=#{name}.#{req.params.file_type};"})
|
||||
|
||||
logger.log url:url, template_name: name, disposition: disposition, "proxying request to templates api"
|
||||
|
||||
getReq = request.get("#{settings.apis.templates_api.url}#{url}")
|
||||
getReq.pipe(res)
|
||||
getReq.on "error", (error) ->
|
||||
|
|
|
@ -87,7 +87,16 @@ module.exports =
|
|||
title:"deleteEntity"
|
||||
sl_all_user_ids:JSON.stringify(allUserIds)
|
||||
queue.enqueue options.project_id, "standardHttpRequest", deleteOptions, callback
|
||||
|
||||
|
||||
pollDropboxForUser: (user_id, callback = (err) ->) ->
|
||||
metrics.inc("tpds.poll-dropbox")
|
||||
logger.log user_id: user_id, "polling dropbox for user"
|
||||
options =
|
||||
method: "POST"
|
||||
uri:"#{settings.apis.thirdPartyDataStore.url}/user/poll"
|
||||
json:
|
||||
user_ids: [user_id]
|
||||
queue.enqueue "poll-dropbox:#{user_id}", "standardHttpRequest", options, callback
|
||||
|
||||
getProjectsUsersIds = (project_id, callback = (err, owner_id, allUserIds)->)->
|
||||
Project.findById project_id, "_id owner_ref readOnly_refs collaberator_refs", (err, project)->
|
||||
|
|
|
@ -10,7 +10,6 @@ EditorHttpController = require("./Features/Editor/EditorHttpController")
|
|||
EditorUpdatesController = require("./Features/Editor/EditorUpdatesController")
|
||||
Settings = require('settings-sharelatex')
|
||||
TpdsController = require('./Features/ThirdPartyDataStore/TpdsController')
|
||||
dropboxHandler = require('./Features/Dropbox/DropboxHandler')
|
||||
SubscriptionRouter = require './Features/Subscription/SubscriptionRouter'
|
||||
UploadsRouter = require './Features/Uploads/UploadsRouter'
|
||||
metrics = require('./infrastructure/Metrics')
|
||||
|
@ -32,13 +31,14 @@ HealthCheckController = require("./Features/HealthCheck/HealthCheckController")
|
|||
ProjectDownloadsController = require "./Features/Downloads/ProjectDownloadsController"
|
||||
FileStoreController = require("./Features/FileStore/FileStoreController")
|
||||
TrackChangesController = require("./Features/TrackChanges/TrackChangesController")
|
||||
DropboxUserController = require("./Features/Dropbox/DropboxUserController")
|
||||
PasswordResetRouter = require("./Features/PasswordReset/PasswordResetRouter")
|
||||
StaticPagesRouter = require("./Features/StaticPages/StaticPagesRouter")
|
||||
ChatController = require("./Features/Chat/ChatController")
|
||||
BlogController = require("./Features/Blog/BlogController")
|
||||
WikiController = require("./Features/Wiki/WikiController")
|
||||
ConnectedUsersController = require("./Features/ConnectedUsers/ConnectedUsersController")
|
||||
DropboxRouter = require "./Features/Dropbox/DropboxRouter"
|
||||
dropboxHandler = require "./Features/Dropbox/DropboxHandler"
|
||||
|
||||
logger = require("logger-sharelatex")
|
||||
_ = require("underscore")
|
||||
|
@ -66,6 +66,7 @@ module.exports = class Router
|
|||
PasswordResetRouter.apply(app)
|
||||
StaticPagesRouter.apply(app)
|
||||
TemplatesRouter.apply(app)
|
||||
DropboxRouter.apply(app)
|
||||
|
||||
app.get '/blog', BlogController.getIndexPage
|
||||
app.get '/blog/*', BlogController.getPage
|
||||
|
@ -80,10 +81,6 @@ module.exports = class Router
|
|||
app.del '/user/newsletter/unsubscribe', AuthenticationController.requireLogin(), UserController.unsubscribe
|
||||
app.del '/user', AuthenticationController.requireLogin(), UserController.deleteUser
|
||||
|
||||
app.get '/dropbox/beginAuth', DropboxUserController.redirectUserToDropboxAuth
|
||||
app.get '/dropbox/completeRegistration', DropboxUserController.completeDropboxRegistration
|
||||
app.get '/dropbox/unlink', DropboxUserController.unlinkDropbox
|
||||
|
||||
app.get '/user/auth_token', AuthenticationController.requireLogin(), AuthenticationController.getAuthToken
|
||||
app.get '/user/personal_info', AuthenticationController.requireLogin(allow_auth_token: true), UserInfoController.getLoggedInUsersPersonalInfo
|
||||
app.get '/user/:user_id/personal_info', httpAuth, UserInfoController.getPersonalInfo
|
||||
|
@ -172,7 +169,7 @@ module.exports = class Router
|
|||
app.post '/admin/dissconectAllUsers', SecurityManager.requestIsAdmin, AdminController.dissconectAllUsers
|
||||
app.post '/admin/syncUserToSubscription', SecurityManager.requestIsAdmin, AdminController.syncUserToSubscription
|
||||
app.post '/admin/flushProjectToTpds', SecurityManager.requestIsAdmin, AdminController.flushProjectToTpds
|
||||
app.post '/admin/pollUsersWithDropbox', SecurityManager.requestIsAdmin, AdminController.pollUsersWithDropbox
|
||||
app.post '/admin/pollDropboxForUser', SecurityManager.requestIsAdmin, AdminController.pollDropboxForUser
|
||||
app.post '/admin/messages', SecurityManager.requestIsAdmin, AdminController.createMessage
|
||||
app.post '/admin/messages/clear', SecurityManager.requestIsAdmin, AdminController.clearMessages
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ block content
|
|||
.form-group
|
||||
button.btn-primary.btn(type='submit') Link
|
||||
|
||||
tab(heading="TPDS Management")
|
||||
tab(heading="TPDS/Dropbox Management")
|
||||
h3 Flush project to TPDS
|
||||
.row
|
||||
form.col-xs-6(enctype='multipart/form-data', method='post',action='/admin/flushProjectToTpds')
|
||||
|
@ -56,10 +56,15 @@ block content
|
|||
.form-group
|
||||
button.btn-primary.btn(type='submit') Flush
|
||||
hr
|
||||
.row-spaced
|
||||
form(enctype='multipart/form-data', method='post',action='/admin/pollUsersWithDropbox')
|
||||
h3 Poll Dropbox for user
|
||||
.row
|
||||
form.col-xs-6(enctype='multipart/form-data', method='post',action='/admin/pollDropboxForUser')
|
||||
input(name="_csrf", type="hidden", value=csrfToken)
|
||||
button.btn.btn-primary(type="submit") Poll users with dropbox
|
||||
.form-group
|
||||
label(for='user_id') user_id
|
||||
input.form-control(type='text', name='user_id', placeholder='user_id', required)
|
||||
.form-group
|
||||
button.btn-primary.btn(type='submit') Poll
|
||||
|
||||
tab(heading="System Messages")
|
||||
each message in systemMessages
|
||||
|
|
|
@ -20,10 +20,6 @@ script(type="text/ng-template", id="dropboxModalTemplate")
|
|||
|
||||
|
||||
div(ng-show="dbState.hasDropboxFeature && dbState.userIsLinkedToDropbox")
|
||||
progressbar.progress-striped.active(value='dbState.percentageLeftTillNextPoll', type="info")
|
||||
p
|
||||
strong {{dbState.minsTillNextPoll}} #{translate("minutes")}
|
||||
span #{translate("until_db_checked_for_changes")}
|
||||
p.small
|
||||
| #{translate("this_project_will_appear_in_your_dropbox_folder_at")}
|
||||
strong Dropbox/sharelatex/{{ project.name }}
|
||||
|
|
|
@ -46,14 +46,15 @@ aside#file-tree(ng-controller="FileTreeController").full-size
|
|||
ng-class="{ 'no-toolbar': !permissions.write }"
|
||||
)
|
||||
|
||||
div(ng-show="ui.pdfLayout == 'flat' && (ui.view == 'editor' || ui.view == 'pdf')")
|
||||
div(ng-show="ui.pdfLayout == 'flat' && (ui.view == 'editor' || ui.view == 'pdf' || ui.view == 'file')")
|
||||
ul.list-unstyled.file-tree-list
|
||||
li(
|
||||
ng-class="{ 'selected': ui.view == 'pdf' }"
|
||||
ng-controller="PdfViewToggleController"
|
||||
)
|
||||
.entity
|
||||
.entity-name(
|
||||
ng-click="ui.view = 'pdf'"
|
||||
ng-click="togglePdfView()"
|
||||
)
|
||||
i.fa.fa-fw.toggle
|
||||
i.fa.fa-fw.fa-file-pdf-o
|
||||
|
@ -115,14 +116,15 @@ script(type='text/ng-template', id='entityListItemTemplate')
|
|||
span(
|
||||
ng-hide="entity.renaming"
|
||||
) {{ entity.name }}
|
||||
input(
|
||||
ng-if="permissions.write",
|
||||
ng-show="entity.renaming",
|
||||
ng-model="inputs.name",
|
||||
ng-blur="finishRenaming()",
|
||||
select-name-when="entity.renaming",
|
||||
on-enter="finishRenaming()"
|
||||
)
|
||||
span.rename-input
|
||||
input(
|
||||
ng-if="permissions.write",
|
||||
ng-show="entity.renaming",
|
||||
ng-model="inputs.name",
|
||||
ng-blur="finishRenaming()",
|
||||
select-name-when="entity.renaming",
|
||||
on-enter="finishRenaming()"
|
||||
)
|
||||
|
||||
span.dropdown(
|
||||
ng-show="entity.selected",
|
||||
|
@ -197,14 +199,15 @@ script(type='text/ng-template', id='entityListItemTemplate')
|
|||
span(
|
||||
ng-hide="entity.renaming"
|
||||
) {{ entity.name }}
|
||||
input(
|
||||
ng-if="permissions.write",
|
||||
ng-show="entity.renaming",
|
||||
ng-model="inputs.name",
|
||||
ng-blur="finishRenaming()",
|
||||
select-name-when="entity.renaming",
|
||||
on-enter="finishRenaming()"
|
||||
)
|
||||
span.rename-input
|
||||
input(
|
||||
ng-if="permissions.write",
|
||||
ng-show="entity.renaming",
|
||||
ng-model="inputs.name",
|
||||
ng-blur="finishRenaming()",
|
||||
select-name-when="entity.renaming",
|
||||
on-enter="finishRenaming()"
|
||||
)
|
||||
|
||||
span.dropdown(
|
||||
ng-if="permissions.write"
|
||||
|
|
|
@ -14,6 +14,17 @@ header.toolbar.toolbar-header(ng-cloak, ng-hide="state.loading")
|
|||
tooltip-append-to-body="true"
|
||||
)
|
||||
i.fa.fa-fw.fa-level-up
|
||||
span(ng-controller="PdfViewToggleController")
|
||||
a(
|
||||
href,
|
||||
ng-show="ui.pdfLayout == 'flat' && fileTreeClosed",
|
||||
tooltip="PDF",
|
||||
tooltip-placement="bottom",
|
||||
tooltip-append-to-body="true",
|
||||
ng-click="togglePdfView()",
|
||||
ng-class="{ 'active': ui.view == 'pdf' }"
|
||||
)
|
||||
i.fa.fa-file-pdf-o
|
||||
|
||||
.toolbar-center.project-name(ng-controller="ProjectNameController")
|
||||
span.name(
|
||||
|
|
|
@ -80,12 +80,12 @@ div.full-size.pdf(ng-controller="PdfController")
|
|||
|
||||
.pdf-errors(ng-show="pdf.timedout || pdf.error")
|
||||
.alert.alert-danger(ng-show="pdf.error")
|
||||
strong #{translate("server_error")}
|
||||
strong #{translate("server_error")}
|
||||
span #{translate("somthing_went_wrong_compiling")}
|
||||
|
||||
.alert.alert-danger(ng-show="pdf.timedout")
|
||||
p
|
||||
strong #{translate("timedout")}.
|
||||
strong #{translate("timedout")}.
|
||||
span #{translate("proj_timed_out_reason")}
|
||||
p
|
||||
a.text-info(href="https://www.sharelatex.com/learn/Debugging_Compilation_timeout_errors", target="_blank")
|
||||
|
|
|
@ -80,11 +80,11 @@ block content
|
|||
.row.ab-bonus
|
||||
.col-md-10.col-md-offset-1.bonus-banner
|
||||
- if (refered_user_count == 0)
|
||||
p.thanks #{translate("you_not_introed_anyone_to_sl")}
|
||||
p.thanks !{translate("you_not_introed_anyone_to_sl")}
|
||||
- else if (refered_user_count == 1)
|
||||
p.thanks #{translate("you_introed_small_number", {numberOfPeople:"<strong>#{refered_user_count}</strong>"})}
|
||||
p.thanks !{translate("you_introed_small_number", {numberOfPeople:"<strong>" + refered_user_count + "</strong>"})}
|
||||
- else
|
||||
p.thanks #{translate("you_introed_high_number", {numberOfPeople:"<strong>#{refered_user_count}</strong>"})}
|
||||
p.thanks !{translate("you_introed_high_number", {numberOfPeople:"<strong>" + refered_user_count + "</strong>"})}
|
||||
|
||||
script(type="text/ng-template", id="BonusLinkToUsModal")
|
||||
.modal-header
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
extends ../layout
|
||||
|
||||
block vars
|
||||
- var meta = "Over 400 LaTeX templates for journal articles, theses, CV and resumes, posters, presentations, and much more"
|
||||
|
||||
block content
|
||||
.content.content-alt
|
||||
.container
|
||||
|
|
|
@ -20,8 +20,8 @@ block content
|
|||
.col-md-6
|
||||
.entry
|
||||
.row
|
||||
.col-md-12
|
||||
a(href=template.pdfUrl)
|
||||
.col-md-12.template-large-pdf-preview
|
||||
a(href="#{template.pdfUrl}?inline=true&name=#{template.name}")
|
||||
img(src="#{template.previewUrl}")
|
||||
|
||||
.col-md-6
|
||||
|
@ -33,7 +33,7 @@ block content
|
|||
|
|
||||
|
||||
a.btn.btn-default(
|
||||
href=template.zipUrl,
|
||||
href="#{template.zipUrl}?name=#{template.name}",
|
||||
rel='nofollow',
|
||||
ng-click='downloadZip()',
|
||||
tooltip-placement="bottom",
|
||||
|
|
|
@ -27,20 +27,10 @@ define [
|
|||
|
||||
$scope.dbState = cachedState
|
||||
$scope.dbState.hasDropboxFeature = $scope.project.features.dropbox
|
||||
|
||||
calculatePollTime = ->
|
||||
ide.socket.emit "getLastTimePollHappned", (err, lastTimePollHappened)=>
|
||||
milisecondsSinceLastPoll = new Date().getTime() - lastTimePollHappened
|
||||
roundedMinsSinceLastPoll = Math.round(milisecondsSinceLastPoll / ONE_MIN_MILI)
|
||||
|
||||
$scope.dbState.minsTillNextPoll = POLLING_INTERVAL - roundedMinsSinceLastPoll
|
||||
$scope.dbState.percentageLeftTillNextPoll = ((roundedMinsSinceLastPoll / POLLING_INTERVAL) * 100)
|
||||
$timeout calculatePollTime, 60 * 1000
|
||||
|
||||
ide.socket.emit "getUserDropboxLinkStatus", user_id, (err, status)=>
|
||||
$scope.dbState.gotLinkStatus = true
|
||||
if status.registered
|
||||
calculatePollTime()
|
||||
if status.registered
|
||||
$scope.dbState.userIsLinkedToDropbox = true
|
||||
cachedState = $scope.dbState
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
define [
|
||||
"ide/pdf/controllers/PdfController"
|
||||
"ide/pdf/controllers/PdfViewToggleController"
|
||||
"ide/pdf/directives/pdfJs"
|
||||
], () ->
|
||||
class PdfManager
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
define [
|
||||
"base"
|
||||
], (App) ->
|
||||
App.controller "PdfViewToggleController", ($scope) ->
|
||||
$scope.togglePdfView = () ->
|
||||
if $scope.ui.view == "pdf"
|
||||
$scope.ui.view = "editor"
|
||||
else
|
||||
$scope.ui.view = "pdf"
|
||||
|
||||
$scope.fileTreeClosed = false
|
||||
$scope.$on "layout:main:resize", (e, state) ->
|
||||
if state.west.initClosed
|
||||
$scope.fileTreeClosed = true
|
||||
else
|
||||
$scope.fileTreeClosed = false
|
||||
$scope.$apply()
|
|
@ -11428,6 +11428,7 @@ var Editor = function(renderer, session) {
|
|||
|
||||
this._signal("change", e);
|
||||
this.$cursorChange();
|
||||
this.$updateHighlightActiveLine();
|
||||
};
|
||||
|
||||
this.onTokenizerUpdate = function(e) {
|
||||
|
@ -14853,8 +14854,10 @@ var VirtualRenderer = function(container, theme) {
|
|||
this.$changedLines.lastRow = lastRow;
|
||||
}
|
||||
|
||||
if (this.$changedLines.firstRow > this.layerConfig.lastRow ||
|
||||
this.$changedLines.lastRow < this.layerConfig.firstRow)
|
||||
if (this.$changedLines.lastRow < this.layerConfig.firstRow)
|
||||
this.$changedLines.lastRow = this.layerConfig.lastRow
|
||||
|
||||
if (this.$changedLines.firstRow > this.layerConfig.lastRow)
|
||||
return;
|
||||
this.$loop.schedule(this.CHANGE_LINES);
|
||||
};
|
||||
|
@ -15218,6 +15221,11 @@ var VirtualRenderer = function(container, theme) {
|
|||
changes & this.CHANGE_H_SCROLL
|
||||
) {
|
||||
changes |= this.$computeLayerConfig();
|
||||
if (config.firstRow != this.layerConfig.firstRow && config.firstRowScreen == this.layerConfig.firstRowScreen) {
|
||||
this.scrollTop = this.scrollTop + (config.firstRow - this.layerConfig.firstRow) * this.lineHeight;
|
||||
changes = changes | this.CHANGE_SCROLL;
|
||||
changes |= this.$computeLayerConfig();
|
||||
}
|
||||
config = this.layerConfig;
|
||||
this.$updateScrollBarV();
|
||||
if (changes & this.CHANGE_H_SCROLL)
|
||||
|
|
|
@ -31,6 +31,7 @@ aside#file-tree {
|
|||
|
||||
li {
|
||||
line-height: 2.6;
|
||||
position: relative;
|
||||
|
||||
.entity-name {
|
||||
color: @gray-darker;
|
||||
|
@ -71,7 +72,7 @@ aside#file-tree {
|
|||
color: @link-color;
|
||||
border-right: 4px solid @link-color;
|
||||
font-weight: bold;
|
||||
i.fa-folder-open, i.fa-folder, i.fa-file, i.fa-image {
|
||||
i.fa-folder-open, i.fa-folder, i.fa-file, i.fa-image, i.fa-file-pdf-o {
|
||||
color: @link-color;
|
||||
}
|
||||
padding-right: 32px;
|
||||
|
@ -85,6 +86,17 @@ aside#file-tree {
|
|||
padding: 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
.rename-input {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
left: 44px;
|
||||
right: 32px;
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -74,20 +74,6 @@
|
|||
}
|
||||
|
||||
.pdf .toolbar {
|
||||
.log-btn {
|
||||
&.active, &:active {
|
||||
.label {
|
||||
display: none;
|
||||
}
|
||||
color: white;
|
||||
background-color: @link-color;
|
||||
.box-shadow(inset 0 3px 5px rgba(0, 0, 0, 0.225));
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.toolbar-right {
|
||||
margin-right: @line-height-computed / 2;
|
||||
a {
|
||||
|
|
|
@ -16,14 +16,25 @@
|
|||
a:not(.btn) {
|
||||
display: inline-block;
|
||||
color: @gray-light;
|
||||
padding: 5px 12px 6px;
|
||||
margin: 0;
|
||||
padding: 4px 10px 5px;
|
||||
margin: 1px 2px;
|
||||
border-radius: @border-radius-small;
|
||||
&:hover {
|
||||
text-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
|
||||
color: @gray-dark;
|
||||
text-decoration: none;
|
||||
}
|
||||
&.active, &:active {
|
||||
.label {
|
||||
display: none;
|
||||
}
|
||||
color: white;
|
||||
background-color: @link-color;
|
||||
.box-shadow(inset 0 3px 5px rgba(0, 0, 0, 0.225));
|
||||
&:hover {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-full-height {
|
||||
|
@ -81,6 +92,7 @@
|
|||
height: 32px;
|
||||
a {
|
||||
padding: 4px 2px 2px;
|
||||
margin: 0;
|
||||
margin-left: 6px;
|
||||
}
|
||||
.toolbar-right {
|
||||
|
|
|
@ -56,4 +56,10 @@
|
|||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.template-large-pdf-preview {
|
||||
img {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
|
@ -40,9 +40,10 @@ describe 'Flushing documents :', ->
|
|||
"range":{"start":{"row":2,"column":2},"end":{"row":2,"column":3}},
|
||||
"text":"e"
|
||||
}
|
||||
@rclient.rpush = sinon.stub().callsArg(2)
|
||||
@rclient.publish = sinon.stub().callsArg(2)
|
||||
@rclient.sadd = sinon.stub().callsArg(2)
|
||||
@rclient.multi = sinon.stub().returns @rclient
|
||||
@rclient.exec = sinon.stub().callsArg(0)
|
||||
@rclient.rpush = sinon.stub()
|
||||
@rclient.sadd = sinon.stub()
|
||||
@callback = sinon.stub()
|
||||
|
||||
describe "successfully", ->
|
||||
|
@ -54,9 +55,9 @@ describe 'Flushing documents :', ->
|
|||
.calledWith("PendingUpdates:#{@doc_id}", JSON.stringify(@change))
|
||||
.should.equal true
|
||||
|
||||
it "should notify the doc updater of the change via pub/sub", ->
|
||||
@rclient.publish
|
||||
.calledWith("pending-updates", "#{@project_id}:#{@doc_id}")
|
||||
it "should notify the doc updater of the change via the pending-updates-list queue", ->
|
||||
@rclient.rpush
|
||||
.calledWith("pending-updates-list", "#{@project_id}:#{@doc_id}")
|
||||
.should.equal true
|
||||
|
||||
it "should push the doc id into the pending updates set", ->
|
||||
|
@ -64,29 +65,14 @@ describe 'Flushing documents :', ->
|
|||
.calledWith("DocsWithPendingUpdates", "#{@project_id}:#{@doc_id}")
|
||||
.should.equal true
|
||||
|
||||
describe "with error connecting to redis during push", ->
|
||||
describe "with error connecting to redis during exec", ->
|
||||
beforeEach ->
|
||||
@rclient.rpush = sinon.stub().callsArgWith(2, new Error("something went wrong"))
|
||||
@rclient.exec = sinon.stub().callsArgWith(0, new Error("something went wrong"))
|
||||
@handler.queueChange(@project_id, @doc_id, @change, @callback)
|
||||
|
||||
it "should return an error", ->
|
||||
@callback.calledWithExactly(sinon.match(Error)).should.equal true
|
||||
|
||||
describe "with error connecting to redis during publish", ->
|
||||
beforeEach ->
|
||||
@rclient.publish = sinon.stub().callsArgWith(2, new Error("something went wrong"))
|
||||
@handler.queueChange(@project_id, @doc_id, @change, @callback)
|
||||
|
||||
it "should return an error", ->
|
||||
@callback.calledWithExactly(sinon.match(Error)).should.equal true
|
||||
|
||||
describe "with error connecting to redis during sadd", ->
|
||||
beforeEach ->
|
||||
@rclient.sadd = sinon.stub().callsArgWith(2, new Error("something went wrong"))
|
||||
@handler.queueChange(@project_id, @doc_id, @change, @callback)
|
||||
|
||||
it "should return an error", ->
|
||||
@callback.calledWithExactly(sinon.match(Error)).should.equal true
|
||||
|
||||
describe 'flushProjectToMongo', ->
|
||||
beforeEach ->
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
SandboxedModule = require('sandboxed-module')
|
||||
assert = require('assert')
|
||||
require('chai').should()
|
||||
sinon = require('sinon')
|
||||
modulePath = require('path').join __dirname, '../../../../app/js/Features/Dropbox/DropboxWebhookController.js'
|
||||
|
||||
describe 'DropboxWebhookController', ->
|
||||
beforeEach ->
|
||||
|
||||
@DropboxWebhookController = SandboxedModule.require modulePath, requires:
|
||||
"./DropboxWebhookHandler": @DropboxWebhookHandler = {}
|
||||
'logger-sharelatex':
|
||||
log:->
|
||||
err:->
|
||||
|
||||
describe "verify", ->
|
||||
beforeEach ->
|
||||
@res =
|
||||
send: sinon.stub()
|
||||
@req.query =
|
||||
challenge: @challenge = "foo"
|
||||
@DropboxWebhookController.verify(@req, @res)
|
||||
|
||||
it "should echo the challenge parameter back", ->
|
||||
@res.send.calledWith(@challenge).should.equal true
|
||||
|
||||
describe "webhook", ->
|
||||
beforeEach ->
|
||||
@req.body =
|
||||
delta:
|
||||
users: @dropbox_uids = [
|
||||
"123456",
|
||||
"789123"
|
||||
]
|
||||
@res.send = sinon.stub()
|
||||
@DropboxWebhookHandler.pollDropboxUids = sinon.stub().callsArg(1)
|
||||
@DropboxWebhookController.webhook(@req, @res)
|
||||
|
||||
it "should poll the Dropbox uids", ->
|
||||
@DropboxWebhookHandler.pollDropboxUids
|
||||
.calledWith(@dropbox_uids)
|
||||
.should.equal true
|
||||
|
||||
it "should return success", ->
|
||||
@res.send
|
||||
.calledWith(200)
|
||||
.should.equal true
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
SandboxedModule = require('sandboxed-module')
|
||||
assert = require('assert')
|
||||
require('chai').should()
|
||||
expect = require("chai").expect
|
||||
sinon = require('sinon')
|
||||
modulePath = require('path').join __dirname, '../../../../app/js/Features/Dropbox/DropboxWebhookHandler.js'
|
||||
|
||||
describe 'DropboxWebhookHandler', ->
|
||||
beforeEach ->
|
||||
@DropboxWebhookHandler = SandboxedModule.require modulePath, requires:
|
||||
"../../models/User": User: @User = {}
|
||||
"../ThirdPartyDataStore/TpdsUpdateSender": @TpdsUpdateSender = {}
|
||||
"redis":
|
||||
createClient: () => @rclient =
|
||||
auth: sinon.stub()
|
||||
'settings-sharelatex': redis: web: {}
|
||||
'logger-sharelatex':
|
||||
log:->
|
||||
err:->
|
||||
@callback = sinon.stub()
|
||||
|
||||
describe "pollDropboxUids", ->
|
||||
beforeEach (done) ->
|
||||
@dropbox_uids = [
|
||||
"123456",
|
||||
"789123"
|
||||
]
|
||||
@DropboxWebhookHandler.pollDropboxUid = sinon.stub().callsArg(1)
|
||||
@DropboxWebhookHandler.pollDropboxUids @dropbox_uids, done
|
||||
|
||||
it "should call pollDropboxUid for each uid", ->
|
||||
for uid in @dropbox_uids
|
||||
@DropboxWebhookHandler.pollDropboxUid
|
||||
.calledWith(uid)
|
||||
.should.equal true
|
||||
|
||||
describe "pollDropboxUid", ->
|
||||
beforeEach ->
|
||||
@dropbox_uid = "dropbox-123456"
|
||||
@user_id = "sharelatex-user-id"
|
||||
@User.find = sinon.stub().callsArgWith(1, null, [ _id: @user_id ])
|
||||
@TpdsUpdateSender.pollDropboxForUser = sinon.stub().callsArg(1)
|
||||
|
||||
describe "when there is already a poll in progress", () ->
|
||||
beforeEach ->
|
||||
@DropboxWebhookHandler._delayAndBatchPoll = sinon.stub().callsArgWith(1, null, false)
|
||||
@DropboxWebhookHandler.pollDropboxUid @dropbox_uid, @callback
|
||||
|
||||
it "should not go ahead with the poll", ->
|
||||
@TpdsUpdateSender.pollDropboxForUser.called.should.equal false
|
||||
|
||||
describe "when we are the one to do the delayed poll", () ->
|
||||
beforeEach ->
|
||||
@DropboxWebhookHandler._delayAndBatchPoll = sinon.stub().callsArgWith(1, null, true)
|
||||
@DropboxWebhookHandler.pollDropboxUid @dropbox_uid, @callback
|
||||
|
||||
it "should look up the user", ->
|
||||
@User.find
|
||||
.calledWith({ "dropbox.access_token.uid": @dropbox_uid, "features.dropbox": true })
|
||||
.should.equal true
|
||||
|
||||
it "should poll the user's Dropbox", ->
|
||||
@TpdsUpdateSender.pollDropboxForUser
|
||||
.calledWith(@user_id)
|
||||
.should.equal true
|
||||
|
||||
it "should call the callback", ->
|
||||
@callback.called.should.equal true
|
||||
|
||||
describe "_delayAndBatchPoll", () ->
|
||||
beforeEach ->
|
||||
@dropbox_uid = "dropbox-uid-123"
|
||||
@DropboxWebhookHandler.POLL_DELAY_IN_MS = 100
|
||||
|
||||
describe "when no one else is polling yet", ->
|
||||
beforeEach (done) ->
|
||||
@rclient.set = sinon.stub().callsArgWith(5, null, "OK")
|
||||
@start = Date.now()
|
||||
@DropboxWebhookHandler._delayAndBatchPoll @dropbox_uid, (error, @shouldPoll) =>
|
||||
@end = Date.now()
|
||||
done()
|
||||
|
||||
it "should set the lock", ->
|
||||
@rclient.set
|
||||
.calledWith("dropbox-poll-lock:#{@dropbox_uid}", "LOCK", "PX", @DropboxWebhookHandler.POLL_DELAY_IN_MS, "NX")
|
||||
.should.equal true
|
||||
|
||||
it "should return the callback after the delay with shouldPoll=true", ->
|
||||
@shouldPoll.should.equal true
|
||||
expect(@end - @start).to.be.at.least(@DropboxWebhookHandler.POLL_DELAY_IN_MS)
|
||||
|
||||
describe "when someone else is already polling", ->
|
||||
beforeEach ->
|
||||
@rclient.set = sinon.stub().callsArgWith(5, null, null)
|
||||
@DropboxWebhookHandler._delayAndBatchPoll @dropbox_uid, @callback
|
||||
|
||||
it "should return the callback immediately with shouldPoll=false", ->
|
||||
@callback.calledWith(null, false).should.equal true
|
||||
|
|
@ -105,3 +105,20 @@ describe 'TpdsUpdateSender', ->
|
|||
job.headers.sl_all_user_ids.should.eql(JSON.stringify([collaberator_ref_1, read_only_ref_1, user_id]))
|
||||
done()
|
||||
@updateSender.moveEntity {project_id:project_id, project_name:oldProjectName, newProjectName:newProjectName}
|
||||
|
||||
it "pollDropboxForUser", (done) ->
|
||||
@requestQueuer.enqueue = sinon.stub().callsArg(3)
|
||||
@updateSender.pollDropboxForUser user_id, (error) =>
|
||||
@requestQueuer.enqueue
|
||||
.calledWith(
|
||||
"poll-dropbox:#{user_id}",
|
||||
"standardHttpRequest",
|
||||
{
|
||||
method: "POST"
|
||||
uri: "#{thirdPartyDataStoreApiUrl}/user/poll"
|
||||
json:
|
||||
user_ids: [user_id]
|
||||
}
|
||||
)
|
||||
.should.equal true
|
||||
done()
|
||||
|
|
Loading…
Reference in a new issue