mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge branch 'master' of github.com:sharelatex/web-sharelatex
This commit is contained in:
commit
3c9f6e0ce0
21 changed files with 259 additions and 156 deletions
|
@ -18,9 +18,11 @@ module.exports = EditorUpdatesController =
|
|||
if takeSnapshot
|
||||
AutomaticSnapshotManager.markProjectAsUpdated(project_id)
|
||||
|
||||
logger.log doc_id: doc_id, project_id: project_id, client_id: update.meta?.source, version: update.v, "sending update to doc updater"
|
||||
|
||||
DocumentUpdaterHandler.queueChange project_id, doc_id, update, (error) ->
|
||||
if error?
|
||||
logger.error err:error, project_id: project_id, "document was not available for update"
|
||||
logger.error err:error, project_id: project_id, doc_id: doc_id, client_id: update.meta?.source, version: update.v, "document was not available for update"
|
||||
client.disconnect()
|
||||
callback(error)
|
||||
|
||||
|
@ -52,8 +54,10 @@ module.exports = EditorUpdatesController =
|
|||
io = require('../../infrastructure/Server').io
|
||||
for client in io.sockets.clients(doc_id)
|
||||
if client.id == update.meta.source
|
||||
logger.log doc_id: doc_id, version: update.v, source: update.meta?.source, "distributing update to sender"
|
||||
client.emit "otUpdateApplied", v: update.v, doc: update.doc
|
||||
else
|
||||
logger.log doc_id: doc_id, version: update.v, source: update.meta?.source, client_id: client.id, "distributing update to collaborator"
|
||||
client.emit "otUpdateApplied", update
|
||||
|
||||
_processErrorFromDocumentUpdater: (doc_id, error, message) ->
|
||||
|
|
|
@ -2,37 +2,43 @@ logger = require('logger-sharelatex')
|
|||
metrics = require('../../infrastructure/Metrics')
|
||||
Settings = require('settings-sharelatex')
|
||||
metrics = require("../../infrastructure/Metrics")
|
||||
ses = require('node-ses')
|
||||
nodemailer = require("nodemailer")
|
||||
|
||||
if Settings.email? and Settings.email.fromAddress?
|
||||
defaultFromAddress = Settings.email.fromAddress
|
||||
else
|
||||
else
|
||||
defaultFromAddress = ""
|
||||
|
||||
if Settings.email?.ses? and Settings.email.ses?.key? and Settings.email.ses?.key != "" and Settings.email.ses?.secret? and Settings.email.ses?.secret != ""
|
||||
client = ses.createClient({ key: Settings.email.ses.key, secret: Settings.email.ses.secret });
|
||||
else
|
||||
logger.warn "AWS SES credentials are not configured. No emails will be sent."
|
||||
client =
|
||||
sendemail: (options, callback = (err, data, res) ->) ->
|
||||
logger.log options: options, "would send email if SES credentials enabled"
|
||||
callback()
|
||||
# provide dummy mailer unless we have a better one configured.
|
||||
client =
|
||||
sendMail: (options, callback = (err,status) ->) ->
|
||||
logger.log options:options, "Would send email if enabled."
|
||||
callback()
|
||||
|
||||
if Settings.email?
|
||||
if Settings.email.transport? and Settings.email.parameters?
|
||||
nm_client = nodemailer.createTransport( Settings.email.transport, Settings.email.parameters )
|
||||
if nm_client
|
||||
client = nm_client
|
||||
else
|
||||
logger.warn "Failed to create email transport. Please check your settings. No email will be sent."
|
||||
else
|
||||
logger.warn "Email transport and/or parameters not defined. No emails will be sent."
|
||||
|
||||
module.exports =
|
||||
|
||||
sendEmail : (options, callback = (error) ->)->
|
||||
logger.log receiver:options.to, subject:options.subject, "sending email"
|
||||
metrics.inc "email"
|
||||
options =
|
||||
options =
|
||||
to: options.to
|
||||
from: defaultFromAddress
|
||||
subject: options.subject
|
||||
message: options.html
|
||||
html: options.html
|
||||
replyTo: options.replyTo || Settings.email.replyToAddress
|
||||
client.sendemail options, (err, data, res)->
|
||||
client.sendMail options, (err, res)->
|
||||
if err?
|
||||
logger.err err:err, "error sending message"
|
||||
else
|
||||
logger.log "Message sent to #{options.to}"
|
||||
callback(err)
|
||||
|
||||
|
||||
|
|
|
@ -217,6 +217,10 @@ module.exports = class Router
|
|||
require("./models/Project").Project.findOne {}, () ->
|
||||
throw new Error("Test error")
|
||||
|
||||
app.post '/error/client', (req, res, next) ->
|
||||
logger.error err: req.body.error, meta: req.body.meta, "client side error"
|
||||
res.send(204)
|
||||
|
||||
app.get '*', HomeController.notFound
|
||||
|
||||
|
||||
|
|
|
@ -22,6 +22,9 @@ html(itemscope, itemtype='http://schema.org/Product')
|
|||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
|
||||
ga('create', '#{gaToken}', 'sharelatex.com');
|
||||
ga('send', 'pageview');
|
||||
- else
|
||||
script(type='text/javascript')
|
||||
window.ga = function() {};
|
||||
|
||||
script
|
||||
window.csrfToken = "#{csrfToken}";
|
||||
|
|
|
@ -77,15 +77,6 @@
|
|||
.modal-body
|
||||
span.message
|
||||
|
||||
#genericServerErrorModal(style='display: none')
|
||||
.modal
|
||||
.modal-header
|
||||
h3 There was a problem talking to the server
|
||||
.modal-body
|
||||
span.message Sorry, we couldn't complete your request right now. Please wait a few moments and try again. If the problem persists, please let us know.
|
||||
.modal-footer
|
||||
button.btn.btn-primary.cancel Ok
|
||||
|
||||
#projectUploadModal(style='display: none')
|
||||
.modal
|
||||
.modal-header
|
||||
|
|
|
@ -136,16 +136,6 @@
|
|||
#subscribeForm
|
||||
.modal-footer
|
||||
|
||||
|
||||
script(type="text/template")#genericModalTemplateWithButton
|
||||
.modal
|
||||
.modal-header
|
||||
h3 {{ title }}
|
||||
.modal-body
|
||||
.message {{ message }}
|
||||
.modal-footer
|
||||
button.btn.btn-primary ok
|
||||
|
||||
script(type="text/template")#genericModalButtonTemplate
|
||||
button(class="btn {{ class }}") {{ text }}
|
||||
|
||||
|
@ -300,22 +290,29 @@
|
|||
-if(session && session.user && session.user.isAdmin)
|
||||
.box
|
||||
.page-header
|
||||
h2 Publish Project
|
||||
h2 Publish project as template
|
||||
|
||||
#publishedAsTemplateArea.show-when-published.alert.alert-success
|
||||
p
|
||||
.btn.btn-warning#unPublishProjectAsTemplate.pull-right Unpublish
|
||||
i.icon-ok
|
||||
| Your project is currently published.
|
||||
a#templateLink(href='{{canonicalUrl}}') View in template gallery.
|
||||
p
|
||||
| Lastest version: {{publishedDate}}.
|
||||
|
||||
#publishedAsTemplateArea(style="display:none;")
|
||||
a#templateLink(href='{{canonicalUrl}}') View Template
|
||||
p published at {{publishedDate}}
|
||||
.btn.btn-warning#unPublishProjectAsTemplate unpublish project as template
|
||||
.btn.btn-success#republishProjectAsTemplate re publish project as template
|
||||
|
||||
#problemWithPublishingArea(style="display:none;")
|
||||
#problemWithPublishingArea
|
||||
p There is a problem with our publishing service, please try again in a few minutes.
|
||||
#publishWorkingArea(style="display:none;")
|
||||
p Working.....
|
||||
#unpublishedAsTemplateArea(style="display:none;")
|
||||
.btn.btn-success#publishProjectAsTemplate Publish project as template
|
||||
div
|
||||
textarea.span6#projectDescription {{description}}
|
||||
#publishWorkingArea
|
||||
p Working...
|
||||
div.show-when-published.show-when-unpublished.project-description
|
||||
label(for="project-description") Description
|
||||
.row-fluid
|
||||
textarea(placeholder="Template description", name="project-description").span12#projectDescription {{description}}
|
||||
#unpublishedAsTemplateArea.show-when-unpublished
|
||||
.btn.btn-success#publishProjectAsTemplate Publish
|
||||
p.show-when-published
|
||||
button.btn.btn-success#republishProjectAsTemplate Re-Publish
|
||||
|
||||
|
||||
script(type="text/template")#settingsPanelTemplate
|
||||
|
|
|
@ -134,6 +134,22 @@ module.exports =
|
|||
{name: "English", code: "en"}
|
||||
]
|
||||
|
||||
# Email support
|
||||
# -------------
|
||||
#
|
||||
# ShareLaTeX uses nodemailer (http://www.nodemailer.com/) to send transactional emails.
|
||||
# To see the range of transport and options they support, see http://www.nodemailer.com/docs/transports
|
||||
#email:
|
||||
# fromAddress: ""
|
||||
# replyTo: ""
|
||||
# lifecycle: false
|
||||
## Example transport and parameter settings for Amazon SES
|
||||
# transport: "SES"
|
||||
# parameters:
|
||||
# AWSAccessKeyID: ""
|
||||
# AWSSecretKey: ""
|
||||
|
||||
|
||||
# Third party services
|
||||
# --------------------
|
||||
#
|
||||
|
@ -153,15 +169,6 @@ module.exports =
|
|||
# tenderUrl: ""
|
||||
#
|
||||
|
||||
# email:
|
||||
# fromAddress: ""
|
||||
# replyTo: ""
|
||||
# lifecycle: false
|
||||
# ShareLaTeX uses Amazon's SES api to send transactional emails.
|
||||
# Uncomment these lines and provide your credentials to be able to send emails.
|
||||
# ses:
|
||||
# "key":""
|
||||
# "secret":""
|
||||
|
||||
# Production Settings
|
||||
# -------------------
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
"fairy": "0.0.2",
|
||||
"node-uuid": "1.4.0",
|
||||
"mongojs": "0.9.8",
|
||||
"node-ses": "0.0.3",
|
||||
"nodemailer": "0.6.1",
|
||||
"bcrypt": "0.7.5",
|
||||
"archiver": "0.5.1",
|
||||
"nodetime": "0.8.15",
|
||||
|
|
|
@ -79,6 +79,14 @@ define () ->
|
|||
\\end{enumerate}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{itemize}..."
|
||||
snippet: """
|
||||
\\begin{itemize}
|
||||
\\item $1
|
||||
\\end{itemize}
|
||||
"""
|
||||
meta: "env"
|
||||
}, {
|
||||
caption: "\\begin{frame}..."
|
||||
snippet: """
|
||||
|
|
|
@ -136,16 +136,18 @@ define [
|
|||
@unBindFromSocketEvents()
|
||||
|
||||
_bindToShareJsDocEvents: () ->
|
||||
@doc.on "error", (error) => @_onError error
|
||||
@doc.on "error", (error, meta) => @_onError error, meta
|
||||
@doc.on "externalUpdate", () => @trigger "externalUpdate"
|
||||
@doc.on "remoteop", () => @trigger "remoteop"
|
||||
@doc.on "op:sent", () => @trigger "op:sent"
|
||||
@doc.on "op:acknowledged", () => @trigger "op:acknowledged"
|
||||
|
||||
_onError: (error) ->
|
||||
console.error "ShareJS error", error
|
||||
ga('send', 'event', 'error', "shareJsError", "#{error.message} - #{ide.socket.socket.transport.name}" )
|
||||
_onError: (error, meta = {}) ->
|
||||
console.error "ShareJS error", error, meta
|
||||
ga?('send', 'event', 'error', "shareJsError", "#{error.message} - #{ide.socket.socket.transport.name}" )
|
||||
@ide.socket.disconnect()
|
||||
meta.doc_id = @doc_id
|
||||
@ide.reportError(error, meta)
|
||||
@doc?.clearInflightAndPendingOps()
|
||||
@_cleanUp()
|
||||
@trigger "error", error
|
||||
|
|
|
@ -121,7 +121,7 @@ define [
|
|||
@aceEditor = aceEditor = AceEditor.edit("editor")
|
||||
|
||||
@on "resize", => @aceEditor.resize()
|
||||
@ide.layoutManager.on "resize", => @aceEditor.resize()
|
||||
@ide.layoutManager.on "resize", => @trigger "resize"
|
||||
|
||||
mode = window.userSettings.mode
|
||||
theme = window.userSettings.theme
|
||||
|
|
|
@ -94,14 +94,17 @@ define [
|
|||
|
||||
INFLIGHT_OP_TIMEOUT: 10000
|
||||
_startInflightOpTimeout: (update) ->
|
||||
meta =
|
||||
v: update.v
|
||||
op_sent_at: new Date()
|
||||
timer = setTimeout () =>
|
||||
@_handleError "Doc op was not acknowledged in time"
|
||||
@_handleError new Error("Doc op was not acknowledged in time"), meta
|
||||
, @INFLIGHT_OP_TIMEOUT
|
||||
@_doc.inflightCallbacks.push () =>
|
||||
clearTimeout timer
|
||||
|
||||
_handleError: (error) ->
|
||||
@trigger "error", error
|
||||
_handleError: (error, meta = {}) ->
|
||||
@trigger "error", error, meta
|
||||
|
||||
_bindToDocChanges: (doc) ->
|
||||
submitOp = doc.submitOp
|
||||
|
|
|
@ -48,7 +48,7 @@ define [
|
|||
SearchManager,
|
||||
Project,
|
||||
User,
|
||||
StandaloneModal,
|
||||
Modal,
|
||||
FileTreeManager,
|
||||
MessageManager,
|
||||
HelpManager,
|
||||
|
@ -144,16 +144,39 @@ define [
|
|||
setTimeout(joinProject, 100)
|
||||
|
||||
showErrorModal: (title, message)->
|
||||
modalOptions =
|
||||
templateId:'genericModalTemplate'
|
||||
isStatic: false
|
||||
new Modal {
|
||||
title: title
|
||||
message:message
|
||||
new Modal modalOptions
|
||||
message: message
|
||||
buttons: [ text: "OK" ]
|
||||
}
|
||||
|
||||
showGenericServerErrorMessage: (message)->
|
||||
new Modal
|
||||
templateId : "genericServerErrorModal"
|
||||
showGenericServerErrorMessage: ()->
|
||||
new Modal {
|
||||
title: "There was a problem talking to the server"
|
||||
message: "Sorry, we couldn't complete your request right now. Please wait a few moments and try again. If the problem persists, please let us know."
|
||||
buttons: [ text: "OK" ]
|
||||
}
|
||||
|
||||
reportError: (error, meta = {}) ->
|
||||
meta.client_id = @socket?.socket?.sessionid
|
||||
meta.transport = @socket?.socket?.transport?.name
|
||||
meta.client_now = new Date()
|
||||
meta.last_connected = @connectionManager.lastConnected
|
||||
meta.second_last_connected = @connectionManager.secondLastConnected
|
||||
meta.last_disconnected = @connectionManager.lastDisconnected
|
||||
meta.second_last_disconnected = @connectionManager.secondLastDisconnected
|
||||
errorObj = {}
|
||||
for key in Object.getOwnPropertyNames(error)
|
||||
errorObj[key] = error[key]
|
||||
$.ajax
|
||||
url: "/error/client"
|
||||
type: "POST"
|
||||
data: JSON.stringify
|
||||
error: errorObj
|
||||
meta: meta
|
||||
contentType: "application/json; charset=utf-8"
|
||||
headers:
|
||||
"X-Csrf-Token": window.csrfToken
|
||||
|
||||
setLoadingMessage: (message) ->
|
||||
$("#loadingMessage").text(message)
|
||||
|
@ -171,45 +194,6 @@ define [
|
|||
ide.layoutManager.resizeAllSplitters()
|
||||
ide.tourManager = new IdeTour ide
|
||||
|
||||
class Modal
|
||||
#templateId, title, message, isStatic, cancelCallback
|
||||
constructor: (options, completeCallback = () -> {})->
|
||||
html = $("##{options.templateId}").html()
|
||||
modal = "<div id='modal' style='display:none'>#{html}</div>"
|
||||
$('body').append(modal)
|
||||
$modal = $('#modal')
|
||||
|
||||
if options.title?
|
||||
$modal.find('h3').text(options.title)
|
||||
if options.message?
|
||||
$modal.find('.message').text(options.message)
|
||||
if options.inputValue?
|
||||
$modal.find('input').val(options.inputValue)
|
||||
|
||||
backdrop = true
|
||||
if options.backdrop?
|
||||
backdrop = options.backdrop
|
||||
|
||||
$modal.modal backdrop:backdrop, show:true, keyboard:true, isStatic:options.isStatic
|
||||
$modal.find('input').focus()
|
||||
|
||||
$modal.find('button').click (e)=>
|
||||
e.preventDefault()
|
||||
$modal.modal('hide')
|
||||
if e.target.className.indexOf("cancel") == -1
|
||||
inputval = $modal.find('input').val()
|
||||
completeCallback(inputval)
|
||||
|
||||
$modal.find('input').keydown (event)=>
|
||||
code = event.keyCode || event.which
|
||||
if code == 13
|
||||
$modal.find('button.primary').click()
|
||||
|
||||
$modal.bind 'hide', ()->
|
||||
if options.cancelCallback?
|
||||
options.cancelCallback()
|
||||
$('#modal').remove()
|
||||
|
||||
ide.savingAreaManager =
|
||||
$savingArea : $('#saving-area')
|
||||
timeOut: undefined
|
||||
|
@ -220,7 +204,7 @@ define [
|
|||
return if @timeOut?
|
||||
@clearTimeout()
|
||||
@timeOut = setTimeout((=>
|
||||
ga('send', 'event', 'editor-interaction', 'notification-shown', "saving")
|
||||
ga?('send', 'event', 'editor-interaction', 'notification-shown', "saving")
|
||||
$("#savingProblems").show()
|
||||
), 1000)
|
||||
|
||||
|
|
|
@ -8,11 +8,15 @@ define [
|
|||
@socket = @ide.socket
|
||||
@socket.on "connect", () =>
|
||||
@connected = true
|
||||
@secondLastConnected = @lastConnected
|
||||
@lastConnected = new Date()
|
||||
@hideModal()
|
||||
@cancelReconnect()
|
||||
|
||||
@socket.on 'disconnect', () =>
|
||||
@connected = false
|
||||
@secondLastDisconnected = @lastDisconnected
|
||||
@lastDisconnected = new Date()
|
||||
@ide.trigger "disconnect"
|
||||
setTimeout(=>
|
||||
ga('send', 'event', 'editor-interaction', 'disconnect')
|
||||
|
|
|
@ -3,9 +3,10 @@ define [
|
|||
"models/ProjectMemberList"
|
||||
"account/AccountManager"
|
||||
"utils/Modal"
|
||||
"moment"
|
||||
"libs/backbone"
|
||||
"libs/mustache"
|
||||
], (User, ProjectMemberList, AccountManager, Modal) ->
|
||||
], (User, ProjectMemberList, AccountManager, Modal, moment) ->
|
||||
INFINITE_COLLABORATORS = -1
|
||||
|
||||
class ProjectMembersManager
|
||||
|
@ -20,6 +21,8 @@ define [
|
|||
name: "Share"
|
||||
content : $(@templates.userPanel)
|
||||
lock: true
|
||||
onShown: () =>
|
||||
@publishProjectView?.refreshPublishStatus()
|
||||
|
||||
setupPublish = _.once =>
|
||||
@publishProjectView = new PublishProjectView
|
||||
|
@ -177,25 +180,32 @@ define [
|
|||
initialize: () ->
|
||||
@ide = @options.ide
|
||||
@model = @ide.project
|
||||
_.bindAll(this, "render");
|
||||
this.model.bind('change', this.render)
|
||||
@refreshPublishStatus()
|
||||
|
||||
@render()
|
||||
|
||||
render: ->
|
||||
viewModel =
|
||||
description: @model.get("description")
|
||||
canonicalUrl: @model.get("template.canonicalUrl")
|
||||
isPublished: @model.get("template.isPublished")
|
||||
publishedDate: @model.get("template.publishedDate")
|
||||
publishedDate: moment(@model.get("template.publishedDate")).format("Do MMM YYYY, h:mm a")
|
||||
|
||||
$(@el).html $(Mustache.to_html(@template, viewModel))
|
||||
@publishedArea = $('#publishedAsTemplateArea')
|
||||
@unpublishedArea = $('#unpublishedAsTemplateArea')
|
||||
@$el.html $(Mustache.to_html(@template, viewModel))
|
||||
@publishedArea = $('.show-when-published')
|
||||
@unpublishedArea = $('.show-when-unpublished')
|
||||
$('#problemWithPublishingArea').hide()
|
||||
$('#publishWorkingArea').hide()
|
||||
|
||||
refreshView: () ->
|
||||
if @model.get("template.isPublished")
|
||||
@$("a#templateLink").attr("href", @model.get("template.canonicalUrl"))
|
||||
@publishedArea.show()
|
||||
else
|
||||
@unpublishedArea.show()
|
||||
|
||||
refreshPublishStatus: ->
|
||||
@showWorking()
|
||||
@ide.socket.emit "getPublishedDetails", @ide.user.get("id"), (err, details)=>
|
||||
@hideWorking()
|
||||
if err?
|
||||
return @showError()
|
||||
|
||||
|
@ -203,16 +213,17 @@ define [
|
|||
if details.exists
|
||||
@model.set("template.canonicalUrl", details.canonicalUrl)
|
||||
@model.set("template.publishedDate", details.publishedDate)
|
||||
@publishedArea.show()
|
||||
@unpublishedArea.hide()
|
||||
else
|
||||
@publishedArea.hide()
|
||||
@unpublishedArea.show()
|
||||
|
||||
@refreshView()
|
||||
|
||||
showError: ->
|
||||
@publishedArea.hide()
|
||||
@unpublishedArea.hide()
|
||||
$('#problemWithPublishingArea').show()
|
||||
|
||||
showWorking: ->
|
||||
@publishedArea.hide()
|
||||
@unpublishedArea.hide()
|
||||
$('#publishWorkingArea').show()
|
||||
|
||||
hideWorking: ->
|
||||
|
|
|
@ -16,11 +16,11 @@ define [
|
|||
@createAceEditor()
|
||||
@aceEditor.setValue(@getPlainDiffContent())
|
||||
@aceEditor.clearSelection()
|
||||
session = @aceEditor.getSession()
|
||||
session.setMode(new LatexMode.Mode())
|
||||
session.setUseWrapMode(true)
|
||||
@$ace = $(@aceEditor.renderer.container).find(".ace_scroller")
|
||||
@insertMarkers()
|
||||
@insertNameTag()
|
||||
@insertMoreChangeLabels()
|
||||
@scrollToFirstChange()
|
||||
return @
|
||||
|
||||
destroy: () ->
|
||||
|
@ -34,12 +34,18 @@ define [
|
|||
@aceEditor.setTheme("ace/theme/#{window.userSettings.theme}")
|
||||
@aceEditor.setReadOnly true
|
||||
@aceEditor.setShowPrintMargin(false)
|
||||
session = @aceEditor.getSession()
|
||||
session.setMode(new LatexMode.Mode())
|
||||
session.setUseWrapMode(true)
|
||||
|
||||
@aceEditor.on "mousemove", (e) =>
|
||||
position = @aceEditor.renderer.screenToTextCoordinates(e.clientX, e.clientY)
|
||||
e.position = position
|
||||
@updateVisibleNames(e)
|
||||
|
||||
session.on "changeScrollTop", (e) =>
|
||||
@updateMoreChangeLabels()
|
||||
|
||||
getPlainDiffContent: () ->
|
||||
content = ""
|
||||
for entry in @model.get("diff") or []
|
||||
|
@ -102,7 +108,6 @@ define [
|
|||
, foreground
|
||||
|
||||
insertNameTag: () ->
|
||||
@$ace = $(@aceEditor.renderer.container).find(".ace_scroller")
|
||||
@$nameTagEl = $("<div class='change-name-marker'></div>")
|
||||
@$nameTagEl.css({
|
||||
position: "absolute"
|
||||
|
@ -110,6 +115,19 @@ define [
|
|||
@$nameTagEl.hide()
|
||||
@$ace.append(@$nameTagEl)
|
||||
|
||||
insertMoreChangeLabels: () ->
|
||||
@$changesBefore = $("<div class='changes-before'><span></span> <i class='icon-arrow-up'></div>")
|
||||
@$changesAfter = $("<div class='changes-after'><span></span> <i class='icon-arrow-down'></div>")
|
||||
@$ace.append(@$changesBefore)
|
||||
@$ace.append(@$changesAfter)
|
||||
@updateMoreChangeLabels()
|
||||
|
||||
scrollToFirstChange: () ->
|
||||
if @entries? and @entries[0]?
|
||||
row = @entries[0].range.start.row
|
||||
@aceEditor.gotoLine(0)
|
||||
@aceEditor.gotoLine(row, 0, true)
|
||||
|
||||
_drawNameTag: (entry, position) ->
|
||||
@$nameTagEl.show()
|
||||
|
||||
|
@ -161,5 +179,30 @@ define [
|
|||
if !visibleName
|
||||
@_hideNameTag()
|
||||
|
||||
updateMoreChangeLabels: () ->
|
||||
return if !@$changesBefore or !@$changesAfter
|
||||
firstRow = @aceEditor.renderer.getFirstFullyVisibleRow()
|
||||
lastRow = @aceEditor.renderer.getLastFullyVisibleRow()
|
||||
changesBefore = 0
|
||||
changesAfter = 0
|
||||
for entry in @entries or []
|
||||
if entry.range.start.row < firstRow
|
||||
changesBefore += 1
|
||||
if entry.range.end.row > lastRow
|
||||
changesAfter += 1
|
||||
if changesBefore > 0
|
||||
@$changesBefore.find("span").text("#{changesBefore} more change#{if changesBefore > 1 then "s" else ""} above")
|
||||
@$changesBefore.show()
|
||||
else
|
||||
@$changesBefore.hide()
|
||||
if changesAfter > 0
|
||||
@$changesAfter.find("span").text("#{changesAfter} more change#{if changesAfter > 1 then "s" else ""} below")
|
||||
@$changesAfter.show()
|
||||
else
|
||||
@$changesAfter.hide()
|
||||
|
||||
resize: () ->
|
||||
@aceEditor.resize()
|
||||
|
||||
return DiffView
|
||||
|
||||
|
|
|
@ -19,6 +19,9 @@ define [
|
|||
@ide.editor.on "change:doc", () =>
|
||||
@hideEl()
|
||||
|
||||
@ide.editor.on "resize", () =>
|
||||
@diffView?.resize()
|
||||
|
||||
@$el.find(".track-changes-close").on "click", (e) =>
|
||||
e.preventDefault
|
||||
@hide()
|
||||
|
|
|
@ -891,6 +891,18 @@ i[class*="sprite-"] {
|
|||
}
|
||||
}
|
||||
|
||||
.project-description {
|
||||
textarea {
|
||||
min-height: 120px;
|
||||
}
|
||||
label {
|
||||
border-bottom: 1px solid #eeeeee;
|
||||
font-size: 1.2em;
|
||||
line-height: 1.8em;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-facebook {
|
||||
color: #ffffff;
|
||||
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
|
||||
|
|
|
@ -56,7 +56,12 @@
|
|||
}
|
||||
}
|
||||
|
||||
.deleted-change-background, .deleted-change-foreground, .inserted-change-background, .change-name-marker {
|
||||
.deleted-change-background,
|
||||
.deleted-change-foreground,
|
||||
.inserted-change-background,
|
||||
.change-name-marker,
|
||||
.changes-before,
|
||||
.changes-after {
|
||||
position: absolute;
|
||||
z-index: 2;
|
||||
}
|
||||
|
@ -71,6 +76,21 @@
|
|||
white-space: pre;
|
||||
}
|
||||
|
||||
.changes-before {
|
||||
top: 6px;
|
||||
right: 6px;
|
||||
}
|
||||
.changes-after {
|
||||
bottom: 6px;
|
||||
right: 6px;
|
||||
}
|
||||
.changes-before, .changes-after {
|
||||
padding: 4px 8px;
|
||||
background-color: #eee;
|
||||
border: 1px solid #999;
|
||||
.border-radius(3px);
|
||||
}
|
||||
|
||||
ul.change-list {
|
||||
li {
|
||||
position: relative;
|
||||
|
|
|
@ -12,7 +12,7 @@ describe "EditorUpdatesController", ->
|
|||
@client = new MockClient()
|
||||
@callback = sinon.stub()
|
||||
@EditorUpdatesController = SandboxedModule.require modulePath, requires:
|
||||
"logger-sharelatex": @logger = { error: sinon.stub() }
|
||||
"logger-sharelatex": @logger = { error: sinon.stub(), log: sinon.stub() }
|
||||
"./EditorRealTimeController" : @EditorRealTimeController = {}
|
||||
"../DocumentUpdater/DocumentUpdaterHandler" : @DocumentUpdaterHandler = {}
|
||||
"../Versioning/AutomaticSnapshotManager" : @AutomaticSnapshotManager = {}
|
||||
|
|
|
@ -12,20 +12,21 @@ describe "Email", ->
|
|||
|
||||
@settings =
|
||||
email:
|
||||
ses:
|
||||
key: "key"
|
||||
secret: "secret"
|
||||
transport: "ses"
|
||||
parameters:
|
||||
AWSAccessKeyID: "key"
|
||||
AWSSecretKey: "secret"
|
||||
fromAddress: "bob@bob.com"
|
||||
replyToAddress: "sally@gmail.com"
|
||||
|
||||
@sesClient =
|
||||
sendemail: sinon.stub()
|
||||
@ses =
|
||||
createClient: => @sesClient
|
||||
@sesClient =
|
||||
sendMail: sinon.stub()
|
||||
@ses =
|
||||
createTransport: => @sesClient
|
||||
@sender = SandboxedModule.require modulePath, requires:
|
||||
'node-ses': @ses
|
||||
'nodemailer': @ses
|
||||
"settings-sharelatex":@settings
|
||||
"logger-sharelatex":
|
||||
"logger-sharelatex":
|
||||
log:->
|
||||
warn:->
|
||||
err:->
|
||||
|
@ -38,44 +39,44 @@ describe "Email", ->
|
|||
describe "sendEmail", ->
|
||||
|
||||
it "should set the properties on the email to send", (done)->
|
||||
@sesClient.sendemail.callsArgWith(1)
|
||||
@sesClient.sendMail.callsArgWith(1)
|
||||
|
||||
@sender.sendEmail @opts, =>
|
||||
args = @sesClient.sendemail.args[0][0]
|
||||
args.message.should.equal @opts.html
|
||||
args = @sesClient.sendMail.args[0][0]
|
||||
args.html.should.equal @opts.html
|
||||
args.to.should.equal @opts.to
|
||||
args.subject.should.equal @opts.subject
|
||||
done()
|
||||
|
||||
it "should return the error", (done)->
|
||||
@sesClient.sendemail.callsArgWith(1, "error")
|
||||
it "should return the error", (done)->
|
||||
@sesClient.sendMail.callsArgWith(1, "error")
|
||||
@sender.sendEmail {}, (err)=>
|
||||
err.should.equal "error"
|
||||
done()
|
||||
|
||||
|
||||
it "should use the from address from settings", (done)->
|
||||
@sesClient.sendemail.callsArgWith(1)
|
||||
@sesClient.sendMail.callsArgWith(1)
|
||||
|
||||
@sender.sendEmail @opts, =>
|
||||
args = @sesClient.sendemail.args[0][0]
|
||||
args = @sesClient.sendMail.args[0][0]
|
||||
args.from.should.equal @settings.email.fromAddress
|
||||
done()
|
||||
|
||||
it "should use the reply to address from settings", (done)->
|
||||
@sesClient.sendemail.callsArgWith(1)
|
||||
@sesClient.sendMail.callsArgWith(1)
|
||||
|
||||
@sender.sendEmail @opts, =>
|
||||
args = @sesClient.sendemail.args[0][0]
|
||||
args = @sesClient.sendMail.args[0][0]
|
||||
args.replyTo.should.equal @settings.email.replyToAddress
|
||||
done()
|
||||
|
||||
|
||||
it "should use the reply to address in options as an override", (done)->
|
||||
@sesClient.sendemail.callsArgWith(1)
|
||||
@sesClient.sendMail.callsArgWith(1)
|
||||
|
||||
@opts.replyTo = "someone@else.com"
|
||||
@sender.sendEmail @opts, =>
|
||||
args = @sesClient.sendemail.args[0][0]
|
||||
args = @sesClient.sendMail.args[0][0]
|
||||
args.replyTo.should.equal @opts.replyTo
|
||||
done()
|
||||
|
|
Loading…
Reference in a new issue