diff --git a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee index 9653e302b5..7428bd4b40 100644 --- a/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee +++ b/services/web/app/coffee/Features/Authentication/AuthenticationController.coffee @@ -6,7 +6,6 @@ Metrics = require('../../infrastructure/Metrics') logger = require("logger-sharelatex") querystring = require('querystring') Url = require("url") -uid = require "uid" module.exports = AuthenticationController = login: (req, res, next = (error) ->) -> diff --git a/services/web/app/coffee/Features/Chat/ChatController.coffee b/services/web/app/coffee/Features/Chat/ChatController.coffee index a38c26a9fc..ddbc9c9a85 100644 --- a/services/web/app/coffee/Features/Chat/ChatController.coffee +++ b/services/web/app/coffee/Features/Chat/ChatController.coffee @@ -24,5 +24,5 @@ module.exports = if err? logger.err err:err, query:query, "problem getting messages from chat api" return res.send 500 - logger.log messages:messages, "sending messages to client" + logger.log length:messages?.length, "sending messages to client" res.send messages diff --git a/services/web/app/coffee/Features/Compile/CompileManager.coffee b/services/web/app/coffee/Features/Compile/CompileManager.coffee index 0089eb7b34..6e7056813d 100755 --- a/services/web/app/coffee/Features/Compile/CompileManager.coffee +++ b/services/web/app/coffee/Features/Compile/CompileManager.coffee @@ -71,9 +71,9 @@ module.exports = CompileManager = return callback(null, true) opts = endpointName:"auto_compile" - timeInterval:15 + timeInterval:20 subjectName:"everyone" - throttle: 15 + throttle: 25 rateLimiter.addCount opts, (err, canCompile)-> if err? canCompile = false diff --git a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee index e060c8ebd5..1ac7d78918 100644 --- a/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEntityHandler.coffee @@ -48,12 +48,13 @@ module.exports = ProjectEntityHandler = for folderPath, folder of folders for doc in folder.docs content = docContents[doc._id.toString()] - docs[path.join(folderPath, doc.name)] = { - _id: doc._id - name: doc.name - lines: content.lines - rev: content.rev - } + if content? + docs[path.join(folderPath, doc.name)] = { + _id: doc._id + name: doc.name + lines: content.lines + rev: content.rev + } logger.log count:_.keys(docs).length, project_id:project_id, "returning docs for project" callback null, docs diff --git a/services/web/app/coffee/Features/Project/ProjectLocator.coffee b/services/web/app/coffee/Features/Project/ProjectLocator.coffee index bfdb34142d..ead0dc3942 100644 --- a/services/web/app/coffee/Features/Project/ProjectLocator.coffee +++ b/services/web/app/coffee/Features/Project/ProjectLocator.coffee @@ -92,6 +92,11 @@ module.exports = Project.getProject project_or_id, "", (err, project)-> + if err? + logger.err err:err, project_or_id:project_or_id, "error getting project for finding element" + return callback(err) + if !project? + return callback("project could not be found for finding a element #{project_or_id}") if needlePath == '' || needlePath == '/' return callback(null, project.rootFolder[0]) diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionHandler.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionHandler.coffee index 4ff938240c..3d9d473f3e 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionHandler.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionHandler.coffee @@ -14,7 +14,6 @@ module.exports = self = @ clientTokenId = "" RecurlyWrapper.createSubscription user, subscriptionDetails, recurly_token_id, (error, recurlySubscription)-> - console.log recurlySubscription return callback(error) if error? SubscriptionUpdater.syncSubscription recurlySubscription, user._id, (error) -> return callback(error) if error? diff --git a/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee b/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee index cfef5c28b1..c3cbbc2b2a 100644 --- a/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee +++ b/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee @@ -53,7 +53,9 @@ module.exports = async.series [ (cb)-> User.update {_id: user._id}, {"$set":{holdingAccount:false}}, cb (cb)-> AuthenticationManager.setUserPassword user._id, userDetails.password, cb - (cb)-> NewsLetterManager.subscribe user, cb + (cb)-> + NewsLetterManager.subscribe user, -> + cb() #this can be slow, just fire it off (cb)-> emailOpts = first_name:user.first_name diff --git a/services/web/app/coffee/Features/Wiki/WikiController.coffee b/services/web/app/coffee/Features/Wiki/WikiController.coffee index 6d1a3a1052..9016a4dd41 100644 --- a/services/web/app/coffee/Features/Wiki/WikiController.coffee +++ b/services/web/app/coffee/Features/Wiki/WikiController.coffee @@ -3,37 +3,47 @@ settings = require("settings-sharelatex") logger = require("logger-sharelatex") ErrorController = require "../Errors/ErrorController" _ = require("underscore") +AuthenticationController = require("../Authentication/AuthenticationController") other_lngs = ["es"] module.exports = WikiController = - getPage: (req, res, next) -> - - page = req.url.replace(/^\/learn/, "").replace(/^\//, "") - if page == "" - page = "Main_Page" - logger.log page: page, "getting page from wiki" - if _.include(other_lngs, req.lng) - lngPage = "#{page}_#{req.lng}" + + _checkIfLoginIsNeeded: (req, res, next)-> + if settings.apis.wiki.requireLogin + AuthenticationController.requireLogin()(req, res, next) else - lngPage = page + next() - WikiController._getPageContent "Contents", (error, contents) -> - return next(error) if error? - WikiController._getPageContent lngPage, (error, pageData) -> + getPage: (req, res, next) -> + WikiController._checkIfLoginIsNeeded req, res, -> + + page = req.url.replace(/^\/learn/, "").replace(/^\//, "") + if page == "" + page = "Main_Page" + + logger.log page: page, "getting page from wiki" + if _.include(other_lngs, req.lng) + lngPage = "#{page}_#{req.lng}" + else + lngPage = page + + WikiController._getPageContent "Contents", (error, contents) -> return next(error) if error? - if pageData.content?.length > 280 - if _.include(other_lngs, req.lng) - pageData.title = pageData.title.slice(0, pageData.title.length - (req.lng.length+1) ) - WikiController._renderPage(pageData, contents, res) - else - WikiController._getPageContent page, (error, pageData) -> - return next(error) if error? + WikiController._getPageContent lngPage, (error, pageData) -> + return next(error) if error? + if pageData.content?.length > 280 + if _.include(other_lngs, req.lng) + pageData.title = pageData.title.slice(0, pageData.title.length - (req.lng.length+1) ) WikiController._renderPage(pageData, contents, res) + else + WikiController._getPageContent page, (error, pageData) -> + return next(error) if error? + WikiController._renderPage(pageData, contents, res) - + _getPageContent: (page, callback = (error, data = { content: "", title: "" }) ->) -> request { diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index 856dcd51a7..8a64cce94b 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -112,6 +112,11 @@ module.exports = (app)-> res.locals.formatPrice = SubscriptionFormatters.formatPrice next() + app.use (req, res, next)-> + res.locals.externalAuthenticationSystemUsed = -> + Settings.ldap? + next() + app.use (req, res, next)-> if req.session.user? res.locals.user = diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.jade index f623df7386..f0f2d6599a 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.jade @@ -27,11 +27,11 @@ html(itemscope, itemtype='http://schema.org/Product') meta(itemprop="name", content="ShareLaTeX, the Online LaTeX Editor") -if (typeof(meta) == "undefined") - meta(itemprop="description", content="An online LaTeX editor that's easy to use. No installation, real-time collaboration, version control, hundreds of LaTeX templates, and more.") + meta(itemprop="description", name="description", content="An online LaTeX editor that's easy to use. No installation, real-time collaboration, version control, hundreds of LaTeX templates, and more.") -else - meta(itemprop="description", content=meta) + meta(itemprop="description", name="description" , content=meta) - meta(itemprop="image", content="https://www.sharelatex.com/favicon.ico") + meta(itemprop="image", name="image", content="https://www.sharelatex.com/favicon.ico") - if (typeof(gaToken) != "undefined") script(type='text/javascript'). @@ -98,7 +98,10 @@ html(itemscope, itemtype='http://schema.org/Product') // Other plugins /127\.0\.0\.1:4001\/isrunning/i, // Cacaoweb /webappstoolbarba\.texthelp\.com\//i, - /metrics\.itunes\.apple\.com\.edgesuite\.net\//i + /metrics\.itunes\.apple\.com\.edgesuite\.net\//i, + /a\.disquscdn\.com/i, + /platform\.twitter\.com/i, + /pstatic\.datafastguru\.info/i ], shouldSendCallback: function(data) { // only send a fraction of errors diff --git a/services/web/app/views/project/editor/left-menu.jade b/services/web/app/views/project/editor/left-menu.jade index 479d501992..ecc4e78164 100644 --- a/services/web/app/views/project/editor/left-menu.jade +++ b/services/web/app/views/project/editor/left-menu.jade @@ -41,12 +41,12 @@ aside#left-menu.full-size( ) i.fa.fa-fw.fa-copy |    #{translate("copy_project")} - !{moduleIncludes("editorLeftMenu:actions", locals)} + != moduleIncludes("editorLeftMenu:actions", locals) if (moduleIncludesAvailable("editorLeftMenu:sync")) div(ng-show="!anonymous") h4() #{translate("sync")} - !{moduleIncludes("editorLeftMenu:sync", locals)} + != moduleIncludes("editorLeftMenu:sync", locals) h4(ng-show="!anonymous") #{translate("settings")} form.settings(ng-controller="SettingsController", ng-show="!anonymous") diff --git a/services/web/app/views/project/editor/track-changes.jade b/services/web/app/views/project/editor/track-changes.jade index 10f4070dee..9300172afc 100644 --- a/services/web/app/views/project/editor/track-changes.jade +++ b/services/web/app/views/project/editor/track-changes.jade @@ -8,9 +8,14 @@ div#trackChanges(ng-show="ui.view == 'track-changes'") ng-class="buttonClass" ng-click="startFreeTrial('track-changes')" ) #{translate("start_free_trial")} + |     + a.small(href, ng-click="toggleTrackChanges()") #{translate("cancel")} p.small(ng-show="startedFreeTrial") #{translate("refresh_page_after_starting_free_trial")} + .message(ng-show="project.owner._id != user.id") p #{translate("ask_proj_owner_to_upgrade_for_history")} + p + a.small(href, ng-click="toggleTrackChanges()") #{translate("cancel")} aside.change-list( ng-controller="TrackChangesListController" diff --git a/services/web/app/views/project/list/side-bar.jade b/services/web/app/views/project/list/side-bar.jade index 15fa928ddd..a287ce5a23 100644 --- a/services/web/app/views/project/list/side-bar.jade +++ b/services/web/app/views/project/list/side-bar.jade @@ -21,7 +21,7 @@ href, ng-click="openUploadProjectModal()" ) #{translate("upload_project")} - !{moduleIncludes("newProjectMenu", locals)} + != moduleIncludes("newProjectMenu", locals) if (templates) li.divider li.dropdown-header #{translate("templates")} diff --git a/services/web/app/views/referal/bonus.jade b/services/web/app/views/referal/bonus.jade index 6c83c67967..5b0183d9c1 100644 --- a/services/web/app/views/referal/bonus.jade +++ b/services/web/app/views/referal/bonus.jade @@ -143,8 +143,9 @@ block content function callback(response) { // document.getElementById('msg').innerHTML = "Post ID: " + response['post_id']; } - - FB.ui(obj, callback); + if (typeof FB !== "undefined" && FB !== null) { + FB.ui(obj, callback); + } } script(type="text/javascript"). diff --git a/services/web/app/views/user/settings.jade b/services/web/app/views/user/settings.jade index d4a213aefb..3969a2c1d0 100644 --- a/services/web/app/views/user/settings.jade +++ b/services/web/app/views/user/settings.jade @@ -19,19 +19,20 @@ block content h3 #{translate("update_account_info")} form(async-form="settings", name="settingsForm", method="POST", action="/user/settings", novalidate) input(type="hidden", name="_csrf", value=csrfToken) - .form-group - label(for='email') #{translate("email")} - input.form-control( - type='email', - name='email', - placeholder="email@example.com" - required, - ng-model="email", - ng-init="email = #{JSON.stringify(user.email)}", - ng-model-options="{ updateOn: 'blur' }" - ) - span.small.text-primary(ng-show="settingsForm.email.$invalid && settingsForm.email.$dirty") - | #{translate("must_be_email_address")} + if !externalAuthenticationSystemUsed() + .form-group + label(for='email') #{translate("email")} + input.form-control( + type='email', + name='email', + placeholder="email@example.com" + required, + ng-model="email", + ng-init="email = #{JSON.stringify(user.email)}", + ng-model-options="{ updateOn: 'blur' }" + ) + span.small.text-primary(ng-show="settingsForm.email.$invalid && settingsForm.email.$dirty") + | #{translate("must_be_email_address")} .form-group label(for='firstName').control-label #{translate("first_name")} input.form-control( @@ -51,75 +52,77 @@ block content type='submit', ng-disabled="settingsForm.$invalid" ) #{translate("update")} - - .col-md-5.col-md-offset-1 - h3 #{translate("change_password")} - form(async-form="changepassword", name="changePasswordForm", action="/user/password/update", method="POST", novalidate) - input(type="hidden", name="_csrf", value=csrfToken) - .form-group - label(for='currentPassword') #{translate("current_password")} - input.form-control( - type='password', - name='currentPassword', - placeholder='*********', - ng-model="currentPassword", - required - ) - span.small.text-primary(ng-show="changePasswordForm.currentPassword.$invalid && changePasswordForm.currentPassword.$dirty") - | #{translate("required")} - .form-group - label(for='newPassword1') #{translate("new_password")} - input.form-control( - id='newPassword1', - type='password', - name='newPassword1', - placeholder='*********', - ng-model="newPassword1", - required - ) - span.small.text-primary(ng-show="changePasswordForm.newPassword1.$invalid && changePasswordForm.newPassword1.$dirty") - | #{translate("required")} - .form-group - label(for='newPassword2') #{translate("confirm_new_password")} - input.form-control( - type='password', - name='newPassword2', - placeholder='*********', - ng-model="newPassword2", - equals="newPassword1" - ) - span.small.text-primary(ng-show="changePasswordForm.newPassword2.$invalid && changePasswordForm.newPassword2.$dirty") - | #{translate("doesnt_match")} - .actions - button.btn.btn-primary( - type='submit', - ng-disabled="changePasswordForm.$invalid" - ) #{translate("change")} + if !externalAuthenticationSystemUsed() + .col-md-5.col-md-offset-1 + h3 #{translate("change_password")} + form(async-form="changepassword", name="changePasswordForm", action="/user/password/update", method="POST", novalidate) + input(type="hidden", name="_csrf", value=csrfToken) + .form-group + label(for='currentPassword') #{translate("current_password")} + input.form-control( + type='password', + name='currentPassword', + placeholder='*********', + ng-model="currentPassword", + required + ) + span.small.text-primary(ng-show="changePasswordForm.currentPassword.$invalid && changePasswordForm.currentPassword.$dirty") + | #{translate("required")} + .form-group + label(for='newPassword1') #{translate("new_password")} + input.form-control( + id='newPassword1', + type='password', + name='newPassword1', + placeholder='*********', + ng-model="newPassword1", + required + ) + span.small.text-primary(ng-show="changePasswordForm.newPassword1.$invalid && changePasswordForm.newPassword1.$dirty") + | #{translate("required")} + .form-group + label(for='newPassword2') #{translate("confirm_new_password")} + input.form-control( + type='password', + name='newPassword2', + placeholder='*********', + ng-model="newPassword2", + equals="newPassword1" + ) + span.small.text-primary(ng-show="changePasswordForm.newPassword2.$invalid && changePasswordForm.newPassword2.$dirty") + | #{translate("doesnt_match")} + .actions + button.btn.btn-primary( + type='submit', + ng-disabled="changePasswordForm.$invalid" + ) #{translate("change")} | !{moduleIncludes("userSettings", locals)} hr - p.small - | #{translate("newsletter_info_and_unsubscribe")} - a( - href, - ng-click="unsubscribe()", - ng-show="subscribed && !unsubscribing" - ) #{translate("unsubscribe")} - span( - ng-show="unsubscribing" - ) - i.fa.fa-spin.fa-refresh - | #{translate("unsubscribing")} - span.text-success( - ng-show="!subscribed" - ) - i.fa.fa-check - | #{translate("unsubscribed")} + if !externalAuthenticationSystemUsed() - p #{translate("need_to_leave")} - a(href, ng-click="deleteAccount()") #{translate("delete_your_account")} + p.small + | #{translate("newsletter_info_and_unsubscribe")} + a( + href, + ng-click="unsubscribe()", + ng-show="subscribed && !unsubscribing" + ) #{translate("unsubscribe")} + span( + ng-show="unsubscribing" + ) + i.fa.fa-spin.fa-refresh + | #{translate("unsubscribing")} + span.text-success( + ng-show="!subscribed" + ) + i.fa.fa-check + | #{translate("unsubscribed")} + + p #{translate("need_to_leave")} + a(href, ng-click="deleteAccount()") #{translate("delete_your_account")} script(type='text/ng-template', id='deleteAccountModalTemplate') diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index 24d7a80e12..79c100bbbe 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -331,3 +331,19 @@ module.exports = proxyUrls: {} reloadModuleViewsOnEachRequest: true + + # ShareLaTeX Server Pro options (https://www.sharelatex.com/university/onsite.html) + # ---------- + + + + # ldap: + # host: 'ldap://ldap.forumsys.com' + # dnObj: 'uid' + # dnSuffix: 'dc=example,dc=com' + # failMessage: 'LDAP User Fail' + # fieldName: 'LDAP User' + # placeholder: 'LDAP User ID' + # emailAtt: 'mail' + + diff --git a/services/web/package.json b/services/web/package.json index cec26f13e7..337f42390a 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -1,6 +1,6 @@ { "name": "web-sharelatex", - "version": "0.1.2", + "version": "0.1.3", "description": "The HTTP front end for ShareLaTeX", "repository": { "type": "git", @@ -19,6 +19,7 @@ "express": "3.3.4", "fairy": "0.0.2", "jade": "~1.3.1", + "ldapjs": "^0.7.1", "logger-sharelatex": "git+https://github.com/sharelatex/logger-sharelatex.git#v1.0.0", "lynx": "0.1.1", "metrics-sharelatex": "git+https://github.com/sharelatex/metrics-sharelatex.git#v1.0.0", @@ -39,7 +40,6 @@ "settings-sharelatex": "git+https://github.com/sharelatex/settings-sharelatex.git#v1.0.0", "socket.io": "0.9.16", "translations-sharelatex": "git+https://github.com/sharelatex/translations-sharelatex.git#master", - "uid": "0.0.2", "underscore": "1.6.0", "underscore.string": "^3.0.2", "v8-profiler": "^5.2.3", diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee index 390406e5fd..c80cf86f3b 100644 --- a/services/web/public/coffee/ide.coffee +++ b/services/web/public/coffee/ide.coffee @@ -83,7 +83,7 @@ define [ $scope.$on "project:joined", () -> return if inited inited = true - if $scope.project.deletedByExternalDataSource + if $scope?.project?.deletedByExternalDataSource ide.showGenericMessageModal("Project Renamed or Deleted", """ This project has either been renamed or deleted by an external data source such as Dropbox. We don't want to delete your data on ShareLaTeX, so this project still contains your history and collaborators. diff --git a/services/web/public/coffee/ide/chat/controllers/ChatButtonController.coffee b/services/web/public/coffee/ide/chat/controllers/ChatButtonController.coffee index f4e1e27df0..cf97c4ab9b 100644 --- a/services/web/public/coffee/ide/chat/controllers/ChatButtonController.coffee +++ b/services/web/public/coffee/ide/chat/controllers/ChatButtonController.coffee @@ -15,7 +15,7 @@ define [ $scope.$on "chat:newMessage", (e, message) -> if message? - if message.user.id != ide.$scope.user.id + if message?.user?.id != ide.$scope.user.id if !$scope.ui.chatOpen $scope.unreadMessages += 1 flashTitle() diff --git a/services/web/public/coffee/ide/chat/controllers/ChatController.coffee b/services/web/public/coffee/ide/chat/controllers/ChatController.coffee index 80a9e9a3e8..85e1818f0b 100644 --- a/services/web/public/coffee/ide/chat/controllers/ChatController.coffee +++ b/services/web/public/coffee/ide/chat/controllers/ChatController.coffee @@ -30,7 +30,7 @@ define [ $scope.loadMoreMessages = -> chatMessages.loadMoreMessages() - $scope.linkify = (message)-> + $scope.linkify = (message = "")-> return autolinker.link(message) \ No newline at end of file diff --git a/services/web/public/coffee/ide/chat/directives/mathjax.coffee b/services/web/public/coffee/ide/chat/directives/mathjax.coffee index 431244a30c..08e167ee66 100644 --- a/services/web/public/coffee/ide/chat/directives/mathjax.coffee +++ b/services/web/public/coffee/ide/chat/directives/mathjax.coffee @@ -15,12 +15,12 @@ define [ processEscapes: true skipStartupTypeset: true - MathJax.Hub.Config(mathjaxConfig); + MathJax?.Hub?.Config(mathjaxConfig); App.directive "mathjax", () -> return { link: (scope, element, attrs) -> setTimeout () -> - MathJax.Hub.Queue(["Typeset", MathJax.Hub, element.get(0)]) + MathJax?.Hub?.Queue(["Typeset", MathJax?.Hub, element.get(0)]) , 0 } \ No newline at end of file diff --git a/services/web/public/coffee/ide/chat/services/chatMessages.coffee b/services/web/public/coffee/ide/chat/services/chatMessages.coffee index f9e47f6e99..36bcf9540f 100644 --- a/services/web/public/coffee/ide/chat/services/chatMessages.coffee +++ b/services/web/public/coffee/ide/chat/services/chatMessages.coffee @@ -18,7 +18,7 @@ define [ justSent = false ide.socket.on "new-chat-message", (message) => - if message.user.id == ide.$scope.user.id and justSent + if message?.user?.id == ide.$scope.user.id and justSent # Nothing to do else ide.$scope.$apply () -> @@ -51,9 +51,9 @@ define [ if messages.length < MESSAGE_LIMIT chat.state.atEnd = true if !messages.reverse? - Raven?.captureException(new Error("messages has no reverse property #{JSON.stringify(messages)}")) + Raven?.captureException(new Error("messages has no reverse property #{typeof(messages)}")) if typeof messages.reverse isnt 'function' - Raven?.captureException(new Error("messages.reverse not a function #{typeof(messages.reverse)} #{JSON.stringify(messages)}")) + Raven?.captureException(new Error("messages.reverse not a function #{typeof(messages.reverse)} #{typeof(messages)}")) chat.state.errored = true else messages.reverse() @@ -65,7 +65,7 @@ define [ prependMessage = (message) -> firstMessage = chat.state.messages[0] shouldGroup = firstMessage? and - firstMessage.user.id == message.user.id and + firstMessage.user.id == message?.user?.id and firstMessage.timestamp - message.timestamp < TIMESTAMP_GROUP_SIZE if shouldGroup firstMessage.timestamp = message.timestamp @@ -86,7 +86,7 @@ define [ lastMessage = chat.state.messages[chat.state.messages.length - 1] shouldGroup = lastMessage? and - lastMessage.user.id == message.user.id and + lastMessage.user.id == message?.user?.id and message.timestamp - lastMessage.timestamp < TIMESTAMP_GROUP_SIZE if shouldGroup lastMessage.timestamp = message.timestamp diff --git a/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee b/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee index 13b0f8abf6..9ea27ae62c 100644 --- a/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee +++ b/services/web/public/coffee/ide/file-tree/FileTreeManager.coffee @@ -162,13 +162,8 @@ define [ return if !path? return path.split("/").slice(0, -1).join("/") - # forEachFolder: (callback) -> - # @forEachEntity (entity) -> - # if entity.type == "folder" - # callback(entity) - loadRootFolder: () -> - @$scope.rootFolder = @_parseFolder(@$scope.project.rootFolder[0]) + @$scope.rootFolder = @_parseFolder(@$scope?.project?.rootFolder[0]) _parseFolder: (rawFolder) -> folder = { @@ -306,6 +301,7 @@ define [ return (child_path.slice(0, parent_path.length) == parent_path) _deleteEntityFromScope: (entity, options = { moveToDeleted: true }) -> + return if !entity? parent_folder = null @forEachEntity (possible_entity, folder) -> if possible_entity == entity diff --git a/services/web/public/coffee/ide/file-tree/controllers/FileTreeFolderController.coffee b/services/web/public/coffee/ide/file-tree/controllers/FileTreeFolderController.coffee index f4b7b1deca..d6bbcc733c 100644 --- a/services/web/public/coffee/ide/file-tree/controllers/FileTreeFolderController.coffee +++ b/services/web/public/coffee/ide/file-tree/controllers/FileTreeFolderController.coffee @@ -17,7 +17,7 @@ define [ # We need this here as well as in FileTreeController # since the file-entity diretive creates a new scope # that doesn't inherit from previous scopes. - return '0' if entity.type == "folder" + return '0' if entity?.type == "folder" return '1' $scope.openNewDocModal = () -> diff --git a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee index 08d1543b31..af5036dd41 100644 --- a/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee +++ b/services/web/public/coffee/ide/pdf/controllers/PdfController.coffee @@ -60,6 +60,9 @@ define [ IGNORE_FILES = ["output.fls", "output.fdb_latexmk"] $scope.pdf.outputFiles = [] + + if !response.outputFiles? + return for file in response.outputFiles if IGNORE_FILES.indexOf(file.path) == -1 # Turn 'output.blg' into 'blg file'. @@ -80,16 +83,17 @@ define [ $scope.pdf.logEntryAnnotations = {} for entry in logEntries.all - entry.file = normalizeFilePath(entry.file) + if entry.file? + entry.file = normalizeFilePath(entry.file) - entity = ide.fileTreeManager.findEntityByPath(entry.file) - if entity? - $scope.pdf.logEntryAnnotations[entity.id] ||= [] - $scope.pdf.logEntryAnnotations[entity.id].push { - row: entry.line - 1 - type: if entry.level == "error" then "error" else "warning" - text: entry.message - } + entity = ide.fileTreeManager.findEntityByPath(entry.file) + if entity? + $scope.pdf.logEntryAnnotations[entity.id] ||= [] + $scope.pdf.logEntryAnnotations[entity.id].push { + row: entry.line - 1 + type: if entry.level == "error" then "error" else "warning" + text: entry.message + } .error () -> $scope.pdf.logEntries = [] diff --git a/services/web/public/coffee/ide/pdfng/directives/pdfHighlights.coffee b/services/web/public/coffee/ide/pdfng/directives/pdfHighlights.coffee index fc2b72bd79..45ec988c38 100644 --- a/services/web/public/coffee/ide/pdfng/directives/pdfHighlights.coffee +++ b/services/web/public/coffee/ide/pdfng/directives/pdfHighlights.coffee @@ -24,6 +24,6 @@ define [ clearHighlights: () -> for h in @highlightElements - h.remove() + h?.remove() @highlightElements = [] ] diff --git a/services/web/public/coffee/main/subscription-dashboard.coffee b/services/web/public/coffee/main/subscription-dashboard.coffee index 55834dd4fa..5cf436fa9d 100644 --- a/services/web/public/coffee/main/subscription-dashboard.coffee +++ b/services/web/public/coffee/main/subscription-dashboard.coffee @@ -45,7 +45,10 @@ define [ pricing.plan(planCode, { quantity: 1 }).currency(MultiCurrencyPricing.currencyCode).done (price)-> totalPriceExTax = parseFloat(price.next.total) $scope.$evalAsync () -> - $scope.prices[planCode] = $scope.currencySymbol + (totalPriceExTax + (totalPriceExTax * taxRate)) + taxAmmount = totalPriceExTax * taxRate + if isNaN(taxAmmount) + taxAmmount = 0 + $scope.prices[planCode] = $scope.currencySymbol + (totalPriceExTax + taxAmmount) price = "" diff --git a/services/web/public/stylesheets/components/fineupload.less b/services/web/public/stylesheets/components/fineupload.less index 8e84c065a0..3347edbc47 100644 --- a/services/web/public/stylesheets/components/fineupload.less +++ b/services/web/public/stylesheets/components/fineupload.less @@ -84,7 +84,6 @@ } .qq-upload-spinner { display: inline-block; - background: url("loading.gif"); width: 15px; height: 15px; vertical-align: text-bottom; diff --git a/services/web/test/UnitTests/coffee/Compile/CompileManagerTests.coffee b/services/web/test/UnitTests/coffee/Compile/CompileManagerTests.coffee index fbcf6787cf..9f4031e776 100644 --- a/services/web/test/UnitTests/coffee/Compile/CompileManagerTests.coffee +++ b/services/web/test/UnitTests/coffee/Compile/CompileManagerTests.coffee @@ -241,9 +241,9 @@ describe "CompileManager", -> @ratelimiter.addCount.callsArgWith(1, null, true) @CompileManager._checkIfAutoCompileLimitHasBeenHit true, (err, canCompile)=> args = @ratelimiter.addCount.args[0][0] - args.throttle.should.equal 15 + args.throttle.should.equal 25 args.subjectName.should.equal "everyone" - args.timeInterval.should.equal 15 + args.timeInterval.should.equal 20 args.endpointName.should.equal "auto_compile" canCompile.should.equal true done() diff --git a/services/web/test/UnitTests/coffee/Project/ProjectLocatorTests.coffee b/services/web/test/UnitTests/coffee/Project/ProjectLocatorTests.coffee index 08c4da865a..b1e2cb9139 100644 --- a/services/web/test/UnitTests/coffee/Project/ProjectLocatorTests.coffee +++ b/services/web/test/UnitTests/coffee/Project/ProjectLocatorTests.coffee @@ -6,7 +6,7 @@ modulePath = "../../../../app/js/Features/Project/ProjectLocator" SandboxedModule = require('sandboxed-module') sinon = require('sinon') Errors = require "../../../../app/js/errors" - +expect = require("chai").expect Project = class Project project = _id : "1234566", rootFolder:[] @@ -221,6 +221,7 @@ describe 'project model', -> assert.equal element, undefined done() + describe "where duplicate folder exists", -> beforeEach -> @@ -266,7 +267,23 @@ describe 'project model', -> element.name.should.equal "other.tex" done() - + + describe "with a null project", -> + beforeEach -> + @project = + rootFolder:[ + folders: [] + fileRefs: [] + docs: [{name:"main.tex"}, null, {name:"other.tex"}] + ] + Project.getProject = sinon.stub() + Project.getProject.callsArgWith(2, null) + + it "should not crash with a null", (done)-> + callback = sinon.stub() + @locator.findElementByPath project._id, "/other.tex", (err, element)-> + expect(err).to.exist + done() describe 'finding a project by user_id and project name', ()-> diff --git a/services/web/test/smoke/coffee/SmokeTests.coffee b/services/web/test/smoke/coffee/SmokeTests.coffee index a882db1998..d14a334b99 100644 --- a/services/web/test/smoke/coffee/SmokeTests.coffee +++ b/services/web/test/smoke/coffee/SmokeTests.coffee @@ -10,6 +10,18 @@ cookeFilePath = "/tmp/smoke-test-cookie-#{port}.txt" buildUrl = (path) -> " -b #{cookeFilePath} --resolve 'smoke#{Settings.cookieDomain}:#{port}:127.0.0.1' http://smoke#{Settings.cookieDomain}:#{port}/#{path}?setLng=en" logger = require "logger-sharelatex" +# Change cookie to be non secure so curl will send it +convertCookieFile = (callback) -> + fs = require("fs") + fs.readFile cookeFilePath, "utf8", (err, data) -> + return callback(err) if err + firstTrue = data.indexOf("TRUE") + secondTrue = data.indexOf("TRUE", firstTrue+4) + result = data.slice(0, secondTrue)+"FALSE"+data.slice(secondTrue+4) + fs.writeFile cookeFilePath, result, "utf8", (err) -> + return callback(err) if err + callback() + describe "Opening", -> before (done) -> @@ -26,20 +38,14 @@ describe "Opening", -> return done("smoke test: does not have csrf token") csrf = csrfMatches[1] - # Change cookie to be non secure so curl will send it - fs = require("fs") - fs.readFile cookeFilePath, "utf8", (err, data) -> - return done(err) if err - firstTrue = data.indexOf("TRUE") - secondTrue = data.indexOf("TRUE", firstTrue+4) - result = data.slice(0, secondTrue)+"FALSE"+data.slice(secondTrue+4) - fs.writeFile cookeFilePath, result, "utf8", (err) -> - return done(err) if err - - command = """ - curl -H "Content-Type: application/json" -H "X-Forwarded-Proto: https" -d '{"_csrf":"#{csrf}", "email":"#{Settings.smokeTest.user}", "password":"#{Settings.smokeTest.password}"}' #{buildUrl('register')} - """ - child.exec command, done + convertCookieFile (err) -> + return done(err) if err? + command = """ + curl -c #{cookeFilePath} -H "Content-Type: application/json" -H "X-Forwarded-Proto: https" -d '{"_csrf":"#{csrf}", "email":"#{Settings.smokeTest.user}", "password":"#{Settings.smokeTest.password}"}' #{buildUrl('register')} + """ + child.exec command, (err) -> + return done(err) if err? + convertCookieFile done after (done)-> command = """