diff --git a/services/web/Jenkinsfile b/services/web/Jenkinsfile index 7953aa0dbc..7b44d70315 100644 --- a/services/web/Jenkinsfile +++ b/services/web/Jenkinsfile @@ -101,7 +101,7 @@ pipeline { } } steps { - sh 'make minify' + sh 'WEBPACK_ENV=production make minify' } } diff --git a/services/web/Makefile b/services/web/Makefile index a0f8c1c503..469ab8e919 100644 --- a/services/web/Makefile +++ b/services/web/Makefile @@ -9,7 +9,7 @@ COFFEE := node_modules/.bin/coffee $(COFFEE_OPTIONS) GRUNT := node_modules/.bin/grunt APP_COFFEE_FILES := $(shell find app/coffee -name '*.coffee') FRONT_END_COFFEE_FILES := $(shell find public/coffee -name '*.coffee') -TEST_COFFEE_FILES := $(shell find test -name '*.coffee') +TEST_COFFEE_FILES := $(shell find test/*/coffee -name '*.coffee') MODULE_MAIN_COFFEE_FILES := $(shell find modules -type f -wholename '*main/index.coffee') MODULE_IDE_COFFEE_FILES := $(shell find modules -type f -wholename '*ide/index.coffee') COFFEE_FILES := app.coffee $(APP_COFFEE_FILES) $(FRONT_END_COFFEE_FILES) $(TEST_COFFEE_FILES) @@ -187,6 +187,9 @@ test: test_unit test_frontend test_acceptance test_unit: npm -q run test:unit -- ${MOCHA_ARGS} +test_unit_app: + npm -q run test:unit:app -- ${MOCHA_ARGS} + test_frontend: test_clean # stop service $(MAKE) compile docker-compose ${DOCKER_COMPOSE_FLAGS} up test_frontend diff --git a/services/web/README.md b/services/web/README.md index 51f73d02ec..7c8cbf0155 100644 --- a/services/web/README.md +++ b/services/web/README.md @@ -3,11 +3,11 @@ web-sharelatex web-sharelatex is the front-end web service of the open-source web-based collaborative LaTeX editor, [ShareLaTeX](https://www.sharelatex.com). -It serves all the HTML pages, CSS and javascript to the client. web-sharelatex also contains +It serves all the HTML pages, CSS and javascript to the client. web-sharelatex also contains a lot of logic around creating and editing projects, and account management. -The rest of the ShareLaTeX stack, along with information about contributing can be found in the +The rest of the ShareLaTeX stack, along with information about contributing can be found in the [sharelatex/sharelatex](https://github.com/sharelatex/sharelatex) repository. Build process @@ -20,7 +20,7 @@ Image processing tasks are commented out in the gruntfile and the needed package New Docker-based build process ------------------------------ -Note that the Grunt workflow from above should still work, but we are transitioning to a +Note that the Grunt workflow from above should still work, but we are transitioning to a Docker based testing workflow, which is documented below: ### Running the app @@ -43,7 +43,6 @@ Unit tests can be run in the `test_unit` container defined in `docker-compose.te The makefile contains a short cut to run these: ``` -make install # Only needs running once, or when npm packages are updated make unit_test ``` @@ -60,19 +59,18 @@ Acceptance tests are run against a live service, which runs in the `acceptance_t To run the tests out-of-the-box, the makefile defines: ``` -make install # Only needs running once, or when npm packages are updated -make acceptance_test +make test_acceptance ``` However, during development it is often useful to leave the service running for rapid iteration on the acceptance tests. This can be done with: ``` -make acceptance_test_start_service -make acceptance_test_run # Run as many times as needed during development -make acceptance_test_stop_service +make test_acceptance_app_start_service +make test_acceptance_app_run # Run as many times as needed during development +make test_acceptance_app_stop_service ``` -`make acceptance_test` just runs these three commands in sequence. +`make test_acceptance` just runs these three commands in sequence and then runs `make test_acceptance_modules` which performs the tests for each module in the `modules` directory. (Note that there is not currently an equivalent to the `-start` / `-run` x _n_ / `-stop` series for modules.) During development it is often useful to only run a subset of tests, which can be configured with arguments to the mocha CLI: @@ -112,12 +110,3 @@ We gratefully acknowledge [IconShock](http://www.iconshock.com) for use of the i in the `public/img/iconshock` directory found via [findicons.com](http://findicons.com/icon/498089/height?id=526085#) - -## Acceptance Tests - -To run the Acceptance tests: - -- set `allowPublicAccess` to true, either in the configuration file, - or by setting the environment variable `SHARELATEX_ALLOW_PUBLIC_ACCESS` to `true` -- start the server (`grunt`) -- in a separate terminal, run `grunt test:acceptance` diff --git a/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee b/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee index 4e96ce113c..1e0577cfc1 100644 --- a/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee +++ b/services/web/app/coffee/Features/BetaProgram/BetaProgramController.coffee @@ -1,5 +1,5 @@ BetaProgramHandler = require './BetaProgramHandler' -UserLocator = require "../User/UserLocator" +UserGetter = require "../User/UserGetter" Settings = require "settings-sharelatex" logger = require 'logger-sharelatex' AuthenticationController = require '../Authentication/AuthenticationController' @@ -30,7 +30,7 @@ module.exports = BetaProgramController = optInPage: (req, res, next)-> user_id = AuthenticationController.getLoggedInUserId(req) logger.log {user_id}, "showing beta participation page for user" - UserLocator.findById user_id, (err, user)-> + UserGetter.getUser user_id, (err, user)-> if err logger.err {err, user_id}, "error fetching user" return next(err) diff --git a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee index 898abe52bb..f74a144bac 100644 --- a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee +++ b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteController.coffee @@ -27,7 +27,7 @@ module.exports = CollaboratorsInviteController = _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) -> + UserGetter.getUserByMainEmail email, {_id: 1}, (err, user) -> return callback(err) if err? userExists = user? and user?._id? callback(null, userExists) diff --git a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee index ecca8ab86f..b511f56e53 100644 --- a/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee +++ b/services/web/app/coffee/Features/Collaborators/CollaboratorsInviteHandler.coffee @@ -32,7 +32,7 @@ module.exports = CollaboratorsInviteHandler = _trySendInviteNotification: (projectId, sendingUser, invite, callback=(err)->) -> email = invite.email - UserGetter.getUser {email: email}, {_id: 1}, (err, existingUser) -> + UserGetter.getUserByMainEmail email, {_id: 1}, (err, existingUser) -> if err? logger.err {projectId, email}, "error checking if user exists" return callback(err) diff --git a/services/web/app/coffee/Features/Compile/CompileManager.coffee b/services/web/app/coffee/Features/Compile/CompileManager.coffee index 3022a214c4..4f4fe89e94 100755 --- a/services/web/app/coffee/Features/Compile/CompileManager.coffee +++ b/services/web/app/coffee/Features/Compile/CompileManager.coffee @@ -18,15 +18,16 @@ module.exports = CompileManager = timer.done() _callback(args...) - @_checkIfAutoCompileLimitHasBeenHit options.isAutoCompile, "everyone", (err, canCompile)-> - if !canCompile - return callback null, "autocompile-backoff", [] - logger.log project_id: project_id, user_id: user_id, "compiling project" - CompileManager._checkIfRecentlyCompiled project_id, user_id, (error, recentlyCompiled) -> - return callback(error) if error? - if recentlyCompiled - logger.warn {project_id, user_id}, "project was recently compiled so not continuing" - return callback null, "too-recently-compiled", [] + logger.log project_id: project_id, user_id: user_id, "compiling project" + CompileManager._checkIfRecentlyCompiled project_id, user_id, (error, recentlyCompiled) -> + return callback(error) if error? + if recentlyCompiled + logger.warn {project_id, user_id}, "project was recently compiled so not continuing" + return callback null, "too-recently-compiled", [] + + CompileManager._checkIfAutoCompileLimitHasBeenHit options.isAutoCompile, "everyone", (err, canCompile)-> + if !canCompile + return callback null, "autocompile-backoff", [] CompileManager._ensureRootDocumentIsSet project_id, (error) -> return callback(error) if error? diff --git a/services/web/app/coffee/Features/Errors/Errors.coffee b/services/web/app/coffee/Features/Errors/Errors.coffee index 1b138d646f..b2ed088d15 100644 --- a/services/web/app/coffee/Features/Errors/Errors.coffee +++ b/services/web/app/coffee/Features/Errors/Errors.coffee @@ -47,6 +47,13 @@ UnsupportedExportRecordsError = (message) -> return error UnsupportedExportRecordsError.prototype.__proto___ = Error.prototype +V1HistoryNotSyncedError = (message) -> + error = new Error(message) + error.name = "V1HistoryNotSyncedError" + error.__proto__ = V1HistoryNotSyncedError.prototype + return error +V1HistoryNotSyncedError.prototype.__proto___ = Error.prototype + ProjectHistoryDisabledError = (message) -> error = new Error(message) error.name = "ProjectHistoryDisabledError " @@ -62,4 +69,5 @@ module.exports = Errors = UnsupportedFileTypeError: UnsupportedFileTypeError UnsupportedBrandError: UnsupportedBrandError UnsupportedExportRecordsError: UnsupportedExportRecordsError + V1HistoryNotSyncedError: V1HistoryNotSyncedError ProjectHistoryDisabledError: ProjectHistoryDisabledError diff --git a/services/web/app/coffee/Features/Exports/ExportsController.coffee b/services/web/app/coffee/Features/Exports/ExportsController.coffee new file mode 100644 index 0000000000..b60f58ba20 --- /dev/null +++ b/services/web/app/coffee/Features/Exports/ExportsController.coffee @@ -0,0 +1,19 @@ +ExportsHandler = require("./ExportsHandler") +AuthenticationController = require("../Authentication/AuthenticationController") +logger = require("logger-sharelatex") + +module.exports = + + exportProject: (req, res) -> + {project_id, brand_variation_id} = req.params + user_id = AuthenticationController.getLoggedInUserId(req) + ExportsHandler.exportProject project_id, user_id, brand_variation_id, (err, export_data) -> + return next(err) if err? + logger.log + user_id:user_id + project_id: project_id + brand_variation_id:brand_variation_id + export_v1_id:export_data.v1_id + "exported project" + res.send export_v1_id: export_data.v1_id + diff --git a/services/web/app/coffee/Features/Exports/ExportsHandler.coffee b/services/web/app/coffee/Features/Exports/ExportsHandler.coffee new file mode 100644 index 0000000000..727b01a575 --- /dev/null +++ b/services/web/app/coffee/Features/Exports/ExportsHandler.coffee @@ -0,0 +1,93 @@ +ProjectGetter = require('../Project/ProjectGetter') +ProjectLocator = require('../Project/ProjectLocator') +UserGetter = require('../User/UserGetter') +logger = require('logger-sharelatex') +settings = require 'settings-sharelatex' +async = require 'async' +request = require 'request' +request = request.defaults() +settings = require 'settings-sharelatex' + +module.exports = ExportsHandler = self = + + exportProject: (project_id, user_id, brand_variation_id, callback=(error, export_data) ->) -> + self._buildExport project_id, user_id, brand_variation_id, (err, export_data) -> + return callback(err) if err? + self._requestExport export_data, (err, export_v1_id) -> + return callback(err) if err? + export_data.v1_id = export_v1_id + # TODO: possibly store the export data in Mongo + callback null, export_data + + _buildExport: (project_id, user_id, brand_variation_id, callback=(err, export_data) ->) -> + jobs = + project: (cb) -> + ProjectGetter.getProject project_id, cb + # TODO: when we update async, signature will change from (cb, results) to (results, cb) + rootDoc: [ 'project', (cb, results) -> + ProjectLocator.findRootDoc {project: results.project, project_id: project_id}, cb + ] + user: (cb) -> + UserGetter.getUser user_id, {first_name: 1, last_name: 1, email: 1}, cb + historyVersion: (cb) -> + self._requestVersion project_id, cb + + async.auto jobs, (err, results) -> + if err? + logger.err err:err, project_id:project_id, user_id:user_id, brand_variation_id:brand_variation_id, "error building project export" + return callback(err) + + {project, rootDoc, user, historyVersion} = results + if !rootDoc[1]? + err = new Error("cannot export project without root doc") + logger.err err:err, project_id: project_id + return callback(err) + + export_data = + project: + id: project_id + rootDocPath: rootDoc[1]?.fileSystem + historyId: project.overleaf?.history?.id + historyVersion: historyVersion + user: + id: user_id + firstName: user.first_name + lastName: user.last_name + email: user.email + orcidId: null # until v2 gets ORCID + destination: + brandVariationId: brand_variation_id + options: + callbackUrl: null # for now, until we want v1 to call us back + callback null, export_data + + _requestExport: (export_data, callback=(err, export_v1_id) ->) -> + request.post { + url: "#{settings.apis.v1.url}/api/v1/sharelatex/exports" + auth: {user: settings.apis.v1.user, pass: settings.apis.v1.pass } + json: export_data + }, (err, res, body) -> + if err? + logger.err err:err, export:export_data, "error making request to v1 export" + callback err + else if 200 <= res.statusCode < 300 + callback null, body.exportId + else + err = new Error("v1 export returned a failure status code: #{res.statusCode}") + logger.err err:err, export:export_data, "v1 export returned failure status code: #{res.statusCode}" + callback err + + _requestVersion: (project_id, callback=(err, export_v1_id) ->) -> + request.get { + url: "#{settings.apis.project_history.url}/project/#{project_id}/version" + json: true + }, (err, res, body) -> + if err? + logger.err err:err, project_id:project_id, "error making request to project history" + callback err + else if res.statusCode >= 200 and res.statusCode < 300 + callback null, body.version + else + err = new Error("project history version returned a failure status code: #{res.statusCode}") + logger.err err:err, project_id:project_id, "project history version returned failure status code: #{res.statusCode}" + callback err diff --git a/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee b/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee index 4e67e9f1f4..3947b63004 100644 --- a/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee +++ b/services/web/app/coffee/Features/PasswordReset/PasswordResetHandler.coffee @@ -9,7 +9,7 @@ logger = require("logger-sharelatex") module.exports = generateAndEmailResetToken:(email, callback = (error, exists) ->)-> - UserGetter.getUser email:email, (err, user)-> + UserGetter.getUserByMainEmail email, (err, user)-> if err then return callback(err) if !user? or user.holdingAccount logger.err email:email, "user could not be found for password reset" diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index 77aabd8524..4ca886fed0 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -157,7 +157,7 @@ module.exports = ProjectController = hasSubscription: (cb)-> LimitationsManager.userHasSubscriptionOrIsGroupMember currentUser, cb user: (cb) -> - User.findById user_id, "featureSwitches overleaf awareOfV2", cb + User.findById user_id, "featureSwitches overleaf awareOfV2 features", cb }, (err, results)-> if err? logger.err err:err, "error getting data for project list page" @@ -172,6 +172,7 @@ module.exports = ProjectController = user = results.user warnings = ProjectController._buildWarningsList results.v1Projects + ProjectController._injectProjectOwners projects, (error, projects) -> return next(error) if error? viewModel = { @@ -193,6 +194,14 @@ module.exports = ProjectController = else viewModel.showUserDetailsArea = false + paidUser = user.features?.github and user.features?.dropbox # use a heuristic for paid account + freeUserProportion = 0.10 + sampleFreeUser = parseInt(user._id.toString().slice(-2), 16) < freeUserProportion * 255 + showFrontWidget = paidUser or sampleFreeUser + logger.log {paidUser, sampleFreeUser, showFrontWidget}, 'deciding whether to show front widget' + if showFrontWidget + viewModel.frontChatWidgetRoomId = Settings.overleaf?.front_chat_widget_room_id + res.render 'project/list', viewModel timer.done() @@ -290,6 +299,8 @@ module.exports = ProjectController = autoPairDelimiters: user.ace.autoPairDelimiters pdfViewer : user.ace.pdfViewer syntaxValidation: user.ace.syntaxValidation + fontFamily: user.ace.fontFamily + lineHeight: user.ace.lineHeight } trackChangesState: project.track_changes privilegeLevel: privilegeLevel @@ -302,6 +313,7 @@ module.exports = ProjectController = maxDocLength: Settings.max_doc_length useV2History: !!project.overleaf?.history?.display showRichText: req.query?.rt == 'true' + showPublishModal: req.query?.pm == 'true' timer.done() _buildProjectList: (allProjects, v1Projects = [])-> diff --git a/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee b/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee index 0b66b38ad6..529164a49e 100644 --- a/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectCreationHandler.coffee @@ -16,38 +16,42 @@ AnalyticsManger = require("../Analytics/AnalyticsManager") module.exports = ProjectCreationHandler = - createBlankProject : (owner_id, projectName, projectHistoryId, callback = (error, project) ->)-> + createBlankProject : (owner_id, projectName, attributes, callback = (error, project) ->)-> metrics.inc("project-creation") if arguments.length == 3 - callback = projectHistoryId - projectHistoryId = null + callback = attributes + attributes = null ProjectDetailsHandler.validateProjectName projectName, (error) -> return callback(error) if error? logger.log owner_id:owner_id, projectName:projectName, "creating blank project" - if projectHistoryId? - ProjectCreationHandler._createBlankProject owner_id, projectName, projectHistoryId, (error, project) -> + if attributes? + ProjectCreationHandler._createBlankProject owner_id, projectName, attributes, (error, project) -> return callback(error) if error? AnalyticsManger.recordEvent( - owner_id, 'project-imported', { projectId: project._id, projectHistoryId: projectHistoryId } + owner_id, 'project-imported', { projectId: project._id, attributes: attributes } ) callback(error, project) else HistoryManager.initializeProject (error, history) -> return callback(error) if error? - ProjectCreationHandler._createBlankProject owner_id, projectName, history?.overleaf_id, (error, project) -> + attributes = overleaf: history: id: history?.overleaf_id + ProjectCreationHandler._createBlankProject owner_id, projectName, attributes, (error, project) -> return callback(error) if error? AnalyticsManger.recordEvent( owner_id, 'project-created', { projectId: project._id } ) callback(error, project) - _createBlankProject : (owner_id, projectName, projectHistoryId, callback = (error, project) ->)-> + _createBlankProject : (owner_id, projectName, attributes, callback = (error, project) ->)-> rootFolder = new Folder {'name':'rootFolder'} - project = new Project - owner_ref : new ObjectId(owner_id) - name : projectName - project.overleaf.history.id = projectHistoryId + + attributes.owner_ref = new ObjectId(owner_id) + attributes.name = projectName + project = new Project attributes + + Object.assign(project, attributes) + if Settings.apis?.project_history?.displayHistoryForNewProjects project.overleaf.history.display = true if Settings.currentImageName? diff --git a/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee index ebf34a9acb..27ead91841 100644 --- a/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee @@ -405,14 +405,41 @@ module.exports = ProjectEntityUpdateHandler = self = DocumentUpdaterHandler.resyncProjectHistory project_id, projectHistoryId, docs, files, callback _cleanUpEntity: (project, entity, entityType, path, userId, callback = (error) ->) -> + self._updateProjectStructureWithDeletedEntity project, entity, entityType, path, userId, (error) -> + return callback(error) if error? + if(entityType.indexOf("file") != -1) + self._cleanUpFile project, entity, path, userId, callback + else if (entityType.indexOf("doc") != -1) + self._cleanUpDoc project, entity, path, userId, callback + else if (entityType.indexOf("folder") != -1) + self._cleanUpFolder project, entity, path, userId, callback + else + callback() + + # Note: the _cleanUpEntity code and _updateProjectStructureWithDeletedEntity + # methods both need to recursively iterate over the entities in folder. + # These are currently using separate implementations of the recursion. In + # future, these could be simplified using a common project entity iterator. + _updateProjectStructureWithDeletedEntity: (project, entity, entityType, entityPath, userId, callback = (error) ->) -> + # compute the changes to the project structure if(entityType.indexOf("file") != -1) - self._cleanUpFile project, entity, path, userId, callback + changes = oldFiles: [ {file: entity, path: entityPath} ] else if (entityType.indexOf("doc") != -1) - self._cleanUpDoc project, entity, path, userId, callback + changes = oldDocs: [ {doc: entity, path: entityPath} ] else if (entityType.indexOf("folder") != -1) - self._cleanUpFolder project, entity, path, userId, callback - else - callback() + changes = {oldDocs: [], oldFiles: []} + _recurseFolder = (folder, folderPath) -> + for doc in folder.docs + changes.oldDocs.push {doc, path: path.join(folderPath, doc.name)} + for file in folder.fileRefs + changes.oldFiles.push {file, path: path.join(folderPath, file.name)} + for childFolder in folder.folders + _recurseFolder(childFolder, path.join(folderPath, childFolder.name)) + _recurseFolder entity, entityPath + # now send the project structure changes to the docupdater + project_id = project._id.toString() + projectHistoryId = project.overleaf?.history?.id + DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, changes, callback _cleanUpDoc: (project, doc, path, userId, callback = (error) ->) -> project_id = project._id.toString() @@ -429,21 +456,10 @@ module.exports = ProjectEntityUpdateHandler = self = return callback(error) if error? DocumentUpdaterHandler.deleteDoc project_id, doc_id, (error) -> return callback(error) if error? - DocstoreManager.deleteDoc project_id, doc_id, (error) -> - return callback(error) if error? - changes = oldDocs: [ {doc, path} ] - projectHistoryId = project.overleaf?.history?.id - DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, changes, callback + DocstoreManager.deleteDoc project_id, doc_id, callback _cleanUpFile: (project, file, path, userId, callback = (error) ->) -> - ProjectEntityMongoUpdateHandler._insertDeletedFileReference project._id, file, (error) -> - return callback(error) if error? - project_id = project._id.toString() - projectHistoryId = project.overleaf?.history?.id - changes = oldFiles: [ {file, path} ] - # we are now keeping a copy of every file versio so we no longer delete - # the file from the filestore - DocumentUpdaterHandler.updateProjectStructure project_id, projectHistoryId, userId, changes, callback + ProjectEntityMongoUpdateHandler._insertDeletedFileReference project._id, file, callback _cleanUpFolder: (project, folder, folderPath, userId, callback = (error) ->) -> jobs = [] diff --git a/services/web/app/coffee/Features/Referal/ReferalAllocator.coffee b/services/web/app/coffee/Features/Referal/ReferalAllocator.coffee index b28979aaf0..53d3696807 100644 --- a/services/web/app/coffee/Features/Referal/ReferalAllocator.coffee +++ b/services/web/app/coffee/Features/Referal/ReferalAllocator.coffee @@ -1,8 +1,8 @@ _ = require("underscore") logger = require('logger-sharelatex') User = require('../../models/User').User -SubscriptionLocator = require "../Subscription/SubscriptionLocator" Settings = require "settings-sharelatex" +FeaturesUpdater = require "../Subscription/FeaturesUpdater" module.exports = ReferalAllocator = allocate: (referal_id, new_user_id, referal_source, referal_medium, callback = ->)-> @@ -25,50 +25,6 @@ module.exports = ReferalAllocator = if err? logger.err err:err, referal_id:referal_id, new_user_id:new_user_id, "something went wrong allocating referal" return callback(err) - ReferalAllocator.assignBonus user._id, callback + FeaturesUpdater.refreshFeatures user._id, callback else callback() - - - - assignBonus: (user_id, callback = (error) ->) -> - query = _id: user_id - User.findOne query, (error, user) -> - return callback(error) if error - return callback(new Error("user not found #{user_id} for assignBonus")) if !user? - logger.log user_id: user_id, refered_user_count: user.refered_user_count, "assigning bonus" - if user.refered_user_count? and user.refered_user_count > 0 - newFeatures = ReferalAllocator._calculateFeatures(user) - if _.isEqual newFeatures, user.features - return callback() - User.update query, { $set: features: newFeatures }, callback - else - callback() - - _calculateFeatures : (user)-> - bonusLevel = ReferalAllocator._getBonusLevel(user) - currentFeatures = _.clone(user.features) #need to clone because we exend with underscore later - betterBonusFeatures = {} - _.each Settings.bonus_features["#{bonusLevel}"], (bonusLevel, key)-> - currentLevel = user?.features?[key] - if _.isBoolean(currentLevel) and currentLevel == false - betterBonusFeatures[key] = bonusLevel - - if _.isNumber(currentLevel) - if currentLevel == -1 - return - bonusIsGreaterThanCurrent = currentLevel < bonusLevel - if bonusIsGreaterThanCurrent or bonusLevel == -1 - betterBonusFeatures[key] = bonusLevel - newFeatures = _.extend(currentFeatures, betterBonusFeatures) - return newFeatures - - - _getBonusLevel: (user)-> - highestBonusLevel = 0 - _.each _.keys(Settings.bonus_features), (level)-> - levelIsLessThanUser = level <= user.refered_user_count - levelIsMoreThanCurrentHighest = level >= highestBonusLevel - if levelIsLessThanUser and levelIsMoreThanCurrentHighest - highestBonusLevel = level - return highestBonusLevel diff --git a/services/web/app/coffee/Features/Referal/ReferalFeatures.coffee b/services/web/app/coffee/Features/Referal/ReferalFeatures.coffee new file mode 100644 index 0000000000..34651ef1f5 --- /dev/null +++ b/services/web/app/coffee/Features/Referal/ReferalFeatures.coffee @@ -0,0 +1,44 @@ +_ = require("underscore") +logger = require('logger-sharelatex') +User = require('../../models/User').User +Settings = require "settings-sharelatex" + +module.exports = ReferalFeatures = + getBonusFeatures: (user_id, callback = (error) ->) -> + query = _id: user_id + User.findOne query, (error, user) -> + return callback(error) if error + return callback(new Error("user not found #{user_id} for assignBonus")) if !user? + logger.log user_id: user_id, refered_user_count: user.refered_user_count, "assigning bonus" + if user.refered_user_count? and user.refered_user_count > 0 + newFeatures = ReferalFeatures._calculateFeatures(user) + callback null, newFeatures + else + callback null, {} + + _calculateFeatures : (user)-> + bonusLevel = ReferalFeatures._getBonusLevel(user) + currentFeatures = _.clone(user.features) #need to clone because we exend with underscore later + betterBonusFeatures = {} + _.each Settings.bonus_features["#{bonusLevel}"], (bonusLevel, key)-> + currentLevel = user?.features?[key] + if _.isBoolean(currentLevel) and currentLevel == false + betterBonusFeatures[key] = bonusLevel + + if _.isNumber(currentLevel) + if currentLevel == -1 + return + bonusIsGreaterThanCurrent = currentLevel < bonusLevel + if bonusIsGreaterThanCurrent or bonusLevel == -1 + betterBonusFeatures[key] = bonusLevel + newFeatures = _.extend(currentFeatures, betterBonusFeatures) + return newFeatures + + _getBonusLevel: (user)-> + highestBonusLevel = 0 + _.each _.keys(Settings.bonus_features), (level)-> + levelIsLessThanUser = level <= user.refered_user_count + levelIsMoreThanCurrentHighest = level >= highestBonusLevel + if levelIsLessThanUser and levelIsMoreThanCurrentHighest + highestBonusLevel = level + return highestBonusLevel diff --git a/services/web/app/coffee/Features/References/ReferencesHandler.coffee b/services/web/app/coffee/Features/References/ReferencesHandler.coffee index 3ceeab93f8..8728896631 100644 --- a/services/web/app/coffee/Features/References/ReferencesHandler.coffee +++ b/services/web/app/coffee/Features/References/ReferencesHandler.coffee @@ -46,7 +46,8 @@ module.exports = ReferencesHandler = _isFullIndex: (project, callback = (err, result) ->) -> UserGetter.getUser project.owner_ref, { features: true }, (err, owner) -> return callback(err) if err? - callback(null, owner?.features?.references == true) + features = owner?.features + callback(null, features?.references == true || features?.referencesSearch == true) indexAll: (projectId, callback=(err, data)->) -> ProjectGetter.getProject projectId, {rootFolder: true, owner_ref: 1}, (err, project) -> diff --git a/services/web/app/coffee/Features/Subscription/FeaturesUpdater.coffee b/services/web/app/coffee/Features/Subscription/FeaturesUpdater.coffee new file mode 100644 index 0000000000..5c176c611f --- /dev/null +++ b/services/web/app/coffee/Features/Subscription/FeaturesUpdater.coffee @@ -0,0 +1,83 @@ +async = require("async") +PlansLocator = require("./PlansLocator") +_ = require("underscore") +SubscriptionLocator = require("./SubscriptionLocator") +UserFeaturesUpdater = require("./UserFeaturesUpdater") +Settings = require("settings-sharelatex") +logger = require("logger-sharelatex") +ReferalFeatures = require("../Referal/ReferalFeatures") +V1SubscriptionManager = require("./V1SubscriptionManager") + +oneMonthInSeconds = 60 * 60 * 24 * 30 + +module.exports = FeaturesUpdater = + refreshFeatures: (user_id, callback)-> + jobs = + individualFeatures: (cb) -> FeaturesUpdater._getIndividualFeatures user_id, cb + groupFeatureSets: (cb) -> FeaturesUpdater._getGroupFeatureSets user_id, cb + v1Features: (cb) -> FeaturesUpdater._getV1Features user_id, cb + bonusFeatures: (cb) -> ReferalFeatures.getBonusFeatures user_id, cb + async.series jobs, (err, results)-> + if err? + logger.err err:err, user_id:user_id, + "error getting subscription or group for refreshFeatures" + return callback(err) + + {individualFeatures, groupFeatureSets, v1Features, bonusFeatures} = results + logger.log {user_id, individualFeatures, groupFeatureSets, v1Features, bonusFeatures}, 'merging user features' + featureSets = groupFeatureSets.concat [individualFeatures, v1Features, bonusFeatures] + features = _.reduce(featureSets, FeaturesUpdater._mergeFeatures, Settings.defaultFeatures) + + logger.log {user_id, features}, 'updating user features' + UserFeaturesUpdater.updateFeatures user_id, features, callback + + _getIndividualFeatures: (user_id, callback = (error, features = {}) ->) -> + SubscriptionLocator.getUsersSubscription user_id, (err, sub)-> + callback err, FeaturesUpdater._subscriptionToFeatures(sub) + + _getGroupFeatureSets: (user_id, callback = (error, featureSets = []) ->) -> + SubscriptionLocator.getGroupSubscriptionsMemberOf user_id, (err, subs) -> + callback err, (subs or []).map FeaturesUpdater._subscriptionToFeatures + + _getV1Features: (user_id, callback = (error, features = {}) ->) -> + V1SubscriptionManager.getPlanCodeFromV1 user_id, (err, planCode) -> + callback err, FeaturesUpdater._planCodeToFeatures(planCode) + + _mergeFeatures: (featuresA, featuresB) -> + features = Object.assign({}, featuresA) + for key, value of featuresB + # Special merging logic for non-boolean features + if key == 'compileGroup' + if features['compileGroup'] == 'priority' or featuresB['compileGroup'] == 'priority' + features['compileGroup'] = 'priority' + else + features['compileGroup'] = 'standard' + else if key == 'collaborators' + if features['collaborators'] == -1 or featuresB['collaborators'] == -1 + features['collaborators'] = -1 + else + features['collaborators'] = Math.max( + features['collaborators'] or 0, + featuresB['collaborators'] or 0 + ) + else if key == 'compileTimeout' + features['compileTimeout'] = Math.max( + features['compileTimeout'] or 0, + featuresB['compileTimeout'] or 0 + ) + else + # Boolean keys, true is better + features[key] = features[key] or featuresB[key] + return features + + _subscriptionToFeatures: (subscription) -> + FeaturesUpdater._planCodeToFeatures(subscription?.planCode) + + _planCodeToFeatures: (planCode) -> + if !planCode? + return {} + plan = PlansLocator.findLocalPlanInSettings planCode + if !plan? + return {} + else + return plan.features \ No newline at end of file diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee index 9da6fc688d..32d2abf594 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionController.coffee @@ -9,6 +9,7 @@ logger = require('logger-sharelatex') GeoIpLookup = require("../../infrastructure/GeoIpLookup") SubscriptionDomainHandler = require("./SubscriptionDomainHandler") UserGetter = require "../User/UserGetter" +FeaturesUpdater = require './FeaturesUpdater' module.exports = SubscriptionController = @@ -237,3 +238,9 @@ module.exports = SubscriptionController = return next(error) if error? req.body = body next() + + refreshUserFeatures: (req, res, next) -> + {user_id} = req.params + FeaturesUpdater.refreshFeatures user_id, (error) -> + return next(error) if error? + res.sendStatus 200 \ No newline at end of file diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee index 5b7c2e0740..d6ce0dde59 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionGroupHandler.coffee @@ -2,7 +2,7 @@ async = require("async") _ = require("underscore") SubscriptionUpdater = require("./SubscriptionUpdater") SubscriptionLocator = require("./SubscriptionLocator") -UserLocator = require("../User/UserLocator") +UserGetter = require("../User/UserGetter") LimitationsManager = require("./LimitationsManager") logger = require("logger-sharelatex") OneTimeTokenHandler = require("../Security/OneTimeTokenHandler") @@ -21,7 +21,7 @@ module.exports = SubscriptionGroupHandler = if limitReached logger.err adminUserId:adminUserId, newEmail:newEmail, "group subscription limit reached not adding user to group" return callback(limitReached:limitReached) - UserLocator.findByEmail newEmail, (err, user)-> + UserGetter.getUserByMainEmail newEmail, (err, user)-> return callback(err) if err? if user? SubscriptionUpdater.addUserToGroup adminUserId, user._id, (err)-> @@ -50,7 +50,7 @@ module.exports = SubscriptionGroupHandler = users.push buildEmailInviteViewModel(email) jobs = _.map subscription.member_ids, (user_id)-> return (cb)-> - UserLocator.findById user_id, (err, user)-> + UserGetter.getUser user_id, (err, user)-> if err? or !user? users.push _id:user_id return cb() diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee index 1452f26d99..33376f504b 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionLocator.coffee @@ -28,8 +28,8 @@ module.exports = getSubscriptionByMemberIdAndId: (user_id, subscription_id, callback)-> Subscription.findOne {member_ids: user_id, _id:subscription_id}, {_id:1}, callback - getGroupSubscriptionMemberOf: (user_id, callback)-> - Subscription.findOne {member_ids: user_id}, {_id:1, planCode:1}, callback + getGroupSubscriptionsMemberOf: (user_id, callback)-> + Subscription.find {member_ids: user_id}, {_id:1, planCode:1}, callback getGroupsWithEmailInvite: (email, callback) -> Subscription.find { invited_emails: email }, callback \ No newline at end of file diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee index d6d049f5bf..8c5f631e12 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionRouter.coffee @@ -46,4 +46,6 @@ module.exports = webRouter.get "/user/subscription/upgrade-annual", AuthenticationController.requireLogin(), SubscriptionController.renderUpgradeToAnnualPlanPage webRouter.post "/user/subscription/upgrade-annual", AuthenticationController.requireLogin(), SubscriptionController.processUpgradeToAnnualPlan + # Currently used in acceptance tests only, as a way to trigger the syncing logic + publicApiRouter.post "/user/:user_id/features/sync", AuthenticationController.httpAuth, SubscriptionController.refreshUserFeatures diff --git a/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee b/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee index 649551b5b2..cb5c39d122 100644 --- a/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee +++ b/services/web/app/coffee/Features/Subscription/SubscriptionUpdater.coffee @@ -2,26 +2,26 @@ async = require("async") _ = require("underscore") Subscription = require('../../models/Subscription').Subscription SubscriptionLocator = require("./SubscriptionLocator") -UserFeaturesUpdater = require("./UserFeaturesUpdater") PlansLocator = require("./PlansLocator") Settings = require("settings-sharelatex") logger = require("logger-sharelatex") -ObjectId = require('mongoose').Types.ObjectId -ReferalAllocator = require("../Referal/ReferalAllocator") +ObjectId = require('mongoose').Types.ObjectId +FeaturesUpdater = require('./FeaturesUpdater') oneMonthInSeconds = 60 * 60 * 24 * 30 module.exports = SubscriptionUpdater = - syncSubscription: (recurlySubscription, adminUser_id, callback) -> logger.log adminUser_id:adminUser_id, recurlySubscription:recurlySubscription, "syncSubscription, creating new if subscription does not exist" SubscriptionLocator.getUsersSubscription adminUser_id, (err, subscription)-> + return callback(err) if err? if subscription? logger.log adminUser_id:adminUser_id, recurlySubscription:recurlySubscription, "subscription does exist" SubscriptionUpdater._updateSubscriptionFromRecurly recurlySubscription, subscription, callback else logger.log adminUser_id:adminUser_id, recurlySubscription:recurlySubscription, "subscription does not exist, creating a new one" SubscriptionUpdater._createNewSubscription adminUser_id, (err, subscription)-> + return callback(err) if err? SubscriptionUpdater._updateSubscriptionFromRecurly recurlySubscription, subscription, callback addUserToGroup: (adminUser_id, user_id, callback)-> @@ -34,7 +34,7 @@ module.exports = SubscriptionUpdater = if err? logger.err err:err, searchOps:searchOps, insertOperation:insertOperation, "error findy and modify add user to group" return callback(err) - UserFeaturesUpdater.updateFeatures user_id, subscription.planCode, callback + FeaturesUpdater.refreshFeatures user_id, callback addEmailInviteToGroup: (adminUser_id, email, callback) -> logger.log {adminUser_id, email}, "adding email into mongo subscription" @@ -53,7 +53,7 @@ module.exports = SubscriptionUpdater = if err? logger.err err:err, searchOps:searchOps, removeOperation:removeOperation, "error removing user from group" return callback(err) - SubscriptionUpdater._setUsersMinimumFeatures user_id, callback + FeaturesUpdater.refreshFeatures user_id, callback removeEmailInviteFromGroup: (adminUser_id, email, callback)-> Subscription.update { @@ -62,9 +62,6 @@ module.exports = SubscriptionUpdater = invited_emails: email }, callback - refreshSubscription: (user_id, callback=(err)->) -> - SubscriptionUpdater._setUsersMinimumFeatures user_id, callback - deleteSubscription: (subscription_id, callback = (error) ->) -> SubscriptionLocator.getSubscription subscription_id, (err, subscription) -> return callback(err) if err? @@ -72,7 +69,7 @@ module.exports = SubscriptionUpdater = logger.log {subscription_id, affected_user_ids}, "deleting subscription and downgrading users" Subscription.remove {_id: ObjectId(subscription_id)}, (err) -> return callback(err) if err? - async.mapSeries affected_user_ids, SubscriptionUpdater._setUsersMinimumFeatures, callback + async.mapSeries affected_user_ids, FeaturesUpdater.refreshFeatures, callback _createNewSubscription: (adminUser_id, callback)-> logger.log adminUser_id:adminUser_id, "creating new subscription" @@ -100,43 +97,5 @@ module.exports = SubscriptionUpdater = allIds = _.union subscription.member_ids, [subscription.admin_id] jobs = allIds.map (user_id)-> return (cb)-> - SubscriptionUpdater._setUsersMinimumFeatures user_id, cb + FeaturesUpdater.refreshFeatures user_id, cb async.series jobs, callback - - _setUsersMinimumFeatures: (user_id, callback)-> - jobs = - subscription: (cb)-> - SubscriptionLocator.getUsersSubscription user_id, cb - groupSubscription: (cb)-> - SubscriptionLocator.getGroupSubscriptionMemberOf user_id, cb - v1PlanCode: (cb) -> - Modules = require '../../infrastructure/Modules' - Modules.hooks.fire 'getV1PlanCode', user_id, (err, results) -> - cb(err, results?[0] || null) - async.series jobs, (err, results)-> - if err? - logger.err err:err, user_id:user_id, - "error getting subscription or group for _setUsersMinimumFeatures" - return callback(err) - {subscription, groupSubscription, v1PlanCode} = results - # Group Subscription - if groupSubscription? and groupSubscription.planCode? - logger.log user_id:user_id, "using group which user is memor of for features" - UserFeaturesUpdater.updateFeatures user_id, groupSubscription.planCode, callback - # Personal Subscription - else if subscription? and subscription.planCode? and subscription.planCode != Settings.defaultPlanCode - logger.log user_id:user_id, "using users subscription plan code for features" - UserFeaturesUpdater.updateFeatures user_id, subscription.planCode, callback - # V1 Subscription - else if v1PlanCode? - logger.log user_id: user_id, "using the V1 plan for features" - UserFeaturesUpdater.updateFeatures user_id, v1PlanCode, callback - # Default - else - logger.log user_id:user_id, "using default features for user with no subscription or group" - UserFeaturesUpdater.updateFeatures user_id, Settings.defaultPlanCode, (err)-> - if err? - logger.err err:err, user_id:user_id, "Error setting minimum user feature" - return callback(err) - ReferalAllocator.assignBonus user_id, callback - diff --git a/services/web/app/coffee/Features/Subscription/UserFeaturesUpdater.coffee b/services/web/app/coffee/Features/Subscription/UserFeaturesUpdater.coffee index c0b691e677..28a49b2126 100644 --- a/services/web/app/coffee/Features/Subscription/UserFeaturesUpdater.coffee +++ b/services/web/app/coffee/Features/Subscription/UserFeaturesUpdater.coffee @@ -1,15 +1,12 @@ logger = require("logger-sharelatex") User = require('../../models/User').User -PlansLocator = require("./PlansLocator") module.exports = - - updateFeatures: (user_id, plan_code, callback = (err, features)->)-> + updateFeatures: (user_id, features, callback = (err, features)->)-> conditions = _id:user_id update = {} - plan = PlansLocator.findLocalPlanInSettings(plan_code) - logger.log user_id:user_id, features:plan.features, plan_code:plan_code, "updating users features" - update["features.#{key}"] = value for key, value of plan.features + logger.log user_id:user_id, features:features, "updating users features" + update["features.#{key}"] = value for key, value of features User.update conditions, update, (err)-> - callback err, plan.features + callback err, features diff --git a/services/web/app/coffee/Features/Subscription/V1SubscriptionManager.coffee b/services/web/app/coffee/Features/Subscription/V1SubscriptionManager.coffee new file mode 100644 index 0000000000..05dc140be2 --- /dev/null +++ b/services/web/app/coffee/Features/Subscription/V1SubscriptionManager.coffee @@ -0,0 +1,50 @@ +UserGetter = require "../User/UserGetter" +request = require "request" +settings = require "settings-sharelatex" +logger = require "logger-sharelatex" + +module.exports = V1SubscriptionManager = + # Returned planCode = 'v1_pro' | 'v1_pro_plus' | 'v1_student' | 'v1_free' | null + # For this to work, we need plans in settings with plan-codes: + # - 'v1_pro' + # - 'v1_pro_plus' + # - 'v1_student' + # - 'v1_free' + getPlanCodeFromV1: (userId, callback=(err, planCode)->) -> + logger.log {userId}, "[V1SubscriptionManager] fetching v1 plan for user" + UserGetter.getUser userId, {'overleaf.id': 1}, (err, user) -> + return callback(err) if err? + v1Id = user?.overleaf?.id + if !v1Id? + logger.log {userId}, "[V1SubscriptionManager] no v1 id found for user" + return callback(null, null) + V1SubscriptionManager._v1PlanRequest v1Id, (err, body) -> + return callback(err) if err? + planName = body?.plan_name + logger.log {userId, planName, body}, "[V1SubscriptionManager] fetched v1 plan for user" + if planName in ['pro', 'pro_plus', 'student', 'free'] + planName = "v1_#{planName}" + else + # Throw away 'anonymous', etc as being equivalent to null + planName = null + return callback(null, planName) + + _v1PlanRequest: (v1Id, callback=(err, body)->) -> + if !settings?.apis?.v1 + return callback null, null + request { + method: 'GET', + url: settings.apis.v1.url + + "/api/v1/sharelatex/users/#{v1Id}/plan_code" + auth: + user: settings.apis.v1.user + pass: settings.apis.v1.pass + sendImmediately: true + json: true, + timeout: 5 * 1000 + }, (error, response, body) -> + return callback(error) if error? + if 200 <= response.statusCode < 300 + return callback null, body + else + return callback new Error("non-success code from v1: #{response.statusCode}") \ No newline at end of file diff --git a/services/web/app/coffee/Features/Uploads/FileTypeManager.coffee b/services/web/app/coffee/Features/Uploads/FileTypeManager.coffee index 918f66df7b..524c6915e0 100644 --- a/services/web/app/coffee/Features/Uploads/FileTypeManager.coffee +++ b/services/web/app/coffee/Features/Uploads/FileTypeManager.coffee @@ -3,7 +3,7 @@ Path = require("path") module.exports = FileTypeManager = TEXT_EXTENSIONS : [ - "tex", "latex", "sty", "cls", "bst", "bib", "bibtex", "txt", "tikz", "rtex", "md", "asy" + "tex", "latex", "sty", "cls", "bst", "bib", "bibtex", "txt", "tikz", "rtex", "md", "asy", "latexmkrc" ] IGNORE_EXTENSIONS : [ @@ -34,7 +34,7 @@ module.exports = FileTypeManager = extension = parts.slice(-1)[0] if extension? extension = extension.toLowerCase() - binaryFile = (@TEXT_EXTENSIONS.indexOf(extension) == -1 or parts.length <= 1) + binaryFile = (@TEXT_EXTENSIONS.indexOf(extension) == -1 or parts.length <= 1) and parts[0] != 'latexmkrc' if binaryFile return callback null, true @@ -52,13 +52,10 @@ module.exports = FileTypeManager = if extension? extension = extension.toLowerCase() ignore = false - if name[0] == "." + if name[0] == "." and extension != 'latexmkrc' ignore = true if @IGNORE_EXTENSIONS.indexOf(extension) != -1 ignore = true if @IGNORE_FILENAMES.indexOf(name) != -1 ignore = true callback null, ignore - - - diff --git a/services/web/app/coffee/Features/User/UserController.coffee b/services/web/app/coffee/Features/User/UserController.coffee index 7dc82c1a7b..cf7e6be33f 100644 --- a/services/web/app/coffee/Features/User/UserController.coffee +++ b/services/web/app/coffee/Features/User/UserController.coffee @@ -1,6 +1,6 @@ UserHandler = require("./UserHandler") UserDeleter = require("./UserDeleter") -UserLocator = require("./UserLocator") +UserGetter = require("./UserGetter") User = require("../../models/User").User newsLetterManager = require('../Newsletter/NewsletterManager') UserRegistrationHandler = require("./UserRegistrationHandler") @@ -45,7 +45,7 @@ module.exports = UserController = unsubscribe: (req, res)-> user_id = AuthenticationController.getLoggedInUserId(req) - UserLocator.findById user_id, (err, user)-> + UserGetter.getUser user_id, (err, user)-> newsLetterManager.unsubscribe user, -> res.send() @@ -81,6 +81,11 @@ module.exports = UserController = user.ace.pdfViewer = req.body.pdfViewer if req.body.syntaxValidation? user.ace.syntaxValidation = req.body.syntaxValidation + if req.body.fontFamily? + user.ace.fontFamily = req.body.fontFamily + if req.body.lineHeight? + user.ace.lineHeight = req.body.lineHeight + user.save (err)-> newEmail = req.body.email?.trim().toLowerCase() if !newEmail? or newEmail == user.email or req.externalAuthenticationSystemUsed() diff --git a/services/web/app/coffee/Features/User/UserCreator.coffee b/services/web/app/coffee/Features/User/UserCreator.coffee index d08b953559..0a0cc8641e 100644 --- a/services/web/app/coffee/Features/User/UserCreator.coffee +++ b/services/web/app/coffee/Features/User/UserCreator.coffee @@ -1,19 +1,10 @@ User = require("../../models/User").User -UserLocator = require("./UserLocator") logger = require("logger-sharelatex") metrics = require('metrics-sharelatex') module.exports = UserCreator = - getUserOrCreateHoldingAccount: (email, callback = (err, user)->)-> - self = @ - UserLocator.findByEmail email, (err, user)-> - if user? - callback(err, user) - else - self.createNewUser email:email, holdingAccount:true, callback - createNewUser: (opts, callback)-> logger.log opts:opts, "creating new user" user = new User() diff --git a/services/web/app/coffee/Features/User/UserGetter.coffee b/services/web/app/coffee/Features/User/UserGetter.coffee index 306fbc7a10..201981625e 100644 --- a/services/web/app/coffee/Features/User/UserGetter.coffee +++ b/services/web/app/coffee/Features/User/UserGetter.coffee @@ -6,6 +6,8 @@ ObjectId = mongojs.ObjectId module.exports = UserGetter = getUser: (query, projection, callback = (error, user) ->) -> + if query?.email? + return callback(new Error("Don't use getUser to find user by email"), null) if arguments.length == 2 callback = projection projection = {} @@ -19,6 +21,13 @@ module.exports = UserGetter = db.users.findOne query, projection, callback + getUserByMainEmail: (email, projection, callback = (error, user) ->) -> + email = email.trim() + if arguments.length == 2 + callback = projection + projection = {} + db.users.findOne email: email, projection, callback + getUsers: (user_ids, projection, callback = (error, users) ->) -> try user_ids = user_ids.map (u) -> ObjectId(u.toString()) @@ -39,6 +48,7 @@ module.exports = UserGetter = [ 'getUser', + 'getUserByMainEmail', 'getUsers', 'getUserOrUserStubById' ].map (method) -> diff --git a/services/web/app/coffee/Features/User/UserLocator.coffee b/services/web/app/coffee/Features/User/UserLocator.coffee deleted file mode 100644 index 9be32c76b0..0000000000 --- a/services/web/app/coffee/Features/User/UserLocator.coffee +++ /dev/null @@ -1,21 +0,0 @@ -mongojs = require("../../infrastructure/mongojs") -metrics = require("metrics-sharelatex") -db = mongojs.db -ObjectId = mongojs.ObjectId -logger = require('logger-sharelatex') - -module.exports = UserLocator = - - findByEmail: (email, callback)-> - email = email.trim() - db.users.findOne email:email, (err, user)-> - callback(err, user) - - findById: (_id, callback)-> - db.users.findOne _id:ObjectId(_id+""), callback - -[ - 'findById', - 'findByEmail' -].map (method) -> - metrics.timeAsyncMethod UserLocator, method, 'mongo.UserLocator', logger diff --git a/services/web/app/coffee/Features/User/UserPagesController.coffee b/services/web/app/coffee/Features/User/UserPagesController.coffee index 25825c35e6..5e6ea7d62b 100644 --- a/services/web/app/coffee/Features/User/UserPagesController.coffee +++ b/services/web/app/coffee/Features/User/UserPagesController.coffee @@ -1,4 +1,3 @@ -UserLocator = require("./UserLocator") UserGetter = require("./UserGetter") UserSessionsManager = require("./UserSessionsManager") ErrorController = require("../Errors/ErrorController") @@ -61,7 +60,7 @@ module.exports = user_id = AuthenticationController.getLoggedInUserId(req) logger.log user: user_id, "loading settings page" shouldAllowEditingDetails = !(Settings?.ldap?.updateUserDetailsOnLogin) and !(Settings?.saml?.updateUserDetailsOnLogin) - UserLocator.findById user_id, (err, user)-> + UserGetter.getUser user_id, (err, user)-> return next(err) if err? res.render 'user/settings', title:'account_settings' diff --git a/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee b/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee index f5db2e54a1..fab438ffa6 100644 --- a/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee +++ b/services/web/app/coffee/Features/User/UserRegistrationHandler.coffee @@ -1,6 +1,7 @@ sanitize = require('sanitizer') User = require("../../models/User").User UserCreator = require("./UserCreator") +UserGetter = require("./UserGetter") AuthenticationManager = require("../Authentication/AuthenticationManager") NewsLetterManager = require("../Newsletter/NewsletterManager") async = require("async") @@ -47,7 +48,7 @@ module.exports = UserRegistrationHandler = if !requestIsValid return callback(new Error("request is not valid")) userDetails.email = userDetails.email?.trim()?.toLowerCase() - User.findOne email:userDetails.email, (err, user)-> + UserGetter.getUserByMainEmail userDetails.email, (err, user) => if err? return callback err if user?.holdingAccount == false diff --git a/services/web/app/coffee/Features/User/UserUpdater.coffee b/services/web/app/coffee/Features/User/UserUpdater.coffee index 530d81063d..174d37bc38 100644 --- a/services/web/app/coffee/Features/User/UserUpdater.coffee +++ b/services/web/app/coffee/Features/User/UserUpdater.coffee @@ -3,7 +3,7 @@ mongojs = require("../../infrastructure/mongojs") metrics = require("metrics-sharelatex") db = mongojs.db ObjectId = mongojs.ObjectId -UserLocator = require("./UserLocator") +UserGetter = require("./UserGetter") module.exports = UserUpdater = updateUser: (query, update, callback = (error) ->) -> @@ -18,7 +18,7 @@ module.exports = UserUpdater = changeEmailAddress: (user_id, newEmail, callback)-> self = @ logger.log user_id:user_id, newEmail:newEmail, "updaing email address of user" - UserLocator.findByEmail newEmail, (error, user) -> + UserGetter.getUserByMainEmail newEmail, (error, user) -> if user? return callback({message:"alread_exists"}) self.updateUser user_id.toString(), { diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index 37cce62ddd..6943a79cb2 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -183,6 +183,9 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)-> # Don't include the query string parameters, otherwise Google # treats ?nocdn=true as the canonical version res.locals.currentUrl = Url.parse(req.originalUrl).pathname + res.locals.capitalize = (string) -> + return "" if string.length == 0 + return string.charAt(0).toUpperCase() + string.slice(1) next() webRouter.use (req, res, next)-> @@ -321,5 +324,7 @@ module.exports = (app, webRouter, privateApiRouter, publicApiRouter)-> chatMessageBorderLightness : if isOl then "40%" else "70%" chatMessageBgSaturation : if isOl then "85%" else "60%" chatMessageBgLightness : if isOl then "40%" else "97%" + defaultFontFamily : if isOl then 'lucida' else 'monaco' + defaultLineHeight : if isOl then 'normal' else 'compact' renderAnnouncements : !isOl next() diff --git a/services/web/app/coffee/infrastructure/Features.coffee b/services/web/app/coffee/infrastructure/Features.coffee index be7cd21d89..afb2eefb9f 100644 --- a/services/web/app/coffee/infrastructure/Features.coffee +++ b/services/web/app/coffee/infrastructure/Features.coffee @@ -14,9 +14,11 @@ module.exports = Features = return Settings.enableGithubSync when 'v1-return-message' return Settings.accountMerge? and Settings.overleaf? - when 'publish-modal' - return Settings.showPublishModal + when 'v2-banner' + return Settings.showV2Banner when 'custom-togglers' return Settings.overleaf? + when 'templates' + return !Settings.overleaf? else throw new Error("unknown feature: #{feature}") diff --git a/services/web/app/coffee/infrastructure/LockManager.coffee b/services/web/app/coffee/infrastructure/LockManager.coffee index dd29bd0bde..b117ce49ea 100644 --- a/services/web/app/coffee/infrastructure/LockManager.coffee +++ b/services/web/app/coffee/infrastructure/LockManager.coffee @@ -3,20 +3,37 @@ Settings = require('settings-sharelatex') RedisWrapper = require("./RedisWrapper") rclient = RedisWrapper.client("lock") logger = require "logger-sharelatex" +os = require "os" +crypto = require "crypto" + +HOST = os.hostname() +PID = process.pid +RND = crypto.randomBytes(4).toString('hex') +COUNT = 0 module.exports = LockManager = LOCK_TEST_INTERVAL: 50 # 50ms between each test of the lock + MAX_TEST_INTERVAL: 1000 # back off to 1s between each test of the lock MAX_LOCK_WAIT_TIME: 10000 # 10s maximum time to spend trying to get the lock REDIS_LOCK_EXPIRY: 30 # seconds. Time until lock auto expires in redis SLOW_EXECUTION_THRESHOLD: 5000 # 5s, if execution takes longer than this then log + # Use a signed lock value as described in + # http://redis.io/topics/distlock#correct-implementation-with-a-single-instance + # to prevent accidental unlocking by multiple processes + randomLock : () -> + time = Date.now() + return "locked:host=#{HOST}:pid=#{PID}:random=#{RND}:time=#{time}:count=#{COUNT++}" + + unlockScript: 'if redis.call("get", KEYS[1]) == ARGV[1] then return redis.call("del", KEYS[1]) else return 0 end' + runWithLock: (namespace, id, runner = ( (releaseLock = (error) ->) -> ), callback = ( (error) -> )) -> # This error is defined here so we get a useful stacktrace slowExecutionError = new Error "slow execution during lock" timer = new metrics.Timer("lock.#{namespace}") key = "lock:web:#{namespace}:#{id}" - LockManager._getLock key, namespace, (error) -> + LockManager._getLock key, namespace, (error, lockValue) -> return callback(error) if error? # The lock can expire in redis but the process carry on. This setTimout call @@ -27,7 +44,7 @@ module.exports = LockManager = exceededLockTimeout = setTimeout countIfExceededLockTimeout, LockManager.REDIS_LOCK_EXPIRY * 1000 runner (error1, values...) -> - LockManager._releaseLock key, (error2) -> + LockManager._releaseLock key, lockValue, (error2) -> clearTimeout exceededLockTimeout timeTaken = new Date - timer.start @@ -39,19 +56,21 @@ module.exports = LockManager = return callback(error) if error? callback null, values... - _tryLock : (key, namespace, callback = (err, isFree)->)-> - rclient.set key, "locked", "EX", LockManager.REDIS_LOCK_EXPIRY, "NX", (err, gotLock)-> + _tryLock : (key, namespace, callback = (err, isFree, lockValue)->)-> + lockValue = LockManager.randomLock() + rclient.set key, lockValue, "EX", LockManager.REDIS_LOCK_EXPIRY, "NX", (err, gotLock)-> return callback(err) if err? if gotLock == "OK" metrics.inc "lock.#{namespace}.try.success" - callback err, true + callback err, true, lockValue else metrics.inc "lock.#{namespace}.try.failed" logger.log key: key, redis_response: gotLock, "lock is locked" callback err, false - _getLock: (key, namespace, callback = (error) ->) -> + _getLock: (key, namespace, callback = (error, lockValue) ->) -> startTime = Date.now() + testInterval = LockManager.LOCK_TEST_INTERVAL attempts = 0 do attempt = () -> if Date.now() - startTime > LockManager.MAX_LOCK_WAIT_TIME @@ -59,13 +78,23 @@ module.exports = LockManager = return callback(new Error("Timeout")) attempts += 1 - LockManager._tryLock key, namespace, (error, gotLock) -> + LockManager._tryLock key, namespace, (error, gotLock, lockValue) -> return callback(error) if error? if gotLock metrics.gauge "lock.#{namespace}.get.success.tries", attempts - callback(null) + callback(null, lockValue) else - setTimeout attempt, LockManager.LOCK_TEST_INTERVAL + setTimeout attempt, testInterval + # back off when the lock is taken to avoid overloading + testInterval = Math.min(testInterval * 2, LockManager.MAX_TEST_INTERVAL) - _releaseLock: (key, callback)-> - rclient.del key, callback + _releaseLock: (key, lockValue, callback)-> + rclient.eval LockManager.unlockScript, 1, key, lockValue, (err, result) -> + if err? + return callback(err) + else if result? and result isnt 1 # successful unlock should release exactly one key + logger.error {key:key, lockValue:lockValue, redis_err:err, redis_result:result}, "unlocking error" + metrics.inc "unlock-error" + return callback(new Error("tried to release timed out lock")) + else + callback(null,result) diff --git a/services/web/app/coffee/infrastructure/Modules.coffee b/services/web/app/coffee/infrastructure/Modules.coffee index 769182ad94..e1b2e11520 100644 --- a/services/web/app/coffee/infrastructure/Modules.coffee +++ b/services/web/app/coffee/infrastructure/Modules.coffee @@ -30,7 +30,8 @@ module.exports = Modules = for module in @modules for view, partial of module.viewIncludes or {} @viewIncludes[view] ||= [] - @viewIncludes[view].push pug.compile(fs.readFileSync(Path.join(MODULE_BASE_PATH, module.name, "app/views", partial + ".pug")), doctype: "html") + filePath = Path.join(MODULE_BASE_PATH, module.name, "app/views", partial + ".pug") + @viewIncludes[view].push pug.compileFile(filePath, doctype: "html") moduleIncludes: (view, locals) -> compiledPartials = Modules.viewIncludes[view] or [] diff --git a/services/web/app/coffee/models/User.coffee b/services/web/app/coffee/models/User.coffee index d3916ab4b0..009f582b2a 100644 --- a/services/web/app/coffee/models/User.coffee +++ b/services/web/app/coffee/models/User.coffee @@ -20,14 +20,16 @@ UserSchema = new Schema loginCount : {type : Number, default: 0} holdingAccount : {type : Boolean, default: false} ace : { - mode : {type : String, default: 'none'} - theme : {type : String, default: 'textmate'} - fontSize : {type : Number, default:'12'} - autoComplete: {type : Boolean, default: true} - autoPairDelimiters: {type : Boolean, default: true} - spellCheckLanguage : {type : String, default: "en"} - pdfViewer : {type : String, default: "pdfjs"} - syntaxValidation : {type : Boolean} + mode : {type : String, default: 'none'} + theme : {type : String, default: 'textmate'} + fontSize : {type : Number, default:'12'} + autoComplete : {type : Boolean, default: true} + autoPairDelimiters : {type : Boolean, default: true} + spellCheckLanguage : {type : String, default: "en"} + pdfViewer : {type : String, default: "pdfjs"} + syntaxValidation : {type : Boolean} + fontFamily : {type : String} + lineHeight : {type : String} } features : { collaborators: { type:Number, default: Settings.defaultFeatures.collaborators } diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 38e3f8ea57..61ffab6498 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -26,6 +26,7 @@ HealthCheckController = require("./Features/HealthCheck/HealthCheckController") ProjectDownloadsController = require "./Features/Downloads/ProjectDownloadsController" FileStoreController = require("./Features/FileStore/FileStoreController") HistoryController = require("./Features/History/HistoryController") +ExportsController = require("./Features/Exports/ExportsController") PasswordResetRouter = require("./Features/PasswordReset/PasswordResetRouter") StaticPagesRouter = require("./Features/StaticPages/StaticPagesRouter") ChatController = require("./Features/Chat/ChatController") @@ -206,6 +207,7 @@ module.exports = class Router webRouter.post "/project/:project_id/restore_file", AuthorizationMiddlewear.ensureUserCanWriteProjectContent, HistoryController.restoreFileFromV2 privateApiRouter.post "/project/:Project_id/history/resync", AuthenticationController.httpAuth, HistoryController.resyncProjectHistory + webRouter.post '/project/:project_id/export/:brand_variation_id', AuthorizationMiddlewear.ensureUserCanAdminProject, ExportsController.exportProject webRouter.get '/Project/:Project_id/download/zip', AuthorizationMiddlewear.ensureUserCanReadProject, ProjectDownloadsController.downloadProject webRouter.get '/project/download/zip', AuthorizationMiddlewear.ensureUserCanReadMultipleProjects, ProjectDownloadsController.downloadMultipleProjects diff --git a/services/web/app/views/contact-us-modal.pug b/services/web/app/views/contact-us-modal.pug deleted file mode 100644 index aad68a53d3..0000000000 --- a/services/web/app/views/contact-us-modal.pug +++ /dev/null @@ -1,67 +0,0 @@ -script(type='text/ng-template', id='supportModalTemplate') - .modal-header - button.close( - type="button" - data-dismiss="modal" - ng-click="close()" - ) × - h3 #{translate("contact_us")} - .modal-body.contact-us-modal - form(name="contactForm") - span(ng-show="sent == false") - .alert.alert-danger(ng-show="error") Something went wrong sending your request :( - label - | #{translate("subject")} - .form-group - input.field.text.medium.span8.form-control( - name="subject", - required - ng-model="form.subject", - ng-model-options="{ updateOn: 'default blur', debounce: {'default': 350, 'blur': 0} }" - maxlength='255', - tabindex='1', - onkeyup='') - .contact-suggestions(ng-show="suggestions.length") - p.contact-suggestion-label !{translate("kb_suggestions_enquiry", { kbLink: "__kb__", kb: translate("knowledge_base") })} - ul.contact-suggestion-list - li(ng-repeat="suggestion in suggestions") - 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") - | #{translate("email")} - .form-group(ng-show="'"+getUserEmail()+"'.length < 1") - input.field.text.medium.span8.form-control( - name="email", - required - 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 - input.field.text.medium.span8.form-control(ng-model="form.project_url", tabindex='3', onkeyup='') - label.desc - | #{translate("contact_message_label")} - .form-group - textarea.field.text.medium.span8.form-control( - name="body", - required - ng-model="form.message", - type='text', - value='', - tabindex='4', - onkeyup='' - ) - .form-group.text-center - input.btn-success.btn.btn-lg( - type='submit', - ng-disabled="contactForm.$invalid || sending", - ng-click="contactUs()" - value=translate("contact_us") - ) - span(ng-show="sent") - p #{translate("request_sent_thank_you")} diff --git a/services/web/app/views/layout.pug b/services/web/app/views/layout.pug index c8140d24b2..687f5e1880 100644 --- a/services/web/app/views/layout.pug +++ b/services/web/app/views/layout.pug @@ -98,7 +98,8 @@ html(itemscope, itemtype='http://schema.org/Product') - if (settings.overleaf && settings.overleaf.useOLFreeTrial) script. - window.redirectToOLFreeTrialUrl = '!{settings.overleaf.host}/users/trial' + window.useV2TrialUrl = true + body if(settings.recaptcha) @@ -146,7 +147,7 @@ html(itemscope, itemtype='http://schema.org/Product') src=buildJsPath('libs/require.js', {hashedPath:true}) ) - include contact-us-modal + != moduleIncludes("contactModal", locals) include v1-tooltip include sentry diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug index b34b9f690d..3b611440dd 100644 --- a/services/web/app/views/project/editor.pug +++ b/services/web/app/views/project/editor.pug @@ -157,7 +157,10 @@ block requirejs }, "ace/ext-language_tools": { "deps": ["ace/ace"] - } + }, + "ace/keybinding-vim": { + "deps": ["ace/ace"] + }, }, "config":{ "moment":{ diff --git a/services/web/app/views/project/editor/editor.pug b/services/web/app/views/project/editor/editor.pug index fed1889d79..3cac3a9490 100644 --- a/services/web/app/views/project/editor/editor.pug +++ b/services/web/app/views/project/editor/editor.pug @@ -58,6 +58,7 @@ div.full-size( read-only="!permissions.write", file-name="editor.open_doc_name", on-ctrl-enter="recompileViaKey", + on-save="recompileViaKey", on-ctrl-j="toggleReviewPanel", on-ctrl-shift-c="addNewCommentFromKbdShortcut", on-ctrl-shift-a="toggleTrackChangesFromKbdShortcut", @@ -68,6 +69,8 @@ div.full-size( track-changes= "editor.trackChanges", doc-id="editor.open_doc_id" renderer-data="reviewPanel.rendererData" + font-family="settings.fontFamily || ui.defaultFontFamily" + line-height="settings.lineHeight || ui.defaultLineHeight" ) != moduleIncludes('editor:body', locals) diff --git a/services/web/app/views/project/editor/left-menu.pug b/services/web/app/views/project/editor/left-menu.pug index bf7b9ba331..ed6ef85b44 100644 --- a/services/web/app/views/project/editor/left-menu.pug +++ b/services/web/app/views/project/editor/left-menu.pug @@ -150,6 +150,26 @@ aside#left-menu.full-size( each size in ['10','11','12','13','14','16','20','24'] option(value=size) #{size}px + .form-controls + label(for="fontFamily") #{translate("font_family")} + select( + name="fontFamily" + ng-model="settings.fontFamily" + ) + option(value="", disabled) #{translate("default")} + each fontFamily in ['monaco', 'lucida'] + option(value=fontFamily) #{capitalize(fontFamily)} + + .form-controls + label(for="lineHeight") #{translate("line_height")} + select( + name="lineHeight" + ng-model="settings.lineHeight" + ) + option(value="", disabled) #{translate("default")} + each lineHeight in ['compact', 'normal', 'wide'] + option(value=lineHeight) #{translate(lineHeight)} + .form-controls label(for="pdfViewer") #{translate("pdf_viewer")} select( diff --git a/services/web/app/views/project/editor/pdf.pug b/services/web/app/views/project/editor/pdf.pug index a874a0b88b..26de2f35b1 100644 --- a/services/web/app/views/project/editor/pdf.pug +++ b/services/web/app/views/project/editor/pdf.pug @@ -371,40 +371,40 @@ div.full-size.pdf(ng-controller="PdfController") a.text-info(href="https://www.sharelatex.com/learn/Debugging_Compilation_timeout_errors", target="_blank") | #{translate("learn_how_to_make_documents_compile_quickly")} - .alert.alert-success(ng-show="pdf.timedout && !hasPremiumCompile") - p(ng-if="project.owner._id == user.id") - strong #{translate("upgrade_for_faster_compiles")} - p(ng-if="project.owner._id != user.id") - strong #{translate("ask_proj_owner_to_upgrade_for_faster_compiles")} - p #{translate("free_accounts_have_timeout_upgrade_to_increase")} - p Plus: - div - ul.list-unstyled - li - i.fa.fa-check   - | #{translate("unlimited_projects")} - li - i.fa.fa-check   - | #{translate("collabs_per_proj", {collabcount:'Multiple'})} - li - i.fa.fa-check   - | #{translate("full_doc_history")} - li - i.fa.fa-check   - | #{translate("sync_to_dropbox")} - li - i.fa.fa-check   - | #{translate("sync_to_github")} - li - i.fa.fa-check   - |#{translate("compile_larger_projects")} - p(ng-controller="FreeTrialModalController", ng-if="project.owner._id == user.id") - a.btn.btn-success.row-spaced-small( - href - ng-class="buttonClass" - ng-click="startFreeTrial('compile-timeout')" - ) #{translate("start_free_trial")} - + if settings.enableSubscriptions + .alert.alert-success(ng-show="pdf.timedout && !hasPremiumCompile") + p(ng-if="project.owner._id == user.id") + strong #{translate("upgrade_for_faster_compiles")} + p(ng-if="project.owner._id != user.id") + strong #{translate("ask_proj_owner_to_upgrade_for_faster_compiles")} + p #{translate("free_accounts_have_timeout_upgrade_to_increase")} + p Plus: + div + ul.list-unstyled + li + i.fa.fa-check   + | #{translate("unlimited_projects")} + li + i.fa.fa-check   + | #{translate("collabs_per_proj", {collabcount:'Multiple'})} + li + i.fa.fa-check   + | #{translate("full_doc_history")} + li + i.fa.fa-check   + | #{translate("sync_to_dropbox")} + li + i.fa.fa-check   + | #{translate("sync_to_github")} + li + i.fa.fa-check   + |#{translate("compile_larger_projects")} + p(ng-controller="FreeTrialModalController", ng-if="project.owner._id == user.id") + a.btn.btn-success.row-spaced-small( + href + ng-class="buttonClass" + ng-click="startFreeTrial('compile-timeout')" + ) #{translate("start_free_trial")} .alert.alert-danger(ng-show="pdf.autoCompileDisabled") p diff --git a/services/web/app/views/project/list/front-chat.pug b/services/web/app/views/project/list/front-chat.pug index 7f0939de29..7e1c9a7123 100644 --- a/services/web/app/views/project/list/front-chat.pug +++ b/services/web/app/views/project/list/front-chat.pug @@ -1,4 +1,4 @@ -- if (settings.overleaf && settings.overleaf.front_chat_widget_room_id != null) +- if (frontChatWidgetRoomId) script. - window.FCSP = '#{settings.overleaf.front_chat_widget_room_id}'; - script(src="https://chat-assets.frontapp.com/v1/chat.bundle.js") \ No newline at end of file + window.FCSP = '#{frontChatWidgetRoomId}'; + script(src="https://chat-assets.frontapp.com/v1/chat.bundle.js") diff --git a/services/web/app/views/project/list/item.pug b/services/web/app/views/project/list/item.pug index a4bb240c4b..bfc53a8360 100644 --- a/services/web/app/views/project/list/item.pug +++ b/services/web/app/views/project/list/item.pug @@ -38,4 +38,7 @@ tooltip-append-to-body="true" ) .col-xs-4 - span.last-modified {{project.lastUpdated | formatDate}} \ No newline at end of file + if settings.overleaf + span.last-modified(tooltip="{{project.lastUpdated | formatDate}}") {{project.lastUpdated | fromNowDate}} + else + span.last-modified {{project.lastUpdated | formatDate}} diff --git a/services/web/app/views/project/list/notifications.pug b/services/web/app/views/project/list/notifications.pug index f173555e1c..ee004039fc 100644 --- a/services/web/app/views/project/list/notifications.pug +++ b/services/web/app/views/project/list/notifications.pug @@ -1,4 +1,4 @@ -if (user.awareOfV2 && !settings.overleaf) +if hasFeature('v2-banner') .userNotifications ul.list-unstyled.notifications-list(ng-controller="OverleafV2NotificationController", ng-show="visible") li.notification_entry diff --git a/services/web/app/views/project/list/project-list.pug b/services/web/app/views/project/list/project-list.pug index 240f62bd54..df3c2bf681 100644 --- a/services/web/app/views/project/list/project-list.pug +++ b/services/web/app/views/project/list/project-list.pug @@ -32,14 +32,16 @@ ng-click="downloadSelectedProjects()" ) i.fa.fa-cloud-download + - var archiveButtonString = settings.overleaf ? translate("archive") : translate("delete") + - var archiveButtonIcon = settings.overleaf ? "fa-inbox" : "fa-trash-o" a.btn.btn-default( href, - tooltip=translate('delete'), + tooltip=`{{ isArchiveableProjectSelected ? '${archiveButtonString}' : '${translate("leave")}' }}`, tooltip-placement="bottom", tooltip-append-to-body="true", ng-click="openArchiveProjectsModal()" ) - i.fa.fa-trash-o + i.fa(ng-class=`isArchiveableProjectSelected ? '${archiveButtonIcon}' : 'fa-sign-out'`) .btn-group.dropdown(ng-hide="selectedProjects.length < 1", dropdown) a.btn.btn-default.dropdown-toggle( diff --git a/services/web/app/views/project/list/side-bar.pug b/services/web/app/views/project/list/side-bar.pug index d01938b72d..e342b792a5 100644 --- a/services/web/app/views/project/list/side-bar.pug +++ b/services/web/app/views/project/list/side-bar.pug @@ -41,7 +41,7 @@ li(ng-class="{active: (filter == 'shared')}", ng-click="filterProjects('shared')") a(href) #{translate("shared_with_you")} li(ng-class="{active: (filter == 'archived')}", ng-click="filterProjects('archived')") - a(href) #{translate("deleted_projects")} + a(href) #{settings.overleaf ? translate("archived_projects") : translate("deleted_projects")} if isShowingV1Projects li(ng-class="{active: (filter == 'v1')}", ng-click="filterProjects('v1')") a(href) #{translate("v1_projects")} diff --git a/services/web/app/views/project/list/v1-item.pug b/services/web/app/views/project/list/v1-item.pug index 68f7b2af63..b4a3ccb99d 100644 --- a/services/web/app/views/project/list/v1-item.pug +++ b/services/web/app/views/project/list/v1-item.pug @@ -22,4 +22,4 @@ span.owner {{ownerName()}} .col-xs-4 - span.last-modified {{project.lastUpdated | formatDate}} \ No newline at end of file + span.last-modified(tooltip="{{project.lastUpdated | formatDate}}") {{project.lastUpdated | fromNowDate}} \ No newline at end of file diff --git a/services/web/app/views/subscriptions/group_admin.pug b/services/web/app/views/subscriptions/group_admin.pug index 39312fd57a..3fe93f3b94 100644 --- a/services/web/app/views/subscriptions/group_admin.pug +++ b/services/web/app/views/subscriptions/group_admin.pug @@ -59,24 +59,29 @@ block content .col-md-12.text-centered small #{translate("no_members")} + hr div(ng-if="users.length < groupSize", ng-cloak) - hr - p - .small #{translate("add_more_members")} - form.form - .row - .col-xs-6 - input.form-control( - name="email", - type="text", - placeholder="jane@example.com, joe@example.com", - ng-model="inputs.emails", - on-enter="addMembers()" - ) - .col-xs-4 - button.btn.btn-primary(ng-click="addMembers()") #{translate("add")} - .col-xs-2 - a(href="/subscription/group/export") Export CSV + p.small #{translate("add_more_members")} + form.form + .row + .col-xs-6 + input.form-control( + name="email", + type="text", + placeholder="jane@example.com, joe@example.com", + ng-model="inputs.emails", + on-enter="addMembers()" + ) + .col-xs-4 + button.btn.btn-primary(ng-click="addMembers()") #{translate("add")} + .col-xs-2 + a(href="/subscription/group/export") Export CSV + + div(ng-if="users.length >= groupSize && users.length > 0", ng-cloak) + .row + .col-xs-2.col-xs-offset-10 + a(href="/subscription/group/export") Export CSV + script(type="text/javascript"). window.users = !{JSON.stringify(users)}; @@ -84,5 +89,3 @@ block content - - diff --git a/services/web/bin/unit_test_app b/services/web/bin/unit_test_app new file mode 100755 index 0000000000..d0c3278618 --- /dev/null +++ b/services/web/bin/unit_test_app @@ -0,0 +1,6 @@ +#!/bin/bash +set -e; + +MOCHA="node_modules/.bin/mocha --exit --recursive --reporter spec" + +$MOCHA "$@" test/unit/js diff --git a/services/web/config/settings.defaults.coffee b/services/web/config/settings.defaults.coffee index d53b1a73ba..0892804778 100644 --- a/services/web/config/settings.defaults.coffee +++ b/services/web/config/settings.defaults.coffee @@ -156,6 +156,10 @@ module.exports = settings = url: process.env['LINKED_URL_PROXY'] thirdpartyreferences: url: "http://#{process.env['THIRD_PARTY_REFERENCES_HOST'] or 'localhost'}:3046" + v1: + url: "http://#{process.env['V1_HOST'] or 'localhost'}:5000" + user: 'overleaf' + pass: 'password' templates: user_id: process.env.TEMPLATES_USER_ID or "5395eb7aad1f29a88756c7f2" @@ -363,7 +367,7 @@ module.exports = settings = appName: "ShareLaTeX (Community Edition)" adminEmail: "placeholder@example.com" - + brandPrefix: "" # Set to 'ol-' for overleaf styles nav: diff --git a/services/web/docker-compose.yml b/services/web/docker-compose.yml index 5a668bc4a3..a062c0df4e 100644 --- a/services/web/docker-compose.yml +++ b/services/web/docker-compose.yml @@ -17,6 +17,7 @@ services: PROJECT_HISTORY_ENABLED: 'true' ENABLED_LINKED_FILE_TYPES: 'url' LINKED_URL_PROXY: 'http://localhost:6543' + SHARELATEX_CONFIG: /app/test/acceptance/config/settings.test.coffee depends_on: - redis - mongo diff --git a/services/web/nodemon.frontend.json b/services/web/nodemon.frontend.json index d0f321fa14..d4d2fdbb26 100644 --- a/services/web/nodemon.frontend.json +++ b/services/web/nodemon.frontend.json @@ -8,7 +8,8 @@ "exec": "make compile || exit 1", "watch": [ "public/coffee/", - "public/stylesheets/" + "public/stylesheets/", + "modules/**/public/coffee/" ], "ext": "coffee less" } \ No newline at end of file diff --git a/services/web/npm-shrinkwrap.json b/services/web/npm-shrinkwrap.json index b753fb0ffb..51545471c4 100644 --- a/services/web/npm-shrinkwrap.json +++ b/services/web/npm-shrinkwrap.json @@ -54,20 +54,6 @@ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", "dev": true }, - "acorn-node": { - "version": "1.3.0", - "from": "acorn-node@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/acorn-node/-/acorn-node-1.3.0.tgz", - "dev": true, - "dependencies": { - "acorn": { - "version": "5.4.1", - "from": "acorn@>=5.4.1 <6.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", - "dev": true - } - } - }, "addressparser": { "version": "0.2.1", "from": "addressparser@>=0.2.0 <0.3.0", @@ -80,18 +66,10 @@ "dev": true }, "agent-base": { - "version": "2.1.1", - "from": "agent-base@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz", - "dev": true, - "dependencies": { - "semver": { - "version": "5.0.3", - "from": "semver@>=5.0.1 <5.1.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", - "dev": true - } - } + "version": "4.2.0", + "from": "agent-base@>=4.2.0 <5.0.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz", + "dev": true }, "ajv": { "version": "5.5.2", @@ -170,9 +148,9 @@ } }, "ansi-escapes": { - "version": "3.0.0", + "version": "3.1.0", "from": "ansi-escapes@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.0.0.tgz", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", "dev": true }, "ansi-html": { @@ -193,9 +171,9 @@ "dev": true }, "anymatch": { - "version": "2.0.0", - "from": "anymatch@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "version": "1.3.2", + "from": "anymatch@>=1.3.0 <2.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", "dev": true }, "aproba": { @@ -255,9 +233,9 @@ } }, "arr-diff": { - "version": "4.0.0", - "from": "arr-diff@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "version": "2.0.0", + "from": "arr-diff@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", "dev": true }, "arr-flatten": { @@ -272,12 +250,6 @@ "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "dev": true }, - "array-filter": { - "version": "0.0.1", - "from": "array-filter@>=0.0.0 <0.1.0", - "resolved": "https://registry.npmjs.org/array-filter/-/array-filter-0.0.1.tgz", - "dev": true - }, "array-find-index": { "version": "1.0.2", "from": "array-find-index@>=1.0.1 <2.0.0", @@ -295,18 +267,6 @@ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.0.3.tgz", "dev": true }, - "array-map": { - "version": "0.0.0", - "from": "array-map@>=0.0.0 <0.1.0", - "resolved": "https://registry.npmjs.org/array-map/-/array-map-0.0.0.tgz", - "dev": true - }, - "array-reduce": { - "version": "0.0.0", - "from": "array-reduce@>=0.0.0 <0.1.0", - "resolved": "https://registry.npmjs.org/array-reduce/-/array-reduce-0.0.0.tgz", - "dev": true - }, "array-slice": { "version": "0.2.3", "from": "array-slice@>=0.2.3 <0.3.0", @@ -326,9 +286,9 @@ "dev": true }, "array-unique": { - "version": "0.3.2", - "from": "array-unique@>=0.3.2 <0.4.0", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "version": "0.2.1", + "from": "array-unique@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", "dev": true }, "arraybuffer.slice": { @@ -354,9 +314,9 @@ "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz" }, "asn1.js": { - "version": "4.9.2", + "version": "4.10.1", "from": "asn1.js@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.9.2.tgz", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", "dev": true }, "assert": { @@ -382,26 +342,12 @@ "dev": true }, "ast-types": { - "version": "0.10.2", + "version": "0.11.3", "from": "ast-types@>=0.0.0 <1.0.0", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.10.2.tgz", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.11.3.tgz", "dev": true, "optional": true }, - "astw": { - "version": "2.2.0", - "from": "astw@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/astw/-/astw-2.2.0.tgz", - "dev": true, - "dependencies": { - "acorn": { - "version": "4.0.13", - "from": "acorn@>=4.0.3 <5.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "dev": true - } - } - }, "async": { "version": "0.6.2", "from": "async@0.6.2", @@ -425,9 +371,9 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" }, "atob": { - "version": "2.0.3", - "from": "atob@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.0.3.tgz", + "version": "2.1.1", + "from": "atob@>=2.1.1 <3.0.0", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.1.tgz", "dev": true }, "autoprefixer": { @@ -482,9 +428,9 @@ "dev": true }, "babel-core": { - "version": "6.26.0", - "from": "babel-core@latest", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", + "version": "6.26.3", + "from": "babel-core@>=6.26.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", "dev": true, "dependencies": { "debug": { @@ -493,12 +439,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "dev": true }, - "json5": { - "version": "0.5.1", - "from": "json5@>=0.5.1 <0.6.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "dev": true - }, "minimatch": { "version": "3.0.4", "from": "minimatch@>=3.0.4 <4.0.0", @@ -514,9 +454,9 @@ } }, "babel-generator": { - "version": "6.26.0", + "version": "6.26.1", "from": "babel-generator@>=6.26.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.0.tgz", + "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", "dev": true, "dependencies": { "source-map": { @@ -606,9 +546,9 @@ "dev": true }, "babel-loader": { - "version": "7.1.2", - "from": "babel-loader@latest", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.2.tgz", + "version": "7.1.4", + "from": "babel-loader@>=7.1.2 <8.0.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-7.1.4.tgz", "dev": true }, "babel-messages": { @@ -726,9 +666,9 @@ "dev": true }, "babel-plugin-transform-es2015-modules-commonjs": { - "version": "6.26.0", + "version": "6.26.2", "from": "babel-plugin-transform-es2015-modules-commonjs@>=6.23.0 <7.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.0.tgz", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz", "dev": true }, "babel-plugin-transform-es2015-modules-systemjs": { @@ -840,15 +780,15 @@ "dev": true }, "babel-preset-env": { - "version": "1.6.1", - "from": "babel-preset-env@latest", - "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.6.1.tgz", + "version": "1.7.0", + "from": "babel-preset-env@>=1.6.1 <2.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-env/-/babel-preset-env-1.7.0.tgz", "dev": true, "dependencies": { "browserslist": { - "version": "2.11.3", - "from": "browserslist@>=2.1.2 <3.0.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-2.11.3.tgz", + "version": "3.2.7", + "from": "browserslist@>=3.2.6 <4.0.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-3.2.7.tgz", "dev": true } } @@ -909,6 +849,18 @@ "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", "dev": true }, + "backbone": { + "version": "1.3.3", + "from": "backbone@latest", + "resolved": "https://registry.npmjs.org/backbone/-/backbone-1.3.3.tgz", + "dependencies": { + "underscore": { + "version": "1.9.0", + "from": "underscore@>=1.8.3", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.0.tgz" + } + } + }, "backo2": { "version": "1.0.2", "from": "backo2@1.0.2", @@ -929,7 +881,45 @@ "version": "0.11.2", "from": "base@>=0.11.1 <0.12.0", "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "dev": true + "dev": true, + "dependencies": { + "define-property": { + "version": "1.0.0", + "from": "define-property@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "dev": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "from": "is-accessor-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "dev": true + }, + "is-data-descriptor": { + "version": "1.0.0", + "from": "is-data-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "dev": true + }, + "is-descriptor": { + "version": "1.0.2", + "from": "is-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "from": "kind-of@^6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "dev": true + } + } }, "base64-arraybuffer": { "version": "0.1.5", @@ -1059,6 +1049,26 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz", "dev": true }, + "body": { + "version": "5.1.0", + "from": "body@>=5.1.0 <6.0.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "dev": true, + "dependencies": { + "bytes": { + "version": "1.0.0", + "from": "bytes@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "dev": true + }, + "raw-body": { + "version": "1.1.7", + "from": "raw-body@>=1.1.0 <1.2.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "dev": true + } + } + }, "body-parser": { "version": "1.18.2", "from": "body-parser@>=1.13.1 <2.0.0", @@ -1108,9 +1118,9 @@ "dev": true }, "ansi-styles": { - "version": "3.2.0", - "from": "ansi-styles@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "version": "3.2.1", + "from": "ansi-styles@>=3.2.1 <4.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "dev": true }, "camelcase": { @@ -1120,15 +1130,15 @@ "dev": true }, "chalk": { - "version": "2.3.0", + "version": "2.4.1", "from": "chalk@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "dev": true }, "has-flag": { - "version": "2.0.0", - "from": "has-flag@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "version": "3.0.0", + "from": "has-flag@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "dev": true }, "is-fullwidth-code-point": { @@ -1150,9 +1160,9 @@ "dev": true }, "supports-color": { - "version": "4.5.0", - "from": "supports-color@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "version": "5.4.0", + "from": "supports-color@>=5.3.0 <6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "dev": true } } @@ -1163,9 +1173,9 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz" }, "braces": { - "version": "2.3.0", - "from": "braces@>=2.3.0 <3.0.0", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.0.tgz", + "version": "1.8.5", + "from": "braces@>=1.8.2 <2.0.0", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", "dev": true }, "broadway": { @@ -1200,123 +1210,27 @@ "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", "dev": true }, - "browser-pack": { - "version": "6.0.4", - "from": "browser-pack@>=6.0.1 <7.0.0", - "resolved": "https://registry.npmjs.org/browser-pack/-/browser-pack-6.0.4.tgz", - "dev": true - }, - "browser-resolve": { - "version": "1.11.2", - "from": "browser-resolve@>=1.11.0 <2.0.0", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", - "dev": true, - "dependencies": { - "resolve": { - "version": "1.1.7", - "from": "resolve@1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "dev": true - } - } - }, "browser-stdout": { "version": "1.3.0", "from": "browser-stdout@1.3.0", "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.0.tgz" }, - "browserify": { - "version": "14.5.0", - "from": "browserify@>=14.5.0 <15.0.0", - "resolved": "https://registry.npmjs.org/browserify/-/browserify-14.5.0.tgz", - "dev": true, - "dependencies": { - "buffer": { - "version": "5.0.8", - "from": "buffer@>=5.0.2 <6.0.0", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.0.8.tgz", - "dev": true - }, - "domain-browser": { - "version": "1.1.7", - "from": "domain-browser@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.1.7.tgz", - "dev": true - }, - "glob": { - "version": "7.1.2", - "from": "glob@^7.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "from": "isarray@~1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "from": "minimatch@^3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "from": "process-nextick-args@~2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "dev": true - }, - "readable-stream": { - "version": "2.3.4", - "from": "readable-stream@>=2.0.2 <3.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", - "dev": true - }, - "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "dev": true - }, - "timers-browserify": { - "version": "1.4.2", - "from": "timers-browserify@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-1.4.2.tgz", - "dev": true - }, - "url": { - "version": "0.11.0", - "from": "url@>=0.11.0 <0.12.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "dev": true, - "dependencies": { - "punycode": { - "version": "1.3.2", - "from": "punycode@1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "dev": true - } - } - } - } - }, "browserify-aes": { - "version": "1.1.1", + "version": "1.2.0", "from": "browserify-aes@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "dev": true }, "browserify-cipher": { - "version": "1.0.0", + "version": "1.0.1", "from": "browserify-cipher@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", "dev": true }, "browserify-des": { - "version": "1.0.0", + "version": "1.0.1", "from": "browserify-des@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.1.tgz", "dev": true }, "browserify-rsa": { @@ -1370,6 +1284,12 @@ "from": "buffer-equal-constant-time@1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz" }, + "buffer-from": { + "version": "1.0.0", + "from": "buffer-from@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", + "dev": true + }, "buffer-indexof": { "version": "1.1.1", "from": "buffer-indexof@>=1.0.0 <2.0.0", @@ -1448,13 +1368,15 @@ "version": "1.0.1", "from": "cache-base@>=1.0.1 <2.0.0", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "dev": true - }, - "cached-path-relative": { - "version": "1.0.1", - "from": "cached-path-relative@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/cached-path-relative/-/cached-path-relative-1.0.1.tgz", - "dev": true + "dev": true, + "dependencies": { + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "dev": true + } + } }, "caller-path": { "version": "0.1.0", @@ -1499,15 +1421,15 @@ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz" }, "caniuse-db": { - "version": "1.0.30000800", + "version": "1.0.30000840", "from": "caniuse-db@>=1.0.30000634 <2.0.0", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000800.tgz", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000840.tgz", "dev": true }, "caniuse-lite": { - "version": "1.0.30000792", - "from": "caniuse-lite@>=1.0.30000792 <2.0.0", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000792.tgz", + "version": "1.0.30000840", + "from": "caniuse-lite@>=1.0.30000835 <2.0.0", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30000840.tgz", "dev": true }, "capture-stack-trace": { @@ -1568,9 +1490,9 @@ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz" }, "chokidar": { - "version": "2.0.0", - "from": "chokidar@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.0.tgz", + "version": "1.7.0", + "from": "chokidar@>=1.4.1 <2.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", "dev": true, "dependencies": { "isarray": { @@ -1585,10 +1507,16 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "dev": true }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, "readable-stream": { - "version": "2.3.3", - "from": "readable-stream@>=2.0.2 <3.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "version": "2.3.6", + "from": "readable-stream@^2.0.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "dev": true }, "readdirp": { @@ -1598,13 +1526,19 @@ "dev": true }, "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@>=1.0.3 <1.1.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "version": "1.1.1", + "from": "string_decoder@~1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true } } }, + "ci-info": { + "version": "1.1.3", + "from": "ci-info@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", + "dev": true + }, "cipher-base": { "version": "1.0.4", "from": "cipher-base@>=1.0.0 <2.0.0", @@ -1612,9 +1546,9 @@ "dev": true }, "circular-json": { - "version": "0.5.1", - "from": "circular-json@>=0.5.1 <0.6.0", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.1.tgz", + "version": "0.3.3", + "from": "circular-json@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", "dev": true }, "class-utils": { @@ -1629,44 +1563,10 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "dev": true }, - "is-accessor-descriptor": { - "version": "0.1.6", - "from": "is-accessor-descriptor@>=0.1.6 <0.2.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "dev": true - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "from": "is-data-descriptor@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "dev": true - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "from": "is-descriptor@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "from": "kind-of@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "dev": true } } @@ -1784,26 +1684,6 @@ "resolved": "https://registry.npmjs.org/combine-lists/-/combine-lists-1.0.1.tgz", "dev": true }, - "combine-source-map": { - "version": "0.8.0", - "from": "combine-source-map@>=0.8.0 <0.9.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", - "dev": true, - "dependencies": { - "convert-source-map": { - "version": "1.1.3", - "from": "convert-source-map@>=1.1.0 <1.2.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "from": "source-map@>=0.5.3 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "dev": true - } - } - }, "combined-stream": { "version": "1.0.5", "from": "combined-stream@>=1.0.5 <1.1.0", @@ -1839,21 +1719,29 @@ "dev": true }, "compressible": { - "version": "2.0.12", - "from": "compressible@>=2.0.11 <2.1.0", - "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.12.tgz", - "dev": true + "version": "2.0.13", + "from": "compressible@>=2.0.13 <2.1.0", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.13.tgz", + "dev": true, + "dependencies": { + "mime-db": { + "version": "1.33.0", + "from": "mime-db@>= 1.33.0 < 2", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "dev": true + } + } }, "compression": { - "version": "1.7.1", + "version": "1.7.2", "from": "compression@>=1.5.2 <2.0.0", - "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.1.tgz", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.2.tgz", "dev": true, "dependencies": { "accepts": { - "version": "1.3.4", - "from": "accepts@>=1.3.4 <1.4.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "version": "1.3.5", + "from": "accepts@~1.3.4", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", "dev": true }, "debug": { @@ -1862,6 +1750,18 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "dev": true }, + "mime-db": { + "version": "1.33.0", + "from": "mime-db@~1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "from": "mime-types@~2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "dev": true + }, "negotiator": { "version": "0.6.1", "from": "negotiator@0.6.1", @@ -1882,9 +1782,9 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz" }, "concat-stream": { - "version": "1.5.2", - "from": "concat-stream@>=1.5.1 <1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.5.2.tgz", + "version": "1.6.2", + "from": "concat-stream@>=1.6.0 <2.0.0", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", "dev": true, "dependencies": { "isarray": { @@ -1893,24 +1793,36 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "dev": true }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, "readable-stream": { - "version": "2.0.6", - "from": "readable-stream@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.0.6.tgz", + "version": "2.3.6", + "from": "readable-stream@>=2.2.2 <3.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "from": "string_decoder@>=1.1.1 <1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true } } }, "configstore": { - "version": "3.1.1", + "version": "3.1.2", "from": "configstore@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.1.tgz", + "resolved": "https://registry.npmjs.org/configstore/-/configstore-3.1.2.tgz", "dev": true }, "connect": { - "version": "3.6.5", + "version": "3.6.6", "from": "connect@>=3.6.0 <4.0.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.5.tgz", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.6.6.tgz", "dev": true, "dependencies": { "debug": { @@ -1926,9 +1838,9 @@ "dev": true }, "finalhandler": { - "version": "1.0.6", - "from": "finalhandler@1.0.6", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.0.6.tgz", + "version": "1.1.0", + "from": "finalhandler@1.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", "dev": true }, "statuses": { @@ -2006,6 +1918,12 @@ "from": "content-type@>=1.0.4 <1.1.0", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz" }, + "continuable-cache": { + "version": "0.3.1", + "from": "continuable-cache@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "dev": true + }, "convert-source-map": { "version": "1.5.1", "from": "convert-source-map@>=1.5.0 <2.0.0", @@ -2047,9 +1965,9 @@ "dev": true }, "core-js": { - "version": "2.5.3", + "version": "2.5.6", "from": "core-js@>=2.4.0 <3.0.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.3.tgz", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.6.tgz", "dev": true }, "core-util-is": { @@ -2068,9 +1986,9 @@ "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-0.2.0.tgz" }, "create-ecdh": { - "version": "4.0.0", + "version": "4.0.3", "from": "create-ecdh@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.3.tgz", "dev": true }, "create-error-class": { @@ -2080,15 +1998,15 @@ "dev": true }, "create-hash": { - "version": "1.1.3", + "version": "1.2.0", "from": "create-hash@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "dev": true }, "create-hmac": { - "version": "1.1.6", + "version": "1.1.7", "from": "create-hmac@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "dev": true }, "create-react-class": { @@ -2103,9 +2021,9 @@ "dev": true, "dependencies": { "lru-cache": { - "version": "4.1.1", + "version": "4.1.3", "from": "lru-cache@>=4.0.1 <5.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", "dev": true }, "which": { @@ -2213,6 +2131,11 @@ "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "dev": true }, + "d3": { + "version": "3.5.16", + "from": "d3@3.5.16", + "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.16.tgz" + }, "dashdash": { "version": "1.14.1", "from": "dashdash@>=1.12.0 <2.0.0", @@ -2247,6 +2170,11 @@ "from": "dateformat@1.0.4-1.2.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-1.0.4-1.2.3.tgz" }, + "daterangepicker": { + "version": "2.1.27", + "from": "daterangepicker@2.1.27", + "resolved": "https://registry.npmjs.org/daterangepicker/-/daterangepicker-2.1.27.tgz" + }, "debug": { "version": "1.0.5", "from": "debug@>=1.0.2 <1.1.0", @@ -2301,16 +2229,42 @@ "dev": true }, "define-property": { - "version": "1.0.0", - "from": "define-property@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "dev": true - }, - "defined": { - "version": "1.0.0", - "from": "defined@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", - "dev": true + "version": "2.0.2", + "from": "define-property@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "dev": true, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "from": "is-accessor-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "dev": true + }, + "is-data-descriptor": { + "version": "1.0.0", + "from": "is-data-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "dev": true + }, + "is-descriptor": { + "version": "1.0.2", + "from": "is-descriptor@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "from": "kind-of@^6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "dev": true + } + } }, "deflate-crc32-stream": { "version": "0.1.2", @@ -2334,9 +2288,9 @@ } }, "del": { - "version": "3.0.0", - "from": "del@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "version": "2.2.2", + "from": "del@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", "dev": true, "dependencies": { "glob": { @@ -2351,6 +2305,12 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "dev": true }, + "pify": { + "version": "2.3.0", + "from": "pify@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "dev": true + }, "rimraf": { "version": "2.6.2", "from": "rimraf@>=2.2.8 <3.0.0", @@ -2379,12 +2339,6 @@ "from": "depd@>=1.1.1 <1.2.0", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" }, - "deps-sort": { - "version": "2.0.0", - "from": "deps-sort@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/deps-sort/-/deps-sort-2.0.0.tgz", - "dev": true - }, "des.js": { "version": "1.0.0", "from": "des.js@>=1.0.0 <2.0.0", @@ -2413,20 +2367,6 @@ "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.0.3.tgz", "dev": true }, - "detective": { - "version": "4.7.1", - "from": "detective@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", - "dev": true, - "dependencies": { - "acorn": { - "version": "5.4.1", - "from": "acorn@>=5.2.1 <6.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", - "dev": true - } - } - }, "di": { "version": "0.0.1", "from": "di@>=0.0.1 <0.0.2", @@ -2451,9 +2391,9 @@ "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.7.tgz" }, "diffie-hellman": { - "version": "5.0.2", + "version": "5.0.3", "from": "diffie-hellman@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.2.tgz", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", "dev": true }, "director": { @@ -2529,15 +2469,15 @@ "dev": true }, "domhandler": { - "version": "2.4.1", + "version": "2.4.2", "from": "domhandler@>=2.3.0 <3.0.0", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.1.tgz", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", "dev": true }, "domutils": { - "version": "1.6.2", + "version": "1.7.0", "from": "domutils@>=1.5.1 <2.0.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.6.2.tgz", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.7.0.tgz", "dev": true }, "dont-sniff-mimetype": { @@ -2573,38 +2513,6 @@ "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz", "dev": true }, - "duplexer2": { - "version": "0.1.4", - "from": "duplexer2@>=0.1.2 <0.2.0", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "dev": true, - "dependencies": { - "isarray": { - "version": "1.0.0", - "from": "isarray@~1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "from": "process-nextick-args@~2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "dev": true - }, - "readable-stream": { - "version": "2.3.4", - "from": "readable-stream@^2.0.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", - "dev": true - }, - "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "dev": true - } - } - }, "duplexer3": { "version": "0.1.4", "from": "duplexer3@>=0.1.4 <0.2.0", @@ -2638,9 +2546,9 @@ "resolved": "https://registry.npmjs.org/ejs/-/ejs-0.8.8.tgz" }, "electron-to-chromium": { - "version": "1.3.31", + "version": "1.3.45", "from": "electron-to-chromium@>=1.2.7 <2.0.0", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.31.tgz", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.45.tgz", "dev": true }, "elliptic": { @@ -2679,15 +2587,15 @@ "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz" }, "engine.io": { - "version": "3.1.4", + "version": "3.1.5", "from": "engine.io@>=3.1.0 <3.2.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.4.tgz", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-3.1.5.tgz", "dev": true, "dependencies": { "accepts": { - "version": "1.3.3", - "from": "accepts@1.3.3", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.3.tgz", + "version": "1.3.5", + "from": "accepts@>=1.3.4 <1.4.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", "dev": true }, "cookie": { @@ -2697,9 +2605,21 @@ "dev": true }, "debug": { - "version": "2.6.9", - "from": "debug@~2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "version": "3.1.0", + "from": "debug@>=3.1.0 <3.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "dev": true + }, + "mime-db": { + "version": "1.33.0", + "from": "mime-db@>=1.33.0 <1.34.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "from": "mime-types@>=2.1.18 <2.2.0", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", "dev": true }, "negotiator": { @@ -2711,15 +2631,15 @@ } }, "engine.io-client": { - "version": "3.1.4", + "version": "3.1.6", "from": "engine.io-client@>=3.1.0 <3.2.0", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.4.tgz", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-3.1.6.tgz", "dev": true, "dependencies": { "debug": { - "version": "2.6.9", - "from": "debug@~2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "version": "3.1.0", + "from": "debug@>=3.1.0 <3.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "dev": true } } @@ -2749,9 +2669,15 @@ "dev": true }, "errno": { - "version": "0.1.6", - "from": "errno@>=0.1.3 <0.2.0", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.6.tgz", + "version": "0.1.7", + "from": "errno@>=0.1.1 <0.2.0", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", + "dev": true + }, + "error": { + "version": "7.0.2", + "from": "error@>=7.0.0 <8.0.0", + "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", "dev": true }, "error-ex": { @@ -2761,9 +2687,9 @@ "dev": true }, "es-abstract": { - "version": "1.10.0", + "version": "1.11.0", "from": "es-abstract@>=1.7.0 <2.0.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.10.0.tgz", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz", "dev": true }, "es-to-primitive": { @@ -2773,9 +2699,9 @@ "dev": true }, "es5-ext": { - "version": "0.10.38", + "version": "0.10.42", "from": "es5-ext@>=0.10.14 <0.11.0", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.38.tgz", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.42.tgz", "dev": true }, "es6-iterator": { @@ -2795,6 +2721,12 @@ "from": "es6-promise@>=4.1.1 <5.0.0", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz" }, + "es6-promisify": { + "version": "5.0.0", + "from": "es6-promisify@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "dev": true + }, "es6-set": { "version": "0.1.5", "from": "es6-set@>=0.1.5 <0.2.0", @@ -2824,9 +2756,9 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz" }, "escodegen": { - "version": "1.9.0", + "version": "1.9.1", "from": "escodegen@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.0.tgz", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", "dev": true, "optional": true, "dependencies": { @@ -2838,9 +2770,9 @@ "optional": true }, "source-map": { - "version": "0.5.7", - "from": "source-map@>=0.5.6 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "version": "0.6.1", + "from": "source-map@>=0.6.1 <0.7.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "dev": true, "optional": true } @@ -2853,9 +2785,9 @@ "dev": true }, "eslint": { - "version": "4.18.1", + "version": "4.19.1", "from": "eslint@>=4.18.1 <5.0.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.18.1.tgz", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", "dev": true, "dependencies": { "ansi-regex": { @@ -2865,9 +2797,9 @@ "dev": true }, "ansi-styles": { - "version": "3.2.0", - "from": "ansi-styles@>=3.2.0 <4.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "version": "3.2.1", + "from": "ansi-styles@>=3.2.1 <4.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "dev": true }, "argparse": { @@ -2877,15 +2809,9 @@ "dev": true }, "chalk": { - "version": "2.3.1", + "version": "2.4.1", "from": "chalk@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", - "dev": true - }, - "concat-stream": { - "version": "1.6.0", - "from": "concat-stream@>=1.6.0 <2.0.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "dev": true }, "debug": { @@ -2907,9 +2833,9 @@ "dev": true }, "globals": { - "version": "11.3.0", + "version": "11.5.0", "from": "globals@>=11.0.1 <12.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.3.0.tgz", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.5.0.tgz", "dev": true }, "has-flag": { @@ -2918,16 +2844,10 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "dev": true }, - "isarray": { - "version": "1.0.0", - "from": "isarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "dev": true - }, "js-yaml": { - "version": "3.10.0", + "version": "3.11.0", "from": "js-yaml@>=3.9.1 <4.0.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz", "dev": true }, "minimatch": { @@ -2936,24 +2856,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "dev": true }, - "process-nextick-args": { - "version": "2.0.0", - "from": "process-nextick-args@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "dev": true - }, - "readable-stream": { - "version": "2.3.4", - "from": "readable-stream@>=2.2.2 <3.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", - "dev": true - }, - "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@>=1.0.3 <1.1.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "dev": true - }, "strip-ansi": { "version": "4.0.0", "from": "strip-ansi@>=4.0.0 <5.0.0", @@ -2961,9 +2863,9 @@ "dev": true }, "supports-color": { - "version": "5.2.0", - "from": "supports-color@>=5.2.0 <6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "version": "5.4.0", + "from": "supports-color@>=5.3.0 <6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "dev": true } } @@ -2989,9 +2891,9 @@ } }, "eslint-module-utils": { - "version": "2.1.1", - "from": "eslint-module-utils@>=2.1.1 <3.0.0", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.1.1.tgz", + "version": "2.2.0", + "from": "eslint-module-utils@>=2.2.0 <3.0.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.2.0.tgz", "dev": true, "dependencies": { "debug": { @@ -3033,9 +2935,9 @@ "dev": true }, "eslint-plugin-import": { - "version": "2.9.0", + "version": "2.11.0", "from": "eslint-plugin-import@>=2.9.0 <3.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.9.0.tgz", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.11.0.tgz", "dev": true, "dependencies": { "debug": { @@ -3061,6 +2963,12 @@ "from": "minimatch@^3.0.3", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "dev": true + }, + "resolve": { + "version": "1.7.1", + "from": "resolve@>=1.6.0 <2.0.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", + "dev": true } } }, @@ -3085,15 +2993,15 @@ } }, "eslint-plugin-promise": { - "version": "3.6.0", + "version": "3.7.0", "from": "eslint-plugin-promise@>=3.6.0 <4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.6.0.tgz", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.7.0.tgz", "dev": true }, "eslint-plugin-standard": { - "version": "3.0.1", + "version": "3.1.0", "from": "eslint-plugin-standard@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz", "dev": true }, "eslint-scope": { @@ -3109,15 +3017,15 @@ "dev": true }, "espree": { - "version": "3.5.3", - "from": "espree@>=3.5.2 <4.0.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.3.tgz", + "version": "3.5.4", + "from": "espree@>=3.5.4 <4.0.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", "dev": true, "dependencies": { "acorn": { - "version": "5.4.1", - "from": "acorn@>=5.4.0 <6.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.4.1.tgz", + "version": "5.5.3", + "from": "acorn@>=5.5.0 <6.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", "dev": true } } @@ -3128,15 +3036,15 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-1.0.4.tgz" }, "esquery": { - "version": "1.0.0", + "version": "1.0.1", "from": "esquery@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", "dev": true }, "esrecurse": { - "version": "4.2.0", + "version": "4.2.1", "from": "esrecurse@>=4.1.0 <5.0.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", "dev": true }, "estraverse": { @@ -3220,12 +3128,6 @@ "resolved": "https://registry.npmjs.org/expand-braces/-/expand-braces-0.1.2.tgz", "dev": true, "dependencies": { - "array-unique": { - "version": "0.2.1", - "from": "array-unique@^0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "dev": true - }, "braces": { "version": "0.1.5", "from": "braces@>=0.1.2 <0.2.0", @@ -3253,96 +3155,16 @@ } }, "expand-brackets": { - "version": "2.1.4", - "from": "expand-brackets@>=2.1.4 <3.0.0", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "dev": true, - "dependencies": { - "debug": { - "version": "2.6.9", - "from": "debug@>=2.3.3 <3.0.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "dev": true - }, - "define-property": { - "version": "0.2.5", - "from": "define-property@>=0.2.5 <0.3.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "from": "is-accessor-descriptor@>=0.1.6 <0.2.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "dev": true - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "from": "is-data-descriptor@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "dev": true - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "from": "is-descriptor@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "from": "kind-of@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "dev": true - } - } + "version": "0.1.5", + "from": "expand-brackets@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "dev": true }, "expand-range": { "version": "1.8.2", "from": "expand-range@>=1.8.1 <2.0.0", "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "dev": true, - "dependencies": { - "fill-range": { - "version": "2.2.3", - "from": "fill-range@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "dev": true - }, - "is-number": { - "version": "2.1.0", - "from": "is-number@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "from": "isarray@1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "from": "isobject@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "dev": true - } - } + "dev": true }, "expect-ct": { "version": "0.1.0", @@ -3426,10 +3248,18 @@ "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz" }, "extend-shallow": { - "version": "2.0.1", - "from": "extend-shallow@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "dev": true + "version": "3.0.2", + "from": "extend-shallow@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "dev": true, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "from": "is-extendable@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "dev": true + } + } }, "extendible": { "version": "0.1.1", @@ -3437,23 +3267,23 @@ "resolved": "https://registry.npmjs.org/extendible/-/extendible-0.1.1.tgz" }, "external-editor": { - "version": "2.1.0", + "version": "2.2.0", "from": "external-editor@>=2.0.4 <3.0.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.1.0.tgz", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", "dev": true, "dependencies": { "iconv-lite": { - "version": "0.4.19", + "version": "0.4.23", "from": "iconv-lite@>=0.4.17 <0.5.0", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", "dev": true } } }, "extglob": { - "version": "2.0.4", - "from": "extglob@>=2.0.2 <3.0.0", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "version": "0.3.2", + "from": "extglob@>=0.3.1 <0.4.0", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", "dev": true }, "extsprintf": { @@ -3488,6 +3318,12 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "dev": true }, + "fastparse": { + "version": "1.1.1", + "from": "fastparse@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/fastparse/-/fastparse-1.1.1.tgz", + "dev": true + }, "faye-websocket": { "version": "0.10.0", "from": "faye-websocket@>=0.10.0 <0.11.0", @@ -3554,9 +3390,9 @@ "dev": true }, "fill-range": { - "version": "4.0.0", - "from": "fill-range@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "version": "2.2.4", + "from": "fill-range@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.4.tgz", "dev": true }, "finalhandler": { @@ -3604,51 +3440,7 @@ "version": "1.3.0", "from": "flat-cache@>=1.2.1 <2.0.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "dev": true, - "dependencies": { - "circular-json": { - "version": "0.3.3", - "from": "circular-json@>=0.3.1 <0.4.0", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "dev": true - }, - "del": { - "version": "2.2.2", - "from": "del@>=2.0.2 <3.0.0", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "dev": true - }, - "glob": { - "version": "7.1.2", - "from": "glob@^7.0.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "dev": true - }, - "globby": { - "version": "5.0.0", - "from": "globby@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "from": "minimatch@^3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "dev": true - }, - "pify": { - "version": "2.3.0", - "from": "pify@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "dev": true - }, - "rimraf": { - "version": "2.6.2", - "from": "rimraf@>=2.2.8 <3.0.0", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "dev": true - } - } + "dev": true }, "flatiron": { "version": "0.4.3", @@ -3913,16 +3705,16 @@ "optional": true }, "readable-stream": { - "version": "2.3.4", + "version": "2.3.6", "from": "readable-stream@2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "dev": true, "optional": true }, "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "version": "1.1.1", + "from": "string_decoder@~1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true, "optional": true } @@ -3960,41 +3752,13 @@ "version": "0.3.0", "from": "glob-base@>=0.3.0 <0.4.0", "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "dev": true, - "dependencies": { - "glob-parent": { - "version": "2.0.0", - "from": "glob-parent@^2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "from": "is-extglob@^1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "from": "is-glob@^2.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "dev": true - } - } + "dev": true }, "glob-parent": { - "version": "3.1.0", - "from": "glob-parent@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "dev": true, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "from": "is-glob@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "dev": true - } - } + "version": "2.0.0", + "from": "glob-parent@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "dev": true }, "global-dirs": { "version": "0.1.1", @@ -4009,9 +3773,9 @@ "dev": true }, "globby": { - "version": "6.1.0", - "from": "globby@>=6.1.0 <7.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "version": "5.0.0", + "from": "globby@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", "dev": true, "dependencies": { "glob": { @@ -4259,24 +4023,32 @@ "version": "0.4.1", "from": "grunt-contrib-requirejs@0.4.1", "resolved": "https://registry.npmjs.org/grunt-contrib-requirejs/-/grunt-contrib-requirejs-0.4.1.tgz", - "dev": true + "dev": true, + "dependencies": { + "requirejs": { + "version": "2.1.22", + "from": "requirejs@>=2.1.0 <2.2.0", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.1.22.tgz", + "dev": true + } + } }, "grunt-contrib-watch": { - "version": "1.0.0", + "version": "1.1.0", "from": "grunt-contrib-watch@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/grunt-contrib-watch/-/grunt-contrib-watch-1.1.0.tgz", "dev": true, "dependencies": { "async": { - "version": "1.5.2", - "from": "async@>=1.5.0 <2.0.0", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "version": "2.6.0", + "from": "async@^2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", "dev": true }, "lodash": { - "version": "3.10.1", - "from": "lodash@>=3.10.1 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "version": "4.17.10", + "from": "lodash@>=4.17.10 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", "dev": true } } @@ -4517,6 +4289,63 @@ "resolved": "https://registry.npmjs.org/handle-thing/-/handle-thing-1.2.5.tgz", "dev": true }, + "handlebars": { + "version": "4.0.11", + "from": "handlebars@>=4.0.11 <5.0.0", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.11.tgz", + "dependencies": { + "async": { + "version": "1.5.2", + "from": "async@>=1.4.0 <2.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz" + }, + "source-map": { + "version": "0.4.4", + "from": "source-map@>=0.4.4 <0.5.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz" + }, + "uglify-js": { + "version": "2.8.29", + "from": "uglify-js@>=2.6.0 <3.0.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "optional": true, + "dependencies": { + "source-map": { + "version": "0.5.7", + "from": "source-map@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "optional": true + } + } + }, + "yargs": { + "version": "3.10.0", + "from": "yargs@>=3.10.0 <3.11.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "optional": true + } + } + }, + "handlebars-loader": { + "version": "1.7.0", + "from": "handlebars-loader@>=1.7.0 <2.0.0", + "resolved": "https://registry.npmjs.org/handlebars-loader/-/handlebars-loader-1.7.0.tgz", + "dev": true, + "dependencies": { + "async": { + "version": "0.2.10", + "from": "async@>=0.2.10 <0.3.0", + "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", + "dev": true + }, + "loader-utils": { + "version": "1.0.4", + "from": "loader-utils@>=1.0.0 <1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.0.4.tgz", + "dev": true + } + } + }, "hang": { "version": "1.0.0", "from": "hang@>=1.0.0 <1.1.0", @@ -4544,9 +4373,9 @@ "dev": true }, "has-binary2": { - "version": "1.0.2", + "version": "1.0.3", "from": "has-binary2@>=1.0.2 <1.1.0", - "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.2.tgz", + "resolved": "https://registry.npmjs.org/has-binary2/-/has-binary2-1.0.3.tgz", "dev": true, "dependencies": { "isarray": { @@ -4584,7 +4413,15 @@ "version": "1.0.0", "from": "has-value@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "dev": true + "dev": true, + "dependencies": { + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "dev": true + } + } }, "has-values": { "version": "1.0.0", @@ -4592,6 +4429,20 @@ "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", "dev": true, "dependencies": { + "is-number": { + "version": "3.0.0", + "from": "is-number@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "dev": true, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "dev": true + } + } + }, "kind-of": { "version": "4.0.0", "from": "kind-of@>=4.0.0 <5.0.0", @@ -4601,9 +4452,9 @@ } }, "hash-base": { - "version": "2.0.2", - "from": "hash-base@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "version": "3.0.4", + "from": "hash-base@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", "dev": true }, "hash.js": { @@ -4677,9 +4528,9 @@ "resolved": "https://registry.npmjs.org/hooks-fixed/-/hooks-fixed-2.0.0.tgz" }, "hosted-git-info": { - "version": "2.5.0", + "version": "2.6.0", "from": "hosted-git-info@>=2.1.4 <3.0.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", "dev": true }, "hpack.js": { @@ -4694,16 +4545,22 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "dev": true }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, "readable-stream": { - "version": "2.3.3", + "version": "2.3.6", "from": "readable-stream@^2.0.1", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "dev": true }, "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "version": "1.1.1", + "from": "string_decoder@~1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true } } @@ -4724,12 +4581,6 @@ "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz", "dev": true }, - "htmlescape": { - "version": "1.1.1", - "from": "htmlescape@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/htmlescape/-/htmlescape-1.1.1.tgz", - "dev": true - }, "htmlparser2": { "version": "3.9.2", "from": "htmlparser2@>=3.9.0 <4.0.0", @@ -4742,16 +4593,22 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "dev": true }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@>=2.0.0 <2.1.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, "readable-stream": { - "version": "2.3.3", - "from": "readable-stream@^2.0.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "version": "2.3.6", + "from": "readable-stream@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "dev": true }, "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "version": "1.1.1", + "from": "string_decoder@>=1.1.1 <1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true } } @@ -4775,9 +4632,9 @@ } }, "http-parser-js": { - "version": "0.4.9", + "version": "0.4.12", "from": "http-parser-js@>=0.4.0", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.9.tgz", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.4.12.tgz", "dev": true }, "http-proxy": { @@ -4786,15 +4643,15 @@ "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.16.2.tgz" }, "http-proxy-agent": { - "version": "1.0.0", - "from": "http-proxy-agent@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-1.0.0.tgz", + "version": "2.1.0", + "from": "http-proxy-agent@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz", "dev": true, "dependencies": { "debug": { - "version": "2.6.9", - "from": "debug@2", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "version": "3.1.0", + "from": "debug@3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "dev": true } } @@ -4805,69 +4662,17 @@ "resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.17.4.tgz", "dev": true, "dependencies": { - "arr-diff": { - "version": "2.0.0", - "from": "arr-diff@^2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "is-extglob": { + "version": "2.1.1", + "from": "is-extglob@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "dev": true }, - "array-unique": { - "version": "0.2.1", - "from": "array-unique@^0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "dev": true - }, - "braces": { - "version": "1.8.5", - "from": "braces@^1.8.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "from": "expand-brackets@^0.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "dev": true - }, - "extglob": { - "version": "0.3.2", - "from": "extglob@^0.3.1", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "dev": true, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "from": "is-extglob@^1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "dev": true - } - } - }, "is-glob": { "version": "3.1.0", "from": "is-glob@>=3.1.0 <4.0.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", "dev": true - }, - "micromatch": { - "version": "2.3.11", - "from": "micromatch@^2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "dev": true, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "from": "is-extglob@^1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "from": "is-glob@^2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "dev": true - } - } } } }, @@ -4903,15 +4708,15 @@ "dev": true }, "https-proxy-agent": { - "version": "1.0.0", - "from": "https-proxy-agent@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", + "version": "2.2.1", + "from": "https-proxy-agent@>=2.2.1 <3.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz", "dev": true, "dependencies": { "debug": { - "version": "2.6.9", - "from": "debug@2", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "version": "3.1.0", + "from": "debug@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "dev": true } } @@ -4923,9 +4728,23 @@ "dev": true }, "i18next": { - "version": "1.7.10", - "from": "i18next@>=1.7.1 <1.8.0", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-1.7.10.tgz", + "version": "1.10.6", + "from": "i18next@>=1.10.6 <2.0.0", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-1.10.6.tgz", + "dev": true, + "dependencies": { + "json5": { + "version": "0.2.0", + "from": "json5@>=0.2.0 <0.3.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.2.0.tgz", + "dev": true + } + } + }, + "i18next-client": { + "version": "1.10.3", + "from": "i18next-client@1.10.3", + "resolved": "https://registry.npmjs.org/i18next-client/-/i18next-client-1.10.3.tgz", "dev": true }, "iconv-lite": { @@ -4944,9 +4763,9 @@ "resolved": "https://registry.npmjs.org/ienoopen/-/ienoopen-1.0.0.tgz" }, "ignore": { - "version": "3.3.7", + "version": "3.3.8", "from": "ignore@>=3.3.3 <4.0.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.8.tgz", "dev": true }, "ignore-by-default": { @@ -5012,20 +4831,6 @@ "from": "ini@>=1.3.0 <1.4.0", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz" }, - "inline-source-map": { - "version": "0.6.2", - "from": "inline-source-map@>=0.6.0 <0.7.0", - "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", - "dev": true, - "dependencies": { - "source-map": { - "version": "0.5.7", - "from": "source-map@>=0.5.3 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "dev": true - } - } - }, "inquirer": { "version": "3.3.0", "from": "inquirer@>=3.0.6 <4.0.0", @@ -5039,15 +4844,15 @@ "dev": true }, "ansi-styles": { - "version": "3.2.0", - "from": "ansi-styles@^3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "version": "3.2.1", + "from": "ansi-styles@>=3.2.1 <4.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "dev": true }, "chalk": { - "version": "2.3.1", - "from": "chalk@^2.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "version": "2.4.1", + "from": "chalk@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "dev": true }, "has-flag": { @@ -5075,35 +4880,9 @@ "dev": true }, "supports-color": { - "version": "5.2.0", - "from": "supports-color@^5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", - "dev": true - } - } - }, - "insert-module-globals": { - "version": "7.0.1", - "from": "insert-module-globals@>=7.0.0 <8.0.0", - "resolved": "https://registry.npmjs.org/insert-module-globals/-/insert-module-globals-7.0.1.tgz", - "dev": true, - "dependencies": { - "combine-source-map": { - "version": "0.7.2", - "from": "combine-source-map@>=0.7.1 <0.8.0", - "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.7.2.tgz", - "dev": true - }, - "convert-source-map": { - "version": "1.1.3", - "from": "convert-source-map@~1.1.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", - "dev": true - }, - "source-map": { - "version": "0.5.7", - "from": "source-map@>=0.5.3 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "version": "5.4.0", + "from": "supports-color@>=5.3.0 <6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "dev": true } } @@ -5121,9 +4900,9 @@ "dev": true }, "invariant": { - "version": "2.2.2", + "version": "2.2.4", "from": "invariant@>=2.2.2 <3.0.0", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.2.tgz", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", "dev": true }, "invert-kv": { @@ -5161,18 +4940,10 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.0.5.tgz" }, "is-accessor-descriptor": { - "version": "1.0.0", - "from": "is-accessor-descriptor@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "from": "kind-of@^6.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "dev": true - } - } + "version": "0.1.6", + "from": "is-accessor-descriptor@>=0.1.6 <0.2.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "dev": true }, "is-arrayish": { "version": "0.2.1", @@ -5203,19 +4974,17 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", "dev": true }, + "is-ci": { + "version": "1.1.0", + "from": "is-ci@>=1.0.10 <2.0.0", + "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", + "dev": true + }, "is-data-descriptor": { - "version": "1.0.0", - "from": "is-data-descriptor@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "from": "kind-of@^6.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "dev": true - } - } + "version": "0.1.4", + "from": "is-data-descriptor@>=0.1.4 <0.2.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "dev": true }, "is-date-object": { "version": "1.0.1", @@ -5224,15 +4993,15 @@ "dev": true }, "is-descriptor": { - "version": "1.0.2", - "from": "is-descriptor@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "version": "0.1.6", + "from": "is-descriptor@>=0.1.0 <0.2.0", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", "dev": true, "dependencies": { "kind-of": { - "version": "6.0.2", - "from": "kind-of@^6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "version": "5.1.0", + "from": "kind-of@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", "dev": true } } @@ -5261,9 +5030,9 @@ "dev": true }, "is-extglob": { - "version": "2.1.1", - "from": "is-extglob@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "version": "1.0.0", + "from": "is-extglob@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", "dev": true }, "is-finite": { @@ -5278,9 +5047,9 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz" }, "is-glob": { - "version": "4.0.0", - "from": "is-glob@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "version": "2.0.1", + "from": "is-glob@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", "dev": true }, "is-installed-globally": { @@ -5289,10 +5058,17 @@ "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", "dev": true }, + "is-my-ip-valid": { + "version": "1.0.0", + "from": "is-my-ip-valid@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "dev": true, + "optional": true + }, "is-my-json-valid": { - "version": "2.17.1", + "version": "2.17.2", "from": "is-my-json-valid@>=2.12.4 <3.0.0", - "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.1.tgz", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.17.2.tgz", "dev": true, "optional": true }, @@ -5303,9 +5079,9 @@ "dev": true }, "is-number": { - "version": "3.0.0", - "from": "is-number@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "version": "2.1.0", + "from": "is-number@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", "dev": true }, "is-obj": { @@ -5315,10 +5091,18 @@ "dev": true }, "is-odd": { - "version": "1.0.0", - "from": "is-odd@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-1.0.0.tgz", - "dev": true + "version": "2.0.0", + "from": "is-odd@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", + "dev": true, + "dependencies": { + "is-number": { + "version": "4.0.0", + "from": "is-number@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "dev": true + } + } }, "is-path-cwd": { "version": "1.0.0", @@ -5327,9 +5111,9 @@ "dev": true }, "is-path-in-cwd": { - "version": "1.0.0", + "version": "1.0.1", "from": "is-path-in-cwd@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", "dev": true }, "is-path-inside": { @@ -5342,7 +5126,15 @@ "version": "2.0.4", "from": "is-plain-object@>=2.0.3 <3.0.0", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "dev": true + "dev": true, + "dependencies": { + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "dev": true + } + } }, "is-posix-bracket": { "version": "0.1.1", @@ -5413,6 +5205,12 @@ "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", "dev": true }, + "is-windows": { + "version": "1.0.2", + "from": "is-windows@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "dev": true + }, "is-wsl": { "version": "1.1.0", "from": "is-wsl@>=1.1.0 <2.0.0", @@ -5436,10 +5234,18 @@ "dev": true }, "isobject": { - "version": "3.0.1", - "from": "isobject@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "dev": true + "version": "2.1.0", + "from": "isobject@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "dev": true, + "dependencies": { + "isarray": { + "version": "1.0.0", + "from": "isarray@1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + } + } }, "isomorphic-fetch": { "version": "2.2.1", @@ -5468,6 +5274,11 @@ "from": "jmespath@0.15.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz" }, + "jquery": { + "version": "1.11.1", + "from": "jquery@1.11.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-1.11.1.tgz" + }, "js-base64": { "version": "2.4.3", "from": "js-base64@>=2.1.9 <3.0.0", @@ -5540,9 +5351,9 @@ "dev": true }, "json5": { - "version": "0.2.0", - "from": "json5@>=0.2.0 <0.3.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.2.0.tgz", + "version": "0.5.1", + "from": "json5@>=0.5.1 <0.6.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", "dev": true }, "jsonfile": { @@ -5555,12 +5366,6 @@ "from": "jsonify@>=0.0.0 <0.1.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz" }, - "jsonparse": { - "version": "1.3.1", - "from": "jsonparse@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "dev": true - }, "jsonpointer": { "version": "4.0.1", "from": "jsonpointer@>=4.0.0 <5.0.0", @@ -5568,12 +5373,6 @@ "dev": true, "optional": true }, - "JSONStream": { - "version": "1.3.2", - "from": "JSONStream@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.2.tgz", - "dev": true - }, "jsonwebtoken": { "version": "8.1.1", "from": "jsonwebtoken@>=8.0.1 <9.0.0", @@ -5629,63 +5428,21 @@ "resolved": "https://registry.npmjs.org/kareem/-/kareem-1.5.0.tgz" }, "karma": { - "version": "2.0.0", - "from": "karma@latest", - "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.0.tgz", + "version": "2.0.2", + "from": "karma@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/karma/-/karma-2.0.2.tgz", "dev": true, "dependencies": { - "anymatch": { - "version": "1.3.2", - "from": "anymatch@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "dev": true - }, - "arr-diff": { - "version": "2.0.0", - "from": "arr-diff@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "dev": true - }, - "array-unique": { - "version": "0.2.1", - "from": "array-unique@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "dev": true - }, "bluebird": { "version": "3.5.1", "from": "bluebird@>=3.3.0 <4.0.0", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz", "dev": true }, - "braces": { - "version": "1.8.5", - "from": "braces@>=1.8.2 <2.0.0", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "dev": true - }, - "chokidar": { - "version": "1.7.0", - "from": "chokidar@>=1.4.1 <2.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", - "dev": true - }, "colors": { - "version": "1.1.2", + "version": "1.2.5", "from": "colors@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "from": "expand-brackets@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "dev": true - }, - "extglob": { - "version": "0.3.2", - "from": "extglob@>=0.3.1 <0.4.0", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.2.5.tgz", "dev": true }, "glob": { @@ -5694,72 +5451,24 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", "dev": true }, - "glob-parent": { - "version": "2.0.0", - "from": "glob-parent@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "from": "is-extglob@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "from": "is-glob@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "from": "isarray@~1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "dev": true - }, "isbinaryfile": { "version": "3.0.2", "from": "isbinaryfile@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-3.0.2.tgz", "dev": true }, - "micromatch": { - "version": "2.3.11", - "from": "micromatch@>=2.1.5 <3.0.0", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "dev": true - }, "minimatch": { "version": "3.0.4", "from": "minimatch@>=3.0.2 <4.0.0", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "dev": true }, - "process-nextick-args": { - "version": "2.0.0", - "from": "process-nextick-args@~2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "dev": true - }, "range-parser": { "version": "1.2.0", "from": "range-parser@>=1.2.0 <2.0.0", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", "dev": true }, - "readable-stream": { - "version": "2.3.4", - "from": "readable-stream@^2.0.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", - "dev": true - }, - "readdirp": { - "version": "2.1.0", - "from": "readdirp@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", - "dev": true - }, "rimraf": { "version": "2.6.2", "from": "rimraf@>=2.6.0 <3.0.0", @@ -5771,12 +5480,6 @@ "from": "source-map@>=0.6.1 <0.7.0", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "dev": true - }, - "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "dev": true } } }, @@ -5827,15 +5530,15 @@ "dev": true }, "ansi-styles": { - "version": "3.2.0", - "from": "ansi-styles@>=3.2.0 <4.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "version": "3.2.1", + "from": "ansi-styles@>=3.2.1 <4.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "dev": true }, "chalk": { - "version": "2.3.1", + "version": "2.4.1", "from": "chalk@>=2.1.0 <3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "dev": true }, "has-flag": { @@ -5851,9 +5554,9 @@ "dev": true }, "supports-color": { - "version": "5.2.0", - "from": "supports-color@>=5.2.0 <6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "version": "5.4.0", + "from": "supports-color@>=5.3.0 <6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "dev": true } } @@ -5865,33 +5568,15 @@ "dev": true }, "karma-webpack": { - "version": "2.0.9", + "version": "2.0.13", "from": "karma-webpack@>=2.0.9 <3.0.0", - "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-2.0.9.tgz", + "resolved": "https://registry.npmjs.org/karma-webpack/-/karma-webpack-2.0.13.tgz", "dev": true, "dependencies": { "async": { - "version": "0.9.2", - "from": "async@>=0.9.0 <0.10.0", - "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", - "dev": true - }, - "json5": { - "version": "0.5.1", - "from": "json5@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "dev": true - }, - "loader-utils": { - "version": "0.2.17", - "from": "loader-utils@>=0.2.5 <0.3.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-0.2.17.tgz", - "dev": true - }, - "lodash": { - "version": "3.10.1", - "from": "lodash@>=3.8.0 <4.0.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", + "version": "2.6.0", + "from": "async@^2.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", "dev": true }, "source-map": { @@ -5919,12 +5604,6 @@ "from": "kind-of@>=3.0.2 <4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz" }, - "labeled-stream-splicer": { - "version": "2.0.0", - "from": "labeled-stream-splicer@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz", - "dev": true - }, "latest-version": { "version": "3.1.0", "from": "latest-version@>=3.0.0 <4.0.0", @@ -6192,12 +5871,6 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "dev": true }, - "lexical-scope": { - "version": "1.2.0", - "from": "lexical-scope@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/lexical-scope/-/lexical-scope-1.2.0.tgz", - "dev": true - }, "libbase64": { "version": "0.1.0", "from": "libbase64@0.1.0", @@ -6367,16 +6040,10 @@ "from": "lodash.keys@>=4.2.0 <5.0.0", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-4.2.0.tgz" }, - "lodash.memoize": { - "version": "3.0.4", - "from": "lodash.memoize@>=3.0.3 <3.1.0", - "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", - "dev": true - }, "lodash.mergewith": { - "version": "4.6.0", + "version": "4.6.1", "from": "lodash.mergewith@>=4.6.0 <5.0.0", - "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.0.tgz", + "resolved": "https://registry.npmjs.org/lodash.mergewith/-/lodash.mergewith-4.6.1.tgz", "dev": true }, "lodash.noop": { @@ -6426,15 +6093,15 @@ "dev": true, "dependencies": { "ansi-styles": { - "version": "3.2.0", - "from": "ansi-styles@^3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "version": "3.2.1", + "from": "ansi-styles@>=3.2.1 <4.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "dev": true }, "chalk": { - "version": "2.3.1", - "from": "chalk@^2.0.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "version": "2.4.1", + "from": "chalk@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "dev": true }, "has-flag": { @@ -6444,17 +6111,17 @@ "dev": true }, "supports-color": { - "version": "5.2.0", - "from": "supports-color@^5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "version": "5.4.0", + "from": "supports-color@>=5.3.0 <6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "dev": true } } }, "log4js": { - "version": "2.5.3", + "version": "2.6.0", "from": "log4js@>=2.3.9 <3.0.0", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.5.3.tgz", + "resolved": "https://registry.npmjs.org/log4js/-/log4js-2.6.0.tgz", "dev": true, "dependencies": { "addressparser": { @@ -6471,6 +6138,12 @@ "dev": true, "optional": true }, + "circular-json": { + "version": "0.5.4", + "from": "circular-json@>=0.5.4 <0.6.0", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.5.4.tgz", + "dev": true + }, "debug": { "version": "3.1.0", "from": "debug@>=3.1.0 <4.0.0", @@ -6542,6 +6215,12 @@ "resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz", "dev": true }, + "semver": { + "version": "5.5.0", + "from": "semver@^5.5.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", + "dev": true + }, "smtp-connection": { "version": "2.12.0", "from": "smtp-connection@2.12.0", @@ -6830,9 +6509,9 @@ "optional": true }, "commander": { - "version": "2.14.1", + "version": "2.15.1", "from": "commander@>=2.9.0 <3.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.14.1.tgz", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", "dev": true, "optional": true }, @@ -6957,9 +6636,9 @@ "dev": true }, "lowercase-keys": { - "version": "1.0.0", + "version": "1.0.1", "from": "lowercase-keys@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "dev": true }, "lpad": { @@ -6989,60 +6668,32 @@ "resolved": "https://registry.npmjs.org/mailcomposer/-/mailcomposer-3.3.2.tgz" }, "mailgun-js": { - "version": "0.7.15", - "from": "mailgun-js@>=0.7.0 <0.8.0", - "resolved": "https://registry.npmjs.org/mailgun-js/-/mailgun-js-0.7.15.tgz", + "version": "0.18.0", + "from": "mailgun-js@>=0.18.0 <0.19.0", + "resolved": "https://registry.npmjs.org/mailgun-js/-/mailgun-js-0.18.0.tgz", "dev": true, "optional": true, "dependencies": { "async": { - "version": "2.1.5", - "from": "async@>=2.1.2 <2.2.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.1.5.tgz", + "version": "2.6.0", + "from": "async@~2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", "dev": true, "optional": true }, "debug": { - "version": "2.2.0", - "from": "debug@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "from": "form-data@>=2.1.1 <2.2.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "dev": true, - "optional": true - }, - "inflection": { - "version": "1.10.0", - "from": "inflection@>=1.10.0 <1.11.0", - "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.10.0.tgz", - "dev": true, - "optional": true - }, - "ms": { - "version": "0.7.1", - "from": "ms@0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "dev": true, - "optional": true - }, - "q": { - "version": "1.4.1", - "from": "q@>=1.4.0 <1.5.0", - "resolved": "https://registry.npmjs.org/q/-/q-1.4.1.tgz", + "version": "3.1.0", + "from": "debug@>=3.1.0 <3.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "dev": true, "optional": true } } }, "make-dir": { - "version": "1.1.0", + "version": "1.3.0", "from": "make-dir@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.3.0.tgz", "dev": true }, "mandrill-api": { @@ -7079,19 +6730,17 @@ "from": "marked@>=0.3.5 <0.4.0", "resolved": "https://registry.npmjs.org/marked/-/marked-0.3.12.tgz" }, + "math-random": { + "version": "1.0.1", + "from": "math-random@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/math-random/-/math-random-1.0.1.tgz", + "dev": true + }, "md5.js": { "version": "1.3.4", "from": "md5.js@>=1.3.4 <2.0.0", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.4.tgz", - "dev": true, - "dependencies": { - "hash-base": { - "version": "3.0.4", - "from": "hash-base@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", - "dev": true - } - } + "dev": true }, "media-typer": { "version": "0.3.0", @@ -7116,16 +6765,22 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "dev": true }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, "readable-stream": { - "version": "2.3.3", - "from": "readable-stream@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "version": "2.3.6", + "from": "readable-stream@^2.0.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "dev": true }, "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@>=1.0.3 <1.1.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "version": "1.1.1", + "from": "string_decoder@~1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true } } @@ -7237,18 +6892,10 @@ } }, "micromatch": { - "version": "3.1.5", - "from": "micromatch@>=3.1.4 <4.0.0", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.5.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "from": "kind-of@>=6.0.0 <7.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "dev": true - } - } + "version": "2.3.11", + "from": "micromatch@>=2.1.5 <3.0.0", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "dev": true }, "microtime-nodejs": { "version": "1.0.0", @@ -7282,15 +6929,15 @@ "resolved": "https://registry.npmjs.org/mimelib/-/mimelib-0.2.14.tgz" }, "mimic-fn": { - "version": "1.1.0", + "version": "1.2.0", "from": "mimic-fn@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", "dev": true }, "minimalistic-assert": { - "version": "1.0.0", + "version": "1.0.1", "from": "minimalistic-assert@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "dev": true }, "minimalistic-crypto-utils": { @@ -7310,9 +6957,9 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" }, "mixin-deep": { - "version": "1.3.0", + "version": "1.3.1", "from": "mixin-deep@>=1.2.0 <2.0.0", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.0.tgz", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.1.tgz", "dev": true, "dependencies": { "is-extendable": { @@ -7375,38 +7022,6 @@ } } }, - "module-deps": { - "version": "4.1.1", - "from": "module-deps@>=4.0.8 <5.0.0", - "resolved": "https://registry.npmjs.org/module-deps/-/module-deps-4.1.1.tgz", - "dev": true, - "dependencies": { - "isarray": { - "version": "1.0.0", - "from": "isarray@~1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "from": "process-nextick-args@~2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "dev": true - }, - "readable-stream": { - "version": "2.3.4", - "from": "readable-stream@^2.0.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", - "dev": true - }, - "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "dev": true - } - } - }, "moment": { "version": "2.20.1", "from": "moment@>=2.10.6 <3.0.0", @@ -7625,15 +7240,27 @@ "resolved": "https://registry.npmjs.org/nan/-/nan-2.3.5.tgz" }, "nanomatch": { - "version": "1.2.7", - "from": "nanomatch@>=1.2.5 <2.0.0", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.7.tgz", + "version": "1.2.9", + "from": "nanomatch@>=1.2.9 <2.0.0", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", "dev": true, "dependencies": { + "arr-diff": { + "version": "4.0.0", + "from": "arr-diff@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "from": "array-unique@>=0.3.2 <0.4.0", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "dev": true + }, "kind-of": { - "version": "5.1.0", - "from": "kind-of@>=5.0.2 <6.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "version": "6.0.2", + "from": "kind-of@^6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "dev": true } } @@ -7675,6 +7302,12 @@ "from": "negotiator@0.5.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.5.3.tgz" }, + "neo-async": { + "version": "2.5.1", + "from": "neo-async@>=2.5.0 <3.0.0", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.5.1.tgz", + "dev": true + }, "netmask": { "version": "1.0.6", "from": "netmask@>=1.0.4 <1.1.0", @@ -7682,6 +7315,12 @@ "dev": true, "optional": true }, + "next-tick": { + "version": "1.0.0", + "from": "next-tick@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "dev": true + }, "nise": { "version": "1.2.2", "from": "nise@>=1.2.0 <2.0.0", @@ -7736,16 +7375,22 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "dev": true }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, "readable-stream": { - "version": "2.3.3", + "version": "2.3.6", "from": "readable-stream@^2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "dev": true }, "string_decoder": { - "version": "1.0.3", + "version": "1.1.1", "from": "string_decoder@^1.0.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true }, "url": { @@ -7844,28 +7489,284 @@ "resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.7.tgz" }, "nodemon": { - "version": "1.14.11", + "version": "1.17.4", "from": "nodemon@>=1.14.3 <2.0.0", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.14.11.tgz", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.17.4.tgz", "dev": true, "dependencies": { + "anymatch": { + "version": "2.0.0", + "from": "anymatch@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "from": "arr-diff@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "from": "array-unique@>=0.3.2 <0.4.0", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "dev": true + }, + "braces": { + "version": "2.3.2", + "from": "braces@>=2.3.0 <3.0.0", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "dev": true, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + } + } + }, + "chokidar": { + "version": "2.0.3", + "from": "chokidar@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", + "dev": true + }, "debug": { "version": "3.1.0", "from": "debug@>=3.1.0 <4.0.0", "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "dev": true }, + "expand-brackets": { + "version": "2.1.4", + "from": "expand-brackets@>=2.1.4 <3.0.0", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "dev": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@^2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true + }, + "define-property": { + "version": "0.2.5", + "from": "define-property@>=0.2.5 <0.3.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "dev": true + }, + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "from": "is-accessor-descriptor@^0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "dev": true, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "dev": true + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "from": "is-data-descriptor@^0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "dev": true, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "dev": true + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "from": "is-descriptor@^0.1.0", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "from": "kind-of@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "from": "extglob@>=2.0.4 <3.0.0", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "dev": true, + "dependencies": { + "define-property": { + "version": "1.0.0", + "from": "define-property@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "dev": true + }, + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + } + } + }, + "fill-range": { + "version": "4.0.0", + "from": "fill-range@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "dev": true, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + } + } + }, + "glob-parent": { + "version": "3.1.0", + "from": "glob-parent@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "dev": true, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "from": "is-glob@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "dev": true + } + } + }, + "has-flag": { + "version": "3.0.0", + "from": "has-flag@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "dev": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "from": "is-accessor-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "dev": true + }, + "is-data-descriptor": { + "version": "1.0.0", + "from": "is-data-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "dev": true + }, + "is-descriptor": { + "version": "1.0.2", + "from": "is-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "from": "is-extglob@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "from": "is-glob@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "from": "is-number@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "dev": true, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "dev": true + } + } + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "from": "kind-of@^6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "from": "micromatch@>=3.1.4 <4.0.0", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "dev": true + }, "minimatch": { "version": "3.0.4", "from": "minimatch@^3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "dev": true }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, + "readable-stream": { + "version": "2.3.6", + "from": "readable-stream@^2.0.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "dev": true + }, + "readdirp": { + "version": "2.1.0", + "from": "readdirp@^2.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "dev": true + }, "semver": { "version": "5.5.0", "from": "semver@>=5.4.1 <6.0.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", "dev": true + }, + "string_decoder": { + "version": "1.1.1", + "from": "string_decoder@~1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "from": "supports-color@>=5.2.0 <6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "dev": true } } }, @@ -7946,6 +7847,11 @@ "from": "number-is-nan@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz" }, + "nvd3": { + "version": "1.8.6", + "from": "nvd3@latest", + "resolved": "https://registry.npmjs.org/nvd3/-/nvd3-1.8.6.tgz" + }, "oauth": { "version": "0.9.15", "from": "oauth@>=0.9.0 <0.10.0", @@ -7978,32 +7884,6 @@ "from": "define-property@>=0.2.5 <0.3.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "from": "is-accessor-descriptor@>=0.1.6 <0.2.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "from": "is-data-descriptor@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "from": "is-descriptor@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "from": "kind-of@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "dev": true - } - } } } }, @@ -8017,7 +7897,15 @@ "version": "1.0.1", "from": "object-visit@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "dev": true + "dev": true, + "dependencies": { + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "dev": true + } + } }, "object.omit": { "version": "2.0.1", @@ -8029,12 +7917,20 @@ "version": "1.3.0", "from": "object.pick@>=1.3.0 <2.0.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "dev": true + "dev": true, + "dependencies": { + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "dev": true + } + } }, "obuf": { - "version": "1.1.1", + "version": "1.1.2", "from": "obuf@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.1.tgz", + "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", "dev": true }, "on-finished": { @@ -8212,9 +8108,9 @@ "dev": true }, "opn": { - "version": "5.2.0", + "version": "5.3.0", "from": "opn@>=5.1.0 <6.0.0", - "resolved": "https://registry.npmjs.org/opn/-/opn-5.2.0.tgz", + "resolved": "https://registry.npmjs.org/opn/-/opn-5.3.0.tgz", "dev": true }, "optimist": { @@ -8308,43 +8204,27 @@ "dev": true }, "pac-proxy-agent": { - "version": "1.1.0", - "from": "pac-proxy-agent@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-1.1.0.tgz", + "version": "2.0.2", + "from": "pac-proxy-agent@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-2.0.2.tgz", "dev": true, "optional": true, "dependencies": { "debug": { - "version": "2.6.9", - "from": "debug@2", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "version": "3.1.0", + "from": "debug@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "dev": true, "optional": true } } }, "pac-resolver": { - "version": "2.0.0", - "from": "pac-resolver@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-2.0.0.tgz", + "version": "3.0.0", + "from": "pac-resolver@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-3.0.0.tgz", "dev": true, - "optional": true, - "dependencies": { - "co": { - "version": "3.0.6", - "from": "co@>=3.0.6 <3.1.0", - "resolved": "https://registry.npmjs.org/co/-/co-3.0.6.tgz", - "dev": true, - "optional": true - }, - "ip": { - "version": "1.0.1", - "from": "ip@1.0.1", - "resolved": "https://registry.npmjs.org/ip/-/ip-1.0.1.tgz", - "dev": true, - "optional": true - } - } + "optional": true }, "package-json": { "version": "4.0.1", @@ -8358,37 +8238,17 @@ "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", "dev": true }, - "parents": { - "version": "1.0.1", - "from": "parents@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/parents/-/parents-1.0.1.tgz", - "dev": true - }, "parse-asn1": { - "version": "5.1.0", + "version": "5.1.1", "from": "parse-asn1@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.0.tgz", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.1.tgz", "dev": true }, "parse-glob": { "version": "3.0.4", "from": "parse-glob@>=3.0.4 <4.0.0", "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "dev": true, - "dependencies": { - "is-extglob": { - "version": "1.0.0", - "from": "is-extglob@^1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "from": "is-glob@^2.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "dev": true - } - } + "dev": true }, "parse-json": { "version": "2.2.0", @@ -8523,12 +8383,6 @@ "from": "path-parse@>=1.0.5 <2.0.0", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz" }, - "path-platform": { - "version": "0.11.15", - "from": "path-platform@>=0.11.15 <0.12.0", - "resolved": "https://registry.npmjs.org/path-platform/-/path-platform-0.11.15.tgz", - "dev": true - }, "path-proxy": { "version": "1.0.0", "from": "path-proxy@>=1.0.0 <1.1.0", @@ -8581,9 +8435,9 @@ "dev": true }, "pbkdf2": { - "version": "3.0.14", + "version": "3.0.16", "from": "pbkdf2@>=3.0.3 <4.0.0", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.14.tgz", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.16.tgz", "dev": true }, "pend": { @@ -8751,6 +8605,13 @@ "from": "promise@>=2.0.0 <2.1.0", "resolved": "https://registry.npmjs.org/promise/-/promise-2.0.0.tgz" }, + "promisify-call": { + "version": "2.0.4", + "from": "promisify-call@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/promisify-call/-/promisify-call-2.0.4.tgz", + "dev": true, + "optional": true + }, "prompt": { "version": "0.2.14", "from": "prompt@0.2.14", @@ -8768,28 +8629,35 @@ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-1.0.10.tgz" }, "proxy-agent": { - "version": "2.0.0", - "from": "proxy-agent@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-2.0.0.tgz", + "version": "3.0.0", + "from": "proxy-agent@>=3.0.0 <3.1.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-3.0.0.tgz", "dev": true, "optional": true, "dependencies": { "debug": { - "version": "2.6.9", - "from": "debug@2", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "version": "3.1.0", + "from": "debug@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "dev": true, "optional": true }, "lru-cache": { - "version": "2.6.5", - "from": "lru-cache@>=2.6.5 <2.7.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.6.5.tgz", + "version": "4.1.3", + "from": "lru-cache@^4.1.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.3.tgz", "dev": true, "optional": true } } }, + "proxy-from-env": { + "version": "1.0.0", + "from": "proxy-from-env@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", + "dev": true, + "optional": true + }, "prr": { "version": "1.0.1", "from": "prr@>=1.0.1 <1.1.0", @@ -8828,9 +8696,9 @@ } }, "public-encrypt": { - "version": "4.0.0", + "version": "4.0.2", "from": "public-encrypt@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.2.tgz", "dev": true }, "pug": { @@ -8962,9 +8830,9 @@ "resolved": "https://registry.npmjs.org/q/-/q-1.1.2.tgz" }, "qjobs": { - "version": "1.1.5", + "version": "1.2.0", "from": "qjobs@>=1.1.4 <2.0.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.1.5.tgz", + "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", "dev": true }, "qs": { @@ -8995,15 +8863,21 @@ "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz" }, "randomatic": { - "version": "1.1.7", - "from": "randomatic@>=1.1.3 <2.0.0", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "version": "3.0.0", + "from": "randomatic@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-3.0.0.tgz", "dev": true, "dependencies": { - "kind-of": { + "is-number": { "version": "4.0.0", - "from": "kind-of@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "from": "is-number@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "from": "kind-of@>=6.0.0 <7.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "dev": true } } @@ -9015,9 +8889,9 @@ "dev": true }, "randomfill": { - "version": "1.0.3", + "version": "1.0.4", "from": "randomfill@>=1.0.3 <2.0.0", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.3.tgz", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", "dev": true }, "range-parser": { @@ -9082,38 +8956,6 @@ "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", "dev": true }, - "read-only-stream": { - "version": "2.0.0", - "from": "read-only-stream@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/read-only-stream/-/read-only-stream-2.0.0.tgz", - "dev": true, - "dependencies": { - "isarray": { - "version": "1.0.0", - "from": "isarray@~1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "from": "process-nextick-args@~2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "dev": true - }, - "readable-stream": { - "version": "2.3.4", - "from": "readable-stream@^2.0.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", - "dev": true - }, - "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "dev": true - } - } - }, "read-pkg": { "version": "2.0.0", "from": "read-pkg@>=2.0.0 <3.0.0", @@ -9212,9 +9054,9 @@ "resolved": "https://registry.npmjs.org/referrer-policy/-/referrer-policy-1.1.0.tgz" }, "regenerate": { - "version": "1.3.3", + "version": "1.4.0", "from": "regenerate@>=1.2.1 <2.0.0", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.3.3.tgz", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.0.tgz", "dev": true }, "regenerator-runtime": { @@ -9236,9 +9078,9 @@ "dev": true }, "regex-not": { - "version": "1.0.0", + "version": "1.0.2", "from": "regex-not@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.0.tgz", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "dev": true }, "regexp-clone": { @@ -9246,6 +9088,12 @@ "from": "regexp-clone@0.0.1", "resolved": "https://registry.npmjs.org/regexp-clone/-/regexp-clone-0.0.1.tgz" }, + "regexpp": { + "version": "1.1.0", + "from": "regexpp@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", + "dev": true + }, "regexpu-core": { "version": "2.0.0", "from": "regexpu-core@>=2.0.0 <3.0.0", @@ -9380,9 +9228,9 @@ } }, "requirejs": { - "version": "2.1.22", - "from": "https://registry.npmjs.org/requirejs/-/requirejs-2.1.22.tgz", - "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.1.22.tgz", + "version": "2.3.5", + "from": "requirejs@>=2.1.22 <3.0.0", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.5.tgz", "dev": true }, "requires-port": { @@ -9426,6 +9274,12 @@ "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", "dev": true }, + "ret": { + "version": "0.1.15", + "from": "ret@>=0.1.10 <0.2.0", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "dev": true + }, "retry-as-promised": { "version": "2.3.2", "from": "retry-as-promised@>=2.0.0 <3.0.0", @@ -9460,9 +9314,9 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.6.tgz" }, "ripemd160": { - "version": "2.0.1", - "from": "ripemd160@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "version": "2.0.2", + "from": "ripemd160@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", "dev": true }, "rndm": { @@ -9506,12 +9360,30 @@ "from": "safe-buffer@>=5.1.1 <5.2.0", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz" }, + "safe-json-parse": { + "version": "1.0.1", + "from": "safe-json-parse@>=1.0.1 <1.1.0", + "resolved": "https://registry.npmjs.org/safe-json-parse/-/safe-json-parse-1.0.1.tgz", + "dev": true + }, "safe-json-stringify": { "version": "1.0.4", "from": "safe-json-stringify@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.0.4.tgz", "optional": true }, + "safe-regex": { + "version": "1.1.0", + "from": "safe-regex@>=1.1.0 <2.0.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "dev": true + }, + "safer-buffer": { + "version": "2.1.2", + "from": "safer-buffer@>=2.1.2 <3.0.0", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "dev": true + }, "samsam": { "version": "1.1.2", "from": "samsam@1.1.2", @@ -9532,42 +9404,34 @@ } }, "sanitize-html": { - "version": "1.17.0", + "version": "1.18.2", "from": "sanitize-html@>=1.14.1 <2.0.0", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.17.0.tgz", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-1.18.2.tgz", "dev": true, "dependencies": { "ansi-styles": { - "version": "3.2.0", - "from": "ansi-styles@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "version": "3.2.1", + "from": "ansi-styles@>=3.2.1 <4.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "dev": true }, "chalk": { - "version": "2.3.0", + "version": "2.4.1", "from": "chalk@>=2.3.0 <3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "dev": true }, "has-flag": { - "version": "2.0.0", - "from": "has-flag@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "version": "3.0.0", + "from": "has-flag@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "dev": true }, "postcss": { - "version": "6.0.16", + "version": "6.0.22", "from": "postcss@>=6.0.14 <7.0.0", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.16.tgz", - "dev": true, - "dependencies": { - "supports-color": { - "version": "5.1.0", - "from": "supports-color@>=5.1.0 <6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", - "dev": true - } - } + "resolved": "https://registry.npmjs.org/postcss/-/postcss-6.0.22.tgz", + "dev": true }, "source-map": { "version": "0.6.1", @@ -9576,9 +9440,9 @@ "dev": true }, "supports-color": { - "version": "4.5.0", - "from": "supports-color@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "version": "5.4.0", + "from": "supports-color@>=5.3.0 <6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "dev": true } } @@ -9600,15 +9464,15 @@ "dev": true }, "selfsigned": { - "version": "1.10.2", + "version": "1.10.3", "from": "selfsigned@>=1.9.1 <2.0.0", - "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.2.tgz", + "resolved": "https://registry.npmjs.org/selfsigned/-/selfsigned-1.10.3.tgz", "dev": true, "dependencies": { "node-forge": { - "version": "0.7.1", - "from": "node-forge@0.7.1", - "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.1.tgz", + "version": "0.7.5", + "from": "node-forge@0.7.5", + "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.5.tgz", "dev": true } } @@ -9692,10 +9556,18 @@ "dev": true, "dependencies": { "accepts": { - "version": "1.3.4", + "version": "1.3.5", "from": "accepts@~1.3.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "dev": true, + "dependencies": { + "mime-types": { + "version": "2.1.18", + "from": "mime-types@~2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "dev": true + } + } }, "debug": { "version": "2.6.9", @@ -9709,6 +9581,12 @@ "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "dev": true }, + "mime-db": { + "version": "1.33.0", + "from": "mime-db@~1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "dev": true + }, "negotiator": { "version": "0.6.1", "from": "negotiator@0.6.1", @@ -9764,12 +9642,6 @@ "from": "set-blocking@>=2.0.0 <2.1.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz" }, - "set-getter": { - "version": "0.1.0", - "from": "set-getter@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", - "dev": true - }, "set-immediate-shim": { "version": "1.0.1", "from": "set-immediate-shim@>=1.0.1 <2.0.0", @@ -9780,7 +9652,15 @@ "version": "2.0.0", "from": "set-value@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "dev": true + "dev": true, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + } + } }, "setimmediate": { "version": "1.0.5", @@ -9805,25 +9685,11 @@ } }, "sha.js": { - "version": "2.4.10", + "version": "2.4.11", "from": "sha.js@>=2.4.0 <3.0.0", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.10.tgz", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", "dev": true }, - "shasum": { - "version": "1.0.2", - "from": "shasum@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/shasum/-/shasum-1.0.2.tgz", - "dev": true, - "dependencies": { - "json-stable-stringify": { - "version": "0.0.1", - "from": "json-stable-stringify@>=0.0.0 <0.1.0", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", - "dev": true - } - } - }, "shebang-command": { "version": "1.2.0", "from": "shebang-command@>=1.2.0 <2.0.0", @@ -9836,12 +9702,6 @@ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", "dev": true }, - "shell-quote": { - "version": "1.6.1", - "from": "shell-quote@>=1.6.1 <2.0.0", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", - "dev": true - }, "shimmer": { "version": "1.1.0", "from": "shimmer@1.1.0", @@ -9923,9 +9783,9 @@ "resolved": "https://registry.npmjs.org/smtpapi/-/smtpapi-1.3.1.tgz" }, "snapdragon": { - "version": "0.8.1", + "version": "0.8.2", "from": "snapdragon@>=0.8.1 <0.9.0", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.1.tgz", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "dev": true, "dependencies": { "debug": { @@ -9940,44 +9800,10 @@ "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "dev": true }, - "is-accessor-descriptor": { - "version": "0.1.6", - "from": "is-accessor-descriptor@>=0.1.6 <0.2.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "dev": true - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "from": "is-data-descriptor@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "dev": true - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "from": "is-descriptor@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "from": "kind-of@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "dev": true }, "source-map": { @@ -9992,7 +9818,45 @@ "version": "2.1.1", "from": "snapdragon-node@>=2.0.1 <3.0.0", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "dev": true + "dev": true, + "dependencies": { + "define-property": { + "version": "1.0.0", + "from": "define-property@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "dev": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "from": "is-accessor-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "dev": true + }, + "is-data-descriptor": { + "version": "1.0.0", + "from": "is-data-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "dev": true + }, + "is-descriptor": { + "version": "1.0.2", + "from": "is-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "from": "kind-of@^6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "dev": true + } + } }, "snapdragon-util": { "version": "3.0.1", @@ -10040,15 +9904,15 @@ } }, "socket.io-parser": { - "version": "3.1.2", + "version": "3.1.3", "from": "socket.io-parser@>=3.1.1 <3.2.0", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.2.tgz", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-3.1.3.tgz", "dev": true, "dependencies": { "debug": { - "version": "2.6.9", - "from": "debug@~2.6.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "version": "3.1.0", + "from": "debug@>=3.1.0 <3.2.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "dev": true }, "isarray": { @@ -10092,9 +9956,9 @@ "dev": true }, "socks-proxy-agent": { - "version": "2.1.1", - "from": "socks-proxy-agent@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-2.1.1.tgz", + "version": "3.0.1", + "from": "socks-proxy-agent@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz", "dev": true }, "source-list-map": { @@ -10109,9 +9973,9 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.34.tgz" }, "source-map-resolve": { - "version": "0.5.1", + "version": "0.5.2", "from": "source-map-resolve@>=0.5.0 <0.6.0", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz", "dev": true }, "source-map-support": { @@ -10135,21 +9999,27 @@ "dev": true }, "spdx-correct": { - "version": "1.0.2", - "from": "spdx-correct@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "version": "3.0.0", + "from": "spdx-correct@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", + "dev": true + }, + "spdx-exceptions": { + "version": "2.1.0", + "from": "spdx-exceptions@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", "dev": true }, "spdx-expression-parse": { - "version": "1.0.4", - "from": "spdx-expression-parse@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "version": "3.0.0", + "from": "spdx-expression-parse@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", "dev": true }, "spdx-license-ids": { - "version": "1.2.2", - "from": "spdx-license-ids@>=1.0.2 <2.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "version": "3.0.0", + "from": "spdx-license-ids@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", "dev": true }, "spdy": { @@ -10167,9 +10037,9 @@ } }, "spdy-transport": { - "version": "2.0.20", + "version": "2.1.0", "from": "spdy-transport@>=2.0.18 <3.0.0", - "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.0.20.tgz", + "resolved": "https://registry.npmjs.org/spdy-transport/-/spdy-transport-2.1.0.tgz", "dev": true, "dependencies": { "debug": { @@ -10184,16 +10054,22 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "dev": true }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, "readable-stream": { - "version": "2.3.3", + "version": "2.3.6", "from": "readable-stream@^2.2.9", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "dev": true }, "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "version": "1.1.1", + "from": "string_decoder@~1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true } } @@ -10208,21 +10084,7 @@ "version": "3.1.0", "from": "split-string@>=3.0.2 <4.0.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "dev": true, - "dependencies": { - "extend-shallow": { - "version": "3.0.2", - "from": "extend-shallow@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "dev": true - }, - "is-extendable": { - "version": "1.0.1", - "from": "is-extendable@>=1.0.1 <2.0.0", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "dev": true - } - } + "dev": true }, "sprintf-js": { "version": "1.0.3", @@ -10257,46 +10119,6 @@ "from": "define-property@>=0.2.5 <0.3.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "from": "is-accessor-descriptor@>=0.1.6 <0.2.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "dev": true - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "from": "is-data-descriptor@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "dev": true - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "from": "is-descriptor@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "from": "kind-of@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "dev": true } } }, @@ -10322,16 +10144,22 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "dev": true }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, "readable-stream": { - "version": "2.3.3", + "version": "2.3.6", "from": "readable-stream@^2.0.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "dev": true }, "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "version": "1.1.1", + "from": "string_decoder@~1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true } } @@ -10342,68 +10170,10 @@ "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", "dev": true }, - "stream-combiner2": { - "version": "1.1.1", - "from": "stream-combiner2@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/stream-combiner2/-/stream-combiner2-1.1.1.tgz", - "dev": true, - "dependencies": { - "isarray": { - "version": "1.0.0", - "from": "isarray@~1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "from": "process-nextick-args@~2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "dev": true - }, - "readable-stream": { - "version": "2.3.4", - "from": "readable-stream@^2.0.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", - "dev": true - }, - "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "dev": true - } - } - }, "stream-http": { - "version": "2.8.0", + "version": "2.8.2", "from": "stream-http@>=2.7.2 <3.0.0", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.0.tgz", - "dev": true, - "dependencies": { - "isarray": { - "version": "1.0.0", - "from": "isarray@~1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "dev": true - }, - "readable-stream": { - "version": "2.3.3", - "from": "readable-stream@^2.3.3", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", - "dev": true - }, - "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "dev": true - } - } - }, - "stream-splicer": { - "version": "2.0.0", - "from": "stream-splicer@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/stream-splicer/-/stream-splicer-2.0.0.tgz", + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.2.tgz", "dev": true, "dependencies": { "isarray": { @@ -10419,15 +10189,15 @@ "dev": true }, "readable-stream": { - "version": "2.3.4", - "from": "readable-stream@^2.0.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "version": "2.3.6", + "from": "readable-stream@^2.3.6", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "dev": true }, "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "version": "1.1.1", + "from": "string_decoder@~1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true } } @@ -10457,15 +10227,15 @@ "dev": true }, "readable-stream": { - "version": "2.3.4", + "version": "2.3.6", "from": "readable-stream@^2.3.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "dev": true }, "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "version": "1.1.1", + "from": "string_decoder@~1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true } } @@ -10480,6 +10250,12 @@ "from": "string_decoder@>=0.10.0 <0.11.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz" }, + "string-template": { + "version": "0.2.1", + "from": "string-template@>=0.2.1 <0.3.0", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "dev": true + }, "string-width": { "version": "1.0.2", "from": "string-width@>=1.0.1 <2.0.0", @@ -10518,32 +10294,12 @@ "from": "strip-json-comments@>=2.0.1 <2.1.0", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz" }, - "subarg": { - "version": "1.0.0", - "from": "subarg@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", - "dev": true, - "dependencies": { - "minimist": { - "version": "1.2.0", - "from": "minimist@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "dev": true - } - } - }, "supports-color": { "version": "3.2.3", "from": "supports-color@>=3.2.3 <4.0.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "dev": true }, - "syntax-error": { - "version": "1.4.0", - "from": "syntax-error@>=1.1.1 <2.0.0", - "resolved": "https://registry.npmjs.org/syntax-error/-/syntax-error-1.4.0.tgz", - "dev": true - }, "table": { "version": "4.0.2", "from": "table@>=4.0.1 <5.0.0", @@ -10557,15 +10313,15 @@ "dev": true }, "ansi-styles": { - "version": "3.2.0", - "from": "ansi-styles@^3.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "version": "3.2.1", + "from": "ansi-styles@>=3.2.1 <4.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "dev": true }, "chalk": { - "version": "2.3.1", - "from": "chalk@^2.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.1.tgz", + "version": "2.4.1", + "from": "chalk@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "dev": true }, "has-flag": { @@ -10593,9 +10349,9 @@ "dev": true }, "supports-color": { - "version": "5.2.0", - "from": "supports-color@^5.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.2.0.tgz", + "version": "5.4.0", + "from": "supports-color@>=5.3.0 <6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", "dev": true } } @@ -10696,38 +10452,6 @@ "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "dev": true }, - "through2": { - "version": "2.0.3", - "from": "through2@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "dev": true, - "dependencies": { - "isarray": { - "version": "1.0.0", - "from": "isarray@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.0", - "from": "process-nextick-args@>=2.0.0 <2.1.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "dev": true - }, - "readable-stream": { - "version": "2.3.4", - "from": "readable-stream@^2.1.5", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.4.tgz", - "dev": true - }, - "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", - "dev": true - } - } - }, "thunkify": { "version": "2.1.2", "from": "thunkify@>=2.1.1 <2.2.0", @@ -10758,9 +10482,9 @@ "resolved": "https://registry.npmjs.org/timekeeper/-/timekeeper-2.0.0.tgz" }, "timers-browserify": { - "version": "2.0.6", + "version": "2.0.10", "from": "timers-browserify@>=2.0.4 <3.0.0", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.6.tgz", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.10.tgz", "dev": true }, "timespan": { @@ -10770,74 +10494,16 @@ "dev": true }, "tiny-lr": { - "version": "0.2.1", - "from": "tiny-lr@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-0.2.1.tgz", + "version": "1.1.1", + "from": "tiny-lr@>=1.1.1 <2.0.0", + "resolved": "https://registry.npmjs.org/tiny-lr/-/tiny-lr-1.1.1.tgz", "dev": true, "dependencies": { - "body-parser": { - "version": "1.14.2", - "from": "body-parser@>=1.14.0 <1.15.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.14.2.tgz", - "dev": true, - "dependencies": { - "qs": { - "version": "5.2.0", - "from": "qs@5.2.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-5.2.0.tgz", - "dev": true - } - } - }, - "bytes": { - "version": "2.2.0", - "from": "bytes@2.2.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.2.0.tgz", - "dev": true - }, "debug": { - "version": "2.2.0", - "from": "debug@>=2.2.0 <2.3.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.2.0.tgz", + "version": "3.1.0", + "from": "debug@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "dev": true - }, - "http-errors": { - "version": "1.3.1", - "from": "http-errors@>=1.3.1 <1.4.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.3.1.tgz", - "dev": true - }, - "iconv-lite": { - "version": "0.4.13", - "from": "iconv-lite@0.4.13", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz", - "dev": true - }, - "ms": { - "version": "0.7.1", - "from": "ms@0.7.1", - "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.1.tgz", - "dev": true - }, - "qs": { - "version": "5.1.0", - "from": "qs@>=5.1.0 <5.2.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-5.1.0.tgz", - "dev": true - }, - "raw-body": { - "version": "2.1.7", - "from": "raw-body@>=2.1.5 <2.2.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.1.7.tgz", - "dev": true, - "dependencies": { - "bytes": { - "version": "2.4.0", - "from": "bytes@2.4.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-2.4.0.tgz", - "dev": true - } - } } } }, @@ -10877,64 +10543,24 @@ "dev": true }, "to-regex": { - "version": "3.0.1", - "from": "to-regex@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.1.tgz", - "dev": true, - "dependencies": { - "define-property": { - "version": "0.2.5", - "from": "define-property@>=0.2.5 <0.3.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "from": "is-accessor-descriptor@>=0.1.6 <0.2.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "dev": true - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "from": "is-data-descriptor@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "dev": true - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "from": "is-descriptor@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "dev": true - }, - "kind-of": { - "version": "5.1.0", - "from": "kind-of@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "dev": true - } - } + "version": "3.0.2", + "from": "to-regex@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "dev": true }, "to-regex-range": { "version": "2.1.1", "from": "to-regex-range@>=2.1.0 <3.0.0", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "dev": true + "dev": true, + "dependencies": { + "is-number": { + "version": "3.0.0", + "from": "is-number@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "dev": true + } + } }, "token-stream": { "version": "0.0.1", @@ -10985,7 +10611,7 @@ "translations-sharelatex": { "version": "0.1.4", "from": "git+https://github.com/sharelatex/translations-sharelatex.git#master", - "resolved": "git+https://github.com/sharelatex/translations-sharelatex.git#6c9d95a6072b4d40621dacbf0f35fefc3469b4b1", + "resolved": "git+https://github.com/sharelatex/translations-sharelatex.git#c3dbe7869c594cb91cd366020f8b69e50a9405c9", "dev": true, "dependencies": { "async": { @@ -11122,16 +10748,10 @@ "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", "dev": true }, - "umd": { - "version": "3.0.1", - "from": "umd@>=3.0.0 <4.0.0", - "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.1.tgz", - "dev": true - }, "undefsafe": { - "version": "2.0.1", - "from": "undefsafe@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.1.tgz", + "version": "2.0.2", + "from": "undefsafe@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", "dev": true, "dependencies": { "debug": { @@ -11158,6 +10778,12 @@ "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", "dev": true, "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + }, "set-value": { "version": "0.4.3", "from": "set-value@>=0.4.3 <0.5.0", @@ -11213,6 +10839,12 @@ "from": "isarray@1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "dev": true + }, + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "dev": true } } }, @@ -11222,34 +10854,54 @@ "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", "dev": true }, + "upath": { + "version": "1.0.5", + "from": "upath@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/upath/-/upath-1.0.5.tgz", + "dev": true + }, "update-notifier": { - "version": "2.3.0", + "version": "2.5.0", "from": "update-notifier@>=2.3.0 <3.0.0", - "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/update-notifier/-/update-notifier-2.5.0.tgz", "dev": true, "dependencies": { "ansi-styles": { - "version": "3.2.0", - "from": "ansi-styles@>=3.1.0 <4.0.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.0.tgz", + "version": "3.2.1", + "from": "ansi-styles@>=3.2.1 <4.0.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "dev": true }, "chalk": { - "version": "2.3.0", + "version": "2.4.1", "from": "chalk@>=2.0.1 <3.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.0.tgz", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", "dev": true }, "has-flag": { - "version": "2.0.0", - "from": "has-flag@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "version": "3.0.0", + "from": "has-flag@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "dev": true }, "supports-color": { - "version": "4.5.0", - "from": "supports-color@>=4.0.0 <5.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "version": "5.4.0", + "from": "supports-color@>=5.3.0 <6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "dev": true + } + } + }, + "uri-js": { + "version": "4.2.1", + "from": "uri-js@>=4.2.1 <5.0.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.1.tgz", + "dev": true, + "dependencies": { + "punycode": { + "version": "2.1.0", + "from": "punycode@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", "dev": true } } @@ -11273,15 +10925,15 @@ } }, "url-parse": { - "version": "1.2.0", + "version": "1.4.0", "from": "url-parse@>=1.1.8 <2.0.0", - "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.2.0.tgz", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.0.tgz", "dev": true, "dependencies": { "querystringify": { - "version": "1.0.0", - "from": "querystringify@>=1.0.0 <1.1.0", - "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-1.0.0.tgz", + "version": "2.0.0", + "from": "querystringify@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.0.0.tgz", "dev": true } } @@ -11293,75 +10945,29 @@ "dev": true }, "use": { - "version": "2.0.2", - "from": "use@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/use/-/use-2.0.2.tgz", + "version": "3.1.0", + "from": "use@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", "dev": true, "dependencies": { - "define-property": { - "version": "0.2.5", - "from": "define-property@>=0.2.5 <0.3.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "from": "is-accessor-descriptor@>=0.1.6 <0.2.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "dev": true - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "from": "is-data-descriptor@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "dev": true, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "from": "kind-of@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "dev": true - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "from": "is-descriptor@>=0.1.0 <0.2.0", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "dev": true - }, "kind-of": { - "version": "5.1.0", - "from": "kind-of@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "dev": true - }, - "lazy-cache": { - "version": "2.0.2", - "from": "lazy-cache@>=2.0.2 <3.0.0", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", + "version": "6.0.2", + "from": "kind-of@^6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "dev": true } } }, "useragent": { - "version": "2.3.0", - "from": "useragent@>=2.1.12 <3.0.0", - "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz", + "version": "2.2.1", + "from": "useragent@2.2.1", + "resolved": "https://registry.npmjs.org/useragent/-/useragent-2.2.1.tgz", "dev": true, "dependencies": { "lru-cache": { - "version": "4.1.1", - "from": "lru-cache@>=4.1.0 <4.2.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "version": "2.2.4", + "from": "lru-cache@>=2.2.0 <2.3.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.2.4.tgz", "dev": true } } @@ -11416,9 +11022,9 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz" }, "uws": { - "version": "0.14.5", - "from": "uws@>=0.14.4 <0.15.0", - "resolved": "https://registry.npmjs.org/uws/-/uws-0.14.5.tgz", + "version": "9.14.0", + "from": "uws@>=9.14.0 <9.15.0", + "resolved": "https://registry.npmjs.org/uws/-/uws-9.14.0.tgz", "dev": true, "optional": true }, @@ -11565,9 +11171,9 @@ "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz" }, "validate-npm-package-license": { - "version": "3.0.1", + "version": "3.0.3", "from": "validate-npm-package-license@>=3.0.1 <4.0.0", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", "dev": true }, "validator": { @@ -11645,87 +11251,229 @@ } }, "watchpack": { - "version": "1.4.0", + "version": "1.6.0", "from": "watchpack@>=1.4.0 <2.0.0", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.6.0.tgz", "dev": true, "dependencies": { "anymatch": { - "version": "1.3.2", - "from": "anymatch@>=1.3.0 <2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "version": "2.0.0", + "from": "anymatch@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", "dev": true }, "arr-diff": { - "version": "2.0.0", - "from": "arr-diff@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "version": "4.0.0", + "from": "arr-diff@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", "dev": true }, "array-unique": { - "version": "0.2.1", - "from": "array-unique@>=0.2.1 <0.3.0", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "dev": true - }, - "async": { - "version": "2.6.0", - "from": "async@^2.1.2", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "version": "0.3.2", + "from": "array-unique@>=0.3.2 <0.4.0", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", "dev": true }, "braces": { - "version": "1.8.5", - "from": "braces@>=1.8.2 <2.0.0", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "dev": true + "version": "2.3.2", + "from": "braces@>=2.3.0 <3.0.0", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "dev": true, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + } + } }, "chokidar": { - "version": "1.7.0", - "from": "chokidar@>=1.7.0 <2.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "version": "2.0.3", + "from": "chokidar@>=2.0.2 <3.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", + "dev": true + }, + "debug": { + "version": "2.6.9", + "from": "debug@^2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "dev": true }, "expand-brackets": { - "version": "0.1.5", - "from": "expand-brackets@>=0.1.4 <0.2.0", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "dev": true + "version": "2.1.4", + "from": "expand-brackets@>=2.1.4 <3.0.0", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "dev": true, + "dependencies": { + "define-property": { + "version": "0.2.5", + "from": "define-property@>=0.2.5 <0.3.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "dev": true + }, + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "from": "is-accessor-descriptor@^0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "dev": true, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "dev": true + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "from": "is-data-descriptor@^0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "dev": true, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "dev": true + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "from": "is-descriptor@^0.1.0", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "from": "kind-of@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "dev": true + } + } }, "extglob": { - "version": "0.3.2", - "from": "extglob@>=0.3.1 <0.4.0", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "dev": true + "version": "2.0.4", + "from": "extglob@>=2.0.4 <3.0.0", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "dev": true, + "dependencies": { + "define-property": { + "version": "1.0.0", + "from": "define-property@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "dev": true + }, + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + } + } + }, + "fill-range": { + "version": "4.0.0", + "from": "fill-range@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "dev": true, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + } + } }, "glob-parent": { - "version": "2.0.0", - "from": "glob-parent@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "version": "3.1.0", + "from": "glob-parent@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "dev": true, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "from": "is-glob@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "dev": true + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "from": "is-accessor-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "dev": true + }, + "is-data-descriptor": { + "version": "1.0.0", + "from": "is-data-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "dev": true + }, + "is-descriptor": { + "version": "1.0.2", + "from": "is-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "dev": true }, "is-extglob": { - "version": "1.0.0", - "from": "is-extglob@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "version": "2.1.1", + "from": "is-extglob@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "dev": true }, "is-glob": { - "version": "2.0.1", - "from": "is-glob@>=2.0.0 <3.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "version": "4.0.0", + "from": "is-glob@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", "dev": true }, + "is-number": { + "version": "3.0.0", + "from": "is-number@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "dev": true, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "dev": true + } + } + }, "isarray": { "version": "1.0.0", "from": "isarray@~1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "dev": true }, + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "from": "kind-of@^6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", + "dev": true + }, "micromatch": { - "version": "2.3.11", - "from": "micromatch@>=2.1.5 <3.0.0", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "version": "3.1.10", + "from": "micromatch@>=3.1.4 <4.0.0", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", "dev": true }, "minimatch": { @@ -11734,10 +11482,16 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "dev": true }, + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "dev": true + }, "readable-stream": { - "version": "2.3.3", + "version": "2.3.6", "from": "readable-stream@^2.0.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", "dev": true }, "readdirp": { @@ -11747,29 +11501,41 @@ "dev": true }, "string_decoder": { - "version": "1.0.3", - "from": "string_decoder@~1.0.3", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "version": "1.1.1", + "from": "string_decoder@~1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true } } }, "wbuf": { - "version": "1.7.2", + "version": "1.7.3", "from": "wbuf@>=1.7.2 <2.0.0", - "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.2.tgz", + "resolved": "https://registry.npmjs.org/wbuf/-/wbuf-1.7.3.tgz", "dev": true }, "webpack": { - "version": "3.10.0", - "from": "webpack@latest", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.10.0.tgz", + "version": "3.12.0", + "from": "webpack@>=3.10.0 <4.0.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.12.0.tgz", "dev": true, "dependencies": { "acorn": { - "version": "5.3.0", - "from": "acorn@>=5.0.0 <6.0.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", + "version": "5.5.3", + "from": "acorn@^5.0.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", + "dev": true + }, + "ajv": { + "version": "6.5.0", + "from": "ajv@>=6.1.0 <7.0.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.5.0.tgz", + "dev": true + }, + "ajv-keywords": { + "version": "3.2.0", + "from": "ajv-keywords@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.2.0.tgz", "dev": true }, "ansi-regex": { @@ -11804,18 +11570,18 @@ } } }, + "fast-deep-equal": { + "version": "2.0.1", + "from": "fast-deep-equal@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "dev": true + }, "has-flag": { "version": "2.0.0", "from": "has-flag@>=2.0.0 <3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", "dev": true }, - "json5": { - "version": "0.5.1", - "from": "json5@^0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "dev": true - }, "source-map": { "version": "0.5.7", "from": "source-map@^0.5.3", @@ -11871,15 +11637,27 @@ } }, "webpack-dev-server": { - "version": "2.11.1", - "from": "webpack-dev-server@latest", - "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.11.1.tgz", + "version": "2.11.2", + "from": "webpack-dev-server@>=2.11.1 <3.0.0", + "resolved": "https://registry.npmjs.org/webpack-dev-server/-/webpack-dev-server-2.11.2.tgz", "dev": true, "dependencies": { "accepts": { - "version": "1.3.4", - "from": "accepts@~1.3.4", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.4.tgz", + "version": "1.3.5", + "from": "accepts@~1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "dev": true + }, + "anymatch": { + "version": "2.0.0", + "from": "anymatch@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "from": "arr-diff@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", "dev": true }, "array-flatten": { @@ -11888,12 +11666,38 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "dev": true }, + "array-unique": { + "version": "0.3.2", + "from": "array-unique@>=0.3.2 <0.4.0", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "dev": true + }, + "braces": { + "version": "2.3.2", + "from": "braces@>=2.3.0 <3.0.0", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "dev": true, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + } + } + }, "camelcase": { "version": "3.0.0", "from": "camelcase@>=3.0.0 <4.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", "dev": true }, + "chokidar": { + "version": "2.0.3", + "from": "chokidar@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.0.3.tgz", + "dev": true + }, "cliui": { "version": "3.2.0", "from": "cliui@^3.2.0", @@ -11918,6 +11722,12 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", "dev": true }, + "del": { + "version": "3.0.0", + "from": "del@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", + "dev": true + }, "destroy": { "version": "1.0.4", "from": "destroy@>=1.0.4 <1.1.0", @@ -11936,10 +11746,76 @@ "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "dev": true }, + "expand-brackets": { + "version": "2.1.4", + "from": "expand-brackets@>=2.1.4 <3.0.0", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "dev": true, + "dependencies": { + "debug": { + "version": "2.6.9", + "from": "debug@^2.3.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "dev": true + }, + "define-property": { + "version": "0.2.5", + "from": "define-property@>=0.2.5 <0.3.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "dev": true + }, + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "from": "is-accessor-descriptor@^0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "dev": true, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "dev": true + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "from": "is-data-descriptor@^0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "dev": true, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "dev": true + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "from": "is-descriptor@^0.1.0", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "dev": true + }, + "kind-of": { + "version": "5.1.0", + "from": "kind-of@>=5.0.0 <6.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "dev": true + } + } + }, "express": { - "version": "4.16.2", + "version": "4.16.3", "from": "express@>=4.16.2 <5.0.0", - "resolved": "https://registry.npmjs.org/express/-/express-4.16.2.tgz", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", "dev": true, "dependencies": { "debug": { @@ -11950,10 +11826,44 @@ } } }, + "extglob": { + "version": "2.0.4", + "from": "extglob@>=2.0.4 <3.0.0", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "dev": true, + "dependencies": { + "define-property": { + "version": "1.0.0", + "from": "define-property@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "dev": true + }, + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + } + } + }, + "fill-range": { + "version": "4.0.0", + "from": "fill-range@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "dev": true, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "from": "extend-shallow@>=2.0.1 <3.0.0", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "dev": true + } + } + }, "finalhandler": { - "version": "1.1.0", - "from": "finalhandler@1.1.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.0.tgz", + "version": "1.1.1", + "from": "finalhandler@1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", "dev": true, "dependencies": { "debug": { @@ -11976,23 +11886,127 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", "dev": true }, + "glob": { + "version": "7.1.2", + "from": "glob@^7.0.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "dev": true + }, + "glob-parent": { + "version": "3.1.0", + "from": "glob-parent@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "dev": true, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "from": "is-glob@>=3.1.0 <4.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "dev": true + } + } + }, + "globby": { + "version": "6.1.0", + "from": "globby@>=6.1.0 <7.0.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "dev": true, + "dependencies": { + "pify": { + "version": "2.3.0", + "from": "pify@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "dev": true + } + } + }, "has-flag": { - "version": "2.0.0", - "from": "has-flag@^2.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", + "version": "3.0.0", + "from": "has-flag@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "dev": true }, "ipaddr.js": { - "version": "1.5.2", - "from": "ipaddr.js@1.5.2", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.5.2.tgz", + "version": "1.6.0", + "from": "ipaddr.js@1.6.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", + "dev": true + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "from": "is-accessor-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "dev": true + }, + "is-data-descriptor": { + "version": "1.0.0", + "from": "is-data-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "dev": true + }, + "is-descriptor": { + "version": "1.0.2", + "from": "is-descriptor@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "dev": true + }, + "is-extglob": { + "version": "2.1.1", + "from": "is-extglob@>=2.1.0 <3.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "dev": true + }, + "is-glob": { + "version": "4.0.0", + "from": "is-glob@>=4.0.0 <5.0.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", + "dev": true + }, + "is-number": { + "version": "3.0.0", + "from": "is-number@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "dev": true, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "from": "kind-of@>=3.0.2 <4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "dev": true + } + } + }, + "isarray": { + "version": "1.0.0", + "from": "isarray@~1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "from": "isobject@>=3.0.1 <4.0.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "from": "kind-of@^6.0.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", "dev": true }, "load-json-file": { "version": "1.1.0", "from": "load-json-file@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "dev": true + "dev": true, + "dependencies": { + "pify": { + "version": "2.3.0", + "from": "pify@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "dev": true + } + } }, "merge-descriptors": { "version": "1.0.1", @@ -12000,12 +12014,36 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", "dev": true }, + "micromatch": { + "version": "3.1.10", + "from": "micromatch@>=3.1.4 <4.0.0", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "dev": true + }, "mime": { "version": "1.4.1", "from": "mime@1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", "dev": true }, + "mime-db": { + "version": "1.33.0", + "from": "mime-db@~1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "dev": true + }, + "mime-types": { + "version": "2.1.18", + "from": "mime-types@~2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "from": "minimatch@^3.0.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "dev": true + }, "negotiator": { "version": "0.6.1", "from": "negotiator@0.6.1", @@ -12034,18 +12072,26 @@ "version": "1.1.0", "from": "path-type@>=1.0.0 <2.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "dev": true + "dev": true, + "dependencies": { + "pify": { + "version": "2.3.0", + "from": "pify@>=2.0.0 <3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "dev": true + } + } }, - "pify": { - "version": "2.3.0", - "from": "pify@^2.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "process-nextick-args": { + "version": "2.0.0", + "from": "process-nextick-args@~2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "dev": true }, "proxy-addr": { - "version": "2.0.2", - "from": "proxy-addr@>=2.0.2 <2.1.0", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.2.tgz", + "version": "2.0.3", + "from": "proxy-addr@>=2.0.3 <2.1.0", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", "dev": true }, "range-parser": { @@ -12066,10 +12112,28 @@ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", "dev": true }, + "readable-stream": { + "version": "2.3.6", + "from": "readable-stream@^2.0.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "dev": true + }, + "readdirp": { + "version": "2.1.0", + "from": "readdirp@^2.0.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "dev": true + }, + "rimraf": { + "version": "2.6.2", + "from": "rimraf@^2.2.8", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "dev": true + }, "send": { - "version": "0.16.1", - "from": "send@0.16.1", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", + "version": "0.16.2", + "from": "send@0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", "dev": true, "dependencies": { "debug": { @@ -12081,9 +12145,9 @@ } }, "serve-static": { - "version": "1.13.1", - "from": "serve-static@1.13.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", + "version": "1.13.2", + "from": "serve-static@1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", "dev": true }, "setprototypeof": { @@ -12092,10 +12156,10 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "dev": true }, - "statuses": { - "version": "1.3.1", - "from": "statuses@>=1.3.1 <1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.3.1.tgz", + "string_decoder": { + "version": "1.1.1", + "from": "string_decoder@~1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "dev": true }, "strip-bom": { @@ -12105,9 +12169,15 @@ "dev": true }, "supports-color": { - "version": "5.1.0", + "version": "5.4.0", "from": "supports-color@>=5.1.0 <6.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.1.0.tgz", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "dev": true + }, + "type-is": { + "version": "1.6.16", + "from": "type-is@>=1.6.16 <1.7.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", "dev": true }, "utils-merge": { @@ -12143,10 +12213,18 @@ } }, "webpack-merge": { - "version": "4.1.1", - "from": "webpack-merge@latest", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.1.tgz", - "dev": true + "version": "4.1.2", + "from": "webpack-merge@>=4.1.1 <5.0.0", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.1.2.tgz", + "dev": true, + "dependencies": { + "lodash": { + "version": "4.17.10", + "from": "lodash@>=4.17.5 <5.0.0", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.10.tgz", + "dev": true + } + } }, "webpack-sources": { "version": "1.1.0", @@ -12256,6 +12334,13 @@ "from": "with@>=3.0.0 <3.1.0", "resolved": "https://registry.npmjs.org/with/-/with-3.0.1.tgz" }, + "with-callback": { + "version": "1.0.2", + "from": "with-callback@>=1.0.2 <2.0.0", + "resolved": "https://registry.npmjs.org/with-callback/-/with-callback-1.0.2.tgz", + "dev": true, + "optional": true + }, "wkx": { "version": "0.2.0", "from": "wkx@0.2.0", diff --git a/services/web/package.json b/services/web/package.json index b6a4d80d35..ab9613e525 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -15,6 +15,7 @@ "test:acceptance:dir": "npm -q run test:acceptance:wait_for_app && npm -q run test:acceptance:run -- $@", "test:acceptance": "npm -q run test:acceptance:dir -- $@ test/acceptance/js", "test:unit": "npm -q run compile && bin/unit_test $@", + "test:unit:app": "npm -q run compile && bin/unit_test_app $@", "test:frontend": "karma start", "compile": "make compile", "start": "npm -q run compile && node $NODE_APP_OPTIONS app.js", @@ -27,6 +28,7 @@ "dependencies": { "archiver": "0.9.0", "async": "0.6.2", + "backbone": "^1.3.3", "base64-stream": "^0.1.2", "basic-auth-connect": "^1.0.0", "bcrypt": "1.0.1", @@ -37,16 +39,20 @@ "cookie": "^0.2.3", "cookie-parser": "1.3.5", "csurf": "^1.8.3", + "d3": "^3.5.16", "dateformat": "1.0.4-1.2.3", + "daterangepicker": "^2.1.27", "express": "4.13.0", "express-http-proxy": "^1.1.0", "express-session": "^1.14.2", "fs-extra": "^4.0.2", "fuse.js": "^3.0.0", + "handlebars": "^4.0.11", "heapdump": "^0.3.7", "helmet": "^3.8.1", "http-proxy": "^1.8.1", "jade": "~1.3.1", + "jquery": "^1.11.1", "jsonwebtoken": "^8.0.1", "ldapjs": "^0.7.1", "lodash": "^4.13.1", @@ -65,6 +71,7 @@ "nodemailer-mandrill-transport": "^1.2.0", "nodemailer-sendgrid-transport": "^0.2.0", "nodemailer-ses-transport": "^1.3.0", + "nvd3": "^1.8.6", "optimist": "0.6.1", "passport": "^0.3.2", "passport-ldapauth": "^0.6.0", @@ -134,6 +141,7 @@ "grunt-postcss": "^0.8.0", "grunt-sed": "^0.1.1", "grunt-shell": "^2.1.0", + "handlebars-loader": "^1.7.0", "karma": "^2.0.0", "karma-chai-sinon": "^0.1.5", "karma-chrome-launcher": "^2.2.0", diff --git a/services/web/public/coffee/filters/formatDate.coffee b/services/web/public/coffee/filters/formatDate.coffee index 5b8c9e10c6..28c4ad23ba 100644 --- a/services/web/public/coffee/filters/formatDate.coffee +++ b/services/web/public/coffee/filters/formatDate.coffee @@ -17,3 +17,7 @@ define [ App.filter "relativeDate", () -> (date) -> moment(date).calendar() + + App.filter "fromNowDate", () -> + (date) -> + moment(date).fromNow() diff --git a/services/web/public/coffee/ide.coffee b/services/web/public/coffee/ide.coffee index 078202655f..7c8602eb76 100644 --- a/services/web/public/coffee/ide.coffee +++ b/services/web/public/coffee/ide.coffee @@ -80,6 +80,8 @@ define [ miniReviewPanelVisible: false chatResizerSizeOpen: window.uiConfig.chatResizerSizeOpen chatResizerSizeClosed: window.uiConfig.chatResizerSizeClosed + defaultFontFamily: window.uiConfig.defaultFontFamily + defaultLineHeight: window.uiConfig.defaultLineHeight } $scope.user = window.user diff --git a/services/web/public/coffee/ide/editor/EditorManager.coffee b/services/web/public/coffee/ide/editor/EditorManager.coffee index 72bbe8509a..e3cabf8e98 100644 --- a/services/web/public/coffee/ide/editor/EditorManager.coffee +++ b/services/web/public/coffee/ide/editor/EditorManager.coffee @@ -1,5 +1,6 @@ define [ "ide/editor/Document" + "ide/editor/components/spellMenu" "ide/editor/directives/aceEditor" "ide/editor/directives/toggleSwitch" "ide/editor/controllers/SavingNotificationController" diff --git a/services/web/public/coffee/ide/editor/components/spellMenu.coffee b/services/web/public/coffee/ide/editor/components/spellMenu.coffee new file mode 100644 index 0000000000..ff3462e03e --- /dev/null +++ b/services/web/public/coffee/ide/editor/components/spellMenu.coffee @@ -0,0 +1,34 @@ +define ["base"], (App) -> + App.component "spellMenu", { + bindings: { + open: "<" + top: "<" + left: "<" + highlight: "<" + replaceWord: "&" + learnWord: "&" + } + template: """ + + """ + } \ No newline at end of file diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee index fd6a8224e4..617b41b845 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor.coffee @@ -3,9 +3,11 @@ define [ "ace/ace" "ace/ext-searchbox" "ace/ext-modelist" + "ace/keybinding-vim" "ide/editor/directives/aceEditor/undo/UndoManager" "ide/editor/directives/aceEditor/auto-complete/AutoCompleteManager" "ide/editor/directives/aceEditor/spell-check/SpellCheckManager" + "ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter" "ide/editor/directives/aceEditor/highlights/HighlightsManager" "ide/editor/directives/aceEditor/cursor-position/CursorPositionManager" "ide/editor/directives/aceEditor/track-changes/TrackChangesManager" @@ -14,9 +16,10 @@ define [ "ide/graphics/services/graphics" "ide/preamble/services/preamble" "ide/files/services/files" -], (App, Ace, SearchBox, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager) -> +], (App, Ace, SearchBox, Vim, ModeList, UndoManager, AutoCompleteManager, SpellCheckManager, SpellCheckAdapter, HighlightsManager, CursorPositionManager, TrackChangesManager, MetadataManager) -> EditSession = ace.require('ace/edit_session').EditSession ModeList = ace.require('ace/ext/modelist') + Vim = ace.require('ace/keyboard/vim').Vim # set the path for ace workers if using a CDN (from editor.pug) if window.aceWorkerPath != "" @@ -60,6 +63,7 @@ define [ onCtrlJ: "=" # Toggle the review panel onCtrlShiftC: "=" # Add a new comment onCtrlShiftA: "=" # Toggle track-changes on/off + onSave: "=" # Cmd/Ctrl-S or :w in Vim syntaxValidation: "=" reviewPanel: "=" eventsBridge: "=" @@ -67,6 +71,8 @@ define [ trackChangesEnabled: "=" docId: "=" rendererData: "=" + lineHeight: "=" + fontFamily: "=" } link: (scope, element, attrs) -> # Don't freak out if we're already in an apply callback @@ -98,7 +104,8 @@ define [ if scope.spellCheck # only enable spellcheck when explicitly required spellCheckCache = $cacheFactory.get("spellCheck-#{scope.name}") || $cacheFactory("spellCheck-#{scope.name}", {capacity: 1000}) - spellCheckManager = new SpellCheckManager(scope, editor, element, spellCheckCache, $http, $q) + spellCheckManager = new SpellCheckManager(scope, spellCheckCache, $http, $q, new SpellCheckAdapter(editor)) + undoManager = new UndoManager(scope, editor, element) highlightsManager = new HighlightsManager(scope, editor, element) cursorPositionManager = new CursorPositionManager(scope, editor, element, localStorage) @@ -106,16 +113,26 @@ define [ metadataManager = new MetadataManager(scope, editor, element, metadata) autoCompleteManager = new AutoCompleteManager(scope, editor, element, metadataManager, graphics, preamble, files) - # Prevert Ctrl|Cmd-S from triggering save dialog - editor.commands.addCommand - name: "save", - bindKey: win: "Ctrl-S", mac: "Command-S" - exec: () -> - readOnly: true + scope.$watch "onSave", (callback) -> + if callback? + Vim.defineEx 'write', 'w', callback + editor.commands.addCommand + name: "save", + bindKey: win: "Ctrl-S", mac: "Command-S" + exec: callback + readOnly: true + # Not technically 'save', but Ctrl-. recompiles in OL v1 + # so maintain compatibility + editor.commands.addCommand + name: "recompile_v1", + bindKey: win: "Ctrl-.", mac: "Ctrl-." + exec: callback + readOnly: true editor.commands.removeCommand "transposeletters" editor.commands.removeCommand "showSettingsMenu" editor.commands.removeCommand "foldall" + # For European keyboards, the / is above 7 so needs Shift pressing. # This comes through as Command-Shift-/ on OS X, which is mapped to # toggleBlockComment. @@ -266,6 +283,29 @@ define [ "font-size": value + "px" }) + scope.$watch "fontFamily", (value) -> + if value? + switch value + when 'monaco' + editor.setOption('fontFamily', '"Monaco", "Menlo", "Ubuntu Mono", "Consolas", "source-code-pro", monospace') + when 'lucida' + editor.setOption('fontFamily', '"Lucida Console", monospace') + else + editor.setOption('fontFamily', null) + + scope.$watch "lineHeight", (value) -> + if value? + switch value + when 'compact' + editor.container.style.lineHeight = 1.33 + when 'normal' + editor.container.style.lineHeight = 1.6 + when 'wide' + editor.container.style.lineHeight = 2 + else + editor.container.style.lineHeight = 1.6 + editor.renderer.updateFontSize() + scope.$watch "sharejsDoc", (sharejs_doc, old_sharejs_doc) -> if old_sharejs_doc? detachFromAce(old_sharejs_doc) @@ -323,6 +363,23 @@ define [ session.setScrollTop(session.getScrollTop() + 1) session.setScrollTop(session.getScrollTop() - 1) + onSessionChangeForSpellCheck = (e) -> + spellCheckManager.onSessionChange() + e.oldSession?.getDocument().off "change", spellCheckManager.onChange + e.session.getDocument().on "change", spellCheckManager.onChange + e.oldSession?.off "changeScrollTop", spellCheckManager.onScroll + e.session.on "changeScrollTop", spellCheckManager.onScroll + + initSpellCheck = () -> + spellCheckManager.init() + editor.on 'changeSession', onSessionChangeForSpellCheck + onSessionChangeForSpellCheck({ session: editor.getSession() }) # Force initial setup + editor.on 'nativecontextmenu', spellCheckManager.onContextMenu + + tearDownSpellCheck = () -> + editor.off 'changeSession', onSessionChangeForSpellCheck + editor.off 'nativecontextmenu', spellCheckManager.onContextMenu + attachToAce = (sharejs_doc) -> lines = sharejs_doc.getSnapshot().split("\n") session = editor.getSession() @@ -368,6 +425,7 @@ define [ editor.initing = false # now ready to edit document editor.setReadOnly(scope.readOnly) # respect the readOnly setting, normally false + initSpellCheck() resetScrollMargins() @@ -429,6 +487,7 @@ define [ scope.$on '$destroy', () -> if scope.sharejsDoc? + tearDownSpellCheck() detachFromAce(scope.sharejsDoc) session = editor.getSession() session?.destroy() @@ -450,22 +509,14 @@ define [ >Dismiss
- +
- @row = options.row - @column = options.column + constructor: (@markerId, @range, options) -> @word = options.word @suggestions = options.suggestions class HighlightedWordManager constructor: (@editor) -> @reset() - - reset: () -> - @highlights = rows: [] - addHighlight: (highlight) -> - unless highlight instanceof Highlight - highlight = new Highlight(highlight) - range = new Range( - highlight.row, highlight.column, - highlight.row, highlight.column + highlight.word.length - ) - highlight.markerId = @editor.getSession().addMarker range, "spelling-highlight", 'text', false - @highlights.rows[highlight.row] ||= [] - @highlights.rows[highlight.row].push highlight + reset: () -> + @highlights?.forEach (highlight) => + @editor.getSession().removeMarker(highlight.markerId) + @highlights = [] + + addHighlight: (options) -> + session = @editor.getSession() + doc = session.getDocument() + # Set up Range that will automatically update it's positions when the + # document changes + range = new Range() + range.start = doc.createAnchor({ + row: options.row, + column: options.column + }) + range.end = doc.createAnchor({ + row: options.row, + column: options.column + options.word.length + }) + # Prevent range from adding newly typed characters to the end of the word. + # This makes it appear as if the spelling error continues to the next word + # even after a space + range.end.$insertRight = true + + markerId = session.addMarker range, "spelling-highlight", 'text', false + + @highlights.push new Highlight(markerId, range, options) removeHighlight: (highlight) -> @editor.getSession().removeMarker(highlight.markerId) - for h, i in @highlights.rows[highlight.row] - if h == highlight - @highlights.rows[highlight.row].splice(i, 1) + @highlights = @highlights.filter (hl) -> + hl != highlight removeWord: (word) -> - toRemove = [] - for row in @highlights.rows - for highlight in (row || []) - if highlight.word == word - toRemove.push(highlight) - for highlight in toRemove - @removeHighlight highlight + @highlights.filter (highlight) -> + highlight.word == word + .forEach (highlight) => + @removeHighlight(highlight) - moveHighlight: (highlight, position) -> - @removeHighlight highlight - highlight.row = position.row - highlight.column = position.column - @addHighlight highlight - - clearRows: (from, to) -> - from ||= 0 - to ||= @highlights.rows.length - 1 - for row in @highlights.rows.slice(from, to + 1) - for highlight in (row || []).slice(0) - @removeHighlight highlight - - insertRows: (offset, number) -> - # rows are inserted after offset. i.e. offset row is not modified - affectedHighlights = [] - for row in @highlights.rows.slice(offset) - affectedHighlights.push(highlight) for highlight in (row || []) - for highlight in affectedHighlights - @moveHighlight highlight, - row: highlight.row + number - column: highlight.column - - removeRows: (offset, number) -> - # offset is the first row to delete - affectedHighlights = [] - for row in @highlights.rows.slice(offset) - affectedHighlights.push(highlight) for highlight in (row || []) - for highlight in affectedHighlights - if highlight.row >= offset + number - @moveHighlight highlight, - row: highlight.row - number - column: highlight.column - else - @removeHighlight highlight + clearRow: (row) -> + @highlights.filter (highlight) -> + highlight.range.start.row == row + .forEach (highlight) => + @removeHighlight(highlight) findHighlightWithinRange: (range) -> - rows = @highlights.rows.slice(range.start.row, range.end.row + 1) - for row in rows - for highlight in (row || []) - if @_doesHighlightOverlapRange(highlight, range.start, range.end) - return highlight - return null - - applyChange: (change) -> - start = change.start - end = change.end - if change.action == "insert" - if start.row != end.row - rowsAdded = end.row - start.row - @insertRows start.row + 1, rowsAdded - # make a copy since we're going to modify in place - oldHighlights = (@highlights.rows[start.row] || []).slice(0) - for highlight in oldHighlights - if highlight.column > start.column - # insertion was fully before this highlight - @moveHighlight highlight, - row: end.row - column: highlight.column + (end.column - start.column) - else if highlight.column + highlight.word.length >= start.column - # insertion was inside this highlight - @removeHighlight highlight - - else if change.action == "remove" - if start.row == end.row - oldHighlights = (@highlights.rows[start.row] || []).slice(0) - else - rowsRemoved = end.row - start.row - oldHighlights = - (@highlights.rows[start.row] || []).concat( - (@highlights.rows[end.row] || []) - ) - @removeRows start.row + 1, rowsRemoved - - for highlight in oldHighlights - if @_doesHighlightOverlapRange highlight, start, end - @removeHighlight highlight - else if @_isHighlightAfterRange highlight, start, end - @moveHighlight highlight, - row: start.row - column: highlight.column - (end.column - start.column) + _.find @highlights, (highlight) => + @_doesHighlightOverlapRange highlight, range.start, range.end _doesHighlightOverlapRange: (highlight, start, end) -> + highlightRow = highlight.range.start.row + highlightStartColumn = highlight.range.start.column + highlightEndColumn = highlight.range.end.column + highlightIsAllBeforeRange = - highlight.row < start.row or - (highlight.row == start.row and highlight.column + highlight.word.length <= start.column) + highlightRow < start.row or + (highlightRow == start.row and highlightEndColumn <= start.column) highlightIsAllAfterRange = - highlight.row > end.row or - (highlight.row == end.row and highlight.column >= end.column) + highlightRow > end.row or + (highlightRow == end.row and highlightStartColumn >= end.column) !(highlightIsAllBeforeRange or highlightIsAllAfterRange) - _isHighlightAfterRange: (highlight, start, end) -> - return true if highlight.row > end.row - return false if highlight.row < end.row - highlight.column >= end.column - + clearHighlightTouchingRange: (range) -> + highlight = _.find @highlights, (hl) => + @_doesHighlightTouchRange hl, range.start, range.end + if highlight + @removeHighlight highlight + _doesHighlightTouchRange: (highlight, start, end) -> + highlightRow = highlight.range.start.row + highlightStartColumn = highlight.range.start.column + highlightEndColumn = highlight.range.end.column - - - + rangeStartIsWithinHighlight = + highlightStartColumn <= start.column and + highlightEndColumn >= start.column + rangeEndIsWithinHighlight = + highlightStartColumn <= end.column and + highlightEndColumn >= end.column + highlightRow == start.row and + (rangeStartIsWithinHighlight or rangeEndIsWithinHighlight) diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee new file mode 100644 index 0000000000..2afc2cf0ff --- /dev/null +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckAdapter.coffee @@ -0,0 +1,56 @@ +define [ + "ace/ace" + "ide/editor/directives/aceEditor/spell-check/HighlightedWordManager" +], (Ace, HighlightedWordManager) -> + Range = ace.require('ace/range').Range + + class SpellCheckAdapter + constructor: (@editor) -> + @highlightedWordManager = new HighlightedWordManager(@editor) + + getLines: () -> + @editor.getValue().split('\n') + + normalizeChangeEvent: (e) -> e + + getCoordsFromContextMenuEvent: (e) -> + e.domEvent.stopPropagation() + return { + x: e.domEvent.clientX, + y: e.domEvent.clientY + } + + preventContextMenuEventDefault: (e) -> + e.domEvent.preventDefault() + + getHighlightFromCoords: (coords) -> + position = @editor.renderer.screenToTextCoordinates(coords.x, coords.y) + @highlightedWordManager.findHighlightWithinRange({ + start: position + end: position + }) + + selectHighlightedWord: (highlight) -> + row = highlight.range.start.row + startColumn = highlight.range.start.column + endColumn = highlight.range.end.column + + @editor.getSession().getSelection().setSelectionRange( + new Range( + row, startColumn, + row, endColumn + ) + ) + + replaceWord: (highlight, newWord) => + row = highlight.range.start.row + startColumn = highlight.range.start.column + endColumn = highlight.range.end.column + + @editor.getSession().replace(new Range( + row, startColumn, + row, endColumn + ), newWord) + + # Bring editor back into focus after clicking on suggestion + @editor.focus() diff --git a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee index acbd636531..cbe4fdbd64 100644 --- a/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee +++ b/services/web/public/coffee/ide/editor/directives/aceEditor/spell-check/SpellCheckManager.coffee @@ -1,129 +1,88 @@ -define [ - "ide/editor/directives/aceEditor/spell-check/HighlightedWordManager" - "ace/ace" -], (HighlightedWordManager) -> - Range = ace.require("ace/range").Range - +define [], () -> class SpellCheckManager - constructor: (@$scope, @editor, @element, @cache, @$http, @$q) -> - $(document.body).append @element.find(".spell-check-menu") + constructor: (@$scope, @cache, @$http, @$q, @adapter) -> + @$scope.spellMenu = { + open: false + top: '0px' + left: '0px' + suggestions: [] + } @inProgressRequest = null @updatedLines = [] - @highlightedWordManager = new HighlightedWordManager(@editor) - @$scope.$watch "spellCheckLanguage", (language, oldLanguage) => + @$scope.$watch 'spellCheckLanguage', (language, oldLanguage) => if language != oldLanguage and oldLanguage? @runFullCheck() - onChange = (e) => - @runCheckOnChange(e) - - onScroll = () => - @closeContextMenu() + @$scope.replaceWord = @adapter.replaceWord + @$scope.learnWord = @learnWord - @editor.on "changeSession", (e) => - @highlightedWordManager.reset() - if @inProgressRequest? - @inProgressRequest.abort() - - if @$scope.spellCheckEnabled and @$scope.spellCheckLanguage and @$scope.spellCheckLanguage != "" - @runSpellCheckSoon(200) - - e.oldSession?.getDocument().off "change", onChange - e.session.getDocument().on "change", onChange - - e.oldSession?.off "changeScrollTop", onScroll - e.session.on "changeScrollTop", onScroll - - @$scope.spellingMenu = {left: '0px', top: '0px'} - - @editor.on "nativecontextmenu", (e) => - e.domEvent.stopPropagation(); - @closeContextMenu(e.domEvent) - @openContextMenu(e.domEvent) - - $(document).on "click", (e) => - if e.which != 3 # Ignore if this was a right click - @closeContextMenu(e) + $(document).on 'click', (e) => + @closeContextMenu() if e.which != 3 # Ignore if right click return true - @$scope.replaceWord = (highlight, suggestion) => - @replaceWord(highlight, suggestion) + init: () -> + @updatedLines = Array(@adapter.getLines().length).fill(true) + @runSpellCheckSoon(200) if @isSpellCheckEnabled() - @$scope.learnWord = (highlight) => - @learnWord(highlight) + isSpellCheckEnabled: () -> + return !!( + @$scope.spellCheck and + @$scope.spellCheckLanguage and + @$scope.spellCheckLanguage != '' + ) - runFullCheck: () -> - @highlightedWordManager.clearRows() - if @$scope.spellCheckLanguage and @$scope.spellCheckLanguage != "" - @runSpellCheck() + onChange: (e) => + if @isSpellCheckEnabled() + @markLinesAsUpdated(@adapter.normalizeChangeEvent(e)) + + @adapter.highlightedWordManager.clearHighlightTouchingRange(e) - runCheckOnChange: (e) -> - if @$scope.spellCheckLanguage and @$scope.spellCheckLanguage != "" - @highlightedWordManager.applyChange(e) - @markLinesAsUpdated(e) @runSpellCheckSoon() + onSessionChange: () => + @adapter.highlightedWordManager.reset() + @inProgressRequest.abort() if @inProgressRequest? + + @runSpellCheckSoon(200) if @isSpellCheckEnabled() + + onContextMenu: (e) => + @closeContextMenu() + @openContextMenu(e) + + onScroll: () => @closeContextMenu() + openContextMenu: (e) -> - position = @editor.renderer.screenToTextCoordinates(e.clientX, e.clientY) - highlight = @highlightedWordManager.findHighlightWithinRange - start: position - end: position - - @$scope.$apply () => - @$scope.spellingMenu.highlight = highlight - + coords = @adapter.getCoordsFromContextMenuEvent(e) + highlight = @adapter.getHighlightFromCoords(coords) if highlight - e.stopPropagation() - e.preventDefault() - - @editor.getSession().getSelection().setSelectionRange( - new Range( - highlight.row, highlight.column - highlight.row, highlight.column + highlight.word.length - ) - ) - + @adapter.preventContextMenuEventDefault(e) + @adapter.selectHighlightedWord(highlight) @$scope.$apply () => - @$scope.spellingMenu.open = true - @$scope.spellingMenu.left = e.clientX + 'px' - @$scope.spellingMenu.top = e.clientY + 'px' + @$scope.spellMenu = { + open: true + top: coords.y + 'px' + left: coords.x + 'px' + highlight: highlight + } return false - closeContextMenu: (e) -> - # this is triggered on scroll, so for performance only apply - # setting when it changes - if @$scope?.spellingMenu?.open != false + closeContextMenu: () -> + # This is triggered on scroll, so for performance only apply setting when + # it changes + if @$scope?.spellMenu and @$scope.spellMenu.open != false @$scope.$apply () => - @$scope.spellingMenu.open = false + @$scope.spellMenu.open = false - replaceWord: (highlight, text) -> - @editor.getSession().replace(new Range( - highlight.row, highlight.column, - highlight.row, highlight.column + highlight.word.length - ), text) - - learnWord: (highlight) -> + learnWord: (highlight) => @apiRequest "/learn", word: highlight.word - @highlightedWordManager.removeWord highlight.word + @adapter.highlightedWordManager.removeWord highlight.word language = @$scope.spellCheckLanguage @cache?.put("#{language}:#{highlight.word}", true) - getHighlightedWordAtCursor: () -> - cursor = @editor.getCursorPosition() - highlight = @highlightedWordManager.findHighlightWithinRange - start: cursor - end: cursor - return highlight - - runSpellCheckSoon: (delay = 1000) -> - run = () => - delete @timeoutId - @runSpellCheck(@updatedLines) - @updatedLines = [] - if @timeoutId? - clearTimeout @timeoutId - @timeoutId = setTimeout run, delay + runFullCheck: () -> + @adapter.highlightedWordManager.reset() + @runSpellCheck() if @isSpellCheckEnabled() markLinesAsUpdated: (change) -> start = change.start @@ -146,6 +105,15 @@ define [ @updatedLines[start.row] = true removeLines() + runSpellCheckSoon: (delay = 1000) -> + run = () => + delete @timeoutId + @runSpellCheck(@updatedLines) + @updatedLines = [] + if @timeoutId? + clearTimeout @timeoutId + @timeoutId = setTimeout run, delay + runSpellCheck: (linesToProcess) -> {words, positions} = @getWords(linesToProcess) language = @$scope.spellCheckLanguage @@ -178,11 +146,11 @@ define [ displayResult = (highlights) => if linesToProcess? for shouldProcess, row in linesToProcess - @highlightedWordManager.clearRows(row, row) if shouldProcess + @adapter.highlightedWordManager.clearRow(row) if shouldProcess else - @highlightedWordManager.clearRows() + @adapter.highlightedWordManager.reset() for highlight in highlights - @highlightedWordManager.addHighlight highlight + @adapter.highlightedWordManager.addHighlight highlight if not words.length displayResult highlights @@ -212,8 +180,24 @@ define [ seen[key] = true displayResult highlights + apiRequest: (endpoint, data, callback = (error, result) ->)-> + data.token = window.user.id + data._csrf = window.csrfToken + # use angular timeout option to cancel request if doc is changed + requestHandler = @$q.defer() + options = {timeout: requestHandler.promise} + httpRequest = @$http.post("/spelling" + endpoint, data, options) + .then (response) => + callback(null, response.data) + .catch (response) => + callback(new Error('api failure')) + # provide a method to cancel the request + abortRequest = () -> + requestHandler.resolve() + return { abort: abortRequest } + getWords: (linesToProcess) -> - lines = @editor.getValue().split("\n") + lines = @adapter.getLines() words = [] positions = [] for line, row in lines @@ -232,22 +216,6 @@ define [ words.push(word) return words: words, positions: positions - apiRequest: (endpoint, data, callback = (error, result) ->)-> - data.token = window.user.id - data._csrf = window.csrfToken - # use angular timeout option to cancel request if doc is changed - requestHandler = @$q.defer() - options = {timeout: requestHandler.promise} - httpRequest = @$http.post("/spelling" + endpoint, data, options) - .then (response) => - callback(null, response.data) - .catch (response) => - callback(new Error('api failure')) - # provide a method to cancel the request - abortRequest = () -> - requestHandler.resolve() - return { abort: abortRequest } - blacklistedCommandRegex: /// \\ # initial backslash (label # any of these commands diff --git a/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee b/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee index df65a2f721..9920372aab 100644 --- a/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee +++ b/services/web/public/coffee/ide/settings/controllers/SettingsController.coffee @@ -8,6 +8,12 @@ define [ if $scope.settings.pdfViewer not in ["pdfjs", "native"] $scope.settings.pdfViewer = "pdfjs" + if $scope.settings.fontFamily? and $scope.settings.fontFamily not in ["monaco", "lucida"] + delete $scope.settings.fontFamily + + if $scope.settings.lineHeight? and $scope.settings.lineHeight not in ["compact", "normal", "wide"] + delete $scope.settings.lineHeight + $scope.fontSizeAsStr = (newVal) -> if newVal? $scope.settings.fontSize = newVal @@ -41,6 +47,14 @@ define [ if syntaxValidation != oldSyntaxValidation settings.saveSettings({syntaxValidation: syntaxValidation}) + $scope.$watch "settings.fontFamily", (fontFamily, oldFontFamily) => + if fontFamily != oldFontFamily + settings.saveSettings({fontFamily: fontFamily}) + + $scope.$watch "settings.lineHeight", (lineHeight, oldLineHeight) => + if lineHeight != oldLineHeight + settings.saveSettings({lineHeight: lineHeight}) + $scope.$watch "project.spellCheckLanguage", (language, oldLanguage) => return if @ignoreUpdates if oldLanguage? and language != oldLanguage diff --git a/services/web/public/coffee/main/account-upgrade.coffee b/services/web/public/coffee/main/account-upgrade.coffee index 638371e45e..f2f67ea13d 100644 --- a/services/web/public/coffee/main/account-upgrade.coffee +++ b/services/web/public/coffee/main/account-upgrade.coffee @@ -11,8 +11,8 @@ define [ w = window.open() go = () -> ga?('send', 'event', 'subscription-funnel', 'upgraded-free-trial', source) - if window.redirectToOLFreeTrialUrl? - url = window.redirectToOLFreeTrialUrl + if window.useV2TrialUrl + url = "/user/trial" else url = "/user/subscription/new?planCode=#{plan}&ssp=true" if couponCode? diff --git a/services/web/public/coffee/main/contact-us.coffee b/services/web/public/coffee/main/contact-us.coffee index 2d1dee6d34..6d3e441c49 100644 --- a/services/web/public/coffee/main/contact-us.coffee +++ b/services/web/public/coffee/main/contact-us.coffee @@ -1,79 +1,7 @@ define [ "base" "libs/platform" - "services/algolia-search" ], (App, platform) -> - App.controller 'ContactModal', ($scope, $modal) -> - $scope.contactUsModal = () -> - modalInstance = $modal.open( - templateUrl: "supportModalTemplate" - controller: "SupportModalController" - ) - - App.controller 'SupportModalController', ($scope, $modalInstance, algoliaSearch, event_tracking) -> - $scope.form = {} - $scope.sent = false - $scope.sending = false - $scope.suggestions = []; - - _handleSearchResults = (success, results) -> - suggestions = for hit in results.hits - page_underscored = hit.pageName.replace(/\s/g,'_') - - suggestion = - url :"/learn/kb/#{page_underscored}" - name : hit._highlightResult.pageName.value - - event_tracking.sendMB "contact-form-suggestions-shown" if results.hits.length - - $scope.$applyAsync () -> - $scope.suggestions = suggestions - - $scope.contactUs = -> - if !$scope.form.email? or $scope.form.email == "" - console.log "email not set" - return - $scope.sending = true - ticketNumber = Math.floor((1 + Math.random()) * 0x10000).toString(32) - message = $scope.form.message - if $scope.form.project_url? - message = "#{message}\n\n project_url = #{$scope.form.project_url}" - params = - email: $scope.form.email - message: message or "" - subject: $scope.form.subject + " - [#{ticketNumber}]" - labels: "support" - about: "
browser: #{platform?.name} #{platform?.version}
-
os: #{platform?.os?.family} #{platform?.os?.version}
" - - Groove.createTicket params, (response)-> - $scope.sending = false - if response.responseText == "" # Blocked request or similar - $scope.error = true - else - data = JSON.parse(response.responseText) - if data.errors? - $scope.error = true - else - $scope.sent = true - $scope.$apply() - - $scope.$watch "form.subject", (newVal, oldVal) -> - if newVal and newVal != oldVal and newVal.length > 3 - algoliaSearch.searchKB newVal, _handleSearchResults, { - hitsPerPage: 3 - typoTolerance: 'strict' - } - else - $scope.suggestions = []; - - $scope.clickSuggestionLink = (url) -> - event_tracking.sendMB "contact-form-suggestions-clicked", { url } - - $scope.close = () -> - $modalInstance.close() - - App.controller 'UniverstiesContactController', ($scope, $modal, $http) -> $scope.form = {} diff --git a/services/web/public/coffee/main/learn.coffee b/services/web/public/coffee/main/learn.coffee index eeae4219b8..375c2f0365 100644 --- a/services/web/public/coffee/main/learn.coffee +++ b/services/web/public/coffee/main/learn.coffee @@ -55,32 +55,4 @@ define [ hits = _.map response.hits, buildHitViewModel updateHits hits - $scope.showMissingTemplateModal = () -> - modalInstance = $modal.open( - templateUrl: "missingWikiPageModal" - controller: "MissingWikiPageController" - ) - - App.controller 'MissingWikiPageController', ($scope, $modalInstance) -> - $scope.form = {} - $scope.sent = false - $scope.sending = false - $scope.contactUs = -> - if !$scope.form.message? - console.log "message not set" - return - $scope.sending = true - ticketNumber = Math.floor((1 + Math.random()) * 0x10000).toString(32) - params = - email: $scope.form.email or "support@sharelatex.com" - message: $scope.form.message or "" - subject: "new wiki page sujection - [#{ticketNumber}]" - labels: "support wiki" - - Groove.createTicket params, (err, json)-> - $scope.sent = true - $scope.$apply() - - $scope.close = () -> - $modalInstance.close() diff --git a/services/web/public/coffee/main/project-list/project-list.coffee b/services/web/public/coffee/main/project-list/project-list.coffee index 5880129294..36520d2cc7 100644 --- a/services/web/public/coffee/main/project-list/project-list.coffee +++ b/services/web/public/coffee/main/project-list/project-list.coffee @@ -8,6 +8,7 @@ define [ $scope.notifications = window.data.notifications $scope.allSelected = false $scope.selectedProjects = [] + $scope.isArchiveableProjectSelected = false $scope.filter = "all" $scope.predicate = "lastUpdated" $scope.nUntagged = 0 @@ -85,6 +86,8 @@ define [ $scope.updateSelectedProjects = () -> $scope.selectedProjects = $scope.projects.filter (project) -> project.selected + $scope.isArchiveableProjectSelected = $scope.selectedProjects.some (project) -> + window.user_id == project.owner._id $scope.getSelectedProjects = () -> $scope.selectedProjects diff --git a/services/web/public/coffee/services/algolia-search.coffee b/services/web/public/coffee/services/algolia-search.coffee index d62bc6389d..9ae5eae077 100644 --- a/services/web/public/coffee/services/algolia-search.coffee +++ b/services/web/public/coffee/services/algolia-search.coffee @@ -8,7 +8,7 @@ define [ kbIdx = client.initIndex(window.sharelatex.algolia?.indexes?.kb) service = - searchWiki: wikiIdx.search.bind(wikiIdx) - searchKB: kbIdx.search.bind(kbIdx) + searchWiki: if wikiIdx then wikiIdx.search.bind(wikiIdx) else null + searchKB: if kbIdx then kbIdx.search.bind(kbIdx) else null return service \ No newline at end of file diff --git a/services/web/public/js/ace-1.2.5/theme-overleaf.js b/services/web/public/js/ace-1.2.5/theme-overleaf.js new file mode 100644 index 0000000000..f2162a542c --- /dev/null +++ b/services/web/public/js/ace-1.2.5/theme-overleaf.js @@ -0,0 +1,71 @@ +ace.define("ace/theme/overleaf",["require","exports","module","ace/lib/dom"], function(require, exports, module) { +"use strict"; + +exports.isDark = false; +exports.cssClass = "ace-overleaf"; +exports.cssText = ".ace-overleaf .ace_gutter {\ +background: #f0f0f0;\ +color: #333;\ +}\ +.ace-overleaf .ace_print-margin {\ +width: 1px;\ +background: #e8e8e8;\ +}\ +.ace-overleaf {\ +background-color: #FFFFFF;\ +color: black;\ +}\ +.ace-overleaf .ace_cursor {\ +color: black;\ +}\ +.ace-overleaf .ace_marker-layer .ace_selection {\ +background: rgb(181, 213, 255);\ +}\ +.ace-overleaf.ace_multiselect .ace_selection.ace_start {\ +box-shadow: 0 0 3px 0px white;\ +}\ +.ace-overleaf .ace_marker-layer .ace_step {\ +background: rgb(252, 255, 0);\ +}\ +.ace-overleaf .ace_marker-layer .ace_bracket {\ +border: 1px solid #5A5CAD;\ +}\ +.ace-overleaf .ace_marker-layer .ace_active-line {\ +background: rgba(0, 0, 0, 0.07);\ +}\ +.ace-overleaf .ace_gutter-active-line {\ +background-color: #dcdcdc;\ +}\ +.ace-overleaf .ace_marker-layer .ace_selected-word {\ +background: rgb(250, 250, 255);\ +border: 1px solid rgb(200, 200, 250);\ +}\ +.ace-overleaf .ace_fold {\ +background-color: #6B72E6;\ +}\ +.ace-overleaf .ace_comment {\ +color: #0080FF;\ +font-style: italic;\ +}\ +.ace-overleaf .ace_storage,\ +.ace-overleaf .ace_keyword {\ +color: #3F7F7F;\ +}\ +.ace-overleaf .ace_variable,\ +.ace-overleaf .ace_string {\ +color: #5A5CAD;\ +}\ +"; +exports.$id = "ace/theme/overleaf"; + +var dom = require("../lib/dom"); +dom.importCssString(exports.cssText, exports.cssClass); +}); + (function() { + ace.require(["ace/theme/overleaf"], function(m) { + if (typeof module == "object" && typeof exports == "object" && module) { + module.exports = m; + } + }); + })(); + \ No newline at end of file diff --git a/services/web/public/stylesheets/_ol_style_includes.less b/services/web/public/stylesheets/_ol_style_includes.less index cc03279a73..70820f9b68 100644 --- a/services/web/public/stylesheets/_ol_style_includes.less +++ b/services/web/public/stylesheets/_ol_style_includes.less @@ -1,2 +1,3 @@ @import "app/sidebar-v2-dash-pane.less"; -@import "app/front-chat-widget.less"; \ No newline at end of file +@import "app/front-chat-widget.less"; +@import "app/ol-chat.less"; diff --git a/services/web/public/stylesheets/_style_includes.less b/services/web/public/stylesheets/_style_includes.less index 39ba48d94a..bbe3463cba 100644 --- a/services/web/public/stylesheets/_style_includes.less +++ b/services/web/public/stylesheets/_style_includes.less @@ -47,6 +47,7 @@ @import "components/tooltip.less"; @import "components/popovers.less"; @import "components/carousel.less"; +@import "components/daterange-picker"; // ngTagsInput @import "components/tags-input.less"; @@ -80,6 +81,7 @@ @import "app/error-pages.less"; @import "app/v1-badge.less"; @import "app/editor/history-v2.less"; +@import "app/metrics.less"; // Vendor CSS @import "../js/libs/pdfListView/TextLayer.css"; diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index 8b07bd9263..0e3d48cf87 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -13,6 +13,7 @@ @import "./editor/hotkeys.less"; @import "./editor/review-panel.less"; @import "./editor/rich-text.less"; +@import "./editor/publish-modal.less"; @ui-layout-toggler-def-height: 50px; @ui-resizer-size: 7px; @@ -204,13 +205,9 @@ } } -.cm-editor-wrapper { - height: 100%; - - .CodeMirror { - height: 100%; - } -} +/************************************** +Ace +***************************************/ // The internal components of the aceEditor directive .ace-editor-wrapper { @@ -285,6 +282,23 @@ } } +/************************************** +CodeMirror +***************************************/ +.cm-editor-wrapper { + position: relative; + height: 100%; +} + +.cm-editor-body { + height: 100%; +} + +// CM (for some reason) has height set to 300px in it's stylesheet +.CodeMirror { + height: 100%; +} + .ui-layout-resizer when (@is-overleaf = false) { width: 6px; background-color: @editor-resizer-bg-color; diff --git a/services/web/public/stylesheets/app/editor/publish-modal.less b/services/web/public/stylesheets/app/editor/publish-modal.less new file mode 100644 index 0000000000..e3320f85f0 --- /dev/null +++ b/services/web/public/stylesheets/app/editor/publish-modal.less @@ -0,0 +1,89 @@ +.modal-body-publish { + #search-input-container { + overflow: hidden; + margin: 5px 0 10px; + } + .table-content-name { + width: 100%; + margin-bottom: 10px; + font-weight: 300; + } + .table-content-category { + font-weight: 300; + text-align: right; + font-style: italic; + width: 30%; + float: right; + text-transform: capitalize + } + .table-content-category ~ .table-content-name { + width: 70%; + display: inline-block; + } + .wl-icon:before{ + font-size: 14px; + } + .button-as-link{ + color: green; + text-transform: none; + background: none; + padding: 0; + border: none; + border-radius: 0; + font-size: 14px; + @extend a; + &:hover, + &:active, + &:focus { + color: green; + background: none; + } + text-align: left; + } + .affix-content-title { + color: @gray-light; + font-size: 1.2em; + padding-left: 10px; + } + + .affix-subcontent { + margin: 5px 0 50px; + } + .overbox { + padding: @line-height-computed / 2; + background-color: white; + margin-top: @line-height-computed / 2; + border: 1px solid @gray-lighter; + } + .content-as-table { + .table-content, + .table-content > * { + display: table; + } + .table-content-icon { + float: left; + height: 80px; + width: 106px; + border: 1px solid @gray-lightest; + display: flex; + align-items: center; + overflow: hidden; + * { + width: 100%; + } + } + .table-content-text { + float: right; + width: calc(~'100% - 106px'); + vertical-align: top; + padding-left: 15px; + } + .table-content-slogan { + height: 80px; + overflow: hidden; + } + .table-content-link { + padding-top: 10px; + } + } +} diff --git a/services/web/public/stylesheets/app/editor/review-panel.less b/services/web/public/stylesheets/app/editor/review-panel.less index 5e846be008..1ed723c3b4 100644 --- a/services/web/public/stylesheets/app/editor/review-panel.less +++ b/services/web/public/stylesheets/app/editor/review-panel.less @@ -775,6 +775,12 @@ } } +#editor-rich-text { + .rp-size-expanded & { + right: @review-panel-width; + } +} + .rp-toggle { display: inline-block; vertical-align: middle; diff --git a/services/web/public/stylesheets/app/editor/rich-text.less b/services/web/public/stylesheets/app/editor/rich-text.less index 493540e705..37a896269d 100644 --- a/services/web/public/stylesheets/app/editor/rich-text.less +++ b/services/web/public/stylesheets/app/editor/rich-text.less @@ -219,4 +219,11 @@ font-style: italic; color: #999; } + + .spelling-error { + background-image: url(/img/spellcheck-underline.png); + background-repeat: repeat-x; + background-position: bottom; + } } + diff --git a/services/web/public/stylesheets/app/metrics.less b/services/web/public/stylesheets/app/metrics.less new file mode 100644 index 0000000000..23d121088d --- /dev/null +++ b/services/web/public/stylesheets/app/metrics.less @@ -0,0 +1,177 @@ +#metrics { + max-width: none; + padding: 0 30px; + width: auto; + + svg.nvd3-svg { + width: 100%; + } + + .overbox { + margin: 0; + padding: 40px 20px; + background: #fff; + border: 1px solid #DFDFDF; + .box { + padding-bottom: 30px; + overflow: hidden; + margin-bottom: 40px; + border-bottom: 1px solid rgb(216, 216, 216); + + .header { + margin-bottom: 20px; + + h4 { + font-size: 19px; + margin: 0; + } + } + } + } + + .print-button { + margin-right: 10px; + font-size: 20px; + } + + .title-button { + margin-right: 5px; + font-size: 20px; + } + + .metric-col { + padding: 15px; + } + + .metric-header-container { + h4 { + margin-bottom: 0; + } + } + + svg { + display: block; + height: 250px; + text { + font-family: "Open Sans", sans-serif; + } + &:not(:root) { + overflow: visible + } + + &.hidden-legend-margin-fix { + margin-top: 15px; + height: 235px; + } + + &.no-fill-opacity { + .nvd3 { + .nv-area { + fill-opacity: 1; + } + } + } + } + + .nvtooltip { + z-index: 10; + } + + .tooltip { + width: 150px; + } + + // BEGIN: Metrics header + .metric-header-container { + > h4 { + margin-top: 0; + margin-bottom: 0; + } + } + // END: Metrics header + + // BEGIN: Metrics footer + .metric-footer-container { + text-align: center; + } + // END: Metrics footer + + // BEGIN: Metrics overlays + .metric-overlay-loading, + .metric-overlay-error, + .metric-overlay-backdrop { + position: absolute; + top: 0; + left: 0; + height: 100%; + width: 100%; + padding: 16px; /* 15px of .metric-col padding + 1px border */ + padding-top: 56px; /* Same as above + 30px for title + 10px overbox padding*/ + } + + .metric-overlay-loading { + padding: 175px 20%; + } + + .metric-overlay-error { + display: none; + text-align: center; + padding-top: 175px; + } + + .metric-overlay-backdrop { + opacity: 0.5; + } + + .metric-overlay-backdrop-inner { + background-color: #fff; + width: 100%; + height: 100%; + } + // END: Metrics overlays +} + +#metrics-header { + @media (min-width: 1200px) { + margin-bottom: 30px; + } + + .section_header { + margin-bottom: 0; + } + + #filters-container { + text-align: right; + + .by { + color: #989898; + font-style: italic; + } + } + + #lags-container { + .dropdown-menu { + min-width: 0; + } + } + + #dates-container { + display: inline-block; + .daterangepicker { + margin-right: 15px; + } + } +} + +#metrics-footer { + margin-top: 30px; + text-align: center; +} + +body.print-loading { + #metrics { + .metric-col { + opacity: 0.5; + } + } +} diff --git a/services/web/public/stylesheets/app/ol-chat.less b/services/web/public/stylesheets/app/ol-chat.less new file mode 100644 index 0000000000..dd1cf692b6 --- /dev/null +++ b/services/web/public/stylesheets/app/ol-chat.less @@ -0,0 +1,6 @@ +// Styles for Chat panel in Overleaf v2 + +.chat .message-wrapper .message .message-content a { + color: inherit; + text-decoration: underline; +} diff --git a/services/web/public/stylesheets/components/daterange-picker.less b/services/web/public/stylesheets/components/daterange-picker.less new file mode 100644 index 0000000000..2d15d2a36d --- /dev/null +++ b/services/web/public/stylesheets/components/daterange-picker.less @@ -0,0 +1,610 @@ +// +// A stylesheet for use with Bootstrap 3.x +// @author: Dan Grossman http://www.dangrossman.info/ +// @copyright: Copyright (c) 2012-2015 Dan Grossman. All rights reserved. +// @license: Licensed under the MIT license. See http://www.opensource.org/licenses/mit-license.php +// @website: https://www.improvely.com/ +// + +// +// VARIABLES +// + +// +// Settings + +// The class name to contain everything within. +@arrow-size: 7px; + +// +// Colors +@daterangepicker-color: @brand-primary; +@daterangepicker-bg-color: #fff; + +@daterangepicker-cell-color: @daterangepicker-color; +@daterangepicker-cell-border-color: transparent; +@daterangepicker-cell-bg-color: @daterangepicker-bg-color; + +@daterangepicker-cell-hover-color: @daterangepicker-color; +@daterangepicker-cell-hover-border-color: @daterangepicker-cell-border-color; +@daterangepicker-cell-hover-bg-color: #eee; + +@daterangepicker-in-range-color: #000; +@daterangepicker-in-range-border-color: transparent; +@daterangepicker-in-range-bg-color: #ebf4f8; + +@daterangepicker-active-color: #fff; +@daterangepicker-active-bg-color: #a93529; +@daterangepicker-active-border-color: transparent; + +@daterangepicker-unselected-color: #999; +@daterangepicker-unselected-border-color: transparent; +@daterangepicker-unselected-bg-color: #fff; + +// +// daterangepicker +@daterangepicker-width: 278px; +@daterangepicker-padding: 4px; +@daterangepicker-z-index: 3000; + +@daterangepicker-border-size: 1px; +@daterangepicker-border-color: #ccc; +@daterangepicker-border-radius: 4px; + + +// +// Calendar +@daterangepicker-calendar-margin: @daterangepicker-padding; +@daterangepicker-calendar-bg-color: @daterangepicker-bg-color; + +@daterangepicker-calendar-border-size: 1px; +@daterangepicker-calendar-border-color: @daterangepicker-bg-color; +@daterangepicker-calendar-border-radius: @daterangepicker-border-radius; + +// +// Calendar Cells +@daterangepicker-cell-size: 20px; +@daterangepicker-cell-width: @daterangepicker-cell-size; +@daterangepicker-cell-height: @daterangepicker-cell-size; + +@daterangepicker-cell-border-radius: @daterangepicker-calendar-border-radius; +@daterangepicker-cell-border-size: 1px; + +// +// Dropdowns +@daterangepicker-dropdown-z-index: @daterangepicker-z-index + 1; + +// +// Controls +@daterangepicker-control-height: 30px; +@daterangepicker-control-line-height: @daterangepicker-control-height; +@daterangepicker-control-color: #555; + +@daterangepicker-control-border-size: 1px; +@daterangepicker-control-border-color: #ccc; +@daterangepicker-control-border-radius: 4px; + +@daterangepicker-control-active-border-size: 1px; +@daterangepicker-control-active-border-color: @brand-primary; +@daterangepicker-control-active-border-radius: @daterangepicker-control-border-radius; + +@daterangepicker-control-disabled-color: #ccc; + +// +// Ranges +@daterangepicker-ranges-color: @brand-primary; +@daterangepicker-ranges-bg-color: daterangepicker-ranges-color; + +@daterangepicker-ranges-border-size: 1px; +@daterangepicker-ranges-border-color: @daterangepicker-ranges-bg-color; +@daterangepicker-ranges-border-radius: @daterangepicker-border-radius; + +@daterangepicker-ranges-hover-color: #fff; +@daterangepicker-ranges-hover-bg-color: @daterangepicker-ranges-color; +@daterangepicker-ranges-hover-border-size: @daterangepicker-ranges-border-size; +@daterangepicker-ranges-hover-border-color: @daterangepicker-ranges-hover-bg-color; +@daterangepicker-ranges-hover-border-radius: @daterangepicker-border-radius; + +@daterangepicker-ranges-active-border-size: @daterangepicker-ranges-border-size; +@daterangepicker-ranges-active-border-color: @daterangepicker-ranges-bg-color; +@daterangepicker-ranges-active-border-radius: @daterangepicker-border-radius; + +// +// STYLESHEETS +// +.daterangepicker { + position: absolute; + color: @daterangepicker-color; + background-color: @daterangepicker-bg-color; + border-radius: @daterangepicker-border-radius; + width: @daterangepicker-width; + padding: @daterangepicker-padding; + margin-top: @daterangepicker-border-size; + + // TODO: Should these be parameterized?? + // top: 100px; + // left: 20px; + + @arrow-prefix-size: @arrow-size; + @arrow-suffix-size: (@arrow-size - @daterangepicker-border-size); + + &:before, &:after { + position: absolute; + display: inline-block; + + border-bottom-color: rgba(0, 0, 0, 0.2); + content: ''; + } + + &:before { + top: -@arrow-prefix-size; + + border-right: @arrow-prefix-size solid transparent; + border-left: @arrow-prefix-size solid transparent; + border-bottom: @arrow-prefix-size solid @daterangepicker-border-color; + } + + &:after { + top: -@arrow-suffix-size; + + border-right: @arrow-suffix-size solid transparent; + border-bottom: @arrow-suffix-size solid @daterangepicker-bg-color; + border-left: @arrow-suffix-size solid transparent; + } + + &.opensleft { + &:before { + // TODO: Make this relative to prefix size. + right: @arrow-prefix-size + 2px; + } + + &:after { + // TODO: Make this relative to suffix size. + right: @arrow-suffix-size + 4px; + } + } + + &.openscenter { + &:before { + left: 0; + right: 0; + width: 0; + margin-left: auto; + margin-right: auto; + } + + &:after { + left: 0; + right: 0; + width: 0; + margin-left: auto; + margin-right: auto; + } + } + + &.opensright { + &:before { + // TODO: Make this relative to prefix size. + left: @arrow-prefix-size + 2px; + } + + &:after { + // TODO: Make this relative to suffix size. + left: @arrow-suffix-size + 4px; + } + } + + &.dropup { + margin-top: -5px; + + // NOTE: Note sure why these are special-cased. + &:before { + top: initial; + bottom: -@arrow-prefix-size; + border-bottom: initial; + border-top: @arrow-prefix-size solid @daterangepicker-border-color; + } + + &:after { + top: initial; + bottom:-@arrow-suffix-size; + border-bottom: initial; + border-top: @arrow-suffix-size solid @daterangepicker-bg-color; + } + } + + &.dropdown-menu { + max-width: none; + z-index: @daterangepicker-dropdown-z-index; + } + + &.single { + .ranges, .calendar { + float: none; + } + } + + /* Calendars */ + &.show-calendar { + .calendar { + display: block; + } + } + + .calendar { + display: none; + max-width: @daterangepicker-width - (@daterangepicker-calendar-margin * 2); + margin: @daterangepicker-calendar-margin; + + &.single { + .calendar-table { + border: none; + } + } + + th, td { + white-space: nowrap; + text-align: center; + + // TODO: Should this actually be hard-coded? + min-width: 32px; + } + } + + .calendar-table { + border: @daterangepicker-calendar-border-size solid @daterangepicker-calendar-border-color; + padding: @daterangepicker-calendar-margin; + border-radius: @daterangepicker-calendar-border-radius; + background-color: @daterangepicker-calendar-bg-color; + } + + table { + width: 100%; + margin: 0; + } + + td, th { + text-align: center; + width: @daterangepicker-cell-width; + height: @daterangepicker-cell-height; + border-radius: @daterangepicker-cell-border-radius; + border: @daterangepicker-cell-border-size solid @daterangepicker-cell-border-color; + white-space: nowrap; + cursor: pointer; + + &.available { + &:hover { + background-color: @daterangepicker-cell-hover-bg-color; + border-color: @daterangepicker-cell-hover-border-color; + color: @daterangepicker-cell-hover-color; + } + } + + &.week { + font-size: 80%; + color: #ccc; + } + } + + td { + &.off { + &, &.in-range, &.start-date, &.end-date { + background-color: @daterangepicker-unselected-bg-color; + border-color: @daterangepicker-unselected-border-color; + color: @daterangepicker-unselected-color; + } + } + + // + // Date Range + &.in-range { + background-color: @daterangepicker-in-range-bg-color; + border-color: @daterangepicker-in-range-border-color; + color: @daterangepicker-in-range-color; + + // TODO: Should this be static or should it be parameterized? + border-radius: 0; + } + + &.start-date { + border-radius: @daterangepicker-cell-border-radius 0 0 @daterangepicker-cell-border-radius; + } + + &.end-date { + border-radius: 0 @daterangepicker-cell-border-radius @daterangepicker-cell-border-radius 0; + } + + &.start-date.end-date { + border-radius: @daterangepicker-cell-border-radius; + } + + &.active { + &, &:hover { + background-color: @daterangepicker-active-bg-color; + border-color: @daterangepicker-active-border-color; + color: @daterangepicker-active-color; + } + } + } + + th { + &.month { + width: auto; + } + } + + // + // Disabled Controls + // + td, option { + &.disabled { + color: #999; + cursor: not-allowed; + text-decoration: line-through; + } + } + + select { + &.monthselect, &.yearselect { + font-size: 12px; + padding: 1px; + height: auto; + margin: 0; + cursor: default; + } + + &.monthselect { + margin-right: 2%; + width: 56%; + } + + &.yearselect { + width: 40%; + } + + &.hourselect, &.minuteselect, &.secondselect, &.ampmselect { + width: 50px; + margin-bottom: 0; + } + } + + // + // Text Input Controls (above calendar) + // + .input-mini { + border: @daterangepicker-control-border-size solid @daterangepicker-control-border-color; + border-radius: @daterangepicker-control-border-radius; + color: @daterangepicker-control-color; + height: @daterangepicker-control-line-height; + line-height: @daterangepicker-control-height; + display: block; + vertical-align: middle; + + // TODO: Should these all be static, too?? + margin: 0 0 5px 0; + padding: 0 6px 0 28px; + width: 100%; + + &.active { + border: @daterangepicker-control-active-border-size solid @daterangepicker-control-active-border-color; + border-radius: @daterangepicker-control-active-border-radius; + } + } + + .daterangepicker_input { + position: relative; + + i { + position: absolute; + + // NOTE: These appear to be eyeballed to me... + left: 8px; + top: 8px; + } + } + &.rtl { + .input-mini { + padding-right: 28px; + padding-left: 6px; + } + .daterangepicker_input i { + left: auto; + right: 8px; + } + } + + // + // Time Picker + // + .calendar-time { + text-align: center; + margin: 5px auto; + line-height: @daterangepicker-control-line-height; + position: relative; + padding-left: 28px; + + select { + &.disabled { + color: @daterangepicker-control-disabled-color; + cursor: not-allowed; + } + } + } +} + +// +// Predefined Ranges +// + +.ranges { + font-size: 11px; + float: none; + margin: 4px; + text-align: left; + + ul { + list-style: none; + margin: 0 auto; + padding: 0; + width: 100%; + } + + li { + font-size: 13px; + background-color: @daterangepicker-ranges-bg-color; + border: @daterangepicker-ranges-border-size solid @daterangepicker-ranges-border-color; + border-radius: @daterangepicker-ranges-border-radius; + color: @daterangepicker-ranges-color; + padding: 3px 12px; + margin-bottom: 8px; + cursor: pointer; + + &:hover { + background-color: @daterangepicker-ranges-hover-bg-color; + border: @daterangepicker-ranges-hover-border-size solid @daterangepicker-ranges-hover-border-color; + color: @daterangepicker-ranges-hover-color; + } + + &.active { + background-color: @daterangepicker-ranges-hover-bg-color; + border: @daterangepicker-ranges-hover-border-size solid @daterangepicker-ranges-hover-border-color; + color: @daterangepicker-ranges-hover-color; + } + } +} + +/* Larger Screen Styling */ +@media (min-width: 564px) { + .daterangepicker { + width: auto; + + .ranges { + ul { + width: 160px; + } + } + + &.single { + .ranges { + ul { + width: 100%; + } + } + + .calendar.left { + clear: none; + } + + &.ltr { + .ranges, .calendar { + float:left; + } + } + &.rtl { + .ranges, .calendar { + float:right; + } + } + } + + &.ltr { + direction: ltr; + text-align: left; + .calendar{ + &.left { + clear: left; + margin-right: 0; + + .calendar-table { + border-right: none; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + } + + &.right { + margin-left: 0; + + .calendar-table { + border-left: none; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } + } + + .left .daterangepicker_input { + padding-right: 12px; + } + + .calendar.left .calendar-table { + padding-right: 12px; + } + + .ranges, .calendar { + float: left; + } + } + &.rtl { + direction: rtl; + text-align: right; + .calendar{ + &.left { + clear: right; + margin-left: 0; + + .calendar-table { + border-left: none; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + } + } + + &.right { + margin-right: 0; + + .calendar-table { + border-right: none; + border-top-right-radius: 0; + border-bottom-right-radius: 0; + } + } + } + + .left .daterangepicker_input { + padding-left: 12px; + } + + .calendar.left .calendar-table { + padding-left: 12px; + } + + .ranges, .calendar { + text-align: right; + float: right; + } + } + } +} + +@media (min-width: 730px) { + .daterangepicker { + .ranges { + width: auto; + } + &.ltr { + .ranges { + float: left; + } + } + &.rtl { + .ranges { + float: right; + } + } + + .calendar.left { + clear: none !important; + } + } +} diff --git a/services/web/test/acceptance/coffee/ExportsTests.coffee b/services/web/test/acceptance/coffee/ExportsTests.coffee new file mode 100644 index 0000000000..7a6784008d --- /dev/null +++ b/services/web/test/acceptance/coffee/ExportsTests.coffee @@ -0,0 +1,56 @@ +expect = require('chai').expect +request = require './helpers/request' +_ = require 'underscore' + + +User = require './helpers/User' +ProjectGetter = require '../../../app/js/Features/Project/ProjectGetter.js' +ExportsHandler = require '../../../app/js/Features/Exports/ExportsHandler.js' + +MockProjectHistoryApi = require './helpers/MockProjectHistoryApi' +MockV1Api = require './helpers/MockV1Api' + +describe 'Exports', -> + before (done) -> + @brand_variation_id = '18' + @owner = new User() + @owner.login (error) => + throw error if error? + @owner.createProject 'example-project', {template: 'example'}, (error, @project_id) => + throw error if error? + done() + + describe 'exporting a project', -> + beforeEach (done) -> + @version = Math.floor(Math.random() * 10000) + MockProjectHistoryApi.setProjectVersion(@project_id, @version) + @export_id = Math.floor(Math.random() * 10000) + MockV1Api.setExportId(@export_id) + MockV1Api.clearExportParams() + @owner.request { + method: 'POST', + url: "/project/#{@project_id}/export/#{@brand_variation_id}", + json: {}, + }, (error, response, body) => + throw error if error? + expect(response.statusCode).to.equal 200 + @exportResponseBody = body + done() + + it 'should have sent correct data to v1', (done) -> + {project, user, destination, options} = MockV1Api.getLastExportParams() + # project details should match + expect(project.id).to.equal @project_id + expect(project.rootDocPath).to.equal '/main.tex' + # version should match what was retrieved from project-history + expect(project.historyVersion).to.equal @version + # user details should match + expect(user.id).to.equal @owner.id + expect(user.email).to.equal @owner.email + # brand-variation should match + expect(destination.brandVariationId).to.equal @brand_variation_id + done() + + it 'should have returned the export ID provided by v1', (done) -> + expect(@exportResponseBody.export_v1_id).to.equal @export_id + done() diff --git a/services/web/test/acceptance/coffee/ProjectInviteTests.coffee b/services/web/test/acceptance/coffee/ProjectInviteTests.coffee index b599578738..02598b320f 100644 --- a/services/web/test/acceptance/coffee/ProjectInviteTests.coffee +++ b/services/web/test/acceptance/coffee/ProjectInviteTests.coffee @@ -16,6 +16,7 @@ createInvite = (sendingUser, projectId, email, callback=(err, invite)->) -> privileges: 'readAndWrite' }, (err, response, body) -> return callback(err) if err + expect(response.statusCode).to.equal 200 callback(null, body.invite) createProject = (owner, projectName, callback=(err, projectId, project)->) -> @@ -207,9 +208,9 @@ describe "ProjectInviteTests", -> @email = 'smoketestuser@example.com' @projectName = 'sharing test' Async.series [ - (cb) => @user.login cb - (cb) => @user.logout cb + (cb) => @user.ensureUserExists cb (cb) => @sendingUser.login cb + (cb) => @sendingUser.setFeatures { collaborators: 10 }, cb ], done describe 'creating invites', -> @@ -266,7 +267,7 @@ describe "ProjectInviteTests", -> (cb) => expectInvitesInJoinProjectCount @sendingUser, @projectId, 0, cb ], done - it 'should allow the project owner to many invites at once', (done) -> + it 'should allow the project owner to create many invites at once', (done) -> @inviteOne = null @inviteTwo = null Async.series [ diff --git a/services/web/test/acceptance/coffee/ProjectStructureMongoLockTest.coffee b/services/web/test/acceptance/coffee/ProjectStructureMongoLockTest.coffee index 02019b74dd..7745a12ac5 100644 --- a/services/web/test/acceptance/coffee/ProjectStructureMongoLockTest.coffee +++ b/services/web/test/acceptance/coffee/ProjectStructureMongoLockTest.coffee @@ -22,6 +22,7 @@ describe "ProjectStructureMongoLock", -> before (done) -> # We want to instantly fail if the lock is taken LockManager.MAX_LOCK_WAIT_TIME = 1 + @lockValue = "lock-value" userDetails = holdingAccount:false, email: 'test@example.com' @@ -33,11 +34,13 @@ describe "ProjectStructureMongoLock", -> @locked_project = project namespace = ProjectEntityMongoUpdateHandler.LOCK_NAMESPACE @lock_key = "lock:web:#{namespace}:#{project._id}" - LockManager._getLock @lock_key, namespace, done + LockManager._getLock @lock_key, namespace, (err, lockValue) => + @lockValue = lockValue + done() return after (done) -> - LockManager._releaseLock @lock_key, done + LockManager._releaseLock @lock_key, @lockValue, done describe 'interacting with the locked project', -> LOCKING_UPDATE_METHODS = ['addDoc', 'addFile', 'mkdirp', 'moveEntity', 'renameEntity', 'addFolder'] diff --git a/services/web/test/acceptance/coffee/SubscriptionTests.coffee b/services/web/test/acceptance/coffee/SubscriptionTests.coffee new file mode 100644 index 0000000000..ce762ea0ee --- /dev/null +++ b/services/web/test/acceptance/coffee/SubscriptionTests.coffee @@ -0,0 +1,151 @@ +expect = require("chai").expect +async = require("async") +UserClient = require "./helpers/User" +request = require "./helpers/request" +settings = require "settings-sharelatex" +{ObjectId} = require("../../../app/js/infrastructure/mongojs") +Subscription = require("../../../app/js/models/Subscription").Subscription +User = require("../../../app/js/models/User").User + +MockV1Api = require "./helpers/MockV1Api" + +syncUserAndGetFeatures = (user, callback = (error, features) ->) -> + request { + method: 'POST', + url: "/user/#{user._id}/features/sync", + auth: + user: 'sharelatex' + pass: 'password' + sendImmediately: true + }, (error, response, body) -> + throw error if error? + expect(response.statusCode).to.equal 200 + User.findById user._id, (error, user) -> + return callback(error) if error? + features = user.toObject().features + delete features.$init # mongoose internals + return callback null, features + +describe "Subscriptions", -> + beforeEach (done) -> + @user = new UserClient() + @user.ensureUserExists (error) -> + throw error if error? + done() + + describe "when user has no subscriptions", -> + it "should set their features to the basic set", (done) -> + syncUserAndGetFeatures @user, (error, features) => + throw error if error? + expect(features).to.deep.equal(settings.defaultFeatures) + done() + + describe "when the user has an individual subscription", -> + beforeEach -> + Subscription.create { + admin_id: @user._id + planCode: 'collaborator' + customAccount: true + } # returns a promise + + it "should set their features to the upgraded set", (done) -> + syncUserAndGetFeatures @user, (error, features) => + throw error if error? + plan = settings.plans.find (plan) -> plan.planCode == 'collaborator' + expect(features).to.deep.equal(plan.features) + done() + + describe "when the user is in a group subscription", -> + beforeEach -> + Subscription.create { + admin_id: ObjectId() + member_ids: [@user._id] + groupAccount: true + planCode: 'collaborator' + customAccount: true + } # returns a promise + + it "should set their features to the upgraded set", (done) -> + syncUserAndGetFeatures @user, (error, features) => + throw error if error? + plan = settings.plans.find (plan) -> plan.planCode == 'collaborator' + expect(features).to.deep.equal(plan.features) + done() + + describe "when the user has bonus features", -> + beforeEach -> + User.update { + _id: @user._id + }, { + refered_user_count: 10 + } # returns a promise + + it "should set their features to the bonus set", (done) -> + syncUserAndGetFeatures @user, (error, features) => + throw error if error? + expect(features).to.deep.equal(Object.assign( + {}, settings.defaultFeatures, settings.bonus_features[9] + )) + done() + + describe "when the user has a v1 plan", -> + beforeEach -> + MockV1Api.setUser 42, plan_name: 'free' + User.update { + _id: @user._id + }, { + overleaf: + id: 42 + } # returns a promise + + it "should set their features to the v1 plan", (done) -> + syncUserAndGetFeatures @user, (error, features) => + throw error if error? + plan = settings.plans.find (plan) -> plan.planCode == 'v1_free' + expect(features).to.deep.equal(plan.features) + done() + + describe "when the user has a v1 plan and bonus features", -> + beforeEach -> + MockV1Api.setUser 42, plan_name: 'free' + User.update { + _id: @user._id + }, { + overleaf: + id: 42 + refered_user_count: 10 + } # returns a promise + + it "should set their features to the best of the v1 plan and bonus features", (done) -> + syncUserAndGetFeatures @user, (error, features) => + throw error if error? + v1plan = settings.plans.find (plan) -> plan.planCode == 'v1_free' + expectedFeatures = Object.assign( + {}, v1plan.features, settings.bonus_features[9] + ) + expect(features).to.deep.equal(expectedFeatures) + done() + + describe "when the user has a group and personal subscription", -> + beforeEach (done) -> + Subscription.create { + admin_id: @user._id + planCode: 'professional' + customAccount: true + }, (error) => + throw error if error? + Subscription.create { + admin_id: ObjectId() + member_ids: [@user._id] + groupAccount: true + planCode: 'collaborator' + customAccount: true + }, done + return + + it "should set their features to the best set", (done) -> + syncUserAndGetFeatures @user, (error, features) => + throw error if error? + plan = settings.plans.find (plan) -> plan.planCode == 'professional' + expect(features).to.deep.equal(plan.features) + done() \ No newline at end of file diff --git a/services/web/test/acceptance/coffee/helpers/MockProjectHistoryApi.coffee b/services/web/test/acceptance/coffee/helpers/MockProjectHistoryApi.coffee index 381d7ab272..b7df202d72 100644 --- a/services/web/test/acceptance/coffee/helpers/MockProjectHistoryApi.coffee +++ b/services/web/test/acceptance/coffee/helpers/MockProjectHistoryApi.coffee @@ -6,9 +6,14 @@ module.exports = MockProjectHistoryApi = oldFiles: {} + projectVersions: {} + addOldFile: (project_id, version, pathname, content) -> @oldFiles["#{project_id}:#{version}:#{pathname}"] = content + setProjectVersion: (project_id, version) -> + @projectVersions[project_id] = version + run: () -> app.post "/project", (req, res, next) => res.json project: id: 1 @@ -21,6 +26,13 @@ module.exports = MockProjectHistoryApi = else res.send 404 + app.get "/project/:project_id/version", (req, res, next) => + {project_id} = req.params + if @projectVersions[project_id]? + res.json version: @projectVersions[project_id] + else + res.send 404 + app.listen 3054, (error) -> throw error if error? .on "error", (error) -> diff --git a/services/web/test/acceptance/coffee/helpers/MockV1Api.coffee b/services/web/test/acceptance/coffee/helpers/MockV1Api.coffee new file mode 100644 index 0000000000..5c2cf47ad9 --- /dev/null +++ b/services/web/test/acceptance/coffee/helpers/MockV1Api.coffee @@ -0,0 +1,45 @@ +express = require("express") +app = express() +bodyParser = require('body-parser') + +app.use(bodyParser.json()) + +module.exports = MockV1Api = + users: { } + + setUser: (id, user) -> + @users[id] = user + + exportId: null + + exportParams: null + + setExportId: (id) -> + @exportId = id + + getLastExportParams: () -> + @exportParams + + clearExportParams: () -> + @exportParams = null + + run: () -> + app.get "/api/v1/sharelatex/users/:ol_user_id/plan_code", (req, res, next) => + user = @users[req.params.ol_user_id] + if user + res.json user + else + res.sendStatus 404 + + app.post "/api/v1/sharelatex/exports", (req, res, next) => + #{project, version, pathname} + @exportParams = Object.assign({}, req.body) + res.json exportId: @exportId + + app.listen 5000, (error) -> + throw error if error? + .on "error", (error) -> + console.error "error starting MockV1Api:", error.message + process.exit(1) + +MockV1Api.run() diff --git a/services/web/test/acceptance/coffee/helpers/User.coffee b/services/web/test/acceptance/coffee/helpers/User.coffee index 7d8e9086d4..a02ab5c42c 100644 --- a/services/web/test/acceptance/coffee/helpers/User.coffee +++ b/services/web/test/acceptance/coffee/helpers/User.coffee @@ -40,6 +40,12 @@ class User @referal_id = user?.referal_id callback(null, @password) + setFeatures: (features, callback = (error) ->) -> + update = {} + for key, value of features + update["features.#{key}"] = value + UserModel.update { _id: @id }, update, callback + logout: (callback = (error) ->) -> @getCsrfToken (error) => return callback(error) if error? diff --git a/services/web/test/acceptance/config/settings.test.coffee b/services/web/test/acceptance/config/settings.test.coffee new file mode 100644 index 0000000000..3d665f8a54 --- /dev/null +++ b/services/web/test/acceptance/config/settings.test.coffee @@ -0,0 +1,95 @@ +module.exports = + enableSubscriptions: true + + features: features = + v1_free: + collaborators: 1 + dropbox: false + versioning: false + github: true + templates: false + references: false + referencesSearch: false + mendeley: true + compileTimeout: 60 + compileGroup: "standard" + trackChanges: false + personal: + collaborators: 1 + dropbox: false + versioning: false + github: false + templates: false + references: false + referencesSearch: false + mendeley: false + compileTimeout: 60 + compileGroup: "standard" + trackChanges: false + collaborator: + collaborators: 10 + dropbox: true + versioning: true + github: true + templates: true + references: true + referencesSearch: true + mendeley: true + compileTimeout: 180 + compileGroup: "priority" + trackChanges: true + professional: + collaborators: -1 + dropbox: true + versioning: true + github: true + templates: true + references: true + referencesSearch: true + mendeley: true + compileTimeout: 180 + compileGroup: "priority" + trackChanges: true + + defaultFeatures: features.personal + defaultPlanCode: 'personal' + + plans: plans = [{ + planCode: "v1_free" + name: "V1 Free" + price: 0 + features: features.v1_free + },{ + planCode: "personal" + name: "Personal" + price: 0 + features: features.personal + },{ + planCode: "collaborator" + name: "Collaborator" + price: 1500 + features: features.collaborator + },{ + planCode: "professional" + name: "Professional" + price: 3000 + features: features.professional + }] + + bonus_features: + 1: + collaborators: 2 + dropbox: false + versioning: false + 3: + collaborators: 4 + dropbox: false + versioning: false + 6: + collaborators: 4 + dropbox: true + versioning: true + 9: + collaborators: -1 + dropbox: true + versioning: true diff --git a/services/web/test/unit/coffee/BetaProgram/BetaProgramControllerTests.coffee b/services/web/test/unit/coffee/BetaProgram/BetaProgramControllerTests.coffee index ab1f1b0567..713179b056 100644 --- a/services/web/test/unit/coffee/BetaProgram/BetaProgramControllerTests.coffee +++ b/services/web/test/unit/coffee/BetaProgram/BetaProgramControllerTests.coffee @@ -23,8 +23,8 @@ describe "BetaProgramController", -> optIn: sinon.stub() optOut: sinon.stub() }, - "../User/UserLocator": @UserLocator = { - findById: sinon.stub() + "../User/UserGetter": @UserGetter = { + getUser: sinon.stub() }, "settings-sharelatex": @settings = { languages: {} @@ -119,7 +119,7 @@ describe "BetaProgramController", -> describe "optInPage", -> beforeEach -> - @UserLocator.findById.callsArgWith(1, null, @user) + @UserGetter.getUser.callsArgWith(1, null, @user) it "should render the opt-in page", () -> @BetaProgramController.optInPage @req, @res, @next @@ -128,10 +128,10 @@ describe "BetaProgramController", -> args[0].should.equal 'beta_program/opt_in' - describe "when UserLocator.findById produces an error", -> + describe "when UserGetter.getUser produces an error", -> beforeEach -> - @UserLocator.findById.callsArgWith(1, new Error('woops')) + @UserGetter.getUser.callsArgWith(1, new Error('woops')) it "should not render the opt-in page", () -> @BetaProgramController.optInPage @req, @res, @next diff --git a/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee b/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee index cdfff78249..4b57ff3697 100644 --- a/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee +++ b/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteControllerTests.coffee @@ -24,11 +24,14 @@ describe "CollaboratorsInviteController", -> addCount: sinon.stub @LimitationsManager = {} + @UserGetter = + getUserByMainEmail: sinon.stub() + getUser: sinon.stub() @CollaboratorsInviteController = SandboxedModule.require modulePath, requires: "../Project/ProjectGetter": @ProjectGetter = {} '../Subscription/LimitationsManager' : @LimitationsManager - '../User/UserGetter': @UserGetter = {getUser: sinon.stub()} + '../User/UserGetter': @UserGetter "./CollaboratorsHandler": @CollaboratorsHandler = {} "./CollaboratorsInviteHandler": @CollaboratorsInviteHandler = {} 'logger-sharelatex': @logger = {err: sinon.stub(), error: sinon.stub(), log: sinon.stub()} @@ -713,7 +716,7 @@ describe "CollaboratorsInviteController", -> beforeEach -> @user = {_id: ObjectId().toString()} - @UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user) + @UserGetter.getUserByMainEmail = sinon.stub().callsArgWith(2, null, @user) it 'should callback with `true`', (done) -> @call (err, shouldAllow) => @@ -725,7 +728,7 @@ describe "CollaboratorsInviteController", -> beforeEach -> @user = null - @UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user) + @UserGetter.getUserByMainEmail = sinon.stub().callsArgWith(2, null, @user) it 'should callback with `false`', (done) -> @call (err, shouldAllow) => @@ -735,15 +738,15 @@ describe "CollaboratorsInviteController", -> it 'should have called getUser', (done) -> @call (err, shouldAllow) => - @UserGetter.getUser.callCount.should.equal 1 - @UserGetter.getUser.calledWith({email: @email}, {_id: 1}).should.equal true + @UserGetter.getUserByMainEmail.callCount.should.equal 1 + @UserGetter.getUserByMainEmail.calledWith(@email, {_id: 1}).should.equal true done() describe 'when getUser produces an error', -> beforeEach -> @user = null - @UserGetter.getUser = sinon.stub().callsArgWith(2, new Error('woops')) + @UserGetter.getUserByMainEmail = sinon.stub().callsArgWith(2, new Error('woops')) it 'should callback with an error', (done) -> @call (err, shouldAllow) => diff --git a/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteHandlerTests.coffee b/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteHandlerTests.coffee index 177c42d4ba..58b373f61f 100644 --- a/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteHandlerTests.coffee +++ b/services/web/test/unit/coffee/Collaborators/CollaboratorsInviteHandlerTests.coffee @@ -605,7 +605,7 @@ describe "CollaboratorsInviteHandler", -> _id: ObjectId() first_name: "jim" @existingUser = {_id: ObjectId()} - @UserGetter.getUser = sinon.stub().callsArgWith(2, null, @existingUser) + @UserGetter.getUserByMainEmail = sinon.stub().callsArgWith(2, null, @existingUser) @fakeProject = _id: @project_id name: "some project" @@ -626,8 +626,8 @@ describe "CollaboratorsInviteHandler", -> it 'should call getUser', (done) -> @call (err) => - @UserGetter.getUser.callCount.should.equal 1 - @UserGetter.getUser.calledWith({email: @invite.email}).should.equal true + @UserGetter.getUserByMainEmail.callCount.should.equal 1 + @UserGetter.getUserByMainEmail.calledWith(@invite.email).should.equal true done() it 'should call getProject', (done) -> @@ -671,7 +671,7 @@ describe "CollaboratorsInviteHandler", -> describe 'when the user does not exist', -> beforeEach -> - @UserGetter.getUser = sinon.stub().callsArgWith(2, null, null) + @UserGetter.getUserByMainEmail = sinon.stub().callsArgWith(2, null, null) it 'should not produce an error', (done) -> @call (err) => @@ -680,8 +680,8 @@ describe "CollaboratorsInviteHandler", -> it 'should call getUser', (done) -> @call (err) => - @UserGetter.getUser.callCount.should.equal 1 - @UserGetter.getUser.calledWith({email: @invite.email}).should.equal true + @UserGetter.getUserByMainEmail.callCount.should.equal 1 + @UserGetter.getUserByMainEmail.calledWith(@invite.email).should.equal true done() it 'should not call getProject', (done) -> @@ -698,7 +698,7 @@ describe "CollaboratorsInviteHandler", -> describe 'when the getUser produces an error', -> beforeEach -> - @UserGetter.getUser = sinon.stub().callsArgWith(2, new Error('woops')) + @UserGetter.getUserByMainEmail = sinon.stub().callsArgWith(2, new Error('woops')) it 'should produce an error', (done) -> @call (err) => @@ -707,8 +707,8 @@ describe "CollaboratorsInviteHandler", -> it 'should call getUser', (done) -> @call (err) => - @UserGetter.getUser.callCount.should.equal 1 - @UserGetter.getUser.calledWith({email: @invite.email}).should.equal true + @UserGetter.getUserByMainEmail.callCount.should.equal 1 + @UserGetter.getUserByMainEmail.calledWith(@invite.email).should.equal true done() it 'should not call getProject', (done) -> diff --git a/services/web/test/unit/coffee/Exports/ExportsControllerTests.coffee b/services/web/test/unit/coffee/Exports/ExportsControllerTests.coffee new file mode 100644 index 0000000000..821eb5b4e0 --- /dev/null +++ b/services/web/test/unit/coffee/Exports/ExportsControllerTests.coffee @@ -0,0 +1,39 @@ +SandboxedModule = require('sandboxed-module') +assert = require('assert') +chai = require('chai') +expect = chai.expect +sinon = require('sinon') +modulePath = require('path').join __dirname, '../../../../app/js/Features/Exports/ExportsController.js' + + +describe 'ExportsController', -> + project_id = "123njdskj9jlk" + user_id = "123nd3ijdks" + brand_variation_id = 22 + + beforeEach -> + @handler = + getUserNotifications: sinon.stub().callsArgWith(1) + @req = + params: + project_id: project_id + brand_variation_id: brand_variation_id + session: + user: + _id:user_id + i18n: + translate:-> + @AuthenticationController = + getLoggedInUserId: sinon.stub().returns(@req.session.user._id) + @controller = SandboxedModule.require modulePath, requires: + "./ExportsHandler":@handler + 'logger-sharelatex': + log:-> + err:-> + '../Authentication/AuthenticationController': @AuthenticationController + + it 'should ask the handler to perform the export', (done) -> + @handler.exportProject = sinon.stub().yields(null, {iAmAnExport: true, v1_id: 897}) + @controller.exportProject @req, send:(body) => + expect(body).to.deep.equal {export_v1_id: 897} + done() diff --git a/services/web/test/unit/coffee/Exports/ExportsHandlerTests.coffee b/services/web/test/unit/coffee/Exports/ExportsHandlerTests.coffee new file mode 100644 index 0000000000..6333db8270 --- /dev/null +++ b/services/web/test/unit/coffee/Exports/ExportsHandlerTests.coffee @@ -0,0 +1,202 @@ +sinon = require('sinon') +chai = require('chai') +should = chai.should() +expect = chai.expect +modulePath = '../../../../app/js/Features/Exports/ExportsHandler.js' +SandboxedModule = require('sandboxed-module') + +describe 'ExportsHandler', -> + + beforeEach -> + @ProjectGetter = {} + @ProjectLocator = {} + @UserGetter = {} + @settings = {} + @stubRequest = {} + @request = defaults: => return @stubRequest + @ExportsHandler = SandboxedModule.require modulePath, requires: + 'logger-sharelatex': + log: -> + err: -> + '../Project/ProjectGetter': @ProjectGetter + '../Project/ProjectLocator': @ProjectLocator + '../User/UserGetter': @UserGetter + 'settings-sharelatex': @settings + 'request': @request + @project_id = "project-id-123" + @project_history_id = 987 + @user_id = "user-id-456" + @brand_variation_id = 789 + @callback = sinon.stub() + + describe 'exportProject', -> + beforeEach (done) -> + @export_data = {iAmAnExport: true} + @response_body = {iAmAResponseBody: true} + @ExportsHandler._buildExport = sinon.stub().yields(null, @export_data) + @ExportsHandler._requestExport = sinon.stub().yields(null, @response_body) + @ExportsHandler.exportProject @project_id, @user_id, @brand_variation_id, (error, export_data) => + @callback(error, export_data) + done() + + it "should build the export", -> + @ExportsHandler._buildExport + .calledWith(@project_id, @user_id, @brand_variation_id) + .should.equal true + + it "should request the export", -> + @ExportsHandler._requestExport + .calledWith(@export_data) + .should.equal true + + it "should return the export", -> + @callback + .calledWith(null, @export_data) + .should.equal true + + describe '_buildExport', -> + beforeEach (done) -> + @project = + id: @project_id + overleaf: + history: + id: @project_history_id + @user = + id: @user_id + first_name: 'Arthur' + last_name: 'Author' + email: 'arthur.author@arthurauthoring.org' + @rootDocPath = 'main.tex' + @historyVersion = 777 + @ProjectGetter.getProject = sinon.stub().yields(null, @project) + @ProjectLocator.findRootDoc = sinon.stub().yields(null, [null, {fileSystem: 'main.tex'}]) + @UserGetter.getUser = sinon.stub().yields(null, @user) + @ExportsHandler._requestVersion = sinon.stub().yields(null, @historyVersion) + done() + + describe "when all goes well", -> + beforeEach (done) -> + @ExportsHandler._buildExport @project_id, @user_id, @brand_variation_id, (error, export_data) => + @callback(error, export_data) + done() + + it "should request the project history version", -> + @ExportsHandler._requestVersion.called + .should.equal true + + it "should return export data", -> + expected_export_data = + project: + id: @project_id + rootDocPath: @rootDocPath + historyId: @project_history_id + historyVersion: @historyVersion + user: + id: @user_id + firstName: @user.first_name + lastName: @user.last_name + email: @user.email + orcidId: null + destination: + brandVariationId: @brand_variation_id + options: + callbackUrl: null + @callback.calledWith(null, expected_export_data) + .should.equal true + + describe "when project is not found", -> + beforeEach (done) -> + @ProjectGetter.getProject = sinon.stub().yields(new Error("project not found")) + @ExportsHandler._buildExport @project_id, @user_id, @brand_variation_id, (error, export_data) => + @callback(error, export_data) + done() + + it "should return an error", -> + (@callback.args[0][0] instanceof Error) + .should.equal true + + describe "when project has no root doc", -> + beforeEach (done) -> + @ProjectLocator.findRootDoc = sinon.stub().yields(null, [null, null]) + @ExportsHandler._buildExport @project_id, @user_id, @brand_variation_id, (error, export_data) => + @callback(error, export_data) + done() + + it "should return an error", -> + (@callback.args[0][0] instanceof Error) + .should.equal true + + describe "when user is not found", -> + beforeEach (done) -> + @UserGetter.getUser = sinon.stub().yields(new Error("user not found")) + @ExportsHandler._buildExport @project_id, @user_id, @brand_variation_id, (error, export_data) => + @callback(error, export_data) + done() + + it "should return an error", -> + (@callback.args[0][0] instanceof Error) + .should.equal true + + describe "when project history request fails", -> + beforeEach (done) -> + @ExportsHandler._requestVersion = sinon.stub().yields(new Error("project history call failed")) + @ExportsHandler._buildExport @project_id, @user_id, @brand_variation_id, (error, export_data) => + @callback(error, export_data) + done() + + it "should return an error", -> + (@callback.args[0][0] instanceof Error) + .should.equal true + + describe '_requestExport', -> + beforeEach (done) -> + @settings.apis = + v1: + url: 'http://localhost:5000' + user: 'overleaf' + pass: 'pass' + @export_data = {iAmAnExport: true} + @export_id = 4096 + @stubPost = sinon.stub().yields(null, {statusCode: 200}, { exportId: @export_id }) + done() + + describe "when all goes well", -> + beforeEach (done) -> + @stubRequest.post = @stubPost + @ExportsHandler._requestExport @export_data, (error, export_v1_id) => + @callback(error, export_v1_id) + done() + + it 'should issue the request', -> + expect(@stubPost.getCall(0).args[0]).to.deep.equal + url: @settings.apis.v1.url + '/api/v1/sharelatex/exports' + auth: + user: @settings.apis.v1.user + pass: @settings.apis.v1.pass + json: @export_data + + it 'should return the v1 export id', -> + @callback.calledWith(null, @export_id) + .should.equal true + + describe "when the request fails", -> + beforeEach (done) -> + @stubRequest.post = sinon.stub().yields(new Error("export request failed")) + @ExportsHandler._requestExport @export_data, (error, export_v1_id) => + @callback(error, export_v1_id) + done() + + it "should return an error", -> + (@callback.args[0][0] instanceof Error) + .should.equal true + + describe "when the request returns an error code", -> + beforeEach (done) -> + @stubRequest.post = sinon.stub().yields(null, {statusCode: 401}, { }) + @ExportsHandler._requestExport @export_data, (error, export_v1_id) => + @callback(error, export_v1_id) + done() + + it "should return the error", -> + (@callback.args[0][0] instanceof Error) + .should.equal true diff --git a/services/web/test/unit/coffee/PasswordReset/PasswordResetHandlerTests.coffee b/services/web/test/unit/coffee/PasswordReset/PasswordResetHandlerTests.coffee index b29839246a..261f5582dd 100644 --- a/services/web/test/unit/coffee/PasswordReset/PasswordResetHandlerTests.coffee +++ b/services/web/test/unit/coffee/PasswordReset/PasswordResetHandlerTests.coffee @@ -16,7 +16,7 @@ describe "PasswordResetHandler", -> getNewToken:sinon.stub() getValueFromTokenAndExpire:sinon.stub() @UserGetter = - getUser:sinon.stub() + getUserByMainEmail:sinon.stub() @EmailHandler = sendEmail:sinon.stub() @AuthenticationManager = @@ -40,7 +40,7 @@ describe "PasswordResetHandler", -> describe "generateAndEmailResetToken", -> it "should check the user exists", (done)-> - @UserGetter.getUser.callsArgWith(1) + @UserGetter.getUserByMainEmail.callsArgWith(1) @OneTimeTokenHandler.getNewToken.callsArgWith(1) @PasswordResetHandler.generateAndEmailResetToken @user.email, (err, exists)=> exists.should.equal false @@ -49,7 +49,7 @@ describe "PasswordResetHandler", -> it "should send the email with the token", (done)-> - @UserGetter.getUser.callsArgWith(1, null, @user) + @UserGetter.getUserByMainEmail.callsArgWith(1, null, @user) @OneTimeTokenHandler.getNewToken.callsArgWith(1, null, @token) @EmailHandler.sendEmail.callsArgWith(2) @PasswordResetHandler.generateAndEmailResetToken @user.email, (err, exists)=> @@ -62,7 +62,7 @@ describe "PasswordResetHandler", -> it "should return exists = false for a holdingAccount", (done) -> @user.holdingAccount = true - @UserGetter.getUser.callsArgWith(1, null, @user) + @UserGetter.getUserByMainEmail.callsArgWith(1, null, @user) @OneTimeTokenHandler.getNewToken.callsArgWith(1) @PasswordResetHandler.generateAndEmailResetToken @user.email, (err, exists)=> exists.should.equal false diff --git a/services/web/test/unit/coffee/Project/ProjectControllerTests.coffee b/services/web/test/unit/coffee/Project/ProjectControllerTests.coffee index 4e1402429b..ec7246754c 100644 --- a/services/web/test/unit/coffee/Project/ProjectControllerTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectControllerTests.coffee @@ -15,6 +15,7 @@ describe "ProjectController", -> @user = _id:"588f3ddae8ebc1bac07c9fa4" first_name: "bjkdsjfk" + features: {} @settings = apis: chat: @@ -300,6 +301,33 @@ describe "ProjectController", -> done() @ProjectController.projectListPage @req, @res + describe 'front widget', (done) -> + beforeEach -> + @settings.overleaf = + front_chat_widget_room_id: 'chat-room-id' + + it 'should show for paid users', (done) -> + @user.features.github = true + @user.features.dropbox = true + @res.render = (pageName, opts)=> + opts.frontChatWidgetRoomId.should.equal @settings.overleaf.front_chat_widget_room_id + done() + @ProjectController.projectListPage @req, @res + + it 'should show for sample users', (done) -> + @user._id = '588f3ddae8ebc1bac07c9f00' # last two digits + @res.render = (pageName, opts)=> + opts.frontChatWidgetRoomId.should.equal @settings.overleaf.front_chat_widget_room_id + done() + @ProjectController.projectListPage @req, @res + + it 'should not show for non sample users', (done) -> + @user._id = '588f3ddae8ebc1bac07c9fff' # last two digits + @res.render = (pageName, opts)=> + expect(opts.frontChatWidgetRoomId).to.equal undefined + done() + @ProjectController.projectListPage @req, @res + describe 'with overleaf-integration-web-module hook', -> beforeEach -> @V1Response = diff --git a/services/web/test/unit/coffee/Project/ProjectCreationHandlerTests.coffee b/services/web/test/unit/coffee/Project/ProjectCreationHandlerTests.coffee index 042dc34d09..6d86545622 100644 --- a/services/web/test/unit/coffee/Project/ProjectCreationHandlerTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectCreationHandlerTests.coffee @@ -98,7 +98,11 @@ describe 'ProjectCreationHandler', -> it "should set the overleaf id if overleaf id provided", (done)-> overleaf_id = 2345 - @handler.createBlankProject ownerId, projectName, overleaf_id, (err, project)-> + attributes = + overleaf: + history: + id: overleaf_id + @handler.createBlankProject ownerId, projectName, attributes, (err, project)-> project.overleaf.history.id.should.equal overleaf_id done() diff --git a/services/web/test/unit/coffee/Project/ProjectEntityUpdateHandlerTests.coffee b/services/web/test/unit/coffee/Project/ProjectEntityUpdateHandlerTests.coffee index 2838db9c56..3a5b7a58bd 100644 --- a/services/web/test/unit/coffee/Project/ProjectEntityUpdateHandlerTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectEntityUpdateHandlerTests.coffee @@ -872,6 +872,12 @@ describe 'ProjectEntityUpdateHandler', -> .calledWith(@project, @entity, @path, userId) .should.equal true + it "should should send the update to the doc updater", -> + oldDocs = [ doc: @entity, path: @path ] + @DocumentUpdaterHandler.updateProjectStructure + .calledWith(project_id, projectHistoryId, userId, {oldDocs}) + .should.equal true + describe "a folder", -> beforeEach (done) -> @folder = @@ -905,6 +911,13 @@ describe 'ProjectEntityUpdateHandler', -> .calledWith(@project, @doc2, "/folder/doc-name-2", userId) .should.equal true + it "should should send one update to the doc updater for all docs and files", -> + oldFiles = [ {file: @file2, path: "/folder/file-name-2"}, {file: @file1, path: "/folder/subfolder/file-name-1"} ] + oldDocs = [ {doc: @doc2, path: "/folder/doc-name-2"}, { doc: @doc1, path: "/folder/subfolder/doc-name-1"} ] + @DocumentUpdaterHandler.updateProjectStructure + .calledWith(project_id, projectHistoryId, userId, {oldFiles, oldDocs}) + .should.equal true + describe "_cleanUpDoc", -> beforeEach -> @doc = @@ -941,12 +954,6 @@ describe 'ProjectEntityUpdateHandler', -> .calledWith(project_id, @doc._id.toString()) .should.equal true - it "should should send the update to the doc updater", -> - oldDocs = [ doc: @doc, path: @path ] - @DocumentUpdaterHandler.updateProjectStructure - .calledWith(project_id, projectHistoryId, userId, {oldDocs}) - .should.equal true - it "should call the callback", -> @callback.called.should.equal true diff --git a/services/web/test/unit/coffee/Referal/ReferalAllocatorTests.coffee b/services/web/test/unit/coffee/Referal/ReferalAllocatorTests.coffee index 3fe925f91c..308c2ce80e 100644 --- a/services/web/test/unit/coffee/Referal/ReferalAllocatorTests.coffee +++ b/services/web/test/unit/coffee/Referal/ReferalAllocatorTests.coffee @@ -4,12 +4,12 @@ require('chai').should() sinon = require('sinon') modulePath = require('path').join __dirname, '../../../../app/js/Features/Referal/ReferalAllocator.js' -describe 'Referalallocator', -> +describe 'ReferalAllocator', -> beforeEach -> @ReferalAllocator = SandboxedModule.require modulePath, requires: '../../models/User': User: @User = {} - "../Subscription/SubscriptionLocator": @SubscriptionLocator = {} + "../Subscription/FeaturesUpdater": @FeaturesUpdater = {} "settings-sharelatex": @Settings = {} 'logger-sharelatex': log:-> @@ -26,7 +26,7 @@ describe 'Referalallocator', -> @referal_source = "bonus" @User.update = sinon.stub().callsArgWith 3, null @User.findOne = sinon.stub().callsArgWith 1, null, { _id: @user_id } - @ReferalAllocator.assignBonus = sinon.stub().callsArg 1 + @FeaturesUpdater.refreshFeatures = sinon.stub().yields() @ReferalAllocator.allocate @referal_id, @new_user_id, @referal_source, @referal_medium, @callback it 'should update the referring user with the refered users id', -> @@ -44,8 +44,8 @@ describe 'Referalallocator', -> .calledWith( referal_id: @referal_id ) .should.equal true - it "shoudl assign the user their bonus", -> - @ReferalAllocator.assignBonus + it "should refresh the user's subscription", -> + @FeaturesUpdater.refreshFeatures .calledWith(@user_id) .should.equal true @@ -57,7 +57,7 @@ describe 'Referalallocator', -> @referal_source = "public_share" @User.update = sinon.stub().callsArgWith 3, null @User.findOne = sinon.stub().callsArgWith 1, null, { _id: @user_id } - @ReferalAllocator.assignBonus = sinon.stub().callsArg 1 + @FeaturesUpdater.refreshFeatures = sinon.stub().yields() @ReferalAllocator.allocate @referal_id, @new_user_id, @referal_source, @referal_medium, @callback it 'should not update the referring user with the refered users id', -> @@ -69,118 +69,7 @@ describe 'Referalallocator', -> .should.equal true it "should not assign the user a bonus", -> - @ReferalAllocator.assignBonus.called.should.equal false + @FeaturesUpdater.refreshFeatures.called.should.equal false it "should call the callback", -> @callback.called.should.equal true - - describe "assignBonus", -> - beforeEach -> - @refered_user_count = 3 - @Settings.bonus_features = - "3": - collaborators: 3 - dropbox: false - versioning: false - stubbedUser = { - refered_user_count: @refered_user_count, - features:{collaborators:1, dropbox:false, versioning:false} - } - - @User.findOne = sinon.stub().callsArgWith 1, null, stubbedUser - @User.update = sinon.stub().callsArgWith 2, null - @ReferalAllocator.assignBonus @user_id, @callback - - it "should get the users number of refered user", -> - @User.findOne - .calledWith(_id: @user_id) - .should.equal true - - it "should update the user to bonus features", -> - @User.update - .calledWith({ - _id: @user_id - }, { - $set: - features: - @Settings.bonus_features[@refered_user_count.toString()] - }) - .should.equal true - - it "should call the callback", -> - @callback.called.should.equal true - - describe "when there is nothing to assign", -> - - beforeEach -> - @ReferalAllocator._calculateBonuses = sinon.stub().returns({}) - @stubbedUser = - refered_user_count:4 - features:{collaborators:3, versioning:true, dropbox:false} - @Settings.bonus_features = - "4": - collaborators:3 - versioning:true - dropbox:false - @User.findOne = sinon.stub().callsArgWith 1, null, @stubbedUser - @User.update = sinon.stub().callsArgWith 2, null - - it "should not call update if there are no bonuses to apply", (done)-> - @ReferalAllocator.assignBonus @user_id, (err)=> - @User.update.called.should.equal false - done() - - describe "when the user has better features already", -> - - beforeEach -> - @refered_user_count = 3 - @stubbedUser = - refered_user_count:4 - features: - collaborators:3 - dropbox:false - versioning:false - @Settings.bonus_features = - "4": - collaborators: 10 - dropbox: true - versioning: false - - @User.findOne = sinon.stub().callsArgWith 1, null, @stubbedUser - @User.update = sinon.stub().callsArgWith 2, null - - it "should not set in in mongo when the feature is better", (done)-> - @ReferalAllocator.assignBonus @user_id, => - @User.update.calledWith({_id: @user_id }, {$set: features:{dropbox:true, versioning:false, collaborators:10} }).should.equal true - done() - - it "should not overright if the user has -1 users", (done)-> - @stubbedUser.features.collaborators = -1 - @ReferalAllocator.assignBonus @user_id, => - @User.update.calledWith({_id: @user_id }, {$set: features:{dropbox:true, versioning:false, collaborators:-1} }).should.equal true - done() - - describe "when the user is not at a bonus level", -> - beforeEach -> - @refered_user_count = 0 - @Settings.bonus_features = - "1": - collaborators: 3 - dropbox: false - versioning: false - @User.findOne = sinon.stub().callsArgWith 1, null, { refered_user_count: @refered_user_count } - @User.update = sinon.stub().callsArgWith 2, null - @ReferalAllocator.assignBonus @user_id, @callback - - it "should get the users number of refered user", -> - @User.findOne - .calledWith(_id: @user_id) - .should.equal true - - it "should not update the user to bonus features", -> - @User.update.called.should.equal false - - it "should call the callback", -> - @callback.called.should.equal true - - diff --git a/services/web/test/unit/coffee/Referal/ReferalFeaturesTests.coffee b/services/web/test/unit/coffee/Referal/ReferalFeaturesTests.coffee new file mode 100644 index 0000000000..dfa70f8ebe --- /dev/null +++ b/services/web/test/unit/coffee/Referal/ReferalFeaturesTests.coffee @@ -0,0 +1,65 @@ +SandboxedModule = require('sandboxed-module') +assert = require('assert') +require('chai').should() +sinon = require('sinon') +modulePath = require('path').join __dirname, '../../../../app/js/Features/Referal/ReferalFeatures.js' + +describe 'ReferalFeatures', -> + + beforeEach -> + @ReferalFeatures = SandboxedModule.require modulePath, requires: + '../../models/User': User: @User = {} + "settings-sharelatex": @Settings = {} + 'logger-sharelatex': + log:-> + err:-> + @callback = sinon.stub() + @referal_id = "referal-id-123" + @referal_medium = "twitter" + @user_id = "user-id-123" + @new_user_id = "new-user-id-123" + + describe "getBonusFeatures", -> + beforeEach -> + @refered_user_count = 3 + @Settings.bonus_features = + "3": + collaborators: 3 + dropbox: false + versioning: false + stubbedUser = { + refered_user_count: @refered_user_count, + features:{collaborators:1, dropbox:false, versioning:false} + } + + @User.findOne = sinon.stub().callsArgWith 1, null, stubbedUser + @ReferalFeatures.getBonusFeatures @user_id, @callback + + it "should get the users number of refered user", -> + @User.findOne + .calledWith(_id: @user_id) + .should.equal true + + it "should call the callback with the features", -> + @callback.calledWith(null, @Settings.bonus_features[3]).should.equal true + + describe "when the user is not at a bonus level", -> + beforeEach -> + @refered_user_count = 0 + @Settings.bonus_features = + "1": + collaborators: 3 + dropbox: false + versioning: false + @User.findOne = sinon.stub().callsArgWith 1, null, { refered_user_count: @refered_user_count } + @ReferalFeatures.getBonusFeatures @user_id, @callback + + it "should get the users number of refered user", -> + @User.findOne + .calledWith(_id: @user_id) + .should.equal true + + it "should call the callback with no features", -> + @callback.calledWith(null, {}).should.equal true + + diff --git a/services/web/test/unit/coffee/References/ReferencesHandlerTests.coffee b/services/web/test/unit/coffee/References/ReferencesHandlerTests.coffee index c69cb3ee0a..9ca565b24d 100644 --- a/services/web/test/unit/coffee/References/ReferencesHandlerTests.coffee +++ b/services/web/test/unit/coffee/References/ReferencesHandlerTests.coffee @@ -385,3 +385,13 @@ describe 'ReferencesHandler', -> @call (err, isFullIndex) => expect(err).to.equal null expect(isFullIndex).to.equal false + + describe 'with referencesSearch', -> + + beforeEach -> + @owner.features = {referencesSearch: true, references: false} + + it 'should return true', -> + @call (err, isFullIndex) => + expect(err).to.equal null + expect(isFullIndex).to.equal true diff --git a/services/web/test/unit/coffee/Subscription/FeaturesUpdaterTests.coffee b/services/web/test/unit/coffee/Subscription/FeaturesUpdaterTests.coffee new file mode 100644 index 0000000000..c8f77bfd40 --- /dev/null +++ b/services/web/test/unit/coffee/Subscription/FeaturesUpdaterTests.coffee @@ -0,0 +1,173 @@ +SandboxedModule = require('sandboxed-module') +should = require('chai').should() +expect = require('chai').expect +sinon = require 'sinon' +modulePath = "../../../../app/js/Features/Subscription/FeaturesUpdater" +assert = require("chai").assert +ObjectId = require('mongoose').Types.ObjectId + +describe "FeaturesUpdater", -> + + beforeEach -> + @user_id = ObjectId().toString() + + @FeaturesUpdater = SandboxedModule.require modulePath, requires: + './UserFeaturesUpdater': @UserFeaturesUpdater = {} + './SubscriptionLocator': @SubscriptionLocator = {} + './PlansLocator': @PlansLocator = {} + "logger-sharelatex": log:-> + 'settings-sharelatex': @Settings = {} + "../Referal/ReferalFeatures" : @ReferalFeatures = {} + "./V1SubscriptionManager": @V1SubscriptionManager = {} + + describe "refreshFeatures", -> + beforeEach -> + @UserFeaturesUpdater.updateFeatures = sinon.stub().yields() + @FeaturesUpdater._getIndividualFeatures = sinon.stub().yields(null, { 'individual': 'features' }) + @FeaturesUpdater._getGroupFeatureSets = sinon.stub().yields(null, [{ 'group': 'features' }, { 'group': 'features2' }]) + @FeaturesUpdater._getV1Features = sinon.stub().yields(null, { 'v1': 'features' }) + @ReferalFeatures.getBonusFeatures = sinon.stub().yields(null, { 'bonus': 'features' }) + @FeaturesUpdater._mergeFeatures = sinon.stub().returns({'merged': 'features'}) + @callback = sinon.stub() + @FeaturesUpdater.refreshFeatures @user_id, @callback + + it "should get the individual features", -> + @FeaturesUpdater._getIndividualFeatures + .calledWith(@user_id) + .should.equal true + + it "should get the group features", -> + @FeaturesUpdater._getGroupFeatureSets + .calledWith(@user_id) + .should.equal true + + it "should get the v1 features", -> + @FeaturesUpdater._getV1Features + .calledWith(@user_id) + .should.equal true + + it "should get the bonus features", -> + @ReferalFeatures.getBonusFeatures + .calledWith(@user_id) + .should.equal true + + it "should merge from the default features", -> + @FeaturesUpdater._mergeFeatures.calledWith(@Settings.defaultFeatures).should.equal true + + it "should merge the individual features", -> + @FeaturesUpdater._mergeFeatures.calledWith(sinon.match.any, { 'individual': 'features' }).should.equal true + + it "should merge the group features", -> + @FeaturesUpdater._mergeFeatures.calledWith(sinon.match.any, { 'group': 'features' }).should.equal true + @FeaturesUpdater._mergeFeatures.calledWith(sinon.match.any, { 'group': 'features2' }).should.equal true + + it "should merge the v1 features", -> + @FeaturesUpdater._mergeFeatures.calledWith(sinon.match.any, { 'v1': 'features' }).should.equal true + + it "should merge the bonus features", -> + @FeaturesUpdater._mergeFeatures.calledWith(sinon.match.any, { 'bonus': 'features' }).should.equal true + + it "should update the user with the merged features", -> + @UserFeaturesUpdater.updateFeatures + .calledWith(@user_id, {'merged': 'features'}) + .should.equal true + + describe "_mergeFeatures", -> + it "should prefer priority over standard for compileGroup", -> + expect(@FeaturesUpdater._mergeFeatures({ + compileGroup: 'priority' + }, { + compileGroup: 'standard' + })).to.deep.equal({ + compileGroup: 'priority' + }) + expect(@FeaturesUpdater._mergeFeatures({ + compileGroup: 'standard' + }, { + compileGroup: 'priority' + })).to.deep.equal({ + compileGroup: 'priority' + }) + expect(@FeaturesUpdater._mergeFeatures({ + compileGroup: 'priority' + }, { + compileGroup: 'priority' + })).to.deep.equal({ + compileGroup: 'priority' + }) + expect(@FeaturesUpdater._mergeFeatures({ + compileGroup: 'standard' + }, { + compileGroup: 'standard' + })).to.deep.equal({ + compileGroup: 'standard' + }) + + it "should prefer -1 over any other for collaborators", -> + expect(@FeaturesUpdater._mergeFeatures({ + collaborators: -1 + }, { + collaborators: 10 + })).to.deep.equal({ + collaborators: -1 + }) + expect(@FeaturesUpdater._mergeFeatures({ + collaborators: 10 + }, { + collaborators: -1 + })).to.deep.equal({ + collaborators: -1 + }) + expect(@FeaturesUpdater._mergeFeatures({ + collaborators: 4 + }, { + collaborators: 10 + })).to.deep.equal({ + collaborators: 10 + }) + + it "should prefer the higher of compileTimeout", -> + expect(@FeaturesUpdater._mergeFeatures({ + compileTimeout: 20 + }, { + compileTimeout: 10 + })).to.deep.equal({ + compileTimeout: 20 + }) + expect(@FeaturesUpdater._mergeFeatures({ + compileTimeout: 10 + }, { + compileTimeout: 20 + })).to.deep.equal({ + compileTimeout: 20 + }) + + it "should prefer the true over false for other keys", -> + expect(@FeaturesUpdater._mergeFeatures({ + github: true + }, { + github: false + })).to.deep.equal({ + github: true + }) + expect(@FeaturesUpdater._mergeFeatures({ + github: false + }, { + github: true + })).to.deep.equal({ + github: true + }) + expect(@FeaturesUpdater._mergeFeatures({ + github: true + }, { + github: true + })).to.deep.equal({ + github: true + }) + expect(@FeaturesUpdater._mergeFeatures({ + github: false + }, { + github: false + })).to.deep.equal({ + github: false + }) diff --git a/services/web/test/unit/coffee/Subscription/SubscriptionControllerTests.coffee b/services/web/test/unit/coffee/Subscription/SubscriptionControllerTests.coffee index 22f571ffa8..04888e2dd7 100644 --- a/services/web/test/unit/coffee/Subscription/SubscriptionControllerTests.coffee +++ b/services/web/test/unit/coffee/Subscription/SubscriptionControllerTests.coffee @@ -75,6 +75,7 @@ describe "SubscriptionController", -> "./SubscriptionDomainHandler":@SubscriptionDomainHandler "../User/UserGetter": @UserGetter "./RecurlyWrapper": @RecurlyWrapper = {} + "./FeaturesUpdater": @FeaturesUpdater = {} @res = new MockResponse() diff --git a/services/web/test/unit/coffee/Subscription/SubscriptionGroupHandlerTests.coffee b/services/web/test/unit/coffee/Subscription/SubscriptionGroupHandlerTests.coffee index 1daf43bbd7..bca9ac7600 100644 --- a/services/web/test/unit/coffee/Subscription/SubscriptionGroupHandlerTests.coffee +++ b/services/web/test/unit/coffee/Subscription/SubscriptionGroupHandlerTests.coffee @@ -24,18 +24,15 @@ describe "SubscriptionGroupHandler", -> getSubscriptionByMemberIdAndId: sinon.stub() getSubscription: sinon.stub() - @UserCreator = - getUserOrCreateHoldingAccount: sinon.stub().callsArgWith(1, null, @user) - @SubscriptionUpdater = addUserToGroup: sinon.stub().callsArgWith(2) removeUserFromGroup: sinon.stub().callsArgWith(2) addEmailInviteToGroup: sinon.stub().callsArgWith(2) removeEmailInviteFromGroup: sinon.stub().callsArgWith(2) - @UserLocator = - findById: sinon.stub() - findByEmail: sinon.stub() + @UserGetter = + getUser: sinon.stub() + getUserByMainEmail: sinon.stub() @LimitationsManager = hasGroupMembersLimitReached: sinon.stub() @@ -59,7 +56,7 @@ describe "SubscriptionGroupHandler", -> "../User/UserCreator": @UserCreator "./SubscriptionUpdater": @SubscriptionUpdater "./SubscriptionLocator": @SubscriptionLocator - "../User/UserLocator": @UserLocator + "../User/UserGetter": @UserGetter "./LimitationsManager": @LimitationsManager "../Security/OneTimeTokenHandler":@OneTimeTokenHandler "../Email/EmailHandler":@EmailHandler @@ -74,11 +71,11 @@ describe "SubscriptionGroupHandler", -> describe "addUserToGroup", -> beforeEach -> @LimitationsManager.hasGroupMembersLimitReached.callsArgWith(1, null, false, @subscription) - @UserLocator.findByEmail.callsArgWith(1, null, @user) + @UserGetter.getUserByMainEmail.callsArgWith(1, null, @user) it "should find the user", (done)-> @Handler.addUserToGroup @adminUser_id, @newEmail, (err)=> - @UserLocator.findByEmail.calledWith(@newEmail).should.equal true + @UserGetter.getUserByMainEmail.calledWith(@newEmail).should.equal true done() it "should add the user to the group", (done)-> @@ -105,7 +102,7 @@ describe "SubscriptionGroupHandler", -> done() it "should add an email invite if no user is found", (done) -> - @UserLocator.findByEmail.callsArgWith(1, null, null) + @UserGetter.getUserByMainEmail.callsArgWith(1, null, null) @Handler.addUserToGroup @adminUser_id, @newEmail, (err)=> @SubscriptionUpdater.addEmailInviteToGroup.calledWith(@adminUser_id, @newEmail).should.equal true done() @@ -122,26 +119,26 @@ describe "SubscriptionGroupHandler", -> beforeEach -> @subscription = {} @SubscriptionLocator.getUsersSubscription.callsArgWith(1, null, @subscription) - @UserLocator.findById.callsArgWith(1, null, {_id:"31232"}) + @UserGetter.getUser.callsArgWith(1, null, {_id:"31232"}) it "should locate the subscription", (done)-> - @UserLocator.findById.callsArgWith(1, null, {_id:"31232"}) + @UserGetter.getUser.callsArgWith(1, null, {_id:"31232"}) @Handler.getPopulatedListOfMembers @adminUser_id, (err, users)=> @SubscriptionLocator.getUsersSubscription.calledWith(@adminUser_id).should.equal true done() it "should get the users by id", (done)-> - @UserLocator.findById.callsArgWith(1, null, {_id:"31232"}) + @UserGetter.getUser.callsArgWith(1, null, {_id:"31232"}) @subscription.member_ids = ["1234", "342432", "312312"] @Handler.getPopulatedListOfMembers @adminUser_id, (err, users)=> - @UserLocator.findById.calledWith(@subscription.member_ids[0]).should.equal true - @UserLocator.findById.calledWith(@subscription.member_ids[1]).should.equal true - @UserLocator.findById.calledWith(@subscription.member_ids[2]).should.equal true + @UserGetter.getUser.calledWith(@subscription.member_ids[0]).should.equal true + @UserGetter.getUser.calledWith(@subscription.member_ids[1]).should.equal true + @UserGetter.getUser.calledWith(@subscription.member_ids[2]).should.equal true users.length.should.equal @subscription.member_ids.length done() it "should just return the id if the user can not be found as they may have deleted their account", (done)-> - @UserLocator.findById.callsArgWith(1) + @UserGetter.getUser.callsArgWith(1) @subscription.member_ids = ["1234", "342432", "312312"] @Handler.getPopulatedListOfMembers @adminUser_id, (err, users)=> assert.deepEqual users[0], {_id:@subscription.member_ids[0]} diff --git a/services/web/test/unit/coffee/Subscription/SubscriptionUpdaterTests.coffee b/services/web/test/unit/coffee/Subscription/SubscriptionUpdaterTests.coffee index 93479a03a1..dd2afad56b 100644 --- a/services/web/test/unit/coffee/Subscription/SubscriptionUpdaterTests.coffee +++ b/services/web/test/unit/coffee/Subscription/SubscriptionUpdaterTests.coffee @@ -1,5 +1,6 @@ SandboxedModule = require('sandboxed-module') should = require('chai').should() +expect = require('chai').expect sinon = require 'sinon' modulePath = "../../../../app/js/Features/Subscription/SubscriptionUpdater" assert = require("chai").assert @@ -22,6 +23,7 @@ describe "SubscriptionUpdater", -> save: sinon.stub().callsArgWith(0) freeTrial:{} planCode:"student_or_something" + @user_id = @adminuser_id @groupSubscription = admin_id: @adminUser._id @@ -48,15 +50,15 @@ describe "SubscriptionUpdater", -> @Settings = freeTrialPlanCode: "collaborator" defaultPlanCode: "personal" + defaultFeatures: { "default": "features" } @UserFeaturesUpdater = - updateFeatures : sinon.stub().callsArgWith(2) + updateFeatures : sinon.stub().yields() @PlansLocator = findLocalPlanInSettings: sinon.stub().returns({}) - @ReferalAllocator = assignBonus:sinon.stub().callsArgWith(1) - @ReferalAllocator.cock = true + @ReferalFeatures = getBonusFeatures: sinon.stub().callsArgWith(1) @Modules = {hooks: {fire: sinon.stub().callsArgWith(2, null, null)}} @SubscriptionUpdater = SandboxedModule.require modulePath, requires: '../../models/Subscription': Subscription:@SubscriptionModel @@ -65,8 +67,7 @@ describe "SubscriptionUpdater", -> './PlansLocator': @PlansLocator "logger-sharelatex": log:-> 'settings-sharelatex': @Settings - "../Referal/ReferalAllocator" : @ReferalAllocator - '../../infrastructure/Modules': @Modules + "./FeaturesUpdater": @FeaturesUpdater = {} describe "syncSubscription", -> @@ -97,7 +98,7 @@ describe "SubscriptionUpdater", -> describe "_updateSubscriptionFromRecurly", -> beforeEach -> - @SubscriptionUpdater._setUsersMinimumFeatures = sinon.stub().callsArgWith(1) + @FeaturesUpdater.refreshFeatures = sinon.stub().callsArgWith(1) it "should update the subscription with token etc when not expired", (done)-> @SubscriptionUpdater._updateSubscriptionFromRecurly @recurlySubscription, @subscription, (err)=> @@ -108,7 +109,7 @@ describe "SubscriptionUpdater", -> assert.equal(@subscription.freeTrial.expiresAt, undefined) assert.equal(@subscription.freeTrial.planCode, undefined) @subscription.save.called.should.equal true - @SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@adminUser._id).should.equal true + @FeaturesUpdater.refreshFeatures.calledWith(@adminUser._id).should.equal true done() it "should remove the recurlySubscription_id when expired", (done)-> @@ -117,15 +118,15 @@ describe "SubscriptionUpdater", -> @SubscriptionUpdater._updateSubscriptionFromRecurly @recurlySubscription, @subscription, (err)=> assert.equal(@subscription.recurlySubscription_id, undefined) @subscription.save.called.should.equal true - @SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@adminUser._id).should.equal true + @FeaturesUpdater.refreshFeatures.calledWith(@adminUser._id).should.equal true done() it "should update all the users features", (done)-> @SubscriptionUpdater._updateSubscriptionFromRecurly @recurlySubscription, @subscription, (err)=> - @SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@adminUser._id).should.equal true - @SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@allUserIds[0]).should.equal true - @SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@allUserIds[1]).should.equal true - @SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@allUserIds[2]).should.equal true + @FeaturesUpdater.refreshFeatures.calledWith(@adminUser._id).should.equal true + @FeaturesUpdater.refreshFeatures.calledWith(@allUserIds[0]).should.equal true + @FeaturesUpdater.refreshFeatures.calledWith(@allUserIds[1]).should.equal true + @FeaturesUpdater.refreshFeatures.calledWith(@allUserIds[2]).should.equal true done() it "should set group to true and save how many members can be added to group", (done)-> @@ -152,6 +153,9 @@ describe "SubscriptionUpdater", -> done() describe "addUserToGroup", -> + beforeEach -> + @FeaturesUpdater.refreshFeatures = sinon.stub().callsArgWith(1) + it "should add the users id to the group as a set", (done)-> @SubscriptionUpdater.addUserToGroup @adminUser._id, @otherUserId, => searchOps = @@ -163,12 +167,12 @@ describe "SubscriptionUpdater", -> it "should update the users features", (done)-> @SubscriptionUpdater.addUserToGroup @adminUser._id, @otherUserId, => - @UserFeaturesUpdater.updateFeatures.calledWith(@otherUserId, @subscription.planCode).should.equal true + @FeaturesUpdater.refreshFeatures.calledWith(@otherUserId).should.equal true done() describe "removeUserFromGroup", -> beforeEach -> - @SubscriptionUpdater._setUsersMinimumFeatures = sinon.stub().callsArgWith(1) + @FeaturesUpdater.refreshFeatures = sinon.stub().callsArgWith(1) it "should pull the users id from the group", (done)-> @SubscriptionUpdater.removeUserFromGroup @adminUser._id, @otherUserId, => @@ -181,69 +185,7 @@ describe "SubscriptionUpdater", -> it "should update the users features", (done)-> @SubscriptionUpdater.removeUserFromGroup @adminUser._id, @otherUserId, => - @SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@otherUserId).should.equal true - done() - - describe "_setUsersMinimumFeatures", -> - - it "should call updateFeatures with the subscription if set", (done)-> - @SubscriptionLocator.getUsersSubscription.callsArgWith(1, null, @subscription) - @SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null) - - @SubscriptionUpdater._setUsersMinimumFeatures @adminUser._id, (err)=> - args = @UserFeaturesUpdater.updateFeatures.args[0] - assert.equal args[0], @adminUser._id - assert.equal args[1], @subscription.planCode - done() - - it "should call updateFeatures with the group subscription if set", (done)-> - @SubscriptionLocator.getUsersSubscription.callsArgWith(1, null) - @SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null, @groupSubscription) - - @SubscriptionUpdater._setUsersMinimumFeatures @adminUser._id, (err)=> - args = @UserFeaturesUpdater.updateFeatures.args[0] - assert.equal args[0], @adminUser._id - assert.equal args[1], @groupSubscription.planCode - done() - - it "should call updateFeatures with the overleaf subscription if set", (done)-> - @SubscriptionLocator.getUsersSubscription.callsArgWith(1, null) - @SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null, null) - @Modules.hooks.fire = sinon.stub().callsArgWith(2, null, ['ol_pro']) - - @SubscriptionUpdater._setUsersMinimumFeatures @adminUser._id, (err)=> - args = @UserFeaturesUpdater.updateFeatures.args[0] - assert.equal args[0], @adminUser._id - assert.equal args[1], 'ol_pro' - done() - - it "should call not call updateFeatures with users subscription if the subscription plan code is the default one (downgraded)", (done)-> - @subscription.planCode = @Settings.defaultPlanCode - @SubscriptionLocator.getUsersSubscription.callsArgWith(1, null, @subscription) - @SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null, @groupSubscription) - @Modules.hooks.fire = sinon.stub().callsArgWith(2, null, null) - @SubscriptionUpdater._setUsersMinimumFeatures @adminuser_id, (err)=> - args = @UserFeaturesUpdater.updateFeatures.args[0] - assert.equal args[0], @adminUser._id - assert.equal args[1], @groupSubscription.planCode - done() - - - it "should call updateFeatures with default if there are no subscriptions for user", (done)-> - @SubscriptionLocator.getUsersSubscription.callsArgWith(1, null) - @SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null) - @Modules.hooks.fire = sinon.stub().callsArgWith(2, null, null) - @SubscriptionUpdater._setUsersMinimumFeatures @adminuser_id, (err)=> - args = @UserFeaturesUpdater.updateFeatures.args[0] - assert.equal args[0], @adminUser._id - assert.equal args[1], @Settings.defaultPlanCode - done() - - it "should call assignBonus", (done)-> - @SubscriptionLocator.getUsersSubscription.callsArgWith(1, null) - @SubscriptionLocator.getGroupSubscriptionMemberOf.callsArgWith(1, null) - @SubscriptionUpdater._setUsersMinimumFeatures @adminuser_id, (err)=> - @ReferalAllocator.assignBonus.calledWith(@adminuser_id).should.equal true + @FeaturesUpdater.refreshFeatures.calledWith(@otherUserId).should.equal true done() describe "deleteSubscription", -> @@ -255,7 +197,7 @@ describe "SubscriptionUpdater", -> member_ids: [ ObjectId(), ObjectId(), ObjectId() ] } @SubscriptionLocator.getSubscription = sinon.stub().yields(null, @subscription) - @SubscriptionUpdater._setUsersMinimumFeatures = sinon.stub().yields() + @FeaturesUpdater.refreshFeatures = sinon.stub().yields() @SubscriptionUpdater.deleteSubscription @subscription_id, done it "should look up the subscription", -> @@ -269,22 +211,12 @@ describe "SubscriptionUpdater", -> .should.equal true it "should downgrade the admin_id", -> - @SubscriptionUpdater._setUsersMinimumFeatures + @FeaturesUpdater.refreshFeatures .calledWith(@subscription.admin_id) .should.equal true it "should downgrade all of the members", -> for user_id in @subscription.member_ids - @SubscriptionUpdater._setUsersMinimumFeatures + @FeaturesUpdater.refreshFeatures .calledWith(user_id) .should.equal true - - describe 'refreshSubscription', -> - beforeEach -> - @SubscriptionUpdater._setUsersMinimumFeatures = sinon.stub() - .callsArgWith(1, null) - - it 'should call to _setUsersMinimumFeatures', -> - @SubscriptionUpdater.refreshSubscription(@adminUser._id, ()->) - @SubscriptionUpdater._setUsersMinimumFeatures.callCount.should.equal 1 - @SubscriptionUpdater._setUsersMinimumFeatures.calledWith(@adminUser._id).should.equal true diff --git a/services/web/test/unit/coffee/Subscription/UserFeaturesUpdaterTests.coffee b/services/web/test/unit/coffee/Subscription/UserFeaturesUpdaterTests.coffee index d388d67c3b..1ba4e0b32f 100644 --- a/services/web/test/unit/coffee/Subscription/UserFeaturesUpdaterTests.coffee +++ b/services/web/test/unit/coffee/Subscription/UserFeaturesUpdaterTests.coffee @@ -4,31 +4,20 @@ sinon = require 'sinon' modulePath = "../../../../app/js/Features/Subscription/UserFeaturesUpdater" assert = require("chai").assert - describe "UserFeaturesUpdater", -> - beforeEach -> - @User = update: sinon.stub().callsArgWith(2) - - @PlansLocator = - findLocalPlanInSettings : sinon.stub() - @UserFeaturesUpdater = SandboxedModule.require modulePath, requires: '../../models/User': User:@User "logger-sharelatex": log:-> - './PlansLocator': @PlansLocator describe "updateFeatures", -> - it "should send the users features", (done)-> user_id = "5208dd34438842e2db000005" - plan_code = "student" - @features = features:{versioning:true, collaborators:10} - @PlansLocator.findLocalPlanInSettings = sinon.stub().returns(@features) - @UserFeaturesUpdater.updateFeatures user_id, plan_code, (err, features)=> + @features = {versioning:true, collaborators:10} + @UserFeaturesUpdater.updateFeatures user_id, @features, (err, features)=> update = {"features.versioning":true, "features.collaborators":10} @User.update.calledWith({"_id":user_id}, update).should.equal true - features.should.deep.equal @features.features + features.should.deep.equal @features done() \ No newline at end of file diff --git a/services/web/test/unit/coffee/Subscription/V1SusbcriptionManagerTests.coffee b/services/web/test/unit/coffee/Subscription/V1SusbcriptionManagerTests.coffee new file mode 100644 index 0000000000..ae6237e627 --- /dev/null +++ b/services/web/test/unit/coffee/Subscription/V1SusbcriptionManagerTests.coffee @@ -0,0 +1,128 @@ +should = require('chai').should() +SandboxedModule = require('sandboxed-module') +assert = require('assert') +path = require('path') +modulePath = path.join __dirname, '../../../../app/js/Features/Subscription/V1SubscriptionManager' +sinon = require("sinon") +expect = require("chai").expect + + +describe 'V1SubscriptionManager', -> + beforeEach -> + @V1SubscriptionManager = SandboxedModule.require modulePath, requires: + "../User/UserGetter": @UserGetter = {} + "logger-sharelatex": + log: sinon.stub() + err: sinon.stub() + warn: sinon.stub() + "settings-sharelatex": + overleaf: + host: @host = "http://overleaf.example.com" + "request": @request = sinon.stub() + @V1SubscriptionManager._v1PlanRequest = sinon.stub() + @userId = 'abcd' + @v1UserId = 42 + @user = + _id: @userId + email: 'user@example.com' + overleaf: + id: @v1UserId + + describe 'getPlanCodeFromV1', -> + beforeEach -> + @responseBody = + id: 32, + plan_name: 'pro' + @UserGetter.getUser = sinon.stub() + .yields(null, @user) + @V1SubscriptionManager._v1PlanRequest = sinon.stub() + .yields(null, @responseBody) + @call = (cb) => + @V1SubscriptionManager.getPlanCodeFromV1 @userId, cb + + describe 'when all goes well', -> + + it 'should call getUser', (done) -> + @call (err, planCode) => + expect( + @UserGetter.getUser.callCount + ).to.equal 1 + expect( + @UserGetter.getUser.calledWith(@userId) + ).to.equal true + done() + + it 'should call _v1PlanRequest', (done) -> + @call (err, planCode) => + expect( + @V1SubscriptionManager._v1PlanRequest.callCount + ).to.equal 1 + expect( + @V1SubscriptionManager._v1PlanRequest.calledWith( + @v1UserId + ) + ).to.equal true + done() + + it 'should produce a plan-code without error', (done) -> + @call (err, planCode) => + expect(err).to.not.exist + expect(planCode).to.equal 'v1_pro' + done() + + describe 'when the plan_name from v1 is null', -> + beforeEach -> + @responseBody.plan_name = null + + it 'should produce a null plan-code without error', (done) -> + @call (err, planCode) => + expect(err).to.not.exist + expect(planCode).to.equal null + done() + + describe 'when getUser produces an error', -> + beforeEach -> + @UserGetter.getUser = sinon.stub() + .yields(new Error('woops')) + + it 'should not call _v1PlanRequest', (done) -> + @call (err, planCode) => + expect( + @V1SubscriptionManager._v1PlanRequest.callCount + ).to.equal 0 + done() + + it 'should produce an error', (done) -> + @call (err, planCode) => + expect(err).to.exist + expect(planCode).to.not.exist + done() + + describe 'when getUser does not find a user', -> + beforeEach -> + @UserGetter.getUser = sinon.stub() + .yields(null, null) + + it 'should not call _v1PlanRequest', (done) -> + @call (err, planCode) => + expect( + @V1SubscriptionManager._v1PlanRequest.callCount + ).to.equal 0 + done() + + it 'should produce a null plan-code, without error', (done) -> + @call (err, planCode) => + expect(err).to.not.exist + expect(planCode).to.not.exist + done() + + describe 'when the request to v1 fails', -> + beforeEach -> + @V1SubscriptionManager._v1PlanRequest = sinon.stub() + .yields(new Error('woops')) + + it 'should produce an error', (done) -> + @call (err, planCode) => + expect(err).to.exist + expect(planCode).to.not.exist + done() diff --git a/services/web/test/unit/coffee/Uploads/FileTypeManagerTests.coffee b/services/web/test/unit/coffee/Uploads/FileTypeManagerTests.coffee index c6fdf64829..be456c74a5 100644 --- a/services/web/test/unit/coffee/Uploads/FileTypeManagerTests.coffee +++ b/services/web/test/unit/coffee/Uploads/FileTypeManagerTests.coffee @@ -39,7 +39,7 @@ describe "FileTypeManager", -> beforeEach -> @stat = { size: 100 } @fs.stat = sinon.stub().callsArgWith(1, null, @stat) - + it "should return .tex files as not binary", -> @FileTypeManager.isBinary "file.tex", "/path/on/disk", (error, binary) -> binary.should.equal false @@ -80,10 +80,18 @@ describe "FileTypeManager", -> @FileTypeManager.isBinary "tex", "/path/on/disk", (error, binary) -> binary.should.equal true + it "should return .latexmkrc file as not binary", -> + @FileTypeManager.isBinary ".latexmkrc", "/path/on/disk", (error, binary) -> + binary.should.equal false + + it "should return latexmkrc file as not binary", -> + @FileTypeManager.isBinary "latexmkrc", "/path/on/disk", (error, binary) -> + binary.should.equal false + it "should ignore the case of an extension", -> @FileTypeManager.isBinary "file.TEX", "/path/on/disk", (error, binary) -> binary.should.equal false - + it "should return large text files as binary", -> @stat.size = 2 * 1024 * 1024 # 2Mb @FileTypeManager.isBinary "file.tex", "/path/on/disk", (error, binary) -> @@ -98,6 +106,10 @@ describe "FileTypeManager", -> @FileTypeManager.shouldIgnore "path/.git", (error, ignore) -> ignore.should.equal true + it "should not ignore .latexmkrc dotfile", -> + @FileTypeManager.shouldIgnore "path/.latexmkrc", (error, ignore) -> + ignore.should.equal false + it "should ignore __MACOSX", -> @FileTypeManager.shouldIgnore "path/__MACOSX", (error, ignore) -> ignore.should.equal true @@ -109,5 +121,3 @@ describe "FileTypeManager", -> it "should ignore the case of the extension", -> @FileTypeManager.shouldIgnore "file.AUX", (error, ignore) -> ignore.should.equal true - - diff --git a/services/web/test/unit/coffee/User/UserControllerTests.coffee b/services/web/test/unit/coffee/User/UserControllerTests.coffee index c358f35b22..e815d8d701 100644 --- a/services/web/test/unit/coffee/User/UserControllerTests.coffee +++ b/services/web/test/unit/coffee/User/UserControllerTests.coffee @@ -30,8 +30,8 @@ describe "UserController", -> @UserDeleter = deleteUser: sinon.stub().callsArgWith(1) - @UserLocator = - findById: sinon.stub().callsArgWith(1, null, @user) + @UserGetter = + getUser: sinon.stub().callsArgWith(1, null, @user) @User = findById: sinon.stub().callsArgWith(1, null, @user) @NewsLetterManager = @@ -63,7 +63,7 @@ describe "UserController", -> @SudoModeHandler = clearSudoMode: sinon.stub() @UserController = SandboxedModule.require modulePath, requires: - "./UserLocator": @UserLocator + "./UserGetter": @UserGetter "./UserDeleter": @UserDeleter "./UserUpdater":@UserUpdater "../../models/User": User:@User diff --git a/services/web/test/unit/coffee/User/UserCreatorTests.coffee b/services/web/test/unit/coffee/User/UserCreatorTests.coffee index 8470e5621f..cc2b1ec150 100644 --- a/services/web/test/unit/coffee/User/UserCreatorTests.coffee +++ b/services/web/test/unit/coffee/User/UserCreatorTests.coffee @@ -15,34 +15,16 @@ describe "UserCreator", -> constructor: -> return self.user - @UserLocator = - findByEmail: sinon.stub() + @UserGetter = + getUserByMainEmail: sinon.stub() @UserCreator = SandboxedModule.require modulePath, requires: "../../models/User": User:@UserModel - "./UserLocator":@UserLocator + "./UserGetter":@UserGetter "logger-sharelatex":{log:->} 'metrics-sharelatex': {timeAsyncMethod: ()->} @email = "bob.oswald@gmail.com" - - describe "getUserOrCreateHoldingAccount", -> - - it "should immediately return the user if found", (done)-> - @UserLocator.findByEmail.callsArgWith(1, null, @user) - @UserCreator.getUserOrCreateHoldingAccount @email, (err, returnedUser)=> - assert.deepEqual returnedUser, @user - done() - - it "should create new holding account if the user is not found", (done)-> - @UserLocator.findByEmail.callsArgWith(1) - @UserCreator.createNewUser = sinon.stub().callsArgWith(1, null, @user) - @UserCreator.getUserOrCreateHoldingAccount @email, (err, returnedUser)=> - @UserCreator.createNewUser.calledWith(email:@email, holdingAccount:true).should.equal true - assert.deepEqual returnedUser, @user - done() - - describe "createNewUser", -> it "should take the opts and put them in the model", (done)-> diff --git a/services/web/test/unit/coffee/User/UserGetterTests.coffee b/services/web/test/unit/coffee/User/UserGetterTests.coffee new file mode 100644 index 0000000000..2c32bd8250 --- /dev/null +++ b/services/web/test/unit/coffee/User/UserGetterTests.coffee @@ -0,0 +1,58 @@ +should = require('chai').should() +SandboxedModule = require('sandboxed-module') +assert = require('assert') +path = require('path') +sinon = require('sinon') +modulePath = path.join __dirname, "../../../../app/js/Features/User/UserGetter" +expect = require("chai").expect + +describe "UserGetter", -> + + beforeEach -> + @fakeUser = {_id:"12390i"} + @findOne = sinon.stub().callsArgWith(2, null, @fakeUser) + @Mongo = + db: users: findOne: @findOne + ObjectId: (id) -> return id + + @UserGetter = SandboxedModule.require modulePath, requires: + "logger-sharelatex": log:-> + "../../infrastructure/mongojs": @Mongo + "metrics-sharelatex": timeAsyncMethod: sinon.stub() + + describe "getUser", -> + it "should get user", (done)-> + query = _id: 'foo' + projection = email: 1 + @UserGetter.getUser query, projection, (error, user) => + @findOne.called.should.equal true + @findOne.calledWith(query, projection).should.equal true + user.should.deep.equal @fakeUser + done() + + it "should not allow email in query", (done)-> + @UserGetter.getUser email: 'foo@bar.com', {}, (error, user) => + error.should.exist + done() + + describe "getUserbyMainEmail", -> + it "query user by main email", (done)-> + email = 'hello@world.com' + projection = emails: 1 + @UserGetter.getUserByMainEmail email, projection, (error, user) => + @findOne.called.should.equal true + @findOne.calledWith(email: email, projection).should.equal true + done() + + it "return user if found", (done)-> + email = 'hello@world.com' + @UserGetter.getUserByMainEmail email, (error, user) => + user.should.deep.equal @fakeUser + done() + + it "trim email", (done)-> + email = 'hello@world.com' + @UserGetter.getUserByMainEmail " #{email} ", (error, user) => + @findOne.called.should.equal true + @findOne.calledWith(email: email).should.equal true + done() diff --git a/services/web/test/unit/coffee/User/UserLocatorTests.coffee b/services/web/test/unit/coffee/User/UserLocatorTests.coffee deleted file mode 100644 index dc3fc84dfa..0000000000 --- a/services/web/test/unit/coffee/User/UserLocatorTests.coffee +++ /dev/null @@ -1,39 +0,0 @@ -sinon = require('sinon') -chai = require('chai') -should = chai.should() -modulePath = "../../../../app/js/Features/User/UserLocator.js" -SandboxedModule = require('sandboxed-module') - -describe "UserLocator", -> - - beforeEach -> - @user = {_id:"12390i"} - @UserLocator = SandboxedModule.require modulePath, requires: - "../../infrastructure/mongojs": db: @db = { users: {} } - "metrics-sharelatex": timeAsyncMethod: sinon.stub() - 'logger-sharelatex' : { log: sinon.stub() } - @db.users = - findOne : sinon.stub().callsArgWith(1, null, @user) - - @email = "bob.oswald@gmail.com" - - - describe "findByEmail", -> - - it "should try and find a user with that email address", (done)-> - @UserLocator.findByEmail @email, (err, user)=> - @db.users.findOne.calledWith(email:@email).should.equal true - done() - - it "should trim white space", (done)-> - @UserLocator.findByEmail "#{@email} ", (err, user)=> - @db.users.findOne.calledWith(email:@email).should.equal true - done() - - it "should return the user if found", (done)-> - @UserLocator.findByEmail @email, (err, user)=> - user.should.deep.equal @user - done() - - - diff --git a/services/web/test/unit/coffee/User/UserPagesControllerTests.coffee b/services/web/test/unit/coffee/User/UserPagesControllerTests.coffee index 529f5b1be6..a0f155846f 100644 --- a/services/web/test/unit/coffee/User/UserPagesControllerTests.coffee +++ b/services/web/test/unit/coffee/User/UserPagesControllerTests.coffee @@ -16,10 +16,7 @@ describe "UserPagesController", -> features:{} email: "joe@example.com" - @UserLocator = - findById: sinon.stub().callsArgWith(1, null, @user) - @UserGetter = - getUser: sinon.stub().callsArgWith(2, null, @user) + @UserGetter = getUser: sinon.stub() @UserSessionsManager = getAllUserSessions: sinon.stub() @dropboxStatus = {} @@ -37,7 +34,6 @@ describe "UserPagesController", -> "logger-sharelatex": log:-> err:-> - "./UserLocator": @UserLocator "./UserGetter": @UserGetter "./UserSessionsManager": @UserSessionsManager "../Errors/ErrorController": @ErrorController @@ -136,6 +132,8 @@ describe "UserPagesController", -> @UserPagesController.sessionsPage @req, @res, @next describe "settingsPage", -> + beforeEach -> + @UserGetter.getUser = sinon.stub().callsArgWith(1, null, @user) it "should render user/settings", (done)-> @res.render = (page)-> @@ -185,6 +183,7 @@ describe "UserPagesController", -> describe "activateAccountPage", -> beforeEach -> + @UserGetter.getUser = sinon.stub().callsArgWith(2, null, @user) @req.query.user_id = @user_id @req.query.token = @token = "mock-token-123" diff --git a/services/web/test/unit/coffee/User/UserRegistrationHandlerTests.coffee b/services/web/test/unit/coffee/User/UserRegistrationHandlerTests.coffee index d0b96da2de..9411059022 100644 --- a/services/web/test/unit/coffee/User/UserRegistrationHandlerTests.coffee +++ b/services/web/test/unit/coffee/User/UserRegistrationHandlerTests.coffee @@ -12,8 +12,9 @@ describe "UserRegistrationHandler", -> @user = _id: @user_id = "31j2lk21kjl" @User = - findOne:sinon.stub() update: sinon.stub().callsArgWith(2) + @UserGetter = + getUserByMainEmail: sinon.stub() @UserCreator = createNewUser:sinon.stub().callsArgWith(1, null, @user) @AuthenticationManager = @@ -26,6 +27,7 @@ describe "UserRegistrationHandler", -> getNewToken: sinon.stub() @handler = SandboxedModule.require modulePath, requires: "../../models/User": {User:@User} + "./UserGetter": @UserGetter "./UserCreator": @UserCreator "../Authentication/AuthenticationManager":@AuthenticationManager "../Newsletter/NewsletterManager":@NewsLetterManager @@ -70,7 +72,7 @@ describe "UserRegistrationHandler", -> beforeEach -> @user.holdingAccount = true @handler._registrationRequestIsValid = sinon.stub().returns true - @User.findOne.callsArgWith(1, null, @user) + @UserGetter.getUserByMainEmail.callsArgWith(1, null, @user) it "should not create a new user if there is a holding account there", (done)-> @handler.registerNewUser @passingRequest, (err)=> @@ -94,7 +96,7 @@ describe "UserRegistrationHandler", -> done() it "should return email registered in the error if there is a non holdingAccount there", (done)-> - @User.findOne.callsArgWith(1, null, @user = {holdingAccount:false}) + @UserGetter.getUserByMainEmail.callsArgWith(1, null, @user = {holdingAccount:false}) @handler.registerNewUser @passingRequest, (err, user)=> err.should.deep.equal new Error("EmailAlreadyRegistered") user.should.deep.equal @user @@ -103,7 +105,7 @@ describe "UserRegistrationHandler", -> describe "validRequest", -> beforeEach -> @handler._registrationRequestIsValid = sinon.stub().returns true - @User.findOne.callsArgWith 1 + @UserGetter.getUserByMainEmail.callsArgWith 1 it "should create a new user", (done)-> @handler.registerNewUser @passingRequest, (err)=> diff --git a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee index a6239e2e65..202a4f3f1a 100644 --- a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee +++ b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee @@ -14,12 +14,12 @@ describe "UserUpdater", -> @mongojs = db:{} ObjectId:(id)-> return id - @UserLocator = - findByEmail:sinon.stub() + @UserGetter = + getUserByMainEmail: sinon.stub() @UserUpdater = SandboxedModule.require modulePath, requires: "settings-sharelatex":@settings "logger-sharelatex": log:-> - "./UserLocator":@UserLocator + "./UserGetter": @UserGetter "../../infrastructure/mongojs":@mongojs "metrics-sharelatex": timeAsyncMethod: sinon.stub() @@ -34,7 +34,7 @@ describe "UserUpdater", -> @UserUpdater.updateUser = sinon.stub().callsArgWith(2) it "should check if the new email already has an account", (done)-> - @UserLocator.findByEmail.callsArgWith(1, null, @stubbedUser) + @UserGetter.getUserByMainEmail.callsArgWith(1, null, @stubbedUser) @UserUpdater.changeEmailAddress @user_id, @stubbedUser.email, (err)=> @UserUpdater.updateUser.called.should.equal false should.exist(err) @@ -42,7 +42,7 @@ describe "UserUpdater", -> it "should set the users password", (done)-> - @UserLocator.findByEmail.callsArgWith(1, null) + @UserGetter.getUserByMainEmail.callsArgWith(1, null) @UserUpdater.changeEmailAddress @user_id, @newEmail, (err)=> @UserUpdater.updateUser.calledWith(@user_id, $set: { "email": @newEmail}).should.equal true done() diff --git a/services/web/test/unit/coffee/infrastructure/LockManager/ReleasingTheLock.coffee b/services/web/test/unit/coffee/infrastructure/LockManager/ReleasingTheLock.coffee index 256f72f95f..76747defbb 100644 --- a/services/web/test/unit/coffee/infrastructure/LockManager/ReleasingTheLock.coffee +++ b/services/web/test/unit/coffee/infrastructure/LockManager/ReleasingTheLock.coffee @@ -3,23 +3,25 @@ assert = require('assert') path = require('path') modulePath = path.join __dirname, '../../../../../app/js/infrastructure/LockManager.js' lockKey = "lock:web:{#{5678}}" +lockValue = "123456" SandboxedModule = require('sandboxed-module') describe 'LockManager - releasing the lock', ()-> - deleteStub = sinon.stub().callsArgWith(1) + deleteStub = sinon.stub().callsArgWith(4) mocks = "logger-sharelatex": log:-> "./RedisWrapper": client: ()-> auth:-> - del:deleteStub - - LockManager = SandboxedModule.require(modulePath, requires: mocks) + eval:deleteStub + LockManager = SandboxedModule.require(modulePath, requires: mocks) + LockManager.unlockScript = "this is the unlock script" + it 'should put a all data into memory', (done)-> - LockManager._releaseLock lockKey, -> - deleteStub.calledWith(lockKey).should.equal true + LockManager._releaseLock lockKey, lockValue, -> + deleteStub.calledWith(LockManager.unlockScript, 1, lockKey, lockValue).should.equal true done() diff --git a/services/web/test/unit/coffee/infrastructure/LockManager/tryLockTests.coffee b/services/web/test/unit/coffee/infrastructure/LockManager/tryLockTests.coffee index b3719623bb..9988b8583b 100644 --- a/services/web/test/unit/coffee/infrastructure/LockManager/tryLockTests.coffee +++ b/services/web/test/unit/coffee/infrastructure/LockManager/tryLockTests.coffee @@ -22,10 +22,11 @@ describe 'LockManager - trying the lock', -> describe "when the lock is not set", -> beforeEach -> @set.callsArgWith(5, null, "OK") + @LockManager.randomLock = sinon.stub().returns("random-lock-value") @LockManager._tryLock @key, @namespace, @callback it "should set the lock key with an expiry if it is not set", -> - @set.calledWith(@key, "locked", "EX", 30, "NX") + @set.calledWith(@key, "random-lock-value", "EX", 30, "NX") .should.equal true it "should return the callback with true", -> diff --git a/services/web/test/unit_frontend/coffee/ide/editor/aceEditor/spell-check/SpellCheckManagerTests.coffee b/services/web/test/unit_frontend/coffee/ide/editor/aceEditor/spell-check/SpellCheckManagerTests.coffee new file mode 100644 index 0000000000..1b0dddced6 --- /dev/null +++ b/services/web/test/unit_frontend/coffee/ide/editor/aceEditor/spell-check/SpellCheckManagerTests.coffee @@ -0,0 +1,46 @@ +define [ + 'ide/editor/directives/aceEditor/spell-check/SpellCheckManager' +], (SpellCheckManager) -> + describe 'SpellCheckManager', -> + beforeEach (done) -> + @timelord = sinon.useFakeTimers() + + window.user = { id: 1 } + window.csrfToken = 'token' + @scope = { + $watch: sinon.stub() + spellCheck: true + spellCheckLanguage: 'en' + } + @highlightedWordManager = { + reset: sinon.stub() + clearRow: sinon.stub() + addHighlight: sinon.stub() + } + @adapter = { + getLines: sinon.stub() + highlightedWordManager: @highlightedWordManager + } + inject ($q, $http, $httpBackend, $cacheFactory) => + @$http = $http + @$q = $q + @$httpBackend = $httpBackend + cache = $cacheFactory('spellCheckTest', {capacity: 1000}) + @spellCheckManager = new SpellCheckManager(@scope, cache, $http, $q, @adapter) + done() + + afterEach -> + @timelord.restore() + + it 'runs a full check soon after init', () -> + @$httpBackend.when('POST', '/spelling/check').respond({ + misspellings: [{ + index: 0 + suggestions: ['opposition'] + }] + }) + @adapter.getLines.returns(['oppozition']) + @spellCheckManager.init() + @timelord.tick(200) + @$httpBackend.flush() + expect(@highlightedWordManager.addHighlight).to.have.been.called diff --git a/services/web/webpack.config.js b/services/web/webpack.config.js index da0dc8257e..34ea397d36 100644 --- a/services/web/webpack.config.js +++ b/services/web/webpack.config.js @@ -1,7 +1,9 @@ const fs = require('fs') const path = require('path') +const webpack = require('webpack') const MODULES_PATH = path.join(__dirname, '/modules') +const webpackENV = process.env.WEBPACK_ENV || 'development' // Generate a hash of entry points, including modules const entryPoints = {} @@ -60,10 +62,35 @@ module.exports = { cacheDirectory: true } }] + }, + { + // These options are necesary for handlebars to have access to helper + // methods + test: /\.handlebars$/, + loader: "handlebars-loader", + options: { + compat: true, + knownHelpersOnly: false, + runtimePath: 'handlebars/runtime', + } }] }, - - // TODO - // plugins: {} + resolve: { + alias: { + // makes handlerbars globally accessible to backbone + handlebars: 'handlebars/dist/handlebars.min.js', + jquery: path.join(__dirname, 'node_modules/jquery/dist/jquery'), + } + }, + plugins: [ + new webpack.DefinePlugin({ + // Swaps out checks for NODE_ENV with the env. This is used by various + // libs to enable dev-only features. These checks then become something + // like `if ('production' == 'production')`. Minification will then strip + // the dev-only code from the bundle + 'process.env': { + NODE_ENV: JSON.stringify(webpackENV) + }, + }) + ] } -