diff --git a/services/web/Gruntfile.coffee b/services/web/Gruntfile.coffee index 7ae6cba36c..f2ce93f671 100644 --- a/services/web/Gruntfile.coffee +++ b/services/web/Gruntfile.coffee @@ -230,11 +230,11 @@ module.exports = (grunt) -> sed: version: - path: "app/views/sentry.jade" + path: "app/views/sentry.pug" pattern: '@@COMMIT@@', replacement: '<%= commit %>', release: - path: "app/views/sentry.jade" + path: "app/views/sentry.pug" pattern: "@@RELEASE@@" replacement: process.env.BUILD_NUMBER || "(unknown build)" @@ -397,5 +397,5 @@ module.exports = (grunt) -> grunt.registerTask 'default', 'run' - grunt.registerTask 'version', "Write the version number into sentry.jade", ['git-rev-parse', 'sed'] + grunt.registerTask 'version', "Write the version number into sentry.pug", ['git-rev-parse', 'sed'] diff --git a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee index 1fde81f5c9..a2314da57f 100644 --- a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee +++ b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee @@ -23,7 +23,7 @@ module.exports = CollaboratorsInviteController = return next(err) res.json({invites: invites}) - _checkShouldInviteEmail: (sendingUser, email, callback=(err, shouldAllowInvite)->) -> + _checkShouldInviteEmail: (email, callback=(err, shouldAllowInvite)->) -> if Settings.restrictInvitesToExistingAccounts == true logger.log {email}, "checking if user exists with this email" UserGetter.getUser {email: email}, {_id: 1}, (err, user) -> @@ -31,19 +31,20 @@ module.exports = CollaboratorsInviteController = userExists = user? and user?._id? callback(null, userExists) else - UserGetter.getUser sendingUser._id, {features:1, _id:1}, (err, user)-> - if err? - return callback(err) - collabLimit = user?.features?.collaborators || 1 - if collabLimit == -1 - collabLimit = 20 - collabLimit = collabLimit * 10 - opts = - endpointName: "invite_to_project" - timeInterval: 60 * 30 - subjectName: sendingUser._id - throttle: collabLimit - rateLimiter.addCount opts, callback + callback(null, true) + + _checkRateLimit: (user_id, callback = (error) ->) -> + LimitationsManager.allowedNumberOfCollaboratorsForUser user_id, (err, collabLimit = 1)-> + return callback(err) if err? + if collabLimit == -1 + collabLimit = 20 + collabLimit = collabLimit * 10 + opts = + endpointName: "invite-to-project-by-user-id" + timeInterval: 60 * 30 + subjectName: user_id + throttle: collabLimit + rateLimiter.addCount opts, callback inviteToProject: (req, res, next) -> projectId = req.params.Project_id @@ -64,20 +65,24 @@ module.exports = CollaboratorsInviteController = if !email? or email == "" logger.log {projectId, email, sendingUserId}, "invalid email address" return res.sendStatus(400) - CollaboratorsInviteController._checkShouldInviteEmail sendingUser, email, (err, shouldAllowInvite)-> - if err? - logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address" - return next(err) - if !shouldAllowInvite - logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address" - return res.json {invite: null, error: 'cannot_invite_non_user'} - CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) -> + CollaboratorsInviteController._checkRateLimit sendingUserId, (error, underRateLimit) -> + return next(error) if error? + if !underRateLimit + return res.sendStatus(429) + CollaboratorsInviteController._checkShouldInviteEmail email, (err, shouldAllowInvite)-> if err? - logger.err {projectId, email, sendingUserId}, "error creating project invite" + logger.err {err, email, projectId, sendingUserId}, "error checking if we can invite this email address" return next(err) - logger.log {projectId, email, sendingUserId}, "invite created" - EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true}) - return res.json {invite: invite} + if !shouldAllowInvite + logger.log {email, projectId, sendingUserId}, "not allowed to send an invite to this email address" + return res.json {invite: null, error: 'cannot_invite_non_user'} + CollaboratorsInviteHandler.inviteToProject projectId, sendingUser, email, privileges, (err, invite) -> + if err? + logger.err {projectId, email, sendingUserId}, "error creating project invite" + return next(err) + logger.log {projectId, email, sendingUserId}, "invite created" + EditorRealTimeController.emitToRoom(projectId, 'project:membership:changed', {invites: true}) + return res.json {invite: invite} revokeInvite: (req, res, next) -> projectId = req.params.Project_id diff --git a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee index 0e6cd8876c..ecca8ab86f 100644 --- a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee +++ b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee @@ -80,7 +80,7 @@ module.exports = CollaboratorsInviteHandler = # Send email and notification in background CollaboratorsInviteHandler._sendMessages projectId, sendingUser, invite, (err) -> if err? - logger.err {projectId, email}, "error sending messages for invite" + logger.err {err, projectId, email}, "error sending messages for invite" callback(null, invite) diff --git a/services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee b/services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee index 8b130d27db..ea7e1f89f8 100644 --- a/services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee +++ b/services/web/app/coffee/Features/Collaborators/CollaboratorsRouter.coffee @@ -22,13 +22,13 @@ module.exports = webRouter.post( '/project/:Project_id/invite', RateLimiterMiddlewear.rateLimit({ - endpointName: "invite-to-project" + endpointName: "invite-to-project-by-project-id" params: ["Project_id"] maxRequests: 100 timeInterval: 60 * 10 }), RateLimiterMiddlewear.rateLimit({ - endpointName: "invite-to-project-ip" + endpointName: "invite-to-project-by-ip" ipOnly:true maxRequests: 100 timeInterval: 60 * 10 diff --git a/services/web/app/coffee/Features/Email/EmailBuilder.coffee b/services/web/app/coffee/Features/Email/EmailBuilder.coffee index 5360adb7a8..0a06a2a175 100644 --- a/services/web/app/coffee/Features/Email/EmailBuilder.coffee +++ b/services/web/app/coffee/Features/Email/EmailBuilder.coffee @@ -97,7 +97,7 @@ Thank you templates.projectInvite = - subject: _.template "<%= project.name.slice(0, 40) %> - shared by <%= owner.email %>" + subject: _.template "<%= project.name %> - shared by <%= owner.email %>" layout: BaseWithHeaderEmailLayout type:"notification" plainTextTemplate: _.template """ @@ -111,16 +111,16 @@ Thank you """ compiledTemplate: (opts) -> SingleCTAEmailBody({ - title: "#{ opts.project.name.slice(0, 40) } – shared by #{ opts.owner.email }" + title: "#{ opts.project.name } – shared by #{ opts.owner.email }" greeting: "Hi," - message: "#{ opts.owner.email } wants to share “#{ opts.project.name.slice(0, 40) }” with you." + message: "#{ opts.owner.email } wants to share “#{ opts.project.name }” with you." secondaryMessage: null ctaText: "View project" ctaURL: opts.inviteUrl gmailGoToAction: target: opts.inviteUrl name: "View project" - description: "Join #{ opts.project.name.slice(0, 40) } at ShareLaTeX" + description: "Join #{ opts.project.name } at ShareLaTeX" }) templates.completeJoinGroupAccount = diff --git a/services/web/app/coffee/Features/StaticPages/HomeController.coffee b/services/web/app/coffee/Features/StaticPages/HomeController.coffee index 6675d55333..c1a8c46323 100755 --- a/services/web/app/coffee/Features/StaticPages/HomeController.coffee +++ b/services/web/app/coffee/Features/StaticPages/HomeController.coffee @@ -7,7 +7,7 @@ fs = require "fs" ErrorController = require "../Errors/ErrorController" AuthenticationController = require('../Authentication/AuthenticationController') -homepageExists = fs.existsSync Path.resolve(__dirname + "/../../../views/external/home.jade") +homepageExists = fs.existsSync Path.resolve(__dirname + "/../../../views/external/home.pug") module.exports = HomeController = index : (req,res)-> @@ -28,10 +28,10 @@ module.exports = HomeController = externalPage: (page, title) -> return (req, res, next = (error) ->) -> - path = Path.resolve(__dirname + "/../../../views/external/#{page}.jade") + path = Path.resolve(__dirname + "/../../../views/external/#{page}.pug") fs.exists path, (exists) -> # No error in this callback - old method in Node.js! if exists - res.render "external/#{page}.jade", + res.render "external/#{page}.pug", title: title else ErrorController.notFound(req, res, next) diff --git a/services/web/app/coffee/Features/Subscription/LimitationsManager.coffee b/services/web/app/coffee/Features/Subscription/LimitationsManager.coffee index 59a0748f36..ec29b9257a 100644 --- a/services/web/app/coffee/Features/Subscription/LimitationsManager.coffee +++ b/services/web/app/coffee/Features/Subscription/LimitationsManager.coffee @@ -1,20 +1,25 @@ logger = require("logger-sharelatex") Project = require("../../models/Project").Project -User = require("../../models/User").User +UserGetter = require("../User/UserGetter") SubscriptionLocator = require("./SubscriptionLocator") Settings = require("settings-sharelatex") CollaboratorsHandler = require("../Collaborators/CollaboratorsHandler") CollaboratorsInvitesHandler = require("../Collaborators/CollaboratorsInviteHandler") module.exports = - allowedNumberOfCollaboratorsInProject: (project_id, callback) -> - getOwnerOfProject project_id, (error, owner)-> + Project.findById project_id, 'owner_ref', (error, project) => return callback(error) if error? - if owner.features? and owner.features.collaborators? - callback null, owner.features.collaborators + @allowedNumberOfCollaboratorsForUser project.owner_ref, callback + + allowedNumberOfCollaboratorsForUser: (user_id, callback) -> + UserGetter.getUser user_id, {features: 1}, (error, user) -> + return callback(error) if error? + if user.features? and user.features.collaborators? + callback null, user.features.collaborators else callback null, Settings.defaultPlanCode.collaborators + canAddXCollaborators: (project_id, x_collaborators, callback = (error, allowed)->) -> @allowedNumberOfCollaboratorsInProject project_id, (error, allowed_number) => @@ -63,8 +68,4 @@ module.exports = logger.log user_id:user_id, limitReached:limitReached, currentTotal: subscription.member_ids.length, membersLimit: subscription.membersLimit, "checking if subscription members limit has been reached" callback(err, limitReached, subscription) -getOwnerOfProject = (project_id, callback)-> - Project.findById project_id, 'owner_ref', (error, project) -> - return callback(error) if error? - User.findById project.owner_ref, (error, owner) -> - callback(error, owner) +getOwnerIdOfProject = (project_id, callback)-> diff --git a/services/web/app/coffee/Features/ThirdPartyDataStore/UpdateMerger.coffee b/services/web/app/coffee/Features/ThirdPartyDataStore/UpdateMerger.coffee index 5e20c2b515..bce0befe22 100644 --- a/services/web/app/coffee/Features/ThirdPartyDataStore/UpdateMerger.coffee +++ b/services/web/app/coffee/Features/ThirdPartyDataStore/UpdateMerger.coffee @@ -4,7 +4,7 @@ editorController = require('../Editor/EditorController') logger = require('logger-sharelatex') Settings = require('settings-sharelatex') FileTypeManager = require('../Uploads/FileTypeManager') -uuid = require('node-uuid') +uuid = require('uuid') fs = require('fs') module.exports = diff --git a/services/web/app/coffee/infrastructure/Modules.coffee b/services/web/app/coffee/infrastructure/Modules.coffee index 1b3c2ea9a5..e7d521fa56 100644 --- a/services/web/app/coffee/infrastructure/Modules.coffee +++ b/services/web/app/coffee/infrastructure/Modules.coffee @@ -1,6 +1,6 @@ fs = require "fs" Path = require "path" -jade = require "jade" +pug = require "pug" async = require "async" MODULE_BASE_PATH = Path.resolve(__dirname + "/../../../modules") @@ -29,7 +29,7 @@ module.exports = Modules = for module in @modules for view, partial of module.viewIncludes or {} @viewIncludes[view] ||= [] - @viewIncludes[view].push jade.compile(fs.readFileSync(Path.join(MODULE_BASE_PATH, module.name, "app/views", partial + ".jade")), doctype: "html") + @viewIncludes[view].push pug.compile(fs.readFileSync(Path.join(MODULE_BASE_PATH, module.name, "app/views", partial + ".pug")), doctype: "html") moduleIncludes: (view, locals) -> compiledPartials = Modules.viewIncludes[view] or [] diff --git a/services/web/app/coffee/infrastructure/Server.coffee b/services/web/app/coffee/infrastructure/Server.coffee index 01d431fa49..2218b72ecb 100644 --- a/services/web/app/coffee/infrastructure/Server.coffee +++ b/services/web/app/coffee/infrastructure/Server.coffee @@ -61,7 +61,7 @@ if Settings.behindProxy webRouter.use express.static(__dirname + '/../../../public', {maxAge: staticCacheAge }) app.set 'views', __dirname + '/../../views' -app.set 'view engine', 'jade' +app.set 'view engine', 'pug' Modules.loadViewIncludes app diff --git a/services/web/app/coffee/models/User.coffee b/services/web/app/coffee/models/User.coffee index c6e469a1f4..44cea29a70 100644 --- a/services/web/app/coffee/models/User.coffee +++ b/services/web/app/coffee/models/User.coffee @@ -2,7 +2,7 @@ Project = require('./Project').Project Settings = require 'settings-sharelatex' _ = require('underscore') mongoose = require('mongoose') -uuid = require('node-uuid') +uuid = require('uuid') Schema = mongoose.Schema ObjectId = Schema.ObjectId diff --git a/services/web/app/views/admin/index.jade b/services/web/app/views/admin/index.pug similarity index 97% rename from services/web/app/views/admin/index.jade rename to services/web/app/views/admin/index.pug index 0fa9017cc1..050829e9d7 100644 --- a/services/web/app/views/admin/index.jade +++ b/services/web/app/views/admin/index.pug @@ -28,10 +28,10 @@ block content tab(heading="Open Sockets") .row-spaced ul - -each agents, url in openSockets + each agents, url in openSockets li #{url} - total : #{agents.length} ul - -each agent in agents + each agent in agents li #{agent} tab(heading="Close Editor") diff --git a/services/web/app/views/admin/register.jade b/services/web/app/views/admin/register.pug similarity index 100% rename from services/web/app/views/admin/register.jade rename to services/web/app/views/admin/register.pug diff --git a/services/web/app/views/beta_program/opt_in.jade b/services/web/app/views/beta_program/opt_in.pug similarity index 100% rename from services/web/app/views/beta_program/opt_in.jade rename to services/web/app/views/beta_program/opt_in.pug diff --git a/services/web/app/views/blog/blog_holder.jade b/services/web/app/views/blog/blog_holder.pug similarity index 100% rename from services/web/app/views/blog/blog_holder.jade rename to services/web/app/views/blog/blog_holder.pug diff --git a/services/web/app/views/contact-us-modal.jade b/services/web/app/views/contact-us-modal.pug similarity index 82% rename from services/web/app/views/contact-us-modal.jade rename to services/web/app/views/contact-us-modal.pug index f4fd8938b7..d3e5aa0e87 100644 --- a/services/web/app/views/contact-us-modal.jade +++ b/services/web/app/views/contact-us-modal.pug @@ -24,10 +24,10 @@ script(type='text/ng-template', id='supportModalTemplate') a.contact-suggestion-list-item(ng-href="{{ suggestion.url }}", ng-click="clickSuggestionLink(suggestion.url);" target="_blank") span(ng-bind-html="suggestion.name") i.fa.fa-angle-right - label.desc(ng-show="'#{getUserEmail()}'.length < 1") + label.desc(ng-show="'"+getUserEmail()+"'.length < 1") | #{translate("email")} - .form-group(ng-show="'#{getUserEmail()}'.length < 1") - input.field.text.medium.span8.form-control(ng-model="form.email", ng-init="form.email = '#{getUserEmail()}'", type='email', spellcheck='false', value='', maxlength='255', tabindex='2') + .form-group(ng-show="'"+getUserEmail()+"'.length < 1") + input.field.text.medium.span8.form-control(ng-model="form.email", ng-init="form.email = '"+getUserEmail()+"'", type='email', spellcheck='false', value='', maxlength='255', tabindex='2') label#title12.desc | #{translate("project_url")} (#{translate("optional")}) .form-group @@ -37,6 +37,6 @@ script(type='text/ng-template', id='supportModalTemplate') .form-group textarea.field.text.medium.span8.form-control(ng-model="form.message",type='text', value='', tabindex='4', onkeyup='') .form-group.text-center - input.btn-success.btn.btn-lg(type='submit', ng-disabled="sending", ng-click="contactUs()" value='#{translate("contact_us")}') + input.btn-success.btn.btn-lg(type='submit', ng-disabled="sending", ng-click="contactUs()" value=translate("contact_us")) span(ng-show="sent") - p #{translate("request_sent_thank_you")} \ No newline at end of file + p #{translate("request_sent_thank_you")} diff --git a/services/web/app/views/general/404.jade b/services/web/app/views/general/404.pug similarity index 100% rename from services/web/app/views/general/404.jade rename to services/web/app/views/general/404.pug diff --git a/services/web/app/views/general/500.jade b/services/web/app/views/general/500.pug similarity index 100% rename from services/web/app/views/general/500.jade rename to services/web/app/views/general/500.pug diff --git a/services/web/app/views/general/closed.jade b/services/web/app/views/general/closed.pug similarity index 100% rename from services/web/app/views/general/closed.jade rename to services/web/app/views/general/closed.pug diff --git a/services/web/app/views/layout.jade b/services/web/app/views/layout.pug similarity index 97% rename from services/web/app/views/layout.jade rename to services/web/app/views/layout.pug index 8f4d1263db..75c96ff276 100644 --- a/services/web/app/views/layout.jade +++ b/services/web/app/views/layout.pug @@ -21,6 +21,8 @@ html(itemscope, itemtype='http://schema.org/Product') link(rel="icon", href="/favicon.ico") link(rel='stylesheet', href=buildCssPath('/style.css')) + block _headLinks + if settings.i18n.subdomainLang each subdomainDetails in settings.i18n.subdomainLang if !subdomainDetails.hide @@ -30,7 +32,7 @@ html(itemscope, itemtype='http://schema.org/Product') meta(itemprop="name", content="ShareLaTeX, the Online LaTeX Editor") -if (typeof(meta) == "undefined") - meta(itemprop="description", name="description", content='#{translate("site_description")}') + meta(itemprop="description", name="description", content=translate("site_description")) -else meta(itemprop="description", name="description" , content=meta) diff --git a/services/web/app/views/layout/footer.jade b/services/web/app/views/layout/footer.pug similarity index 87% rename from services/web/app/views/layout/footer.jade rename to services/web/app/views/layout/footer.pug index efd64b6f6e..62a98ecfaa 100644 --- a/services/web/app/views/layout/footer.jade +++ b/services/web/app/views/layout/footer.pug @@ -13,9 +13,9 @@ footer.site-footer data-toggle="dropdown", aria-haspopup="true", aria-expanded="false", - tooltip="#{translate('language')}" + tooltip=translate('language') ) - figure(class="sprite-icon sprite-icon-lang sprite-icon-#{currentLngCode}") + figure(class="sprite-icon sprite-icon-lang sprite-icon-"+currentLngCode) ul.dropdown-menu(role="menu") li.dropdown-header #{translate("language")} @@ -23,7 +23,7 @@ footer.site-footer if !subdomainDetails.hide li.lngOption a.menu-indent(href=subdomainDetails.url+currentUrl) - figure(class="sprite-icon sprite-icon-lang sprite-icon-#{subdomainDetails.lngCode}") + figure(class="sprite-icon sprite-icon-lang sprite-icon-"+subdomainDetails.lngCode) | #{translate(subdomainDetails.lngCode)} //- img(src="/img/flags/24/.png") each item in nav.left_footer diff --git a/services/web/app/views/layout/navbar.jade b/services/web/app/views/layout/navbar.pug similarity index 96% rename from services/web/app/views/layout/navbar.jade rename to services/web/app/views/layout/navbar.pug index 4d78d02fba..54509d6565 100644 --- a/services/web/app/views/layout/navbar.jade +++ b/services/web/app/views/layout/navbar.pug @@ -4,7 +4,7 @@ nav.navbar.navbar-default button.navbar-toggle(ng-init="navCollapsed = true", ng-click="navCollapsed = !navCollapsed", ng-class="{active: !navCollapsed}") i.fa.fa-bars if settings.nav.custom_logo - a(href='/', style='background-image:url("#{settings.nav.custom_logo}")').navbar-brand + a(href='/', style='background-image:url("'+settings.nav.custom_logo+'")').navbar-brand else if (nav.title) a(href='/').navbar-title #{nav.title} else diff --git a/services/web/app/views/project/editor.jade b/services/web/app/views/project/editor.pug similarity index 100% rename from services/web/app/views/project/editor.jade rename to services/web/app/views/project/editor.pug diff --git a/services/web/app/views/project/editor/binary-file.jade b/services/web/app/views/project/editor/binary-file.pug similarity index 100% rename from services/web/app/views/project/editor/binary-file.jade rename to services/web/app/views/project/editor/binary-file.pug diff --git a/services/web/app/views/project/editor/chat.jade b/services/web/app/views/project/editor/chat.pug similarity index 97% rename from services/web/app/views/project/editor/chat.jade rename to services/web/app/views/project/editor/chat.pug index 47a1752834..fcd47a81e3 100644 --- a/services/web/app/views/project/editor/chat.jade +++ b/services/web/app/views/project/editor/chat.pug @@ -50,7 +50,7 @@ aside.chat( .new-message textarea( - placeholder="#{translate('your_message')}...", + placeholder=translate('your_message')+"...", on-enter="sendMessage()", ng-model="newMessageContent", ng-click="resetUnreadMessages()" diff --git a/services/web/app/views/project/editor/editor.jade b/services/web/app/views/project/editor/editor.pug similarity index 96% rename from services/web/app/views/project/editor/editor.jade rename to services/web/app/views/project/editor/editor.pug index bcb778fda4..98a2840069 100644 --- a/services/web/app/views/project/editor/editor.jade +++ b/services/web/app/views/project/editor/editor.pug @@ -70,7 +70,7 @@ div.full-size( ng-controller="PdfSynctexController" ) a.btn.btn-default.btn-xs( - tooltip="#{translate('go_to_code_location_in_pdf')}" + tooltip=translate('go_to_code_location_in_pdf') tooltip-placement="right" tooltip-append-to-body="true" ng-click="syncToPdf()" @@ -78,7 +78,7 @@ div.full-size( i.fa.fa-long-arrow-right br a.btn.btn-default.btn-xs( - tooltip-html="'#{translate('go_to_pdf_location_in_code')}'" + tooltip-html="'"+translate('go_to_pdf_location_in_code')+"'" tooltip-placement="right" tooltip-append-to-body="true" ng-click="syncToCode()" @@ -90,4 +90,4 @@ div.full-size( ng-show="ui.view == 'pdf'" ) include ./pdf - \ No newline at end of file + diff --git a/services/web/app/views/project/editor/feature-onboarding.jade b/services/web/app/views/project/editor/feature-onboarding.pug similarity index 100% rename from services/web/app/views/project/editor/feature-onboarding.jade rename to services/web/app/views/project/editor/feature-onboarding.pug diff --git a/services/web/app/views/project/editor/file-tree.jade b/services/web/app/views/project/editor/file-tree.pug similarity index 97% rename from services/web/app/views/project/editor/file-tree.jade rename to services/web/app/views/project/editor/file-tree.pug index 92af8b627d..03c2bd79b7 100644 --- a/services/web/app/views/project/editor/file-tree.jade +++ b/services/web/app/views/project/editor/file-tree.pug @@ -3,21 +3,21 @@ aside#file-tree(ng-controller="FileTreeController", ng-class="{ 'multi-selected' a( href, ng-click="openNewDocModal()", - tooltip-html="'#{translate('new_file').replace(' ', '
')}'", + tooltip-html="'"+translate('new_file').replace(' ', '
')+"'", tooltip-placement="bottom" ) i.fa.fa-file a( href, ng-click="openNewFolderModal()", - tooltip-html="'#{translate('new_folder').replace(' ', '
')}'", + tooltip-html="'"+translate('new_folder').replace(' ', '
')+"'", tooltip-placement="bottom" ) i.fa.fa-folder a( href, ng-click="openUploadFileModal()", - tooltip="#{translate('upload')}", + tooltip=translate('upload'), tooltip-placement="bottom" ) i.fa.fa-upload @@ -26,7 +26,7 @@ aside#file-tree(ng-controller="FileTreeController", ng-class="{ 'multi-selected' a( href, ng-click="startRenamingSelected()", - tooltip="#{translate('rename')}", + tooltip=translate('rename'), tooltip-placement="bottom", ng-show="multiSelectedCount == 0" ) @@ -34,7 +34,7 @@ aside#file-tree(ng-controller="FileTreeController", ng-class="{ 'multi-selected' a( href, ng-click="openDeleteModalForSelected()", - tooltip="#{translate('delete')}", + tooltip=translate('delete'), tooltip-placement="bottom", tooltip-append-to-body="true" ) @@ -431,4 +431,4 @@ script(type='text/ng-template', id='invalidFileNameModalTemplate') .modal-footer button.btn.btn-default( ng-click="$close()" - ) #{translate('ok')} \ No newline at end of file + ) #{translate('ok')} diff --git a/services/web/app/views/project/editor/header.jade b/services/web/app/views/project/editor/header.pug similarity index 96% rename from services/web/app/views/project/editor/header.jade rename to services/web/app/views/project/editor/header.pug index 601b18e9cf..475ba1da52 100644 --- a/services/web/app/views/project/editor/header.jade +++ b/services/web/app/views/project/editor/header.pug @@ -45,7 +45,7 @@ header.toolbar.toolbar-header.toolbar-with-labels( ng-if="permissions.admin", href='#', tooltip-placement="bottom", - tooltip="#{translate('rename')}", + tooltip=translate('rename'), tooltip-append-to-body="true", ng-click="startRenaming()", ng-show="!state.renaming" @@ -71,7 +71,7 @@ header.toolbar.toolbar-header.toolbar-with-labels( span.dropdown(dropdown, ng-if="onlineUsersArray.length >= 4") span.online-user.online-user-multi( dropdown-toggle, - tooltip="#{translate('connected_users')}", + tooltip=translate('connected_users'), tooltip-placement="left" ) strong {{ onlineUsersArray.length }} @@ -121,4 +121,4 @@ header.toolbar.toolbar-header.toolbar-with-labels( span.label.label-info( ng-show="unreadMessages > 0" ) {{ unreadMessages }} - p.toolbar-label #{translate("chat")} \ No newline at end of file + p.toolbar-label #{translate("chat")} diff --git a/services/web/app/views/project/editor/history.jade b/services/web/app/views/project/editor/history.pug similarity index 100% rename from services/web/app/views/project/editor/history.jade rename to services/web/app/views/project/editor/history.pug diff --git a/services/web/app/views/project/editor/hotkeys.jade b/services/web/app/views/project/editor/hotkeys.pug similarity index 100% rename from services/web/app/views/project/editor/hotkeys.jade rename to services/web/app/views/project/editor/hotkeys.pug diff --git a/services/web/app/views/project/editor/left-menu.jade b/services/web/app/views/project/editor/left-menu.pug similarity index 96% rename from services/web/app/views/project/editor/left-menu.jade rename to services/web/app/views/project/editor/left-menu.pug index 80d5c606ab..4e86b31620 100644 --- a/services/web/app/views/project/editor/left-menu.jade +++ b/services/web/app/views/project/editor/left-menu.pug @@ -24,7 +24,7 @@ aside#left-menu.full-size( | PDF div.link-disabled( ng-if="!pdf.url" - tooltip="#{translate('please_compile_pdf_before_download')}" + tooltip=translate('please_compile_pdf_before_download') tooltip-placement="bottom" ) i.fa.fa-file-pdf-o.fa-2x @@ -47,7 +47,7 @@ aside#left-menu.full-size( a(href, ng-if="pdf.url" ,ng-click="openWordCountModal()") i.fa.fa-fw.fa-eye span    #{translate("word_count")} - a.link-disabled(href, ng-if="!pdf.url" , tooltip="#{translate('please_compile_pdf_before_word_count')}") + a.link-disabled(href, ng-if="!pdf.url" , tooltip=translate('please_compile_pdf_before_word_count')) i.fa.fa-fw.fa-eye span.link-disabled    #{translate("word_count")} @@ -200,7 +200,7 @@ script(type='text/ng-template', id='wordCountModalTemplate') span   #{translate("loading")}... div.pdf-disabled( ng-if="!pdf.url" - tooltip="#{translate('please_compile_pdf_before_word_count')}" + tooltip=translate('please_compile_pdf_before_word_count') tooltip-placement="bottom" ) div(ng-if="!status.loading") diff --git a/services/web/app/views/project/editor/pdf.jade b/services/web/app/views/project/editor/pdf.pug similarity index 97% rename from services/web/app/views/project/editor/pdf.jade rename to services/web/app/views/project/editor/pdf.pug index 074856bd7c..ba03f068a6 100644 --- a/services/web/app/views/project/editor/pdf.jade +++ b/services/web/app/views/project/editor/pdf.pug @@ -2,7 +2,7 @@ div.full-size.pdf(ng-controller="PdfController") .toolbar.toolbar-tall .btn-group( dropdown, - tooltip-html="'#{translate('recompile_pdf')} ({{modifierKey}} + Enter)'" + tooltip-html="'"+translate('recompile_pdf')+" ({{modifierKey}} + Enter)'" tooltip-class="keyboard-tooltip" tooltip-popup-delay="500" tooltip-append-to-body="true" @@ -53,7 +53,7 @@ div.full-size.pdf(ng-controller="PdfController") href ng-click="stop()" ng-show="pdf.compiling", - tooltip="#{translate('stop_compile')}" + tooltip=translate('stop_compile') tooltip-placement="bottom" ) i.fa.fa-stop() @@ -61,7 +61,7 @@ div.full-size.pdf(ng-controller="PdfController") href ng-click="toggleLogs()" ng-class="{ 'active': shouldShowLogs == true }" - tooltip="#{translate('logs_and_output_files')}" + tooltip=translate('logs_and_output_files') tooltip-placement="bottom" ) i.fa.fa-file-text-o @@ -77,7 +77,7 @@ div.full-size.pdf(ng-controller="PdfController") ng-href="{{pdf.downloadUrl || pdf.url}}" target="_blank" ng-if="pdf.url" - tooltip="#{translate('download_pdf')}" + tooltip=translate('download_pdf') tooltip-placement="bottom" ) i.fa.fa-download @@ -87,7 +87,7 @@ div.full-size.pdf(ng-controller="PdfController") href, ng-click="switchToFlatLayout()" ng-show="ui.pdfLayout == 'sideBySide'" - tooltip="#{translate('full_screen')}" + tooltip=translate('full_screen') tooltip-placement="bottom" tooltip-append-to-body="true" ) @@ -96,7 +96,7 @@ div.full-size.pdf(ng-controller="PdfController") href, ng-click="switchToSideBySideLayout()" ng-show="ui.pdfLayout == 'flat'" - tooltip="#{translate('split_screen')}" + tooltip=translate('split_screen') tooltip-placement="bottom" tooltip-append-to-body="true" ) @@ -233,7 +233,7 @@ div.full-size.pdf(ng-controller="PdfController") .files-dropdown-container a.btn.btn-default.btn-sm( href, - tooltip="#{translate('clear_cached_files')}", + tooltip=translate('clear_cached_files'), tooltip-placement="top", tooltip-append-to-body="true", ng-click="openClearCacheModal()" diff --git a/services/web/app/views/project/editor/publish-template.jade b/services/web/app/views/project/editor/publish-template.pug similarity index 100% rename from services/web/app/views/project/editor/publish-template.jade rename to services/web/app/views/project/editor/publish-template.pug diff --git a/services/web/app/views/project/editor/review-panel.jade b/services/web/app/views/project/editor/review-panel.pug similarity index 98% rename from services/web/app/views/project/editor/review-panel.jade rename to services/web/app/views/project/editor/review-panel.pug index 9b31516b3f..43a854b591 100644 --- a/services/web/app/views/project/editor/review-panel.jade +++ b/services/web/app/views/project/editor/review-panel.pug @@ -41,7 +41,6 @@ .rp-entry-list-inner .rp-entry-wrapper( ng-repeat="(entry_id, entry) in reviewPanel.entries[editor.open_doc_id]" - ng-if="!(entry.type === 'comment' && reviewPanel.commentThreads[entry.thread_id].resolved === true)" ) div(ng-if="entry.type === 'insert' || entry.type === 'delete'") change-entry( @@ -50,6 +49,7 @@ on-reject="rejectChange(entry_id);" on-accept="acceptChange(entry_id);" on-indicator-click="toggleReviewPanel();" + on-body-click="gotoEntry(editor.open_doc_id, entry)" permissions="permissions" ) @@ -62,6 +62,7 @@ on-indicator-click="toggleReviewPanel();" on-save-edit="saveEdit(entry.thread_id, comment)" on-delete="deleteComment(entry.thread_id, comment)" + on-body-click="gotoEntry(editor.open_doc_id, entry)" permissions="permissions" ng-if="!reviewPanel.loadingThreads" ) @@ -191,7 +192,7 @@ script(type='text/ng-template', id='commentEntryTemplate') .rp-loading(ng-if="!threads[entry.thread_id].submitting && (!threads[entry.thread_id] || threads[entry.thread_id].messages.length == 0)") | No comments - div + .rp-comment-loaded .rp-comment( ng-repeat="comment in threads[entry.thread_id].messages track by comment.id" ) diff --git a/services/web/app/views/project/editor/share.jade b/services/web/app/views/project/editor/share.pug similarity index 97% rename from services/web/app/views/project/editor/share.jade rename to services/web/app/views/project/editor/share.pug index 78fb69c333..32e0404afc 100644 --- a/services/web/app/views/project/editor/share.jade +++ b/services/web/app/views/project/editor/share.pug @@ -38,7 +38,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate') .col-xs-1 a( href - tooltip="#{translate('remove_collaborator')}" + tooltip=translate('remove_collaborator') tooltip-placement="bottom" ng-click="removeMember(member)" ) @@ -55,7 +55,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate') .col-xs-1 a( href - tooltip="#{translate('revoke_invite')}" + tooltip=translate('revoke_invite') tooltip-placement="bottom" ng-click="revokeInvite(invite)" ) @@ -66,7 +66,7 @@ script(type='text/ng-template', id='shareProjectModalTemplate') .form-group tags-input( template="shareTagTemplate" - placeholder="#{settings.customisation.shareProjectPlaceholder || 'joe@example.com, sue@example.com, ...'}" + placeholder=settings.customisation.shareProjectPlaceholder || 'joe@example.com, sue@example.com, ...' ng-model="inputs.contacts" focus-on="open" display-property="display" diff --git a/services/web/app/views/project/invite/not-valid.jade b/services/web/app/views/project/invite/not-valid.pug similarity index 100% rename from services/web/app/views/project/invite/not-valid.jade rename to services/web/app/views/project/invite/not-valid.pug diff --git a/services/web/app/views/project/invite/show.jade b/services/web/app/views/project/invite/show.pug similarity index 83% rename from services/web/app/views/project/invite/show.jade rename to services/web/app/views/project/invite/show.pug index eed30d3d19..d129fe015d 100644 --- a/services/web/app/views/project/invite/show.jade +++ b/services/web/app/views/project/invite/show.pug @@ -20,12 +20,12 @@ block content form.form( name="acceptForm", method="POST", - action="/project/#{invite.projectId}/invite/token/#{invite.token}/accept" + action="/project/"+invite.projectId+"/invite/token/"+invite.token+"/accept" ) input(name='_csrf', type='hidden', value=csrfToken) - input(name='token', type='hidden', value="#{invite.token}") + input(name='token', type='hidden', value=invite.token) .form-group.text-center button.btn.btn-lg.btn-primary(type="submit") | #{translate("join_project")} .form-group.text-center - \ No newline at end of file + diff --git a/services/web/app/views/project/list.jade b/services/web/app/views/project/list.pug similarity index 100% rename from services/web/app/views/project/list.jade rename to services/web/app/views/project/list.pug diff --git a/services/web/app/views/project/list/empty-project-list.jade b/services/web/app/views/project/list/empty-project-list.pug similarity index 100% rename from services/web/app/views/project/list/empty-project-list.jade rename to services/web/app/views/project/list/empty-project-list.pug diff --git a/services/web/app/views/project/list/modals.jade b/services/web/app/views/project/list/modals.pug similarity index 100% rename from services/web/app/views/project/list/modals.jade rename to services/web/app/views/project/list/modals.pug diff --git a/services/web/app/views/project/list/notifications.jade b/services/web/app/views/project/list/notifications.pug similarity index 100% rename from services/web/app/views/project/list/notifications.jade rename to services/web/app/views/project/list/notifications.pug diff --git a/services/web/app/views/project/list/project-list.jade b/services/web/app/views/project/list/project-list.pug similarity index 96% rename from services/web/app/views/project/list/project-list.jade rename to services/web/app/views/project/list/project-list.pug index 01007213d0..0bb93e8336 100644 --- a/services/web/app/views/project/list/project-list.jade +++ b/services/web/app/views/project/list/project-list.pug @@ -6,7 +6,7 @@ form.project-search.form-horizontal(role="form") .form-group.has-feedback.has-feedback-left.col-md-7.col-xs-12 input.form-control.col-md-7.col-xs-12( - placeholder="#{translate('search_projects')}…", + placeholder=translate('search_projects')+"…", autofocus='autofocus', ng-model="searchText.value", focus-on='search:clear', @@ -25,7 +25,7 @@ .btn-group(ng-hide="selectedProjects.length < 1") a.btn.btn-default( href, - tooltip="#{translate('download')}", + tooltip=translate('download'), tooltip-placement="bottom", tooltip-append-to-body="true", ng-click="downloadSelectedProjects()" @@ -33,7 +33,7 @@ i.fa.fa-cloud-download a.btn.btn-default( href, - tooltip="#{translate('delete')}", + tooltip=translate('delete'), tooltip-placement="bottom", tooltip-append-to-body="true", ng-click="openArchiveProjectsModal()" @@ -45,7 +45,7 @@ href, data-toggle="dropdown", dropdown-toggle, - tooltip="#{translate('add_to_folders')}", + tooltip=translate('add_to_folders'), tooltip-append-to-body="true", tooltip-placement="bottom" ) diff --git a/services/web/app/views/project/list/side-bar.jade b/services/web/app/views/project/list/side-bar.pug similarity index 100% rename from services/web/app/views/project/list/side-bar.jade rename to services/web/app/views/project/list/side-bar.pug diff --git a/services/web/app/views/referal/bonus.jade b/services/web/app/views/referal/bonus.pug similarity index 87% rename from services/web/app/views/referal/bonus.jade rename to services/web/app/views/referal/bonus.pug index 5b0183d9c1..f3a65c6bc4 100644 --- a/services/web/app/views/referal/bonus.jade +++ b/services/web/app/views/referal/bonus.pug @@ -24,7 +24,7 @@ block content .row .col-md-8.col-md-offset-2.bonus-banner .title - a(href='https://twitter.com/share?text=is%20trying%20out%20the%20online%20LaTeX%20Editor%20ShareLaTeX&url=#{encodeURIComponent(buildReferalUrl("t"))}&counturl=https://www.sharelatex.com', target="_blank").twitter Tweet + a(href='https://twitter.com/share?text=is%20trying%20out%20the%20online%20LaTeX%20Editor%20ShareLaTeX&url='+encodeURIComponent(buildReferalUrl("t"))+'&counturl=https://www.sharelatex.com', target="_blank").twitter Tweet .row .col-md-8.col-md-offset-2.bonus-banner @@ -34,12 +34,12 @@ block content .row .col-md-8.col-md-offset-2.bonus-banner .title - a(href="https://plus.google.com/share?url=#{encodeURIComponent(buildReferalUrl('gp'))}", onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;").google-plus #{translate("share_us_on_googleplus")} + a(href="https://plus.google.com/share?url="+encodeURIComponent(buildReferalUrl('gp')), onclick="javascript:window.open(this.href, '', 'menubar=no,toolbar=no,resizable=yes,scrollbars=yes,height=600,width=600');return false;").google-plus #{translate("share_us_on_googleplus")} .row .col-md-8.col-md-offset-2.bonus-banner .title - a(href='mailto:?subject=Online LaTeX editor you may like &body=Hey, I have been using the online LaTeX editor ShareLaTeX recently and thought you might like to check it out. #{encodeURIComponent(buildReferalUrl("e"))}', title='Share by Email').email #{translate("email_us_to_your_friends")} + a(href='mailto:?subject=Online LaTeX editor you may like &body=Hey, I have been using the online LaTeX editor ShareLaTeX recently and thought you might like to check it out. '+encodeURIComponent(buildReferalUrl("e")), title='Share by Email').email #{translate("email_us_to_your_friends")} .row .col-md-8.col-md-offset-2.bonus-banner @@ -58,9 +58,9 @@ block content .col-md-10.col-md-offset-1.bonus-banner(style="position: relative; height: 30px; margin-top: 20px;") - for (var i = 0; i <= 10; i++) { - if (refered_user_count == i) - .number(style="left: #{i}0%").active #{i} + .number(style="left: "+i+"0%").active #{i} - else - .number(style="left: #{i}0%") #{i} + .number(style="left: "+i+"0%") #{i} - } .row.ab-bonus @@ -68,7 +68,7 @@ block content .progress - if (refered_user_count == 0) div(style="text-align: center; padding: 4px;") #{translate("spread_the_word_and_fill_bar")} - .progress-bar.progress-bar-info(style="width: #{refered_user_count}0%") + .progress-bar.progress-bar-info(style="width: "+refered_user_count+"0%") .row.ab-bonus .col-md-10.col-md-offset-1.bonus-banner(style="position: relative; height: 70px;") diff --git a/services/web/app/views/restore.jade b/services/web/app/views/restore.pug similarity index 84% rename from services/web/app/views/restore.jade rename to services/web/app/views/restore.pug index 51c0ee03fc..1cd7ad458a 100644 --- a/services/web/app/views/restore.jade +++ b/services/web/app/views/restore.pug @@ -19,11 +19,11 @@ block content .row-fluid table.table - -each project in projects + each project in projects tr - project_id = project._id.toString() td(width="50%") #{project.name} td(width="25%") - a.btn(href="/project/#{project_id}/zip") Download latest version as Zip + a.btn(href="/project/"+project_id+"/zip") Download latest version as Zip include general/small-footer diff --git a/services/web/app/views/scribtex-modal.jade b/services/web/app/views/scribtex-modal.pug similarity index 71% rename from services/web/app/views/scribtex-modal.jade rename to services/web/app/views/scribtex-modal.pug index 1d4e2ce005..3efbf19314 100644 --- a/services/web/app/views/scribtex-modal.jade +++ b/services/web/app/views/scribtex-modal.pug @@ -5,4 +5,4 @@ script(type='text/ng-template', id='scribtexModalTemplate') p ScribTeX has moved to https://scribtex.sharelatex.com. Please update your bookmarks. p(style="text-align: center") You can find the page you were looking for here: p(style="text-align: center") - a(href="https://scribtex.sharelatex.com#{scribtexPath}", style="font-size: 16px") https://scribtex.sharelatex.com#{scribtexPath} \ No newline at end of file + a(href="https://scribtex.sharelatex.com"+scribtexPath, style="font-size: 16px") https://scribtex.sharelatex.com#{scribtexPath} diff --git a/services/web/app/views/sentry.jade b/services/web/app/views/sentry.pug similarity index 96% rename from services/web/app/views/sentry.jade rename to services/web/app/views/sentry.pug index 0a51686015..9e10d7837e 100644 --- a/services/web/app/views/sentry.jade +++ b/services/web/app/views/sentry.pug @@ -1,8 +1,8 @@ - if (typeof(sentrySrc) != "undefined") - if (sentrySrc.match(/^([a-z]+:)?\/\//i)) - script(src="#{sentrySrc}") + script(src=sentrySrc) - else - script(src=buildJsPath("libs/#{sentrySrc}", {fingerprint:false})) + script(src=buildJsPath("libs/"+sentrySrc, {fingerprint:false})) - if (typeof(sentrySrc) != "undefined") script(type="text/javascript"). if (typeof(Raven) != "undefined" && Raven.config) { diff --git a/services/web/app/views/subscriptions/custom_account.jade b/services/web/app/views/subscriptions/custom_account.pug similarity index 100% rename from services/web/app/views/subscriptions/custom_account.jade rename to services/web/app/views/subscriptions/custom_account.pug diff --git a/services/web/app/views/subscriptions/dashboard.jade b/services/web/app/views/subscriptions/dashboard.pug similarity index 91% rename from services/web/app/views/subscriptions/dashboard.jade rename to services/web/app/views/subscriptions/dashboard.pug index 3448dc9072..fb4f834c34 100644 --- a/services/web/app/views/subscriptions/dashboard.jade +++ b/services/web/app/views/subscriptions/dashboard.pug @@ -12,7 +12,7 @@ block scripts mixin printPlan(plan) -if (!plan.hideFromUsers) - tr(ng-controller="ChangePlanFormController", ng-init="plan=#{JSON.stringify(plan)}", ng-show="shouldShowPlan(plan.planCode)") + tr(ng-controller="ChangePlanFormController", ng-init="plan="+JSON.stringify(plan), ng-show="shouldShowPlan(plan.planCode)") td strong #{plan.name} td {{refreshPrice(plan.planCode)}} @@ -22,18 +22,18 @@ mixin printPlan(plan) | {{prices[plan.planCode]}} / #{translate("month")} td -if (subscription.state == "free-trial") - a(href="/user/subscription/new?planCode=#{plan.planCode}").btn.btn-success #{translate("subscribe_to_this_plan")} + a(href="/user/subscription/new?planCode="+plan.planCode).btn.btn-success #{translate("subscribe_to_this_plan")} -else if (typeof(subscription.planCode) != "undefined" && plan.planCode == subscription.planCode.split("_")[0]) button.btn.disabled #{translate("your_plan")} -else form - input(type="hidden", ng-model="plan_code", name="plan_code", value="#{plan.planCode}") + input(type="hidden", ng-model="plan_code", name="plan_code", value=plan.planCode) input(type="submit", ng-click="changePlan()", value=translate("change_to_this_plan")).btn.btn-success mixin printPlans(plans) - -each plan in plans - mixin printPlan(plan) + each plan in plans + +printPlan(plan) block content .content.content-alt(ng-cloak) @@ -46,7 +46,7 @@ block content |   | #{translate("your_billing_details_were_saved")} .card(ng-if="view == 'overview'") - .page-header(x-current-plan="#{subscription.planCode}") + .page-header(x-current-plan=subscription.planCode) h1 #{translate("your_subscription")} - if (subscription && user._id+'' == subscription.admin_id+'') @@ -97,9 +97,9 @@ block content th !{translate("name")} th !{translate("price")} th - mixin printPlans(plans.studentAccounts) - mixin printPlans(plans.individualMonthlyPlans) - mixin printPlans(plans.individualAnnualPlans) + +printPlans(plans.studentAccounts) + +printPlans(plans.individualMonthlyPlans) + +printPlans(plans.individualAnnualPlans) each groupSubscription in groupSubscriptions @@ -107,7 +107,7 @@ block content div p !{translate("member_of_group_subscription", {admin_email: "" + groupSubscription.admin_id.email + ""})} span - button.btn.btn-danger(ng-click="removeSelfFromGroup('#{groupSubscription.admin_id._id}')") #{translate("leave_group")} + button.btn.btn-danger(ng-click="removeSelfFromGroup('"+groupSubscription.admin_id._id+"')") #{translate("leave_group")} -if(subscription.groupPlan && user._id+'' == subscription.admin_id+'') div diff --git a/services/web/app/views/subscriptions/edit-billing-details.jade b/services/web/app/views/subscriptions/edit-billing-details.pug similarity index 100% rename from services/web/app/views/subscriptions/edit-billing-details.jade rename to services/web/app/views/subscriptions/edit-billing-details.pug diff --git a/services/web/app/views/subscriptions/group/invite.jade b/services/web/app/views/subscriptions/group/invite.pug similarity index 100% rename from services/web/app/views/subscriptions/group/invite.jade rename to services/web/app/views/subscriptions/group/invite.pug diff --git a/services/web/app/views/subscriptions/group/successful_join.jade b/services/web/app/views/subscriptions/group/successful_join.pug similarity index 100% rename from services/web/app/views/subscriptions/group/successful_join.jade rename to services/web/app/views/subscriptions/group/successful_join.pug diff --git a/services/web/app/views/subscriptions/group_admin.jade b/services/web/app/views/subscriptions/group_admin.pug similarity index 100% rename from services/web/app/views/subscriptions/group_admin.jade rename to services/web/app/views/subscriptions/group_admin.pug diff --git a/services/web/app/views/subscriptions/new.jade b/services/web/app/views/subscriptions/new.pug similarity index 99% rename from services/web/app/views/subscriptions/new.jade rename to services/web/app/views/subscriptions/new.pug index 1465b24c82..2d410f8fba 100644 --- a/services/web/app/views/subscriptions/new.jade +++ b/services/web/app/views/subscriptions/new.pug @@ -164,7 +164,7 @@ block content ng-change="updateCountry()" required ) - mixin countries_options() + +countries_options() span.input-feedback-message {{ simpleCCForm.country.$error.required ? 'This field is required' : '' }} if (showVatField) diff --git a/services/web/app/views/subscriptions/plans.jade b/services/web/app/views/subscriptions/plans.pug similarity index 100% rename from services/web/app/views/subscriptions/plans.jade rename to services/web/app/views/subscriptions/plans.pug diff --git a/services/web/app/views/subscriptions/successful_subscription.jade b/services/web/app/views/subscriptions/successful_subscription.pug similarity index 100% rename from services/web/app/views/subscriptions/successful_subscription.jade rename to services/web/app/views/subscriptions/successful_subscription.pug diff --git a/services/web/app/views/subscriptions/upgradeToAnnual.jade b/services/web/app/views/subscriptions/upgradeToAnnual.pug similarity index 93% rename from services/web/app/views/subscriptions/upgradeToAnnual.jade rename to services/web/app/views/subscriptions/upgradeToAnnual.pug index d92290ad0e..0ce41856f7 100644 --- a/services/web/app/views/subscriptions/upgradeToAnnual.jade +++ b/services/web/app/views/subscriptions/upgradeToAnnual.pug @@ -6,7 +6,7 @@ block content .container(ng-controller="AnnualUpgradeController") .row(ng-cloak) .col-md-6.col-md-offset-3 - .card(ng-init="planName = #{JSON.stringify(planName)}") + .card(ng-init="planName = "+JSON.stringify(planName)) .page-header h1.text-centered #{translate("move_to_annual_billing")} div(ng-hide="upgradeComplete") diff --git a/services/web/app/views/tests.jade b/services/web/app/views/tests.pug similarity index 100% rename from services/web/app/views/tests.jade rename to services/web/app/views/tests.pug diff --git a/services/web/app/views/translations/translation_message.jade b/services/web/app/views/translations/translation_message.pug similarity index 100% rename from services/web/app/views/translations/translation_message.jade rename to services/web/app/views/translations/translation_message.pug diff --git a/services/web/app/views/university/case_study.jade b/services/web/app/views/university/case_study.pug similarity index 100% rename from services/web/app/views/university/case_study.jade rename to services/web/app/views/university/case_study.pug diff --git a/services/web/app/views/university/university_holder.jade b/services/web/app/views/university/university_holder.pug similarity index 100% rename from services/web/app/views/university/university_holder.jade rename to services/web/app/views/university/university_holder.pug diff --git a/services/web/app/views/user/activate.jade b/services/web/app/views/user/activate.pug similarity index 97% rename from services/web/app/views/user/activate.jade rename to services/web/app/views/user/activate.pug index 7961876389..8b60b10471 100644 --- a/services/web/app/views/user/activate.jade +++ b/services/web/app/views/user/activate.pug @@ -36,7 +36,7 @@ block content placeholder="email@example.com" required, ng-model="email", - ng-init="email = #{JSON.stringify(email)}", + ng-init="email = "+JSON.stringify(email), ng-model-options="{ updateOn: 'blur' }", disabled ) diff --git a/services/web/app/views/user/login.jade b/services/web/app/views/user/login.pug similarity index 96% rename from services/web/app/views/user/login.jade rename to services/web/app/views/user/login.pug index 8339f27189..823299d660 100644 --- a/services/web/app/views/user/login.jade +++ b/services/web/app/views/user/login.pug @@ -19,7 +19,7 @@ block content placeholder='email@example.com', ng-model="email", ng-model-options="{ updateOn: 'blur' }", - ng-init="email = #{JSON.stringify(email)}", + ng-init="email = "+JSON.stringify(email), focus="true" ) span.small.text-primary(ng-show="loginForm.email.$invalid && loginForm.email.$dirty") diff --git a/services/web/app/views/user/passwordReset.jade b/services/web/app/views/user/passwordReset.pug similarity index 100% rename from services/web/app/views/user/passwordReset.jade rename to services/web/app/views/user/passwordReset.pug diff --git a/services/web/app/views/user/register.jade b/services/web/app/views/user/register.pug similarity index 100% rename from services/web/app/views/user/register.jade rename to services/web/app/views/user/register.pug diff --git a/services/web/app/views/user/restricted.jade b/services/web/app/views/user/restricted.pug similarity index 100% rename from services/web/app/views/user/restricted.jade rename to services/web/app/views/user/restricted.pug diff --git a/services/web/app/views/user/sessions.jade b/services/web/app/views/user/sessions.pug similarity index 100% rename from services/web/app/views/user/sessions.jade rename to services/web/app/views/user/sessions.pug diff --git a/services/web/app/views/user/setPassword.jade b/services/web/app/views/user/setPassword.pug similarity index 100% rename from services/web/app/views/user/setPassword.jade rename to services/web/app/views/user/setPassword.pug diff --git a/services/web/app/views/user/settings.jade b/services/web/app/views/user/settings.pug similarity index 99% rename from services/web/app/views/user/settings.jade rename to services/web/app/views/user/settings.pug index 310912cf07..a4217d8ca4 100644 --- a/services/web/app/views/user/settings.jade +++ b/services/web/app/views/user/settings.pug @@ -28,7 +28,7 @@ block content placeholder="email@example.com" required, ng-model="email", - ng-init="email = #{JSON.stringify(user.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") diff --git a/services/web/app/views/view_templates/bonus_templates.jade b/services/web/app/views/view_templates/bonus_templates.pug similarity index 100% rename from services/web/app/views/view_templates/bonus_templates.jade rename to services/web/app/views/view_templates/bonus_templates.pug diff --git a/services/web/modules/.gitignore b/services/web/modules/.gitignore index b90beee9f7..1d30263cb3 100644 --- a/services/web/modules/.gitignore +++ b/services/web/modules/.gitignore @@ -2,3 +2,11 @@ */test/unit/js */index.js ldap +admin-panel +groovehq +launchpad +learn-wiki +references-search +sharelatex-saml +templates +tpr-webmodule diff --git a/services/web/npm-shrinkwrap.json b/services/web/npm-shrinkwrap.json index 03abe920f9..7f6d37910e 100644 --- a/services/web/npm-shrinkwrap.json +++ b/services/web/npm-shrinkwrap.json @@ -20,7 +20,8 @@ }, "isarray": { "version": "0.0.1", - "from": "isarray@0.0.1" + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" }, "string_decoder": { "version": "0.10.31", @@ -100,7 +101,7 @@ }, "glob": { "version": "3.2.11", - "from": "glob@~3.2.6", + "from": "glob@~3.2.9", "dependencies": { "inherits": { "version": "2.0.3", @@ -124,7 +125,7 @@ }, "minimatch": { "version": "0.2.14", - "from": "minimatch@~0.2.12", + "from": "minimatch@~0.2.9", "dependencies": { "lru-cache": { "version": "2.7.3", @@ -256,7 +257,8 @@ }, "setprototypeof": { "version": "1.0.2", - "from": "setprototypeof@1.0.2" + "from": "setprototypeof@1.0.2", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.2.tgz" }, "statuses": { "version": "1.3.1", @@ -266,7 +268,8 @@ }, "iconv-lite": { "version": "0.4.15", - "from": "iconv-lite@0.4.15" + "from": "iconv-lite@0.4.15", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz" }, "on-finished": { "version": "2.3.0", @@ -280,7 +283,8 @@ }, "qs": { "version": "6.2.1", - "from": "qs@6.2.1" + "from": "qs@6.2.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.2.1.tgz" }, "raw-body": { "version": "2.2.0", @@ -341,11 +345,12 @@ "from": "double-ended-queue@^2.1.0-0" }, "redis-commands": { - "version": "1.3.0", - "from": "redis-commands@^1.2.0" + "version": "1.3.1", + "from": "redis-commands@^1.2.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.1.tgz" }, "redis-parser": { - "version": "2.3.0", + "version": "2.4.0", "from": "redis-parser@^2.0.0" } } @@ -446,15 +451,17 @@ }, "rndm": { "version": "1.2.0", - "from": "rndm@1.2.0" + "from": "rndm@1.2.0", + "resolved": "https://registry.npmjs.org/rndm/-/rndm-1.2.0.tgz" }, "tsscmp": { "version": "1.0.5", - "from": "tsscmp@1.0.5" + "from": "tsscmp@1.0.5", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.5.tgz" }, "uid-safe": { "version": "2.1.3", - "from": "uid-safe@2.1.3", + "from": "uid-safe@~2.1.3", "dependencies": { "random-bytes": { "version": "1.0.0", @@ -466,7 +473,7 @@ }, "http-errors": { "version": "1.5.1", - "from": "http-errors@~1.5.1", + "from": "http-errors@~1.5.0", "dependencies": { "inherits": { "version": "2.0.3", @@ -474,7 +481,8 @@ }, "setprototypeof": { "version": "1.0.2", - "from": "setprototypeof@1.0.2" + "from": "setprototypeof@1.0.2", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.2.tgz" }, "statuses": { "version": "1.3.1", @@ -491,6 +499,7 @@ "express": { "version": "4.13.0", "from": "express@4.13.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.13.0.tgz", "dependencies": { "accepts": { "version": "1.2.13", @@ -696,7 +705,7 @@ }, "type-is": { "version": "1.6.14", - "from": "type-is@~1.6.3", + "from": "type-is@~1.6.14", "dependencies": { "media-typer": { "version": "0.3.0", @@ -738,11 +747,12 @@ }, "crc": { "version": "3.4.4", - "from": "crc@3.4.4" + "from": "crc@3.4.4", + "resolved": "https://registry.npmjs.org/crc/-/crc-3.4.4.tgz" }, "debug": { "version": "2.6.0", - "from": "debug@^2.2.0", + "from": "debug@*", "dependencies": { "ms": { "version": "0.7.2", @@ -760,7 +770,7 @@ }, "parseurl": { "version": "1.3.1", - "from": "parseurl@~1.3.1" + "from": "parseurl@~1.3.0" }, "uid-safe": { "version": "2.1.3", @@ -800,7 +810,8 @@ }, "dateformat": { "version": "1.0.2-1.2.3", - "from": "dateformat@1.0.2-1.2.3" + "from": "dateformat@1.0.2-1.2.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.2-1.2.3.tgz" }, "eventemitter2": { "version": "0.4.14", @@ -990,7 +1001,7 @@ }, "debug": { "version": "2.6.0", - "from": "debug@^2.2.0", + "from": "debug@*", "dependencies": { "ms": { "version": "0.7.2", @@ -1007,8 +1018,9 @@ "from": "flexbuffer@0.0.6" }, "redis-commands": { - "version": "1.3.0", - "from": "redis-commands@^1.2.0" + "version": "1.3.1", + "from": "redis-commands@^1.2.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.3.1.tgz" }, "redis-parser": { "version": "1.3.0", @@ -1160,7 +1172,8 @@ }, "window-size": { "version": "0.1.0", - "from": "window-size@0.1.0" + "from": "window-size@0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz" }, "wordwrap": { "version": "0.0.2", @@ -1178,7 +1191,7 @@ "dependencies": { "uglify-js": { "version": "2.4.24", - "from": "uglify-js@~2.4.0", + "from": "uglify-js@~2.4.12", "dependencies": { "async": { "version": "0.2.10", @@ -1212,7 +1225,8 @@ }, "window-size": { "version": "0.1.0", - "from": "window-size@0.1.0" + "from": "window-size@0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz" }, "wordwrap": { "version": "0.0.2", @@ -1388,7 +1402,7 @@ "dependencies": { "core-util-is": { "version": "1.0.2", - "from": "core-util-is@1.0.2" + "from": "core-util-is@~1.0.0" }, "extsprintf": { "version": "1.3.0", @@ -1402,7 +1416,7 @@ "dependencies": { "nan": { "version": "2.5.1", - "from": "nan@^2.3.3" + "from": "nan@^2.0.8" } } } @@ -1519,7 +1533,8 @@ }, "coffee-script": { "version": "1.4.0", - "from": "coffee-script@1.4.0" + "from": "coffee-script@1.4.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.4.0.tgz" }, "raven": { "version": "0.8.1", @@ -1533,6 +1548,10 @@ "version": "0.0.3", "from": "lsmod@~0.0.3" }, + "node-uuid": { + "version": "1.4.7", + "from": "node-uuid@~1.4.1" + }, "stack-trace": { "version": "0.0.7", "from": "stack-trace@0.0.7" @@ -1594,7 +1613,8 @@ "dependencies": { "coffee-script": { "version": "1.6.0", - "from": "coffee-script@1.6.0" + "from": "coffee-script@1.6.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz" } } }, @@ -1608,7 +1628,8 @@ "dependencies": { "iconv-lite": { "version": "0.4.15", - "from": "iconv-lite@~0.4.13" + "from": "iconv-lite@~0.4.13", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz" } } }, @@ -1633,6 +1654,7 @@ "jade": { "version": "0.26.3", "from": "jade@0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", "dependencies": { "commander": { "version": "0.6.1", @@ -1660,7 +1682,7 @@ }, "mkdirp": { "version": "0.3.5", - "from": "mkdirp@0.3.5" + "from": "mkdirp@~0.3.5" }, "glob": { "version": "3.2.3", @@ -1695,6 +1717,7 @@ "mongojs": { "version": "0.18.2", "from": "mongojs@0.18.2", + "resolved": "https://registry.npmjs.org/mongojs/-/mongojs-0.18.2.tgz", "dependencies": { "thunky": { "version": "0.1.0", @@ -1702,7 +1725,7 @@ }, "readable-stream": { "version": "1.1.14", - "from": "readable-stream@~1.1.9", + "from": "readable-stream@1.1.x", "dependencies": { "core-util-is": { "version": "1.0.2", @@ -1710,7 +1733,8 @@ }, "isarray": { "version": "0.0.1", - "from": "isarray@0.0.1" + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" }, "string_decoder": { "version": "0.10.31", @@ -1725,6 +1749,7 @@ "mongodb": { "version": "1.4.32", "from": "mongodb@1.4.32", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-1.4.32.tgz", "dependencies": { "bson": { "version": "0.2.22", @@ -1739,6 +1764,7 @@ "kerberos": { "version": "0.0.9", "from": "kerberos@0.0.9", + "resolved": "https://registry.npmjs.org/kerberos/-/kerberos-0.0.9.tgz", "dependencies": { "nan": { "version": "1.6.2", @@ -1854,7 +1880,8 @@ }, "isarray": { "version": "0.0.1", - "from": "isarray@0.0.1" + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" }, "string_decoder": { "version": "0.10.31", @@ -1942,7 +1969,8 @@ }, "isarray": { "version": "0.0.1", - "from": "isarray@0.0.1" + "from": "isarray@0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz" }, "string_decoder": { "version": "0.10.31", @@ -1950,7 +1978,7 @@ }, "inherits": { "version": "2.0.3", - "from": "inherits@~2.0.1" + "from": "inherits@2" } } } @@ -1986,10 +2014,6 @@ } } }, - "node-uuid": { - "version": "1.4.1", - "from": "node-uuid@1.4.1" - }, "nodemailer": { "version": "2.1.0", "from": "nodemailer@2.1.0", @@ -2127,7 +2151,7 @@ "from": "nodemailer-ses-transport@^1.3.0", "dependencies": { "aws-sdk": { - "version": "2.7.28", + "version": "2.9.0", "from": "aws-sdk@^2.6.12", "dependencies": { "buffer": { @@ -2342,6 +2366,388 @@ } } }, + "pug": { + "version": "2.0.0-beta9", + "from": "pug@^2.0.0-beta6", + "resolved": "https://registry.npmjs.org/pug/-/pug-2.0.0-beta9.tgz", + "dependencies": { + "pug-code-gen": { + "version": "1.1.1", + "from": "pug-code-gen@^1.1.1", + "resolved": "https://registry.npmjs.org/pug-code-gen/-/pug-code-gen-1.1.1.tgz", + "dependencies": { + "constantinople": { + "version": "3.1.0", + "from": "constantinople@^3.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.0.tgz", + "dependencies": { + "acorn": { + "version": "3.3.0", + "from": "acorn@^3.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz" + }, + "is-expression": { + "version": "2.1.0", + "from": "is-expression@^2.0.1", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-2.1.0.tgz", + "dependencies": { + "object-assign": { + "version": "4.1.1", + "from": "object-assign@^4.0.1" + } + } + } + } + }, + "doctypes": { + "version": "1.1.0", + "from": "doctypes@^1.1.0", + "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz" + }, + "js-stringify": { + "version": "1.0.2", + "from": "js-stringify@^1.0.1", + "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz" + }, + "pug-attrs": { + "version": "2.0.2", + "from": "pug-attrs@^2.0.2", + "resolved": "https://registry.npmjs.org/pug-attrs/-/pug-attrs-2.0.2.tgz" + }, + "pug-error": { + "version": "1.3.2", + "from": "pug-error@^1.3.2", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz" + }, + "void-elements": { + "version": "2.0.1", + "from": "void-elements@^2.0.1", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz" + }, + "with": { + "version": "5.1.1", + "from": "with@^5.0.0", + "resolved": "https://registry.npmjs.org/with/-/with-5.1.1.tgz", + "dependencies": { + "acorn": { + "version": "3.3.0", + "from": "acorn@^3.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz" + }, + "acorn-globals": { + "version": "3.0.0", + "from": "acorn-globals@^3.0.0", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.0.0.tgz" + } + } + } + } + }, + "pug-filters": { + "version": "2.1.0", + "from": "pug-filters@^2.1.0", + "resolved": "https://registry.npmjs.org/pug-filters/-/pug-filters-2.1.0.tgz", + "dependencies": { + "constantinople": { + "version": "3.1.0", + "from": "constantinople@^3.0.1", + "resolved": "https://registry.npmjs.org/constantinople/-/constantinople-3.1.0.tgz", + "dependencies": { + "acorn": { + "version": "3.3.0", + "from": "acorn@^3.1.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz" + }, + "is-expression": { + "version": "2.1.0", + "from": "is-expression@^2.0.1", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-2.1.0.tgz", + "dependencies": { + "object-assign": { + "version": "4.1.1", + "from": "object-assign@^4.0.1" + } + } + } + } + }, + "pug-error": { + "version": "1.3.2", + "from": "pug-error@^1.3.2", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz" + }, + "pug-walk": { + "version": "1.1.0", + "from": "pug-walk@^1.1.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.0.tgz" + }, + "jstransformer": { + "version": "1.0.0", + "from": "jstransformer@1.0.0", + "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", + "dependencies": { + "is-promise": { + "version": "2.1.0", + "from": "is-promise@^2.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz" + }, + "promise": { + "version": "7.1.1", + "from": "promise@^7.0.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.1.1.tgz", + "dependencies": { + "asap": { + "version": "2.0.5", + "from": "asap@~2.0.3", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz" + } + } + } + } + }, + "resolve": { + "version": "1.2.0", + "from": "resolve@^1.1.6", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.2.0.tgz" + }, + "uglify-js": { + "version": "2.7.5", + "from": "uglify-js@^2.6.1", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.7.5.tgz", + "dependencies": { + "async": { + "version": "0.2.10", + "from": "async@~0.2.6" + }, + "source-map": { + "version": "0.5.6", + "from": "source-map@~0.5.1" + }, + "uglify-to-browserify": { + "version": "1.0.2", + "from": "uglify-to-browserify@~1.0.0" + }, + "yargs": { + "version": "3.10.0", + "from": "yargs@~3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "dependencies": { + "camelcase": { + "version": "1.2.1", + "from": "camelcase@^1.0.2" + }, + "cliui": { + "version": "2.1.0", + "from": "cliui@^2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "dependencies": { + "center-align": { + "version": "0.1.3", + "from": "center-align@^0.1.1", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "dependencies": { + "align-text": { + "version": "0.1.4", + "from": "align-text@^0.1.1", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "dependencies": { + "kind-of": { + "version": "3.1.0", + "from": "kind-of@^3.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.1.0.tgz", + "dependencies": { + "is-buffer": { + "version": "1.1.4", + "from": "is-buffer@^1.0.2", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.4.tgz" + } + } + }, + "longest": { + "version": "1.0.1", + "from": "longest@^1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz" + }, + "repeat-string": { + "version": "1.6.1", + "from": "repeat-string@^1.5.2", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" + } + } + }, + "lazy-cache": { + "version": "1.0.4", + "from": "lazy-cache@^1.0.3", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz" + } + } + }, + "right-align": { + "version": "0.1.3", + "from": "right-align@^0.1.1", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "dependencies": { + "align-text": { + "version": "0.1.4", + "from": "align-text@^0.1.1", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "dependencies": { + "kind-of": { + "version": "3.1.0", + "from": "kind-of@^3.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.1.0.tgz", + "dependencies": { + "is-buffer": { + "version": "1.1.4", + "from": "is-buffer@^1.0.2", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.4.tgz" + } + } + }, + "longest": { + "version": "1.0.1", + "from": "longest@^1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz" + }, + "repeat-string": { + "version": "1.6.1", + "from": "repeat-string@^1.5.2", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz" + } + } + } + } + }, + "wordwrap": { + "version": "0.0.2", + "from": "wordwrap@0.0.2" + } + } + }, + "decamelize": { + "version": "1.2.0", + "from": "decamelize@^1.0.0" + }, + "window-size": { + "version": "0.1.0", + "from": "window-size@0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz" + } + } + } + } + } + } + }, + "pug-lexer": { + "version": "2.3.2", + "from": "pug-lexer@^2.3.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-2.3.2.tgz", + "dependencies": { + "character-parser": { + "version": "2.2.0", + "from": "character-parser@^2.1.1", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "dependencies": { + "is-regex": { + "version": "1.0.3", + "from": "is-regex@^1.0.3", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.3.tgz" + } + } + }, + "is-expression": { + "version": "3.0.0", + "from": "is-expression@^3.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", + "dependencies": { + "acorn": { + "version": "4.0.4", + "from": "acorn@~4.0.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.4.tgz" + }, + "object-assign": { + "version": "4.1.1", + "from": "object-assign@^4.0.1" + } + } + }, + "pug-error": { + "version": "1.3.2", + "from": "pug-error@^1.3.2", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz" + } + } + }, + "pug-linker": { + "version": "2.0.1", + "from": "pug-linker@^2.0.1", + "resolved": "https://registry.npmjs.org/pug-linker/-/pug-linker-2.0.1.tgz", + "dependencies": { + "pug-error": { + "version": "1.3.2", + "from": "pug-error@^1.3.2", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz" + }, + "pug-walk": { + "version": "1.1.0", + "from": "pug-walk@^1.1.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.0.tgz" + } + } + }, + "pug-load": { + "version": "2.0.4", + "from": "pug-load@^2.0.4", + "resolved": "https://registry.npmjs.org/pug-load/-/pug-load-2.0.4.tgz", + "dependencies": { + "object-assign": { + "version": "4.1.1", + "from": "object-assign@^4.1.0" + }, + "pug-walk": { + "version": "1.1.0", + "from": "pug-walk@^1.1.0", + "resolved": "https://registry.npmjs.org/pug-walk/-/pug-walk-1.1.0.tgz" + } + } + }, + "pug-parser": { + "version": "2.0.2", + "from": "pug-parser@^2.0.2", + "resolved": "https://registry.npmjs.org/pug-parser/-/pug-parser-2.0.2.tgz", + "dependencies": { + "pug-error": { + "version": "1.3.2", + "from": "pug-error@^1.3.2", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz" + }, + "token-stream": { + "version": "0.0.1", + "from": "token-stream@0.0.1", + "resolved": "https://registry.npmjs.org/token-stream/-/token-stream-0.0.1.tgz" + } + } + }, + "pug-runtime": { + "version": "2.0.3", + "from": "pug-runtime@^2.0.3", + "resolved": "https://registry.npmjs.org/pug-runtime/-/pug-runtime-2.0.3.tgz" + }, + "pug-strip-comments": { + "version": "1.0.2", + "from": "pug-strip-comments@^1.0.2", + "resolved": "https://registry.npmjs.org/pug-strip-comments/-/pug-strip-comments-1.0.2.tgz", + "dependencies": { + "pug-error": { + "version": "1.3.2", + "from": "pug-error@^1.3.2", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-1.3.2.tgz" + } + } + } + } + }, "redis": { "version": "0.10.1", "from": "redis@0.10.1" @@ -2349,6 +2755,7 @@ "redis-sharelatex": { "version": "0.0.9", "from": "redis-sharelatex@0.0.9", + "resolved": "https://registry.npmjs.org/redis-sharelatex/-/redis-sharelatex-0.0.9.tgz", "dependencies": { "chai": { "version": "1.9.1", @@ -2360,7 +2767,7 @@ }, "deep-eql": { "version": "0.1.3", - "from": "deep-eql@0.1.3", + "from": "deep-eql@^0.1.3", "dependencies": { "type-detect": { "version": "0.1.1", @@ -2456,7 +2863,7 @@ }, "mkdirp": { "version": "0.5.1", - "from": "mkdirp@^0.5.0", + "from": "mkdirp@~0.5.1", "dependencies": { "minimist": { "version": "0.0.8", @@ -2557,6 +2964,7 @@ "jade": { "version": "0.26.3", "from": "jade@0.26.3", + "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", "dependencies": { "commander": { "version": "0.6.1", @@ -2584,7 +2992,7 @@ }, "mkdirp": { "version": "0.3.5", - "from": "mkdirp@0.3.5" + "from": "mkdirp@~0.3.5" }, "glob": { "version": "3.2.3", @@ -2618,11 +3026,13 @@ }, "redis": { "version": "0.12.1", - "from": "redis@0.12.1" + "from": "redis@0.12.1", + "resolved": "https://registry.npmjs.org/redis/-/redis-0.12.1.tgz" }, "redis-sentinel": { "version": "0.1.1", "from": "redis-sentinel@0.1.1", + "resolved": "https://registry.npmjs.org/redis-sentinel/-/redis-sentinel-0.1.1.tgz", "dependencies": { "redis": { "version": "0.11.0", @@ -2955,10 +3365,6 @@ "tunnel-agent": { "version": "0.4.3", "from": "tunnel-agent@~0.4.1" - }, - "uuid": { - "version": "3.0.1", - "from": "uuid@^3.0.0" } } }, @@ -3039,7 +3445,7 @@ }, "depd": { "version": "1.1.0", - "from": "depd@^1.1.0" + "from": "depd@~1.1.0" }, "dottie": { "version": "1.1.1", @@ -3047,10 +3453,11 @@ }, "generic-pool": { "version": "2.4.2", - "from": "generic-pool@2.4.2" + "from": "generic-pool@2.4.2", + "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.2.tgz" }, "inflection": { - "version": "1.10.0", + "version": "1.12.0", "from": "inflection@^1.6.0" }, "lodash": { @@ -3127,7 +3534,8 @@ }, "shimmer": { "version": "1.1.0", - "from": "shimmer@1.1.0" + "from": "shimmer@1.1.0", + "resolved": "https://registry.npmjs.org/shimmer/-/shimmer-1.1.0.tgz" }, "terraformer-wkt-parser": { "version": "1.1.2", @@ -3149,7 +3557,8 @@ }, "wkx": { "version": "0.2.0", - "from": "wkx@0.2.0" + "from": "wkx@0.2.0", + "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.2.0.tgz" } } }, @@ -3160,7 +3569,8 @@ "dependencies": { "coffee-script": { "version": "1.6.0", - "from": "coffee-script@1.6.0" + "from": "coffee-script@1.6.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.6.0.tgz" } } }, @@ -3182,6 +3592,10 @@ "version": "1.6.0", "from": "underscore@1.6.0" }, + "uuid": { + "version": "3.0.1", + "from": "uuid@^3.0.1" + }, "v8-profiler": { "version": "5.6.5", "from": "v8-profiler@^5.2.3", @@ -3442,7 +3856,7 @@ }, "inherits": { "version": "2.0.3", - "from": "inherits@2" + "from": "inherits@~2.0.0" } } }, @@ -3484,7 +3898,7 @@ }, "minimatch": { "version": "3.0.3", - "from": "minimatch@^3.0.2", + "from": "minimatch@^3.0.0", "dependencies": { "brace-expansion": { "version": "1.1.6", diff --git a/services/web/package.json b/services/web/package.json index 3408ede468..100b55f75c 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -42,7 +42,6 @@ "mongojs": "0.18.2", "mongoose": "4.1.0", "multer": "^0.1.8", - "node-uuid": "1.4.1", "nodemailer": "2.1.0", "nodemailer-sendgrid-transport": "^0.2.0", "nodemailer-ses-transport": "^1.3.0", @@ -64,6 +63,8 @@ "v8-profiler": "^5.2.3", "xml2js": "0.2.0", "passport-saml": "^0.15.0", + "pug": "^2.0.0-beta6", + "uuid": "^3.0.1", "rolling-rate-limiter": "git+https://github.com/ShaneKilkelly/rolling-rate-limiter.git#master" }, "devDependencies": { diff --git a/services/web/public/coffee/ide/editor/Document.coffee b/services/web/public/coffee/ide/editor/Document.coffee index 9d7eca813a..1de01b5467 100644 --- a/services/web/public/coffee/ide/editor/Document.coffee +++ b/services/web/public/coffee/ide/editor/Document.coffee @@ -84,6 +84,9 @@ define [ setTrackingChanges: (track_changes) -> @doc.track_changes = track_changes + getTrackingChanges: () -> + !!@doc.track_changes + setTrackChangesIdSeeds: (id_seeds) -> @doc.track_changes_id_seeds = id_seeds diff --git a/services/web/public/coffee/ide/editor/EditorManager.coffee b/services/web/public/coffee/ide/editor/EditorManager.coffee index 22fcef1a69..3b6672fae8 100644 --- a/services/web/public/coffee/ide/editor/EditorManager.coffee +++ b/services/web/public/coffee/ide/editor/EditorManager.coffee @@ -162,8 +162,9 @@ define [ @_syncTimeout = null want = @$scope.editor.wantTrackChanges - have = @$scope.editor.trackChanges + have = doc.getTrackingChanges() if want == have + @$scope.editor.trackChanges = want return do tryToggle = () => diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index 41c70b4dee..93775fd78f 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -13,7 +13,7 @@ define [ EditSession = ace.require('ace/edit_session').EditSession ModeList = ace.require('ace/ext/modelist') - # set the path for ace workers if using a CDN (from editor.jade) + # set the path for ace workers if using a CDN (from editor.pug) if window.aceWorkerPath != "" syntaxValidationEnabled = true ace.config.set('workerPath', "#{window.aceWorkerPath}") @@ -279,7 +279,7 @@ define [ session.setUseWrapMode(true) # use syntax validation only when explicitly set - if scope.syntaxValidation? and syntaxValidationEnabled + if scope.syntaxValidation? and syntaxValidationEnabled and !scope.fileName.match(/\.bib$/) session.setOption("useWorker", scope.syntaxValidation); # now attach session to editor diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee index 2d57100cc5..ed15da2958 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/track-changes/TrackChangesManager.coffee @@ -35,8 +35,8 @@ define [ @$scope.$on "comment:remove", (e, comment_id) => @removeCommentId(comment_id) - @$scope.$on "comment:resolve_thread", (e, thread_id) => - @resolveCommentByThreadId(thread_id) + @$scope.$on "comment:resolve_threads", (e, thread_ids) => + @resolveCommentByThreadIds(thread_ids) @$scope.$on "comment:unresolve_thread", (e, thread_id) => @unresolveCommentByThreadId(thread_id) @@ -105,29 +105,45 @@ define [ # ace has updated @rangesTracker.on "insert:added", (change) => sl_console.log "[insert:added]", change - setTimeout () => @_onInsertAdded(change) + setTimeout () => + @_onInsertAdded(change) + @broadcastChange() @rangesTracker.on "insert:removed", (change) => sl_console.log "[insert:removed]", change - setTimeout () => @_onInsertRemoved(change) + setTimeout () => + @_onInsertRemoved(change) + @broadcastChange() @rangesTracker.on "delete:added", (change) => sl_console.log "[delete:added]", change - setTimeout () => @_onDeleteAdded(change) + setTimeout () => + @_onDeleteAdded(change) + @broadcastChange() @rangesTracker.on "delete:removed", (change) => sl_console.log "[delete:removed]", change - setTimeout () => @_onDeleteRemoved(change) + setTimeout () => + @_onDeleteRemoved(change) + @broadcastChange() @rangesTracker.on "changes:moved", (changes) => sl_console.log "[changes:moved]", changes - setTimeout () => @_onChangesMoved(changes) + setTimeout () => + @_onChangesMoved(changes) + @broadcastChange() @rangesTracker.on "comment:added", (comment) => sl_console.log "[comment:added]", comment - setTimeout () => @_onCommentAdded(comment) + setTimeout () => + @_onCommentAdded(comment) + @broadcastChange() @rangesTracker.on "comment:moved", (comment) => sl_console.log "[comment:moved]", comment - setTimeout () => @_onCommentMoved(comment) + setTimeout () => + @_onCommentMoved(comment) + @broadcastChange() @rangesTracker.on "comment:removed", (comment) => sl_console.log "[comment:removed]", comment - setTimeout () => @_onCommentRemoved(comment) + setTimeout () => + @_onCommentRemoved(comment) + @broadcastChange() @rangesTracker.on "clear", () => @clearAnnotations() @@ -150,6 +166,8 @@ define [ for comment in @rangesTracker.comments @_onCommentAdded(comment) + + @broadcastChange() addComment: (offset, content, thread_id) -> op = { c: content, p: offset, t: thread_id } @@ -190,15 +208,20 @@ define [ removeCommentId: (comment_id) -> @rangesTracker.removeCommentId(comment_id) - resolveCommentByThreadId: (thread_id) -> + resolveCommentByThreadIds: (thread_ids) -> + resolve_ids = {} + for id in thread_ids + resolve_ids[id] = true for comment in @rangesTracker?.comments or [] - if comment.op.t == thread_id + if resolve_ids[comment.op.t] @_onCommentRemoved(comment) + @broadcastChange() unresolveCommentByThreadId: (thread_id) -> for comment in @rangesTracker?.comments or [] if comment.op.t == thread_id @_onCommentAdded(comment) + @broadcastChange() checkMapping: () -> # TODO: reintroduce this check @@ -303,7 +326,6 @@ define [ background_marker_id = session.addMarker background_range, "track-changes-marker track-changes-added-marker", "text" callout_marker_id = @_createCalloutMarker(start, "track-changes-added-marker-callout") @changeIdToMarkerIdMap[change.id] = { background_marker_id, callout_marker_id } - @broadcastChange() _onDeleteAdded: (change) -> position = @_shareJsOffsetToAcePosition(change.op.p) @@ -318,7 +340,6 @@ define [ callout_marker_id = @_createCalloutMarker(position, "track-changes-deleted-marker-callout") @changeIdToMarkerIdMap[change.id] = { background_marker_id, callout_marker_id } - @broadcastChange() _onInsertRemoved: (change) -> {background_marker_id, callout_marker_id} = @changeIdToMarkerIdMap[change.id] @@ -326,7 +347,6 @@ define [ session = @editor.getSession() session.removeMarker background_marker_id session.removeMarker callout_marker_id - @broadcastChange() _onDeleteRemoved: (change) -> {background_marker_id, callout_marker_id} = @changeIdToMarkerIdMap[change.id] @@ -334,7 +354,6 @@ define [ session = @editor.getSession() session.removeMarker background_marker_id session.removeMarker callout_marker_id - @broadcastChange() _onCommentAdded: (comment) -> if @rangesTracker.resolvedThreadIds[comment.op.t] @@ -350,7 +369,6 @@ define [ background_marker_id = session.addMarker background_range, "track-changes-marker track-changes-comment-marker", "text" callout_marker_id = @_createCalloutMarker(start, "track-changes-comment-marker-callout") @changeIdToMarkerIdMap[comment.id] = { background_marker_id, callout_marker_id } - @broadcastChange() _onCommentRemoved: (comment) -> if @changeIdToMarkerIdMap[comment.id]? @@ -360,7 +378,6 @@ define [ session = @editor.getSession() session.removeMarker background_marker_id session.removeMarker callout_marker_id - @broadcastChange() _aceRangeToShareJs: (range) -> lines = @editor.getSession().getDocument().getLines 0, range.row @@ -385,14 +402,12 @@ define [ end = start @_updateMarker(change.id, start, end) @editor.renderer.updateBackMarkers() - @broadcastChange() _onCommentMoved: (comment) -> start = @_shareJsOffsetToAcePosition(comment.op.p) end = @_shareJsOffsetToAcePosition(comment.op.p + comment.op.c.length) @_updateMarker(comment.id, start, end) @editor.renderer.updateBackMarkers() - @broadcastChange() _updateMarker: (change_id, start, end) -> return if !@changeIdToMarkerIdMap[change_id]? diff --git a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee index 1a1a37cf21..5f346ec3d6 100644 --- a/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee +++ b/services/web/public/coffee/ide/review-panel/controllers/ReviewPanelController.coffee @@ -70,6 +70,10 @@ define [ ide.socket.on "reopen-thread", (thread_id) -> _onCommentReopened(thread_id) + ide.socket.on "delete-thread", (thread_id) -> + _onThreadDeleted(thread_id) + $scope.$apply () -> + ide.socket.on "edit-message", (thread_id, message_id, content) -> _onCommentEdited(thread_id, message_id, content) $scope.$apply () -> @@ -226,8 +230,10 @@ define [ delete delete_changes[comment.id] if $scope.reviewPanel.resolvedThreadIds[comment.op.t] new_comment = resolvedComments[comment.id] ?= {} + delete entries[comment.id] else new_comment = entries[comment.id] ?= {} + delete resolvedComments[comment.id] new_entry = { type: "comment" thread_id: comment.op.t @@ -360,7 +366,7 @@ define [ thread.resolved_by_user = formatUser(user) thread.resolved_at = new Date() $scope.reviewPanel.resolvedThreadIds[thread_id] = true - $scope.$broadcast "comment:resolve_thread", thread_id + $scope.$broadcast "comment:resolve_threads", [thread_id] _onCommentReopened = (thread_id) -> thread = getThread(thread_id) @@ -374,6 +380,7 @@ define [ _onThreadDeleted = (thread_id) -> delete $scope.reviewPanel.resolvedThreadIds[thread_id] delete $scope.reviewPanel.commentThreads[thread_id] + $scope.$broadcast "comment:remove", thread_id _onCommentEdited = (thread_id, comment_id, content) -> thread = getThread(thread_id) @@ -398,7 +405,6 @@ define [ 'X-CSRF-Token': window.csrfToken } }) - $scope.$broadcast "comment:remove", entry_id event_tracking.sendMB "rp-comment-delete" $scope.saveEdit = (thread_id, comment) -> @@ -481,9 +487,9 @@ define [ for comment in thread.messages formatComment(comment) if thread.resolved_by_user? - $scope.$broadcast "comment:resolve_thread", thread_id thread.resolved_by_user = formatUser(thread.resolved_by_user) $scope.reviewPanel.resolvedThreadIds[thread_id] = true + $scope.$broadcast "comment:resolve_threads", [thread_id] $scope.reviewPanel.commentThreads = threads $timeout () -> $scope.$broadcast "review-panel:layout" diff --git a/services/web/public/coffee/ide/review-panel/directives/changeEntry.coffee b/services/web/public/coffee/ide/review-panel/directives/changeEntry.coffee index 96f5e50016..9dc1ef2a37 100644 --- a/services/web/public/coffee/ide/review-panel/directives/changeEntry.coffee +++ b/services/web/public/coffee/ide/review-panel/directives/changeEntry.coffee @@ -11,11 +11,16 @@ define [ onAccept: "&" onReject: "&" onIndicatorClick: "&" + onBodyClick: "&" link: (scope, element, attrs) -> scope.contentLimit = 40 scope.isCollapsed = true scope.needsCollapsing = false + element.on "click", (e) -> + if $(e.target).is('.rp-entry, .rp-entry-description, .rp-entry-body, .rp-entry-action-icon i') + scope.onBodyClick() + scope.toggleCollapse = () -> scope.isCollapsed = !scope.isCollapsed $timeout () -> diff --git a/services/web/public/coffee/ide/review-panel/directives/commentEntry.coffee b/services/web/public/coffee/ide/review-panel/directives/commentEntry.coffee index 7c7811d553..b2f09c96af 100644 --- a/services/web/public/coffee/ide/review-panel/directives/commentEntry.coffee +++ b/services/web/public/coffee/ide/review-panel/directives/commentEntry.coffee @@ -13,10 +13,15 @@ define [ onIndicatorClick: "&" onSaveEdit: "&" onDelete: "&" + onBodyClick: "&" link: (scope, element, attrs) -> scope.state = animating: false + element.on "click", (e) -> + if $(e.target).is('.rp-entry, .rp-comment-loaded, .rp-comment-content, .rp-comment-reply, .rp-entry-metadata') + scope.onBodyClick() + scope.handleCommentReplyKeyPress = (ev) -> if ev.keyCode == 13 and !ev.shiftKey and !ev.ctrlKey and !ev.metaKey ev.preventDefault() diff --git a/services/web/test/UnitTests/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee b/services/web/test/UnitTests/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee index bc1cb2e3b4..453296b3d6 100644 --- a/services/web/test/UnitTests/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee +++ b/services/web/test/UnitTests/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee @@ -114,7 +114,8 @@ describe "CollaboratorsInviteController", -> describe 'when all goes well', -> beforeEach -> - @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, null, true) + @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, true) + @CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true) @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true) @CollaboratorsInviteController.inviteToProject @req, @res, @next @@ -128,7 +129,7 @@ describe "CollaboratorsInviteController", -> it 'should have called _checkShouldInviteEmail', -> @CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal 1 - @CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@sendingUser, @targetEmail).should.equal true + @CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@targetEmail).should.equal true it 'should have called inviteToProject', -> @CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 1 @@ -141,7 +142,8 @@ describe "CollaboratorsInviteController", -> describe 'when the user is not allowed to add more collaborators', -> beforeEach -> - @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, null, true) + @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, true) + @CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true) @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, false) @CollaboratorsInviteController.inviteToProject @req, @res, @next @@ -159,7 +161,8 @@ describe "CollaboratorsInviteController", -> describe 'when canAddXCollaborators produces an error', -> beforeEach -> - @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, null, true) + @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, true) + @CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true) @err = new Error('woops') @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, @err) @CollaboratorsInviteController.inviteToProject @req, @res, @next @@ -178,7 +181,8 @@ describe "CollaboratorsInviteController", -> describe 'when inviteToProject produces an error', -> beforeEach -> - @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, null, true) + @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, true) + @CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true) @err = new Error('woops') @CollaboratorsInviteHandler.inviteToProject = sinon.stub().callsArgWith(4, @err) @CollaboratorsInviteController.inviteToProject @req, @res, @next @@ -193,7 +197,7 @@ describe "CollaboratorsInviteController", -> it 'should have called _checkShouldInviteEmail', -> @CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal 1 - @CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@sendingUser, @targetEmail).should.equal true + @CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@targetEmail).should.equal true it 'should have called inviteToProject', -> @CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 1 @@ -202,7 +206,8 @@ describe "CollaboratorsInviteController", -> describe 'when _checkShouldInviteEmail disallows the invite', -> beforeEach -> - @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, null, false) + @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, false) + @CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true) @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true) @CollaboratorsInviteController.inviteToProject @req, @res, @next @@ -212,7 +217,7 @@ describe "CollaboratorsInviteController", -> it 'should have called _checkShouldInviteEmail', -> @CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal 1 - @CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@sendingUser, @targetEmail).should.equal true + @CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@targetEmail).should.equal true it 'should not have called inviteToProject', -> @CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 0 @@ -220,7 +225,8 @@ describe "CollaboratorsInviteController", -> describe 'when _checkShouldInviteEmail produces an error', -> beforeEach -> - @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, new Error('woops')) + @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, new Error('woops')) + @CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true) @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true) @CollaboratorsInviteController.inviteToProject @req, @res, @next @@ -230,7 +236,7 @@ describe "CollaboratorsInviteController", -> it 'should have called _checkShouldInviteEmail', -> @CollaboratorsInviteController._checkShouldInviteEmail.callCount.should.equal 1 - @CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@sendingUser, @targetEmail).should.equal true + @CollaboratorsInviteController._checkShouldInviteEmail.calledWith(@targetEmail).should.equal true it 'should not have called inviteToProject', -> @CollaboratorsInviteHandler.inviteToProject.callCount.should.equal 0 @@ -240,7 +246,8 @@ describe "CollaboratorsInviteController", -> beforeEach -> @req.session.user = {_id: 'abc', email: 'me@example.com'} @req.body.email = 'me@example.com' - @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(2, null, true) + @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, true) + @CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, true) @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true) @CollaboratorsInviteController.inviteToProject @req, @res, @next @@ -261,6 +268,22 @@ describe "CollaboratorsInviteController", -> it 'should not have called emitToRoom', -> @EditorRealTimeController.emitToRoom.callCount.should.equal 0 + describe 'when _checkRateLimit returns false', -> + + beforeEach -> + @CollaboratorsInviteController._checkShouldInviteEmail = sinon.stub().callsArgWith(1, null, true) + @CollaboratorsInviteController._checkRateLimit = sinon.stub().yields(null, false) + @LimitationsManager.canAddXCollaborators = sinon.stub().callsArgWith(2, null, true) + @CollaboratorsInviteController.inviteToProject @req, @res, @next + + it 'should send a 429 response', -> + @res.sendStatus.calledWith(429).should.equal true + + it 'should not call inviteToProject', -> + @CollaboratorsInviteHandler.inviteToProject.called.should.equal false + + it 'should not call emitToRoom', -> + @EditorRealTimeController.emitToRoom.called.should.equal false describe "viewInvite", -> @@ -679,13 +702,12 @@ describe "CollaboratorsInviteController", -> beforeEach -> @email = 'user@example.com' - describe 'when we should be restricting to existing accounts', -> beforeEach -> @settings.restrictInvitesToExistingAccounts = true @call = (callback) => - @CollaboratorsInviteController._checkShouldInviteEmail {}, @email, callback + @CollaboratorsInviteController._checkShouldInviteEmail @email, callback describe 'when user account is present', -> @@ -730,46 +752,43 @@ describe "CollaboratorsInviteController", -> expect(shouldAllow).to.equal undefined done() - describe 'when we should not be restricting on only registered users but do rate limit', -> + describe '_checkRateLimit', -> + beforeEach -> + @settings.restrictInvitesToExistingAccounts = false + @sendingUserId = "32312313" + @LimitationsManager.allowedNumberOfCollaboratorsForUser = sinon.stub() + @LimitationsManager.allowedNumberOfCollaboratorsForUser.withArgs(@sendingUserId).yields(null, 17) - beforeEach -> - @settings.restrictInvitesToExistingAccounts = false - @sendingUser = - _id:"32312313" - features: - collaborators:17.8 - @UserGetter.getUser = sinon.stub().callsArgWith(2, null, @sendingUser) + it 'should callback with `true` when rate limit under', (done) -> + @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true) + @CollaboratorsInviteController._checkRateLimit @sendingUserId, (err, result)=> + @RateLimiter.addCount.called.should.equal true + result.should.equal true + done() - it 'should callback with `true` when rate limit under', (done) -> - @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true) - @CollaboratorsInviteController._checkShouldInviteEmail @sendingUser, @email, (err, result)=> - @RateLimiter.addCount.called.should.equal true - result.should.equal true - done() + it 'should callback with `false` when rate limit hit', (done) -> + @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, false) + @CollaboratorsInviteController._checkRateLimit @sendingUserId, (err, result)=> + @RateLimiter.addCount.called.should.equal true + result.should.equal false + done() + + it 'should call rate limiter with 10x the collaborators', (done) -> + @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true) + @CollaboratorsInviteController._checkRateLimit @sendingUserId, (err, result)=> + @RateLimiter.addCount.args[0][0].throttle.should.equal(170) + done() - it 'should callback with `false` when rate limit hit', (done) -> - @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, false) - @CollaboratorsInviteController._checkShouldInviteEmail @sendingUser, @email, (err, result)=> - @RateLimiter.addCount.called.should.equal true - result.should.equal false - done() - - it 'should call rate limiter with 10x the collaborators', (done) -> - @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true) - @CollaboratorsInviteController._checkShouldInviteEmail @sendingUser, @email, (err, result)=> - @RateLimiter.addCount.args[0][0].throttle.should.equal(178) - done() + it 'should call rate limiter with 200 when collaborators is -1', (done) -> + @LimitationsManager.allowedNumberOfCollaboratorsForUser.withArgs(@sendingUserId).yields(null, -1) + @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true) + @CollaboratorsInviteController._checkRateLimit @sendingUserId, (err, result)=> + @RateLimiter.addCount.args[0][0].throttle.should.equal(200) + done() - it 'should call rate limiter with 200 when collaborators is -1', (done) -> - @sendingUser.features.collaborators = -1 - @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true) - @CollaboratorsInviteController._checkShouldInviteEmail @sendingUser, @email, (err, result)=> - @RateLimiter.addCount.args[0][0].throttle.should.equal(200) - done() - - it 'should call rate limiter with 10 when user has no collaborators set', (done) -> - delete @sendingUser.features - @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true) - @CollaboratorsInviteController._checkShouldInviteEmail @sendingUser, @email, (err, result)=> - @RateLimiter.addCount.args[0][0].throttle.should.equal(10) - done() \ No newline at end of file + it 'should call rate limiter with 10 when user has no collaborators set', (done) -> + @LimitationsManager.allowedNumberOfCollaboratorsForUser.withArgs(@sendingUserId).yields(null) + @RateLimiter.addCount = sinon.stub().callsArgWith(1, null, true) + @CollaboratorsInviteController._checkRateLimit @sendingUserId, (err, result)=> + @RateLimiter.addCount.args[0][0].throttle.should.equal(10) + done() \ No newline at end of file diff --git a/services/web/test/UnitTests/coffee/Subscription/LimitationsManagerTests.coffee b/services/web/test/UnitTests/coffee/Subscription/LimitationsManagerTests.coffee index 93f00afad5..f81433e156 100644 --- a/services/web/test/UnitTests/coffee/Subscription/LimitationsManagerTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/LimitationsManagerTests.coffee @@ -6,17 +6,17 @@ Settings = require("settings-sharelatex") describe "LimitationsManager", -> beforeEach -> - @project = { _id: "project-id" } - @user = { _id: "user-id", features:{} } + @project = { _id: @project_id = "project-id" } + @user = { _id: @user_id = "user-id", features:{} } @Project = findById: (project_id, fields, callback) => if project_id == @project_id callback null, @project else callback null, null - @User = - findById: (user_id, callback) => - if user_id == @user.id + @UserGetter = + getUser: (user_id, filter, callback) => + if user_id == @user_id callback null, @user else callback null, null @@ -26,7 +26,7 @@ describe "LimitationsManager", -> @LimitationsManager = SandboxedModule.require modulePath, requires: '../../models/Project' : Project: @Project - '../../models/User' : User: @User + '../User/UserGetter' : @UserGetter './SubscriptionLocator':@SubscriptionLocator 'settings-sharelatex' : @Settings = {} "../Collaborators/CollaboratorsHandler": @CollaboratorsHandler = {} @@ -37,6 +37,7 @@ describe "LimitationsManager", -> describe "when the project is owned by a user without a subscription", -> beforeEach -> @Settings.defaultPlanCode = collaborators: 23 + @project.owner_ref = @user_id delete @user.features @callback = sinon.stub() @LimitationsManager.allowedNumberOfCollaboratorsInProject(@project_id, @callback) @@ -46,6 +47,7 @@ describe "LimitationsManager", -> describe "when the project is owned by a user with a subscription", -> beforeEach -> + @project.owner_ref = @user_id @user.features = collaborators: 21 @callback = sinon.stub() @@ -53,6 +55,27 @@ describe "LimitationsManager", -> it "should return the number of collaborators the user is allowed", -> @callback.calledWith(null, @user.features.collaborators).should.equal true + + describe "allowedNumberOfCollaboratorsForUser", -> + describe "when the user has no features", -> + beforeEach -> + @Settings.defaultPlanCode = collaborators: 23 + delete @user.features + @callback = sinon.stub() + @LimitationsManager.allowedNumberOfCollaboratorsForUser(@user_id, @callback) + + it "should return the default number", -> + @callback.calledWith(null, @Settings.defaultPlanCode.collaborators).should.equal true + + describe "when the user has features", -> + beforeEach -> + @user.features = + collaborators: 21 + @callback = sinon.stub() + @LimitationsManager.allowedNumberOfCollaboratorsForUser(@user_id, @callback) + + it "should return the number of collaborators the user is allowed", -> + @callback.calledWith(null, @user.features.collaborators).should.equal true describe "canAddXCollaborators", -> beforeEach ->