diff --git a/services/web/app/coffee/Features/Editor/EditorUpdatesController.coffee b/services/web/app/coffee/Features/Editor/EditorUpdatesController.coffee index 4a0162eca0..2e8748281f 100644 --- a/services/web/app/coffee/Features/Editor/EditorUpdatesController.coffee +++ b/services/web/app/coffee/Features/Editor/EditorUpdatesController.coffee @@ -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) -> diff --git a/services/web/app/coffee/Features/Email/EmailSender.coffee b/services/web/app/coffee/Features/Email/EmailSender.coffee index 35b629de6a..b4b3954d31 100644 --- a/services/web/app/coffee/Features/Email/EmailSender.coffee +++ b/services/web/app/coffee/Features/Email/EmailSender.coffee @@ -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) - \ No newline at end of file + diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 764ae8afd6..a4f0837bc1 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -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 diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade index 50d595cd89..396d8a4cb1 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.jade @@ -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}"; diff --git a/services/web/app/views/modals.jade b/services/web/app/views/modals.jade index ffd110760f..ef85ce7da9 100644 --- a/services/web/app/views/modals.jade +++ b/services/web/app/views/modals.jade @@ -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 diff --git a/services/web/app/views/templates.jade b/services/web/app/views/templates.jade index 586719a62d..4ab4640e1f 100644 --- a/services/web/app/views/templates.jade +++ b/services/web/app/views/templates.jade @@ -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 diff --git a/services/web/config/settings.development.coffee b/services/web/config/settings.development.coffee index eefda7b1a5..8b6344fbce 100644 --- a/services/web/config/settings.development.coffee +++ b/services/web/config/settings.development.coffee @@ -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 # ------------------- diff --git a/services/web/package.json b/services/web/package.json index 46830b5875..4d08ecc1f2 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -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", diff --git a/services/web/public/coffee/auto-complete/Snippets.coffee b/services/web/public/coffee/auto-complete/Snippets.coffee index 06332562c9..6856acbac0 100644 --- a/services/web/public/coffee/auto-complete/Snippets.coffee +++ b/services/web/public/coffee/auto-complete/Snippets.coffee @@ -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: """ diff --git a/services/web/public/coffee/editor/Document.coffee b/services/web/public/coffee/editor/Document.coffee index 41cf1f7e11..d08d4baca4 100644 --- a/services/web/public/coffee/editor/Document.coffee +++ b/services/web/public/coffee/editor/Document.coffee @@ -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 diff --git a/services/web/public/coffee/editor/Editor.coffee b/services/web/public/coffee/editor/Editor.coffee index d4313fe6eb..c7eb19d9d8 100644 --- a/services/web/public/coffee/editor/Editor.coffee +++ b/services/web/public/coffee/editor/Editor.coffee @@ -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 diff --git a/services/web/public/coffee/editor/ShareJsDoc.coffee b/services/web/public/coffee/editor/ShareJsDoc.coffee index e887ddf588..78d3232994 100644 --- a/services/web/public/coffee/editor/ShareJsDoc.coffee +++ b/services/web/public/coffee/editor/ShareJsDoc.coffee @@ -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 diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee index e98163f87d..5b6eb591a7 100644 --- a/services/web/public/coffee/ide.coffee +++ b/services/web/public/coffee/ide.coffee @@ -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 = "" - $('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) diff --git a/services/web/public/coffee/ide/ConnectionManager.coffee b/services/web/public/coffee/ide/ConnectionManager.coffee index 9556ec1d1c..18be9e007e 100644 --- a/services/web/public/coffee/ide/ConnectionManager.coffee +++ b/services/web/public/coffee/ide/ConnectionManager.coffee @@ -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') diff --git a/services/web/public/coffee/project-members/ProjectMembersManager.coffee b/services/web/public/coffee/project-members/ProjectMembersManager.coffee index f68949f613..cb92104a0e 100644 --- a/services/web/public/coffee/project-members/ProjectMembersManager.coffee +++ b/services/web/public/coffee/project-members/ProjectMembersManager.coffee @@ -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: -> diff --git a/services/web/public/coffee/track-changes/DiffView.coffee b/services/web/public/coffee/track-changes/DiffView.coffee index 73006e2f3d..7fc95e77ba 100644 --- a/services/web/public/coffee/track-changes/DiffView.coffee +++ b/services/web/public/coffee/track-changes/DiffView.coffee @@ -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 = $("
") @$nameTagEl.css({ position: "absolute" @@ -110,6 +115,19 @@ define [ @$nameTagEl.hide() @$ace.append(@$nameTagEl) + insertMoreChangeLabels: () -> + @$changesBefore = $("
") + @$changesAfter = $("
") + @$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 diff --git a/services/web/public/coffee/track-changes/TrackChangesManager.coffee b/services/web/public/coffee/track-changes/TrackChangesManager.coffee index 4e35ebe45f..24079e6832 100644 --- a/services/web/public/coffee/track-changes/TrackChangesManager.coffee +++ b/services/web/public/coffee/track-changes/TrackChangesManager.coffee @@ -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() diff --git a/services/web/public/stylesheets/less/editor.less b/services/web/public/stylesheets/less/editor.less index 4844db82ed..31f93bf488 100644 --- a/services/web/public/stylesheets/less/editor.less +++ b/services/web/public/stylesheets/less/editor.less @@ -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); diff --git a/services/web/public/stylesheets/less/trackchanges.less b/services/web/public/stylesheets/less/trackchanges.less index d4adeb4495..aa1f697d76 100644 --- a/services/web/public/stylesheets/less/trackchanges.less +++ b/services/web/public/stylesheets/less/trackchanges.less @@ -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; diff --git a/services/web/test/UnitTests/coffee/Editor/EditorUpdatesControllerTests.coffee b/services/web/test/UnitTests/coffee/Editor/EditorUpdatesControllerTests.coffee index 03217801e5..1d7f519168 100644 --- a/services/web/test/UnitTests/coffee/Editor/EditorUpdatesControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Editor/EditorUpdatesControllerTests.coffee @@ -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 = {} diff --git a/services/web/test/UnitTests/coffee/Email/EmailSenderTests.coffee b/services/web/test/UnitTests/coffee/Email/EmailSenderTests.coffee index 92b5908173..555e9c1a1e 100644 --- a/services/web/test/UnitTests/coffee/Email/EmailSenderTests.coffee +++ b/services/web/test/UnitTests/coffee/Email/EmailSenderTests.coffee @@ -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()