diff --git a/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee b/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee index e340187dbb..9ecff2185e 100644 --- a/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee +++ b/services/web/app/coffee/Features/DocumentUpdater/DocumentUpdaterHandler.coffee @@ -174,6 +174,23 @@ module.exports = DocumentUpdaterHandler = oldEntitiesHash = _.indexBy oldEntities, (entity) -> entity[entityType]._id.toString() newEntitiesHash = _.indexBy newEntities, (entity) -> entity[entityType]._id.toString() + # Send deletes before adds (and renames) to keep a 1:1 mapping between + # paths and ids + # + # When a file is replaced, we first delete the old file and then add the + # new file. If the 'add' operation is sent to project history before the + # 'delete' then we would have two files with the same path at that point + # in time. + for id, oldEntity of oldEntitiesHash + newEntity = newEntitiesHash[id] + + if !newEntity? + # entity deleted + updates.push + id: id + pathname: oldEntity.path + newPathname: '' + for id, newEntity of newEntitiesHash oldEntity = oldEntitiesHash[id] @@ -191,16 +208,6 @@ module.exports = DocumentUpdaterHandler = pathname: oldEntity.path newPathname: newEntity.path - for id, oldEntity of oldEntitiesHash - newEntity = newEntitiesHash[id] - - if !newEntity? - # entity deleted - updates.push - id: id - pathname: oldEntity.path - newPathname: '' - updates PENDINGUPDATESKEY = "PendingUpdates" diff --git a/services/web/app/coffee/Features/Editor/EditorController.coffee b/services/web/app/coffee/Features/Editor/EditorController.coffee index 7294e88bea..846042312d 100644 --- a/services/web/app/coffee/Features/Editor/EditorController.coffee +++ b/services/web/app/coffee/Features/Editor/EditorController.coffee @@ -41,11 +41,13 @@ module.exports = EditorController = callback err, doc upsertFile: (project_id, folder_id, fileName, fsPath, linkedFileData, source, user_id, callback = (err, file) ->) -> - ProjectEntityUpdateHandler.upsertFile project_id, folder_id, fileName, fsPath, linkedFileData, user_id, (err, file, didAddFile) -> + ProjectEntityUpdateHandler.upsertFile project_id, folder_id, fileName, fsPath, linkedFileData, user_id, (err, newFile, didAddFile, existingFile) -> return callback(err) if err? - if didAddFile - EditorRealTimeController.emitToRoom project_id, 'reciveNewFile', folder_id, file, source, linkedFileData - callback null, file + if not didAddFile # replacement, so remove the existing file from the client + EditorRealTimeController.emitToRoom project_id, 'removeEntity', existingFile._id, source + # now add the new file on the client + EditorRealTimeController.emitToRoom project_id, 'reciveNewFile', folder_id, newFile, source, linkedFileData + callback null, newFile upsertDocWithPath: (project_id, elementPath, docLines, source, user_id, callback) -> ProjectEntityUpdateHandler.upsertDocWithPath project_id, elementPath, docLines, source, user_id, (err, doc, didAddNewDoc, newFolders, lastFolder) -> @@ -57,12 +59,14 @@ module.exports = EditorController = callback() upsertFileWithPath: (project_id, elementPath, fsPath, linkedFileData, source, user_id, callback) -> - ProjectEntityUpdateHandler.upsertFileWithPath project_id, elementPath, fsPath, linkedFileData, user_id, (err, file, didAddFile, newFolders, lastFolder) -> + ProjectEntityUpdateHandler.upsertFileWithPath project_id, elementPath, fsPath, linkedFileData, user_id, (err, newFile, didAddFile, existingFile, newFolders, lastFolder) -> return callback(err) if err? EditorController._notifyProjectUsersOfNewFolders project_id, newFolders, (err) -> return callback(err) if err? - if didAddFile - EditorRealTimeController.emitToRoom project_id, 'reciveNewFile', lastFolder._id, file, source, linkedFileData + if not didAddFile # replacement, so remove the existing file from the client + EditorRealTimeController.emitToRoom project_id, 'removeEntity', existingFile._id, source + # now add the new file on the client + EditorRealTimeController.emitToRoom project_id, 'reciveNewFile', lastFolder._id, newFile, source, linkedFileData callback() addFolder : (project_id, folder_id, folderName, source, callback = (error, folder)->)-> diff --git a/services/web/app/coffee/Features/Project/ProjectController.coffee b/services/web/app/coffee/Features/Project/ProjectController.coffee index cd3b3d1905..77aabd8524 100644 --- a/services/web/app/coffee/Features/Project/ProjectController.coffee +++ b/services/web/app/coffee/Features/Project/ProjectController.coffee @@ -301,6 +301,7 @@ module.exports = ProjectController = themes: THEME_LIST maxDocLength: Settings.max_doc_length useV2History: !!project.overleaf?.history?.display + showRichText: req.query?.rt == 'true' timer.done() _buildProjectList: (allProjects, v1Projects = [])-> diff --git a/services/web/app/coffee/Features/Project/ProjectEntityMongoUpdateHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEntityMongoUpdateHandler.coffee index 63138d1ca0..57f65a67dc 100644 --- a/services/web/app/coffee/Features/Project/ProjectEntityMongoUpdateHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEntityMongoUpdateHandler.coffee @@ -48,28 +48,28 @@ module.exports = ProjectEntityMongoUpdateHandler = self = self._confirmFolder project, folder_id, (folder_id)-> self._putElement project, folder_id, fileRef, "file", callback - replaceFile: wrapWithLock (project_id, file_id, linkedFileData, callback) -> + replaceFileWithNew: wrapWithLock (project_id, file_id, newFileRef, callback) -> ProjectGetter.getProjectWithoutLock project_id, {rootFolder: true, name:true}, (err, project) -> return callback(err) if err? ProjectLocator.findElement {project:project, element_id: file_id, type: 'file'}, (err, fileRef, path)=> return callback(err) if err? - conditions = _id:project._id - inc = {} - inc["#{path.mongo}.rev"] = 1 - # currently we do not need to increment the project version number for changes that are replacements - # but when we make switch to having immutable files the replace operation will add a new file, and - # this will require a version increase. We will start incrementing the project version now as it does - # no harm and will help to test it. - inc['version'] = 1 - set = {} - set["#{path.mongo}.created"] = new Date() - set["#{path.mongo}.linkedFileData"] = linkedFileData - update = - "$inc": inc - "$set": set - Project.update conditions, update, {}, (err) -> + ProjectEntityMongoUpdateHandler._insertDeletedFileReference project_id, fileRef, (err) -> return callback(err) if err? - callback null, fileRef, project, path + conditions = _id:project._id + inc = {} + # increment the project structure version as we are adding a new file here + inc['version'] = 1 + set = {} + set["#{path.mongo}._id"] = newFileRef._id + set["#{path.mongo}.created"] = new Date() + set["#{path.mongo}.linkedFileData"] = newFileRef.linkedFileData + set["#{path.mongo}.rev"] = 1 + update = + "$inc": inc + "$set": set + Project.update conditions, update, {}, (err) -> + return callback(err) if err? + callback null, fileRef, project, path mkdirp: wrapWithLock (project_id, path, callback) -> folders = path.split('/') @@ -300,3 +300,29 @@ module.exports = ProjectEntityMongoUpdateHandler = self = if isNestedFolder return callback(new Errors.InvalidNameError("destination folder is a child folder of me")) callback() + + _insertDeletedDocReference: (project_id, doc, callback = (error) ->) -> + Project.update { + _id: project_id + }, { + $push: { + deletedDocs: { + _id: doc._id + name: doc.name + } + } + }, {}, callback + + _insertDeletedFileReference: (project_id, fileRef, callback = (error) ->) -> + Project.update { + _id: project_id + }, { + $push: { + deletedFiles: { + _id: fileRef._id + name: fileRef.name + linkedFileData: fileRef.linkedFileData + deletedAt: new Date() + } + } + }, {}, callback \ No newline at end of file diff --git a/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee b/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee index 4bc6e1a420..ac51e65957 100644 --- a/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee +++ b/services/web/app/coffee/Features/Project/ProjectEntityUpdateHandler.coffee @@ -24,12 +24,25 @@ wrapWithLock = (methodWithoutLock) -> # This lock is used to make sure that the project structure updates are made # sequentially. In particular the updates must be made in mongo and sent to # the doc-updater in the same order. - methodWithLock = (project_id, args..., callback) -> - LockManager.runWithLock LOCK_NAMESPACE, project_id, - (cb) -> methodWithoutLock project_id, args..., cb - callback - methodWithLock.withoutLock = methodWithoutLock - methodWithLock + if typeof methodWithoutLock is 'function' + methodWithLock = (project_id, args..., callback) -> + LockManager.runWithLock LOCK_NAMESPACE, project_id, + (cb) -> methodWithoutLock project_id, args..., cb + callback + methodWithLock.withoutLock = methodWithoutLock + methodWithLock + else + # handle case with separate setup and locked stages + wrapWithSetup = methodWithoutLock.beforeLock # a function to set things up before the lock + mainTask = methodWithoutLock.withLock # function to execute inside the lock + methodWithLock = wrapWithSetup (project_id, args..., callback) -> + LockManager.runWithLock(LOCK_NAMESPACE, project_id, (cb) -> + mainTask(project_id, args..., cb) + callback) + methodWithLock.withoutLock = wrapWithSetup mainTask + methodWithLock.beforeLock = methodWithoutLock.beforeLock + methodWithLock.mainTask = methodWithoutLock.withLock + methodWithLock module.exports = ProjectEntityUpdateHandler = self = # this doesn't need any locking because it's only called by ProjectDuplicator @@ -120,31 +133,72 @@ module.exports = ProjectEntityUpdateHandler = self = return callback(error) if error? callback null, doc, folder_id - addFile: wrapWithLock (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (error, fileRef, folder_id) ->)-> - self.addFileWithoutUpdatingHistory.withoutLock project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, folder_id, path, fileStoreUrl) -> - return callback(error) if error? - newFiles = [ - file: fileRef - path: path - url: fileStoreUrl - ] - DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, (error) -> - return callback(error) if error? - callback null, fileRef, folder_id + _uploadFile: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (error, fileRef, fileStoreUrl) ->)-> + if not SafePath.isCleanFilename fileName + return callback new Errors.InvalidNameError("invalid element name") + fileRef = new File( + name: fileName + linkedFileData: linkedFileData + ) + FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)-> + if err? + logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error uploading image to s3" + return callback(err) + callback(null, fileRef, fileStoreUrl) - replaceFile: wrapWithLock (project_id, file_id, fsPath, linkedFileData, userId, callback)-> - FileStoreHandler.uploadFileFromDisk project_id, file_id, fsPath, (err, fileStoreUrl)-> - return callback(err) if err? - ProjectEntityMongoUpdateHandler.replaceFile project_id, file_id, linkedFileData, (err, fileRef, project, path) -> + _addFileAndSendToTpds: (project_id, folder_id, fileName, fileRef, callback = (error) ->)-> + ProjectEntityMongoUpdateHandler.addFile project_id, folder_id, fileRef, (err, result, project) -> + if err? + logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error adding file with project" + return callback(err) + TpdsUpdateSender.addFile {project_id:project_id, file_id:fileRef._id, path:result?.path?.fileSystem, project_name:project.name, rev:fileRef.rev}, (err) -> + return callback(err) if err? + callback(null, result, project) + + addFile: wrapWithLock + beforeLock: (next) -> + (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback) -> + ProjectEntityUpdateHandler._uploadFile project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, fileStoreUrl) -> + return callback(error) if error? + next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback) + withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback = (error, fileRef, folder_id) ->)-> + ProjectEntityUpdateHandler._addFileAndSendToTpds project_id, folder_id, fileName, fileRef, (err, result, project) -> return callback(err) if err? newFiles = [ file: fileRef + path: result?.path?.fileSystem + url: fileStoreUrl + ] + DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, (error) -> + return callback(error) if error? + callback(null, fileRef, folder_id) + + replaceFile: wrapWithLock + beforeLock: (next) -> + (project_id, file_id, fsPath, linkedFileData, userId, callback)-> + # create a new file + fileRef = new File( + name: "dummy-upload-filename" + linkedFileData: linkedFileData + ) + FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)-> + return callback(err) if err? + next project_id, file_id, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback + withLock: (project_id, file_id, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, callback)-> + ProjectEntityMongoUpdateHandler.replaceFileWithNew project_id, file_id, newFileRef, (err, oldFileRef, project, path) -> + return callback(err) if err? + oldFiles = [ + file: oldFileRef + path: path.fileSystem + ] + newFiles = [ + file: newFileRef path: path.fileSystem url: fileStoreUrl ] - TpdsUpdateSender.addFile {project_id:project._id, file_id:fileRef._id, path:path.fileSystem, rev:fileRef.rev+1, project_name:project.name}, (err) -> + TpdsUpdateSender.addFile {project_id:project._id, file_id:newFileRef._id, path:path.fileSystem, rev:newFileRef.rev+1, project_name:project.name}, (err) -> return callback(err) if err? - DocumentUpdaterHandler.updateProjectStructure project_id, userId, {newFiles}, callback + DocumentUpdaterHandler.updateProjectStructure project_id, userId, {oldFiles, newFiles}, callback addDocWithoutUpdatingHistory: wrapWithLock (project_id, folder_id, docName, docLines, userId, callback = (error, doc, folder_id) ->)=> # This method should never be called directly, except when importing a project @@ -171,29 +225,19 @@ module.exports = ProjectEntityUpdateHandler = self = return callback(err) if err? callback(null, doc, folder_id, result?.path?.fileSystem) - addFileWithoutUpdatingHistory: wrapWithLock (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (error, fileRef, folder_id, path, fileStoreUrl) ->)-> + addFileWithoutUpdatingHistory: wrapWithLock # This method should never be called directly, except when importing a project # from Overleaf. It skips sending updates to the project history, which will break # the history unless you are making sure it is updated in some other way. - - if not SafePath.isCleanFilename fileName - return callback new Errors.InvalidNameError("invalid element name") - - fileRef = new File( - name: fileName - linkedFileData: linkedFileData - ) - FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)-> - if err? - logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error uploading image to s3" - return callback(err) - ProjectEntityMongoUpdateHandler.addFile project_id, folder_id, fileRef, (err, result, project) -> - if err? - logger.err err:err, project_id: project_id, folder_id: folder_id, file_name: fileName, fileRef:fileRef, "error adding file with project" - return callback(err) - TpdsUpdateSender.addFile {project_id:project_id, file_id:fileRef._id, path:result?.path?.fileSystem, project_name:project.name, rev:fileRef.rev}, (err) -> - return callback(err) if err? - callback(null, fileRef, folder_id, result?.path?.fileSystem, fileStoreUrl) + beforeLock: (next) -> + (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback) -> + ProjectEntityUpdateHandler._uploadFile project_id, folder_id, fileName, fsPath, linkedFileData, userId, (error, fileRef, fileStoreUrl) -> + return callback(error) if error? + next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback) + withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback = (error, fileRef, folder_id, path, fileStoreUrl) ->)-> + ProjectEntityUpdateHandler._addFileAndSendToTpds project_id, folder_id, fileName, fileRef, (err, result, project) -> + return callback(err) if err? + callback(null, fileRef, folder_id, result?.path?.fileSystem, fileStoreUrl) upsertDoc: wrapWithLock (project_id, folder_id, docName, docLines, source, userId, callback = (err, doc, folder_id, isNewDoc)->)-> ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) -> @@ -215,23 +259,36 @@ module.exports = ProjectEntityUpdateHandler = self = return callback(err) if err? callback null, doc, !existingDoc? - upsertFile: wrapWithLock (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback = (err, file, isNewFile)->)-> - ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) -> - return callback(error) if error? - return callback(new Error("Couldn't find folder")) if !folder? - existingFile = null - for fileRef in folder.fileRefs - if fileRef.name == fileName - existingFile = fileRef - break - if existingFile? - self.replaceFile.withoutLock project_id, existingFile._id, fsPath, linkedFileData, userId, (err) -> + upsertFile: wrapWithLock + beforeLock: (next) -> + (project_id, folder_id, fileName, fsPath, linkedFileData, userId, callback)-> + # create a new file + fileRef = new File( + name: fileName + linkedFileData: linkedFileData + ) + FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)-> return callback(err) if err? - callback null, existingFile, !existingFile? - else - self.addFile.withoutLock project_id, folder_id, fileName, fsPath, linkedFileData, userId, (err, file) -> - return callback(err) if err? - callback null, file, !existingFile? + next(project_id, folder_id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback) + withLock: (project_id, folder_id, fileName, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, callback = (err, file, isNewFile, existingFile)->)-> + ProjectLocator.findElement project_id: project_id, element_id: folder_id, type: "folder", (error, folder) -> + return callback(error) if error? + return callback(new Error("Couldn't find folder")) if !folder? + existingFile = null + for fileRef in folder.fileRefs + if fileRef.name == fileName + existingFile = fileRef + break + if existingFile? + # this calls directly into the replaceFile main task (without the beforeLock part) + self.replaceFile.mainTask project_id, existingFile._id, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, (err) -> + return callback(err) if err? + callback null, newFileRef, !existingFile?, existingFile + else + # this calls directly into the addFile main task (without the beforeLock part) + self.addFile.mainTask project_id, folder_id, fileName, fsPath, linkedFileData, userId, newFileRef, fileStoreUrl, (err) -> + return callback(err) if err? + callback null, newFileRef, !existingFile?, existingFile upsertDocWithPath: wrapWithLock (project_id, elementPath, docLines, source, userId, callback) -> docName = path.basename(elementPath) @@ -242,14 +299,26 @@ module.exports = ProjectEntityUpdateHandler = self = return callback(err) if err? callback null, doc, isNewDoc, newFolders, folder - upsertFileWithPath: wrapWithLock (project_id, elementPath, fsPath, linkedFileData, userId, callback) -> - fileName = path.basename(elementPath) - folderPath = path.dirname(elementPath) - self.mkdirp.withoutLock project_id, folderPath, (err, newFolders, folder) -> - return callback(err) if err? - self.upsertFile.withoutLock project_id, folder._id, fileName, fsPath, linkedFileData, userId, (err, file, isNewFile) -> + upsertFileWithPath: wrapWithLock + beforeLock: (next) -> + (project_id, elementPath, fsPath, linkedFileData, userId, callback)-> + fileName = path.basename(elementPath) + folderPath = path.dirname(elementPath) + # create a new file + fileRef = new File( + name: fileName + linkedFileData: linkedFileData + ) + FileStoreHandler.uploadFileFromDisk project_id, fileRef._id, fsPath, (err, fileStoreUrl)-> + return callback(err) if err? + next project_id, folderPath, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback + withLock: (project_id, folderPath, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, callback) -> + self.mkdirp.withoutLock project_id, folderPath, (err, newFolders, folder) -> return callback(err) if err? - callback null, file, isNewFile, newFolders, folder + # this calls directly into the upsertFile main task (without the beforeLock part) + self.upsertFile.mainTask project_id, folder._id, fileName, fsPath, linkedFileData, userId, fileRef, fileStoreUrl, (err, newFile, isNewFile, existingFile) -> + return callback(err) if err? + callback null, newFile, isNewFile, existingFile, newFolders, folder deleteEntity: wrapWithLock (project_id, entity_id, entityType, userId, callback = (error) ->)-> logger.log entity_id:entity_id, entityType:entityType, project_id:project_id, "deleting project entity" @@ -326,7 +395,7 @@ module.exports = ProjectEntityUpdateHandler = self = path: file.path url: FileStoreHandler._buildUrl(project_id, file.file._id) - DocumentUpdaterHandler.resyncProjectHistory project_id, docs, files, callback + DocumentUpdaterHandler.resyncProjectHistory project_id, docs, files, callback _cleanUpEntity: (project, entity, entityType, path, userId, callback = (error) ->) -> if(entityType.indexOf("file") != -1) self._cleanUpFile project, entity, path, userId, callback @@ -348,7 +417,7 @@ module.exports = ProjectEntityUpdateHandler = self = unsetRootDocIfRequired (error) -> return callback(error) if error? - self._insertDeletedDocReference project._id, doc, (error) -> + ProjectEntityMongoUpdateHandler._insertDeletedDocReference project._id, doc, (error) -> return callback(error) if error? DocumentUpdaterHandler.deleteDoc project_id, doc_id, (error) -> return callback(error) if error? @@ -358,11 +427,12 @@ module.exports = ProjectEntityUpdateHandler = self = DocumentUpdaterHandler.updateProjectStructure project_id, userId, changes, callback _cleanUpFile: (project, file, path, userId, callback = (error) ->) -> - project_id = project._id.toString() - file_id = file._id.toString() - FileStoreHandler.deleteFile project_id, file_id, (error) -> + ProjectEntityMongoUpdateHandler._insertDeletedFileReference project._id, file, (error) -> return callback(error) if error? + project_id = project._id.toString() 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, userId, changes, callback _cleanUpFolder: (project, folder, folderPath, userId, callback = (error) ->) -> @@ -383,15 +453,3 @@ module.exports = ProjectEntityUpdateHandler = self = jobs.push (callback) -> self._cleanUpFolder project, childFolder, folderPath, userId, callback async.series jobs, callback - - _insertDeletedDocReference: (project_id, doc, callback = (error) ->) -> - Project.update { - _id: project_id - }, { - $push: { - deletedDocs: { - _id: doc._id - name: doc.name - } - } - }, {}, callback diff --git a/services/web/app/coffee/infrastructure/ExpressLocals.coffee b/services/web/app/coffee/infrastructure/ExpressLocals.coffee index ee0933c612..37cce62ddd 100644 --- a/services/web/app/coffee/infrastructure/ExpressLocals.coffee +++ b/services/web/app/coffee/infrastructure/ExpressLocals.coffee @@ -15,6 +15,7 @@ htmlEncoder = new require("node-html-encoder").Encoder("numerical") hashedFiles = {} Path = require 'path' Features = require "./Features" +Modules = require "./Modules" jsPath = if Settings.useMinifiedJs @@ -41,10 +42,9 @@ pathList = [ "#{jsPath}ide.js" "#{jsPath}main.js" "#{jsPath}libraries.js" - "#{jsPath}es/richText.js" "/stylesheets/style.css" "/stylesheets/ol-style.css" -] +].concat(Modules.moduleAssetFiles(jsPath)) if !Settings.useMinifiedJs logger.log "not using minified JS, not hashing static files" diff --git a/services/web/app/coffee/infrastructure/Features.coffee b/services/web/app/coffee/infrastructure/Features.coffee index 5dfe73f05f..be7cd21d89 100644 --- a/services/web/app/coffee/infrastructure/Features.coffee +++ b/services/web/app/coffee/infrastructure/Features.coffee @@ -14,8 +14,8 @@ module.exports = Features = return Settings.enableGithubSync when 'v1-return-message' return Settings.accountMerge? and Settings.overleaf? - when 'rich-text' - return Settings.showRichText + when 'publish-modal' + return Settings.showPublishModal when 'custom-togglers' return Settings.overleaf? else diff --git a/services/web/app/coffee/infrastructure/Modules.coffee b/services/web/app/coffee/infrastructure/Modules.coffee index 9d4da24c8b..769182ad94 100644 --- a/services/web/app/coffee/infrastructure/Modules.coffee +++ b/services/web/app/coffee/infrastructure/Modules.coffee @@ -43,6 +43,13 @@ module.exports = Modules = moduleIncludesAvailable: (view) -> return (Modules.viewIncludes[view] or []).length > 0 + moduleAssetFiles: (pathPrefix) -> + assetFiles = [] + for module in @modules + for assetFile in module.assetFiles or [] + assetFiles.push "#{pathPrefix}#{assetFile}" + return assetFiles + attachHooks: () -> for module in @modules if module.hooks? diff --git a/services/web/app/coffee/models/Project.coffee b/services/web/app/coffee/models/Project.coffee index fb0bc0fd6b..00c2f1be52 100644 --- a/services/web/app/coffee/models/Project.coffee +++ b/services/web/app/coffee/models/Project.coffee @@ -12,6 +12,10 @@ ObjectId = Schema.ObjectId DeletedDocSchema = new Schema name: String +DeletedFileSchema = new Schema + name: String + deletedAt: {type: Date} + ProjectSchema = new Schema name : {type:String, default:'new project'} lastUpdated : {type:Date, default: () -> new Date()} @@ -30,6 +34,7 @@ ProjectSchema = new Schema description : {type:String, default:''} archived : { type: Boolean } deletedDocs : [DeletedDocSchema] + deletedFiles : [DeletedFileSchema] imageName : { type: String } track_changes : { type: Object } tokens : diff --git a/services/web/app/views/project/editor.pug b/services/web/app/views/project/editor.pug index 214ae202fe..da13deaa1f 100644 --- a/services/web/app/views/project/editor.pug +++ b/services/web/app/views/project/editor.pug @@ -55,6 +55,7 @@ block content include ./editor/header include ./editor/share + != moduleIncludes("publish:body", locals) #ide-body( ng-cloak, @@ -137,6 +138,7 @@ block requirejs "ide": "#{buildJsPath('ide.js', {hashedPath:settings.useMinifiedJs, removeExtension:true})}", "libraries": "#{buildJsPath('libraries.js', {hashedPath:settings.useMinifiedJs, removeExtension:true})}", !{moduleIncludes("editor:script", locals)} + !{moduleIncludes("publish:script", locals)} }, "waitSeconds": 0, "shim": { diff --git a/services/web/app/views/project/editor/editor.pug b/services/web/app/views/project/editor/editor.pug index 2b42cda965..b1570fa55d 100644 --- a/services/web/app/views/project/editor/editor.pug +++ b/services/web/app/views/project/editor/editor.pug @@ -39,6 +39,7 @@ div.full-size( ace-editor="editor", ng-if="!editor.richText", ng-show="!!editor.sharejs_doc && !editor.opening", + style=showRichText ? "top: 40px" : "", theme="settings.theme", keybindings="settings.mode", font-size="settings.fontSize", diff --git a/services/web/app/views/project/editor/header.pug b/services/web/app/views/project/editor/header.pug index c80c4d7b3d..03b56d9e20 100644 --- a/services/web/app/views/project/editor/header.pug +++ b/services/web/app/views/project/editor/header.pug @@ -107,6 +107,9 @@ header.toolbar.toolbar-header.toolbar-with-labels( ) i.fa.fa-fw.fa-group p.toolbar-label #{translate("share")} + + != moduleIncludes('publish:button', locals) + a.btn.btn-full-height( href, ng-click="toggleHistory();", diff --git a/services/web/app/views/project/editor/history.pug b/services/web/app/views/project/editor/history.pug index d24dfcf670..d366d5f41a 100644 --- a/services/web/app/views/project/editor/history.pug +++ b/services/web/app/views/project/editor/history.pug @@ -1,5 +1,5 @@ div#history(ng-show="ui.view == 'history'") - span(ng-controller="HistoryPremiumPopup") + span .upgrade-prompt(ng-if="project.features.versioning === false && ui.view === 'history'") .message(ng-if="project.owner._id == user.id") p.text-center: strong #{translate("upgrade_to_get_feature", {feature:"full Project History"})} @@ -33,7 +33,6 @@ div#history(ng-show="ui.view == 'history'") href ng-class="buttonClass" ng-click="startFreeTrial('history')" - sixpack-convert="teaser-history" ) #{translate("start_free_trial")} .message(ng-show="project.owner._id != user.id") diff --git a/services/web/app/views/project/editor/pdf.pug b/services/web/app/views/project/editor/pdf.pug index a87b765cf2..a874a0b88b 100644 --- a/services/web/app/views/project/editor/pdf.pug +++ b/services/web/app/views/project/editor/pdf.pug @@ -402,7 +402,6 @@ div.full-size.pdf(ng-controller="PdfController") a.btn.btn-success.row-spaced-small( href ng-class="buttonClass" - sixpack-convert="track_changes_feature_info" ng-click="startFreeTrial('compile-timeout')" ) #{translate("start_free_trial")} diff --git a/services/web/app/views/project/editor/share.pug b/services/web/app/views/project/editor/share.pug index b63038cf86..d2aa1cc173 100644 --- a/services/web/app/views/project/editor/share.pug +++ b/services/web/app/views/project/editor/share.pug @@ -173,7 +173,6 @@ script(type='text/ng-template', id='shareProjectModalTemplate') a.btn.btn-success( href ng-class="buttonClass" - sixpack-convert="track_changes_feature_info" ng-click="startFreeTrial('projectMembers')" ) #{translate("start_free_trial")} diff --git a/services/web/app/views/project/list/side-bar.pug b/services/web/app/views/project/list/side-bar.pug index c4186a01d5..c695dfd30c 100644 --- a/services/web/app/views/project/list/side-bar.pug +++ b/services/web/app/views/project/list/side-bar.pug @@ -10,19 +10,16 @@ li a( href, - sixpack-convert="first_sign_up", ng-click="openCreateProjectModal()" ) #{translate("blank_project")} li a( href, - sixpack-convert="first_sign_up", ng-click="openCreateProjectModal('example')" ) #{translate("example_project")} li a( href, - sixpack-convert="first_sign_up", ng-click="openUploadProjectModal()" ) #{translate("upload_project")} != moduleIncludes("newProjectMenu", locals) @@ -31,7 +28,7 @@ li.dropdown-header #{translate("templates")} each item in templates li - a.menu-indent(href=item.url, sixpack-convert="first_sign_up") #{translate(item.name)} + a.menu-indent(href=item.url) #{translate(item.name)} .row-spaced(ng-if="projects.length > 0", ng-cloak) ul.list-unstyled.folders-menu( @@ -133,7 +130,7 @@ hr p.small #{translate("on_free_sl")} p - a(href="/user/subscription/plans", sixpack-convert="left-menu-upgraed-rotation").btn.btn-primary #{translate("upgrade")} + a(href="/user/subscription/plans").btn.btn-primary #{translate("upgrade")} p.small.text-centered | #{translate("or_unlock_features_bonus")} a(href="/user/bonus") #{translate("sharing_sl")} . diff --git a/services/web/app/views/subscriptions/new.pug b/services/web/app/views/subscriptions/new.pug index 8995af6192..6e9d473307 100644 --- a/services/web/app/views/subscriptions/new.pug +++ b/services/web/app/views/subscriptions/new.pug @@ -1,8 +1,6 @@ extends ../layout block scripts - script(src="https://js.recurly.com/v3/recurly.js") - script(type='text/javascript'). window.countryCode = '#{countryCode}' window.plan_code = '#{plan_code}' @@ -41,19 +39,20 @@ block content span !{translate("first_few_days_free", {trialLen:'{{trialLength}}'})} span(ng-if="discountMonths && discountRate")   - {{discountMonths}} #{translate("month")}s {{discountRate}}% Off div(ng-if="price") - strong {{price.currency.symbol}}{{price.next.total}} + strong {{plans[currencyCode]['symbol']}}{{price.next.total}} span(ng-if="monthlyBilling") #{translate("every")} #{translate("month")} span(ng-if="!monthlyBilling") #{translate("every")} #{translate("year")} div(ng-if="normalPrice") - span.small Normally {{price.currency.symbol}}{{normalPrice}} + span.small Normally {{plans[currencyCode]['symbol']}}{{normalPrice}} .row div() .col-md-12() form( - ng-if="planName" name="simpleCCForm" novalidate ) + + div.payment-method-toggle a.payment-method-toggle-switch( href @@ -75,10 +74,10 @@ block content .alert.alert-warning.small(ng-show="genericError") strong {{genericError}} - div(ng-if="paymentMethod.value === 'credit_card'") + div(ng-show="paymentMethod.value === 'credit_card'") .row .col-xs-6 - .form-group(ng-class="validation.errorFields.first_name || inputHasError(simpleCCForm.firstName) ? 'has-error' : ''") + .form-group(ng-class="validation.errorFields.first_name || inputHasError(simpleCCForm.firstName) ? 'has-external-error' : ''") label(for="first-name") #{translate('first_name')} input#first-name.form-control( type="text" @@ -90,7 +89,7 @@ block content ) span.input-feedback-message {{ simpleCCForm.firstName.$error.required ? 'This field is required' : '' }} .col-xs-6 - .form-group(for="last-name",ng-class="validation.errorFields.last_name || inputHasError(simpleCCForm.lastName)? 'has-error' : ''") + .form-group(for="last-name",ng-class="validation.errorFields.last_name || inputHasError(simpleCCForm.lastName)? 'has-external-error' : ''") label(for="last-name") #{translate('last_name')} input#last-name.form-control( type="text" @@ -100,47 +99,41 @@ block content ng-model="data.last_name" required ) - span.input-feedback-message {{ simpleCCForm.lastName.$error.required ? 'This field is required' : '' }} - .form-group(ng-class="validation.correctCardNumber == false || validation.errorFields.number || inputHasError(simpleCCForm.ccNumber) ? 'has-error' : ''") + .form-group(ng-class="validation.errorFields.number ? 'has-external-error' : ''") label(for="card-no") #{translate("credit_card_number")} - input#card-no.form-control( + div#card-no( type="text" - ng-model="data.number" name="ccNumber" - ng-focus="validation.correctCardNumber = true; validation.errorFields.number = false;" - ng-blur="validateCardNumber();" - required - cc-format-card-number + data-recurly='number' ) - span.input-feedback-message {{ simpleCCForm.ccNumber.$error.required ? 'This field is required' : 'Please re-check the card number' }} .row - .col-xs-6 - .form-group.has-feedback(ng-class="validation.correctExpiry == false || validation.errorFields.expiry || inputHasError(simpleCCForm.expiry) ? 'has-error' : ''") - label #{translate("expiry")} - input.form-control( - type="text" - ng-model="data.mmYY" - name="expiry" - placeholder="MM / YY" - ng-focus="validation.correctExpiry = true; validation.errorFields.expiry = false;" - ng-blur="updateExpiry(); validateExpiry()" - required - cc-format-expiry + .col-xs-3 + .form-group.has-feedback(ng-class="validation.errorFields.month ? 'has-external-error' : ''") + label(for="month") #{translate("month")} + div( + type="number" + name="month" + data-recurly="month" ) - span.input-feedback-message {{ simpleCCForm.expiry.$error.required ? 'This field is required' : 'Please re-check the expiry date' }} - + .col-xs-3 + .form-group.has-feedback(ng-class="validation.errorFields.year ? 'has-external-error' : ''") + label(for="year") #{translate("year")} + div( + type="number" + name="year" + data-recurly="year" + ) + .col-xs-6 - .form-group.has-feedback(ng-class="validation.correctCvv == false || validation.errorFields.cvv || inputHasError(simpleCCForm.cvv) ? 'has-error' : ''") + .form-group.has-feedback(ng-class="validation.errorFields.cvv ? 'has-external-error' : ''") label #{translate("security_code")} - input.form-control( - type="text" + div( + type="number" ng-model="data.cvv" - ng-focus="validation.correctCvv = true; validation.errorFields.cvv = false;" - ng-blur="validateCvv()" + data-recurly="cvv" name="cvv" - required cc-format-sec-code ) .form-control-feedback @@ -151,11 +144,9 @@ block content tooltip-trigger="mouseenter" tooltip-append-to-body="true" ) ? - span.input-feedback-message {{ simpleCCForm.cvv.$error.required ? 'This field is required' : 'Please re-check the security code' }} - div - .form-group(ng-class="validation.errorFields.country || inputHasError(simpleCCForm.country) ? 'has-error' : ''") + .form-group(ng-class="validation.errorFields.country || inputHasError(simpleCCForm.country) ? 'has-external-error' : ''") label(for="country") #{translate('country')} select#country.form-control( data-recurly="country" @@ -189,8 +180,8 @@ block content div.price-breakdown(ng-if="price.next.tax !== '0.00'") hr.thin span Total: - strong {{price.currency.symbol}}{{price.next.total}} - span ({{price.currency.symbol}}{{price.next.subtotal}} + {{price.currency.symbol}}{{price.next.tax}} tax) + strong {{plans[currencyCode]['symbol']}}{{price.next.total}} + span ({{plans[currencyCode]['symbol']}}{{price.next.subtotal}} + {{plans[currencyCode]['symbol']}}{{price.next.tax}} tax) span(ng-if="monthlyBilling") #{translate("every")} #{translate("month")} span(ng-if="!monthlyBilling") #{translate("every")} #{translate("year")} hr.thin @@ -198,7 +189,7 @@ block content div.payment-submit button.btn.btn-success.btn-block( ng-click="submit()" - ng-disabled="processing || !isFormValid(simpleCCForm);" + ng-disabled="processing || !isFormValid(simpleCCForm);" ) span(ng-show="processing") i.fa.fa-spinner.fa-spin diff --git a/services/web/app/views/subscriptions/plans.pug b/services/web/app/views/subscriptions/plans.pug index bafb855ff7..7c40c38a52 100644 --- a/services/web/app/views/subscriptions/plans.pug +++ b/services/web/app/views/subscriptions/plans.pug @@ -76,7 +76,7 @@ block content br a.btn.btn-info( href="/register" - style=(getLoggedInUserId() === undefined ? "" : "visibility: hidden") + style=(getLoggedInUserId() === null ? "" : "visibility: hidden") ) #{translate("sign_up_now")} .col-md-4 .card.card-highlighted @@ -90,10 +90,8 @@ block content | {{plans[currencyCode]['collaborator']['annual']}} span.small /yr ul.list-unstyled - li - strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:10})} - strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:8})} - strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:12})} + li + strong #{translate("collabs_per_proj", {collabcount:10})} li #{translate("full_doc_history")} li #{translate("sync_to_dropbox")} li #{translate("sync_to_github")} @@ -144,7 +142,7 @@ block content br a.btn.btn-info( href="/register" - style=(getLoggedInUserId() === undefined ? "" : "visibility: hidden") + style=(getLoggedInUserId() === null ? "" : "visibility: hidden") ) #{translate("sign_up_now")} .col-md-4 @@ -157,9 +155,7 @@ block content span.small /mo ul.list-unstyled li - strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:6})} - strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:4})} - strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:8})} + strong #{translate("collabs_per_proj", {collabcount:6})} li #{translate("full_doc_history")} li #{translate("sync_to_dropbox")} li #{translate("sync_to_github")} @@ -180,9 +176,7 @@ block content span.small /yr ul.list-unstyled li - strong(ng-show="plansVariant == 'default'") #{translate("collabs_per_proj", {collabcount:6})} - strong(ng-show="plansVariant == 'heron'") #{translate("collabs_per_proj", {collabcount:4})} - strong(ng-show="plansVariant == 'ibis'") #{translate("collabs_per_proj", {collabcount:8})} + strong #{translate("collabs_per_proj", {collabcount:6})} li #{translate("full_doc_history")} li #{translate("sync_to_dropbox")} li #{translate("sync_to_github")} diff --git a/services/web/npm-shrinkwrap.json b/services/web/npm-shrinkwrap.json index 8f09d13777..b753fb0ffb 100644 --- a/services/web/npm-shrinkwrap.json +++ b/services/web/npm-shrinkwrap.json @@ -533,6 +533,12 @@ "resolved": "https://registry.npmjs.org/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz", "dev": true }, + "babel-helper-builder-react-jsx": { + "version": "6.26.0", + "from": "babel-helper-builder-react-jsx@>=6.24.1 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz", + "dev": true + }, "babel-helper-call-delegate": { "version": "6.24.1", "from": "babel-helper-call-delegate@>=6.24.1 <7.0.0", @@ -629,6 +635,18 @@ "resolved": "https://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz", "dev": true }, + "babel-plugin-syntax-flow": { + "version": "6.18.0", + "from": "babel-plugin-syntax-flow@>=6.18.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz", + "dev": true + }, + "babel-plugin-syntax-jsx": { + "version": "6.18.0", + "from": "babel-plugin-syntax-jsx@>=6.3.13 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", + "dev": true + }, "babel-plugin-syntax-trailing-function-commas": { "version": "6.22.0", "from": "babel-plugin-syntax-trailing-function-commas@>=6.22.0 <7.0.0", @@ -779,6 +797,36 @@ "resolved": "https://registry.npmjs.org/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz", "dev": true }, + "babel-plugin-transform-flow-strip-types": { + "version": "6.22.0", + "from": "babel-plugin-transform-flow-strip-types@>=6.22.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz", + "dev": true + }, + "babel-plugin-transform-react-display-name": { + "version": "6.25.0", + "from": "babel-plugin-transform-react-display-name@>=6.23.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz", + "dev": true + }, + "babel-plugin-transform-react-jsx": { + "version": "6.24.1", + "from": "babel-plugin-transform-react-jsx@>=6.24.1 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz", + "dev": true + }, + "babel-plugin-transform-react-jsx-self": { + "version": "6.22.0", + "from": "babel-plugin-transform-react-jsx-self@>=6.22.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz", + "dev": true + }, + "babel-plugin-transform-react-jsx-source": { + "version": "6.22.0", + "from": "babel-plugin-transform-react-jsx-source@>=6.22.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz", + "dev": true + }, "babel-plugin-transform-regenerator": { "version": "6.26.0", "from": "babel-plugin-transform-regenerator@>=6.22.0 <7.0.0", @@ -805,6 +853,18 @@ } } }, + "babel-preset-flow": { + "version": "6.23.0", + "from": "babel-preset-flow@>=6.23.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz", + "dev": true + }, + "babel-preset-react": { + "version": "6.24.1", + "from": "babel-preset-react@>=6.16.0 <7.0.0", + "resolved": "https://registry.npmjs.org/babel-preset-react/-/babel-preset-react-6.24.1.tgz", + "dev": true + }, "babel-register": { "version": "6.26.0", "from": "babel-register@>=6.26.0 <7.0.0", @@ -2031,6 +2091,11 @@ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.6.tgz", "dev": true }, + "create-react-class": { + "version": "15.6.3", + "from": "create-react-class@>=15.6.0 <16.0.0", + "resolved": "https://registry.npmjs.org/create-react-class/-/create-react-class-15.6.3.tgz" + }, "cross-spawn": { "version": "5.1.0", "from": "cross-spawn@>=5.0.1 <6.0.0", @@ -3429,6 +3494,23 @@ "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", "dev": true }, + "fbjs": { + "version": "0.8.16", + "from": "fbjs@>=0.8.9 <0.9.0", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.8.16.tgz", + "dependencies": { + "core-js": { + "version": "1.2.7", + "from": "core-js@>=1.0.0 <2.0.0", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz" + }, + "promise": { + "version": "7.3.1", + "from": "promise@>=7.1.1 <8.0.0", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz" + } + } + }, "fd-slicer": { "version": "1.0.1", "from": "fd-slicer@>=1.0.1 <1.1.0", @@ -3744,6 +3826,11 @@ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", "dev": true }, + "fuse.js": { + "version": "3.2.0", + "from": "fuse.js@>=3.0.0 <4.0.0", + "resolved": "https://registry.npmjs.org/fuse.js/-/fuse.js-3.2.0.tgz" + }, "gauge": { "version": "2.7.4", "from": "gauge@>=2.7.3 <2.8.0", @@ -5307,8 +5394,7 @@ "is-stream": { "version": "1.1.0", "from": "is-stream@>=1.1.0 <2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz" }, "is-symbol": { "version": "1.0.1", @@ -5355,6 +5441,11 @@ "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", "dev": true }, + "isomorphic-fetch": { + "version": "2.2.1", + "from": "isomorphic-fetch@>=2.1.1 <3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz" + }, "isstream": { "version": "0.1.2", "from": "isstream@>=0.1.2 <0.2.0", @@ -5391,8 +5482,7 @@ "js-tokens": { "version": "3.0.2", "from": "js-tokens@>=3.0.2 <4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz" }, "js-yaml": { "version": "2.0.5", @@ -6858,8 +6948,7 @@ "loose-envify": { "version": "1.3.1", "from": "loose-envify@>=1.0.0 <2.0.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz" }, "loud-rejection": { "version": "1.6.0", @@ -7620,6 +7709,11 @@ "from": "nocache@2.0.0", "resolved": "https://registry.npmjs.org/nocache/-/nocache-2.0.0.tgz" }, + "node-fetch": { + "version": "1.7.3", + "from": "node-fetch@>=1.0.1 <2.0.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz" + }, "node-forge": { "version": "0.2.24", "from": "node-forge@0.2.24", @@ -8663,6 +8757,11 @@ "resolved": "https://registry.npmjs.org/prompt/-/prompt-0.2.14.tgz", "dev": true }, + "prop-types": { + "version": "15.6.1", + "from": "prop-types@>=15.5.10 <16.0.0", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.6.1.tgz" + }, "proxy-addr": { "version": "1.0.10", "from": "proxy-addr@>=1.0.8 <1.1.0", @@ -8967,6 +9066,16 @@ } } }, + "react": { + "version": "15.6.2", + "from": "react@>=15.4.2 <16.0.0", + "resolved": "https://registry.npmjs.org/react/-/react-15.6.2.tgz" + }, + "react-dom": { + "version": "15.6.2", + "from": "react-dom@>=15.4.2 <16.0.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-15.6.2.tgz" + }, "read": { "version": "1.0.7", "from": "read@>=1.0.0 <1.1.0", @@ -9676,8 +9785,7 @@ "setimmediate": { "version": "1.0.5", "from": "setimmediate@>=1.0.4 <2.0.0", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "dev": true + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" }, "setprototypeof": { "version": "1.0.3", @@ -10945,6 +11053,11 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "dev": true }, + "ua-parser-js": { + "version": "0.7.17", + "from": "ua-parser-js@>=0.7.9 <0.8.0", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.17.tgz" + }, "uglify-js": { "version": "2.4.24", "from": "uglify-js@>=2.4.0 <2.5.0", @@ -12061,6 +12174,11 @@ "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.3.tgz", "dev": true }, + "whatwg-fetch": { + "version": "2.0.4", + "from": "whatwg-fetch@>=0.10.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-2.0.4.tgz" + }, "when": { "version": "3.7.8", "from": "when@>=3.7.7 <4.0.0", diff --git a/services/web/package.json b/services/web/package.json index ae9073fcfa..b6a4d80d35 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -42,6 +42,7 @@ "express-http-proxy": "^1.1.0", "express-session": "^1.14.2", "fs-extra": "^4.0.2", + "fuse.js": "^3.0.0", "heapdump": "^0.3.7", "helmet": "^3.8.1", "http-proxy": "^1.8.1", @@ -72,6 +73,8 @@ "passport-oauth2-refresh": "^1.0.0", "passport-saml": "^0.15.0", "pug": "^2.0.0-beta6", + "react": "^15.4.2", + "react-dom": "^15.4.2", "redis-sharelatex": "git+https://github.com/sharelatex/redis-sharelatex.git#v1.0.4", "request": "^2.69.0", "requestretry": "^1.13.0", @@ -95,6 +98,7 @@ "babel-core": "^6.26.0", "babel-loader": "^7.1.2", "babel-preset-env": "^1.6.1", + "babel-preset-react": "^6.16.0", "bunyan": "0.22.1", "chai": "3.5.0", "chai-spies": "", diff --git a/services/web/public/coffee/ide/directives/layout.coffee b/services/web/public/coffee/ide/directives/layout.coffee index 3c8f2900f1..b4e2e7f83d 100644 --- a/services/web/public/coffee/ide/directives/layout.coffee +++ b/services/web/public/coffee/ide/directives/layout.coffee @@ -119,6 +119,7 @@ define [ customTogglerScope = scope.$new() customTogglerScope.isOpen = true + customTogglerScope.isVisible = true if state[customTogglerPane]?.initClosed == true customTogglerScope.isOpen = false @@ -132,6 +133,7 @@ define [ repositionCustomToggler() customTogglerEl = $compile(" + customTogglerEl.scope().isOpen = !value + customTogglerEl.scope().isVisible = !value + post: (scope, element, attrs) -> name = attrs.layout diff --git a/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee b/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee index c736014c8b..f16cace816 100644 --- a/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee +++ b/services/web/public/coffee/ide/history/controllers/HistoryListController.coffee @@ -3,15 +3,6 @@ define [ "ide/history/util/displayNameForUser" ], (App, displayNameForUser) -> - App.controller "HistoryPremiumPopup", ($scope, ide, sixpack)-> - $scope.$watch "ui.view", -> - if $scope.ui.view == "history" - if $scope.project?.features?.versioning - $scope.versioningPopupType = "default" - else if $scope.ui.view == "history" - sixpack.participate 'history-discount', ['default', 'discount'], (chosenVariation, rawResponse)-> - $scope.versioningPopupType = chosenVariation - App.controller "HistoryListController", ["$scope", "ide", ($scope, ide) -> $scope.hoveringOverListSelectors = false diff --git a/services/web/public/coffee/main/account-upgrade.coffee b/services/web/public/coffee/main/account-upgrade.coffee index 0cabc224a0..638371e45e 100644 --- a/services/web/public/coffee/main/account-upgrade.coffee +++ b/services/web/public/coffee/main/account-upgrade.coffee @@ -19,24 +19,8 @@ define [ url = "#{url}&cc=#{couponCode}" $scope.startedFreeTrial = true - switch source - when "dropbox" - sixpack.participate 'teaser-dropbox-text', ['default', 'dropbox-focused'], (variant) -> - event_tracking.sendMB "subscription-start-trial", { source, plan, variant } - - when "history" - sixpack.participate 'teaser-history', ['default', 'focused'], (variant) -> - event_tracking.sendMB "subscription-start-trial", { source, plan, variant } - - else - event_tracking.sendMB "subscription-start-trial", { source, plan } + event_tracking.sendMB "subscription-start-trial", { source, plan } w.location = url - if $scope.shouldABTestPlans - sixpack.participate 'plans-1610', ['default', 'heron', 'ibis'], (chosenVariation, rawResponse)-> - if chosenVariation in ['heron', 'ibis'] - plan = "collaborator_#{chosenVariation}" - go() - else - go() + go() diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee index 6e8af93156..02939a3b9c 100644 --- a/services/web/public/coffee/main/new-subscription.coffee +++ b/services/web/public/coffee/main/new-subscription.coffee @@ -1,6 +1,7 @@ define [ "base", "directives/creditCards" + "libs/recurly-4.8.5" ], (App)-> App.controller "NewSubscriptionController", ($scope, MultiCurrencyPricing, abTestManager, $http, sixpack, event_tracking, ccUtils)-> @@ -21,10 +22,6 @@ define [ value: "credit_card" $scope.data = - number: "" - month: "" - year: "" - cvv: "" first_name: "" last_name: "" postal_code: "" @@ -34,16 +31,25 @@ define [ city:"" country:window.countryCode coupon: window.couponCode - mmYY: "" - - $scope.validation = - correctCardNumber : true - correctExpiry: true - correctCvv: true + + $scope.validation = {} $scope.processing = false - recurly.configure window.recurlyApiKey + recurly.configure + publicKey: window.recurlyApiKey + style: + all: + fontFamily: '"Open Sans", sans-serif', + fontSize: '16px', + fontColor: '#7a7a7a' + month: + placeholder: 'MM' + year: + placeholder: 'YY' + cvv: + placeholder: 'CVV' + pricing = recurly.Pricing() window.pricing = pricing @@ -73,6 +79,8 @@ define [ $scope.normalPrice += (basePrice * pricing.price.taxes[0].rate) $scope.$apply() + + $scope.applyCoupon = -> pricing.coupon($scope.data.coupon).done() @@ -83,26 +91,6 @@ define [ $scope.currencyCode = newCurrency pricing.currency(newCurrency).done() - $scope.updateExpiry = () -> - parsedDateObj = ccUtils.parseExpiry $scope.data.mmYY - if parsedDateObj? - $scope.data.month = parsedDateObj.month - $scope.data.year = parsedDateObj.year - - $scope.validateCardNumber = validateCardNumber = -> - $scope.validation.errorFields = {} - if $scope.data.number?.length != 0 - $scope.validation.correctCardNumber = recurly.validate.cardNumber($scope.data.number) - - $scope.validateExpiry = validateExpiry = -> - $scope.validation.errorFields = {} - if $scope.data.month?.length != 0 and $scope.data.year?.length != 0 - $scope.validation.correctExpiry = recurly.validate.expiry($scope.data.month, $scope.data.year) - - $scope.validateCvv = validateCvv = -> - $scope.validation.errorFields = {} - if $scope.data.cvv?.length != 0 - $scope.validation.correctCvv = recurly.validate.cvv($scope.data.cvv) $scope.inputHasError = inputHasError = (formItem) -> if !formItem? @@ -114,10 +102,7 @@ define [ if $scope.paymentMethod.value == 'paypal' return $scope.data.country != "" else - return (form.$valid and - $scope.validation.correctCardNumber and - $scope.validation.correctExpiry and - $scope.validation.correctCvv) + return form.$valid $scope.updateCountry = -> pricing.address({country:$scope.data.country}).done() diff --git a/services/web/public/coffee/main/plans.coffee b/services/web/public/coffee/main/plans.coffee index 060517a9d5..9a62420d66 100644 --- a/services/web/public/coffee/main/plans.coffee +++ b/services/web/public/coffee/main/plans.coffee @@ -1,6 +1,6 @@ define [ "base" - "libs/recurly-3.0.5" + "libs/recurly-4.8.5" ], (App, recurly) -> @@ -11,164 +11,6 @@ define [ return { currencyCode:currencyCode - heron: - USD: - student: - monthly: "$6" - annual: "$60" - collaborator: - monthly: "$12" - annual: "$144" - EUR: - student: - monthly: "€5" - annual: "€50" - collaborator: - monthly: "€11" - annual: "€132" - GBP: - student: - monthly: "£5" - annual: "£50" - collaborator: - monthly: "£10" - annual: "£120" - SEK: - student: - monthly: "45 kr" - annual: "450 kr" - collaborator: - monthly: "90 kr" - annual: "1080 kr" - CAD: - student: - monthly: "$7" - annual: "$70" - collaborator: - monthly: "$14" - annual: "$168" - NOK: - student: - monthly: "45 kr" - annual: "450 kr" - collaborator: - monthly: "90 kr" - annual: "1080 kr" - DKK: - student: - monthly: "40 kr" - annual: "400 kr" - collaborator: - monthly: "70 kr" - annual: "840 kr" - AUD: - student: - monthly: "$8" - annual: "$80" - collaborator: - monthly: "$15" - annual: "$180" - NZD: - student: - monthly: "$8" - annual: "$80" - collaborator: - monthly: "$15" - annual: "$180" - CHF: - student: - monthly: "Fr 6" - annual: "Fr 60" - collaborator: - monthly: "Fr 12" - annual: "Fr 144" - SGD: - student: - monthly: "$8" - annual: "$80" - collaborator: - monthly: "$16" - annual: "$192" - - ibis: - USD: - student: - monthly: "$10" - annual: "$100" - collaborator: - monthly: "$18" - annual: "$216" - EUR: - student: - monthly: "€9" - annual: "€90" - collaborator: - monthly: "€17" - annual: "€204" - GBP: - student: - monthly: "£7" - annual: "£70" - collaborator: - monthly: "£14" - annual: "£168" - SEK: - student: - monthly: "75 kr" - annual: "750 kr" - collaborator: - monthly: "140 kr" - annual: "1680 kr" - CAD: - student: - monthly: "$12" - annual: "$120" - collaborator: - monthly: "$22" - annual: "$264" - NOK: - student: - monthly: "75 kr" - annual: "750 kr" - collaborator: - monthly: "140 kr" - annual: "1680 kr" - DKK: - student: - monthly: "68 kr" - annual: "680 kr" - collaborator: - monthly: "110 kr" - annual: "1320 kr" - AUD: - student: - monthly: "$13" - annual: "$130" - collaborator: - monthly: "$22" - annual: "$264" - NZD: - student: - monthly: "$14" - annual: "$140" - collaborator: - monthly: "$22" - annual: "$264" - CHF: - student: - monthly: "Fr 10" - annual: "Fr 100" - collaborator: - monthly: "Fr 18" - annual: "Fr 216" - SGD: - student: - monthly: "$14" - annual: "$140" - collaborator: - monthly: "$25" - annual: "$300" - plans: USD: symbol: "$" @@ -312,14 +154,6 @@ define [ $scope.shouldABTestPlans = window.shouldABTestPlans if $scope.shouldABTestPlans - sixpack.participate 'plans-1610', ['default', 'heron', 'ibis'], (chosenVariation, rawResponse)-> - $scope.plansVariant = chosenVariation - event_tracking.sendMB 'plans-page', {plans_variant: chosenVariation} - if chosenVariation in ['heron', 'ibis'] - # overwrite student plans with alternative - for currency, _v of $scope.plans - $scope.plans[currency]['student'] = MultiCurrencyPricing[chosenVariation][currency]['student'] - $scope.plans[currency]['collaborator'] = MultiCurrencyPricing[chosenVariation][currency]['collaborator'] $scope.showPlans = true else $scope.showPlans = true diff --git a/services/web/public/coffee/main/subscription-dashboard.coffee b/services/web/public/coffee/main/subscription-dashboard.coffee index c971b9712a..28d5af013b 100644 --- a/services/web/public/coffee/main/subscription-dashboard.coffee +++ b/services/web/public/coffee/main/subscription-dashboard.coffee @@ -3,8 +3,6 @@ define [ ], (App)-> App.controller 'SuccessfulSubscriptionController', ($scope, sixpack) -> - sixpack.convert 'plans-1610', () -> - SUBSCRIPTION_URL = "/user/subscription/update" diff --git a/services/web/public/js/libs/recurly-3.0.5.js b/services/web/public/js/libs/recurly-3.0.5.js deleted file mode 100644 index 9ae06622aa..0000000000 --- a/services/web/public/js/libs/recurly-3.0.5.js +++ /dev/null @@ -1,4218 +0,0 @@ -;(function(){ - -/** - * Require the given path. - * - * @param {String} path - * @return {Object} exports - * @api public - */ - -function require(path, parent, orig) { - var resolved = require.resolve(path); - - // lookup failed - if (null == resolved) { - orig = orig || path; - parent = parent || 'root'; - var err = new Error('Failed to require "' + orig + '" from "' + parent + '"'); - err.path = orig; - err.parent = parent; - err.require = true; - throw err; - } - - var module = require.modules[resolved]; - - // perform real require() - // by invoking the module's - // registered function - if (!module._resolving && !module.exports) { - var mod = {}; - mod.exports = {}; - mod.client = mod.component = true; - module._resolving = true; - module.call(this, mod.exports, require.relative(resolved), mod); - delete module._resolving; - module.exports = mod.exports; - } - - return module.exports; -} - -/** - * Registered modules. - */ - -require.modules = {}; - -/** - * Registered aliases. - */ - -require.aliases = {}; - -/** - * Resolve `path`. - * - * Lookup: - * - * - PATH/index.js - * - PATH.js - * - PATH - * - * @param {String} path - * @return {String} path or null - * @api private - */ - -require.resolve = function(path) { - if (path.charAt(0) === '/') path = path.slice(1); - - var paths = [ - path, - path + '.js', - path + '.json', - path + '/index.js', - path + '/index.json' - ]; - - for (var i = 0; i < paths.length; i++) { - var path = paths[i]; - if (require.modules.hasOwnProperty(path)) return path; - if (require.aliases.hasOwnProperty(path)) return require.aliases[path]; - } -}; - -/** - * Normalize `path` relative to the current path. - * - * @param {String} curr - * @param {String} path - * @return {String} - * @api private - */ - -require.normalize = function(curr, path) { - var segs = []; - - if ('.' != path.charAt(0)) return path; - - curr = curr.split('/'); - path = path.split('/'); - - for (var i = 0; i < path.length; ++i) { - if ('..' == path[i]) { - curr.pop(); - } else if ('.' != path[i] && '' != path[i]) { - segs.push(path[i]); - } - } - - return curr.concat(segs).join('/'); -}; - -/** - * Register module at `path` with callback `definition`. - * - * @param {String} path - * @param {Function} definition - * @api private - */ - -require.register = function(path, definition) { - require.modules[path] = definition; -}; - -/** - * Alias a module definition. - * - * @param {String} from - * @param {String} to - * @api private - */ - -require.alias = function(from, to) { - if (!require.modules.hasOwnProperty(from)) { - throw new Error('Failed to alias "' + from + '", it does not exist'); - } - require.aliases[to] = from; -}; - -/** - * Return a require function relative to the `parent` path. - * - * @param {String} parent - * @return {Function} - * @api private - */ - -require.relative = function(parent) { - var p = require.normalize(parent, '..'); - - /** - * lastIndexOf helper. - */ - - function lastIndexOf(arr, obj) { - var i = arr.length; - while (i--) { - if (arr[i] === obj) return i; - } - return -1; - } - - /** - * The relative require() itself. - */ - - function localRequire(path) { - var resolved = localRequire.resolve(path); - return require(resolved, parent, path); - } - - /** - * Resolve relative to the parent. - */ - - localRequire.resolve = function(path) { - var c = path.charAt(0); - if ('/' == c) return path.slice(1); - if ('.' == c) return require.normalize(p, path); - - // resolve deps by returning - // the dep in the nearest "deps" - // directory - var segs = parent.split('/'); - var i = lastIndexOf(segs, 'deps') + 1; - if (!i) i = 0; - path = segs.slice(0, i + 1).join('/') + '/deps/' + path; - return path; - }; - - /** - * Check if module is defined at `path`. - */ - - localRequire.exists = function(path) { - return require.modules.hasOwnProperty(localRequire.resolve(path)); - }; - - return localRequire; -}; -require.register("visionmedia-node-querystring/index.js", function(exports, require, module){ -/** - * Object#toString() ref for stringify(). - */ - -var toString = Object.prototype.toString; - -/** - * Object#hasOwnProperty ref - */ - -var hasOwnProperty = Object.prototype.hasOwnProperty; - -/** - * Array#indexOf shim. - */ - -var indexOf = typeof Array.prototype.indexOf === 'function' - ? function(arr, el) { return arr.indexOf(el); } - : function(arr, el) { - for (var i = 0; i < arr.length; i++) { - if (arr[i] === el) return i; - } - return -1; - }; - -/** - * Array.isArray shim. - */ - -var isArray = Array.isArray || function(arr) { - return toString.call(arr) == '[object Array]'; -}; - -/** - * Object.keys shim. - */ - -var objectKeys = Object.keys || function(obj) { - var ret = []; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - ret.push(key); - } - } - return ret; -}; - -/** - * Array#forEach shim. - */ - -var forEach = typeof Array.prototype.forEach === 'function' - ? function(arr, fn) { return arr.forEach(fn); } - : function(arr, fn) { - for (var i = 0; i < arr.length; i++) fn(arr[i]); - }; - -/** - * Array#reduce shim. - */ - -var reduce = function(arr, fn, initial) { - if (typeof arr.reduce === 'function') return arr.reduce(fn, initial); - var res = initial; - for (var i = 0; i < arr.length; i++) res = fn(res, arr[i]); - return res; -}; - -/** - * Cache non-integer test regexp. - */ - -var isint = /^[0-9]+$/; - -function promote(parent, key) { - if (parent[key].length == 0) return parent[key] = {} - var t = {}; - for (var i in parent[key]) { - if (hasOwnProperty.call(parent[key], i)) { - t[i] = parent[key][i]; - } - } - parent[key] = t; - return t; -} - -function parse(parts, parent, key, val) { - var part = parts.shift(); - - // illegal - if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return; - - // end - if (!part) { - if (isArray(parent[key])) { - parent[key].push(val); - } else if ('object' == typeof parent[key]) { - parent[key] = val; - } else if ('undefined' == typeof parent[key]) { - parent[key] = val; - } else { - parent[key] = [parent[key], val]; - } - // array - } else { - var obj = parent[key] = parent[key] || []; - if (']' == part) { - if (isArray(obj)) { - if ('' != val) obj.push(val); - } else if ('object' == typeof obj) { - obj[objectKeys(obj).length] = val; - } else { - obj = parent[key] = [parent[key], val]; - } - // prop - } else if (~indexOf(part, ']')) { - part = part.substr(0, part.length - 1); - if (!isint.test(part) && isArray(obj)) obj = promote(parent, key); - parse(parts, obj, part, val); - // key - } else { - if (!isint.test(part) && isArray(obj)) obj = promote(parent, key); - parse(parts, obj, part, val); - } - } -} - -/** - * Merge parent key/val pair. - */ - -function merge(parent, key, val){ - if (~indexOf(key, ']')) { - var parts = key.split('[') - , len = parts.length - , last = len - 1; - parse(parts, parent, 'base', val); - // optimize - } else { - if (!isint.test(key) && isArray(parent.base)) { - var t = {}; - for (var k in parent.base) t[k] = parent.base[k]; - parent.base = t; - } - set(parent.base, key, val); - } - - return parent; -} - -/** - * Compact sparse arrays. - */ - -function compact(obj) { - if ('object' != typeof obj) return obj; - - if (isArray(obj)) { - var ret = []; - - for (var i in obj) { - if (hasOwnProperty.call(obj, i)) { - ret.push(obj[i]); - } - } - - return ret; - } - - for (var key in obj) { - obj[key] = compact(obj[key]); - } - - return obj; -} - -/** - * Parse the given obj. - */ - -function parseObject(obj){ - var ret = { base: {} }; - - forEach(objectKeys(obj), function(name){ - merge(ret, name, obj[name]); - }); - - return compact(ret.base); -} - -/** - * Parse the given str. - */ - -function parseString(str){ - var ret = reduce(String(str).split('&'), function(ret, pair){ - var eql = indexOf(pair, '=') - , brace = lastBraceInKey(pair) - , key = pair.substr(0, brace || eql) - , val = pair.substr(brace || eql, pair.length) - , val = val.substr(indexOf(val, '=') + 1, val.length); - - // ?foo - if ('' == key) key = pair, val = ''; - if ('' == key) return ret; - - return merge(ret, decode(key), decode(val)); - }, { base: {} }).base; - - return compact(ret); -} - -/** - * Parse the given query `str` or `obj`, returning an object. - * - * @param {String} str | {Object} obj - * @return {Object} - * @api public - */ - -exports.parse = function(str){ - if (null == str || '' == str) return {}; - return 'object' == typeof str - ? parseObject(str) - : parseString(str); -}; - -/** - * Turn the given `obj` into a query string - * - * @param {Object} obj - * @return {String} - * @api public - */ - -var stringify = exports.stringify = function(obj, prefix) { - if (isArray(obj)) { - return stringifyArray(obj, prefix); - } else if ('[object Object]' == toString.call(obj)) { - return stringifyObject(obj, prefix); - } else if ('string' == typeof obj) { - return stringifyString(obj, prefix); - } else { - return prefix + '=' + encodeURIComponent(String(obj)); - } -}; - -/** - * Stringify the given `str`. - * - * @param {String} str - * @param {String} prefix - * @return {String} - * @api private - */ - -function stringifyString(str, prefix) { - if (!prefix) throw new TypeError('stringify expects an object'); - return prefix + '=' + encodeURIComponent(str); -} - -/** - * Stringify the given `arr`. - * - * @param {Array} arr - * @param {String} prefix - * @return {String} - * @api private - */ - -function stringifyArray(arr, prefix) { - var ret = []; - if (!prefix) throw new TypeError('stringify expects an object'); - for (var i = 0; i < arr.length; i++) { - ret.push(stringify(arr[i], prefix + '[' + i + ']')); - } - return ret.join('&'); -} - -/** - * Stringify the given `obj`. - * - * @param {Object} obj - * @param {String} prefix - * @return {String} - * @api private - */ - -function stringifyObject(obj, prefix) { - var ret = [] - , keys = objectKeys(obj) - , key; - - for (var i = 0, len = keys.length; i < len; ++i) { - key = keys[i]; - if ('' == key) continue; - if (null == obj[key]) { - ret.push(encodeURIComponent(key) + '='); - } else { - ret.push(stringify(obj[key], prefix - ? prefix + '[' + encodeURIComponent(key) + ']' - : encodeURIComponent(key))); - } - } - - return ret.join('&'); -} - -/** - * Set `obj`'s `key` to `val` respecting - * the weird and wonderful syntax of a qs, - * where "foo=bar&foo=baz" becomes an array. - * - * @param {Object} obj - * @param {String} key - * @param {String} val - * @api private - */ - -function set(obj, key, val) { - var v = obj[key]; - if (Object.getOwnPropertyDescriptor(Object.prototype, key)) return; - if (undefined === v) { - obj[key] = val; - } else if (isArray(v)) { - v.push(val); - } else { - obj[key] = [v, val]; - } -} - -/** - * Locate last brace in `str` within the key. - * - * @param {String} str - * @return {Number} - * @api private - */ - -function lastBraceInKey(str) { - var len = str.length - , brace - , c; - for (var i = 0; i < len; ++i) { - c = str[i]; - if (']' == c) brace = false; - if ('[' == c) brace = true; - if ('=' == c && !brace) return i; - } -} - -/** - * Decode `str`. - * - * @param {String} str - * @return {String} - * @api private - */ - -function decode(str) { - try { - return decodeURIComponent(str.replace(/\+/g, ' ')); - } catch (err) { - return str; - } -} - -}); -require.register("component-emitter/index.js", function(exports, require, module){ - -/** - * Expose `Emitter`. - */ - -module.exports = Emitter; - -/** - * Initialize a new `Emitter`. - * - * @api public - */ - -function Emitter(obj) { - if (obj) return mixin(obj); -}; - -/** - * Mixin the emitter properties. - * - * @param {Object} obj - * @return {Object} - * @api private - */ - -function mixin(obj) { - for (var key in Emitter.prototype) { - obj[key] = Emitter.prototype[key]; - } - return obj; -} - -/** - * Listen on the given `event` with `fn`. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - -Emitter.prototype.on = -Emitter.prototype.addEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - (this._callbacks[event] = this._callbacks[event] || []) - .push(fn); - return this; -}; - -/** - * Adds an `event` listener that will be invoked a single - * time then automatically removed. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - -Emitter.prototype.once = function(event, fn){ - var self = this; - this._callbacks = this._callbacks || {}; - - function on() { - self.off(event, on); - fn.apply(this, arguments); - } - - on.fn = fn; - this.on(event, on); - return this; -}; - -/** - * Remove the given callback for `event` or all - * registered callbacks. - * - * @param {String} event - * @param {Function} fn - * @return {Emitter} - * @api public - */ - -Emitter.prototype.off = -Emitter.prototype.removeListener = -Emitter.prototype.removeAllListeners = -Emitter.prototype.removeEventListener = function(event, fn){ - this._callbacks = this._callbacks || {}; - - // all - if (0 == arguments.length) { - this._callbacks = {}; - return this; - } - - // specific event - var callbacks = this._callbacks[event]; - if (!callbacks) return this; - - // remove all handlers - if (1 == arguments.length) { - delete this._callbacks[event]; - return this; - } - - // remove specific handler - var cb; - for (var i = 0; i < callbacks.length; i++) { - cb = callbacks[i]; - if (cb === fn || cb.fn === fn) { - callbacks.splice(i, 1); - break; - } - } - return this; -}; - -/** - * Emit `event` with the given args. - * - * @param {String} event - * @param {Mixed} ... - * @return {Emitter} - */ - -Emitter.prototype.emit = function(event){ - this._callbacks = this._callbacks || {}; - var args = [].slice.call(arguments, 1) - , callbacks = this._callbacks[event]; - - if (callbacks) { - callbacks = callbacks.slice(0); - for (var i = 0, len = callbacks.length; i < len; ++i) { - callbacks[i].apply(this, args); - } - } - - return this; -}; - -/** - * Return array of callbacks for `event`. - * - * @param {String} event - * @return {Array} - * @api public - */ - -Emitter.prototype.listeners = function(event){ - this._callbacks = this._callbacks || {}; - return this._callbacks[event] || []; -}; - -/** - * Check if this emitter has `event` handlers. - * - * @param {String} event - * @return {Boolean} - * @api public - */ - -Emitter.prototype.hasListeners = function(event){ - return !! this.listeners(event).length; -}; - -}); -require.register("component-indexof/index.js", function(exports, require, module){ -module.exports = function(arr, obj){ - if (arr.indexOf) return arr.indexOf(obj); - for (var i = 0; i < arr.length; ++i) { - if (arr[i] === obj) return i; - } - return -1; -}; -}); -require.register("component-object/index.js", function(exports, require, module){ - -/** - * HOP ref. - */ - -var has = Object.prototype.hasOwnProperty; - -/** - * Return own keys in `obj`. - * - * @param {Object} obj - * @return {Array} - * @api public - */ - -exports.keys = Object.keys || function(obj){ - var keys = []; - for (var key in obj) { - if (has.call(obj, key)) { - keys.push(key); - } - } - return keys; -}; - -/** - * Return own values in `obj`. - * - * @param {Object} obj - * @return {Array} - * @api public - */ - -exports.values = function(obj){ - var vals = []; - for (var key in obj) { - if (has.call(obj, key)) { - vals.push(obj[key]); - } - } - return vals; -}; - -/** - * Merge `b` into `a`. - * - * @param {Object} a - * @param {Object} b - * @return {Object} a - * @api public - */ - -exports.merge = function(a, b){ - for (var key in b) { - if (has.call(b, key)) { - a[key] = b[key]; - } - } - return a; -}; - -/** - * Return length of `obj`. - * - * @param {Object} obj - * @return {Number} - * @api public - */ - -exports.length = function(obj){ - return exports.keys(obj).length; -}; - -/** - * Check if `obj` is empty. - * - * @param {Object} obj - * @return {Boolean} - * @api public - */ - -exports.isEmpty = function(obj){ - return 0 == exports.length(obj); -}; -}); -require.register("component-event/index.js", function(exports, require, module){ -var bind = window.addEventListener ? 'addEventListener' : 'attachEvent', - unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent', - prefix = bind !== 'addEventListener' ? 'on' : ''; - -/** - * Bind `el` event `type` to `fn`. - * - * @param {Element} el - * @param {String} type - * @param {Function} fn - * @param {Boolean} capture - * @return {Function} - * @api public - */ - -exports.bind = function(el, type, fn, capture){ - el[bind](prefix + type, fn, capture || false); - return fn; -}; - -/** - * Unbind `el` event `type`'s callback `fn`. - * - * @param {Element} el - * @param {String} type - * @param {Function} fn - * @param {Boolean} capture - * @return {Function} - * @api public - */ - -exports.unbind = function(el, type, fn, capture){ - el[unbind](prefix + type, fn, capture || false); - return fn; -}; -}); -require.register("component-clone/index.js", function(exports, require, module){ -/** - * Module dependencies. - */ - -var type; -try { - type = require('component-type'); -} catch (_) { - type = require('type'); -} - -/** - * Module exports. - */ - -module.exports = clone; - -/** - * Clones objects. - * - * @param {Mixed} any object - * @api public - */ - -function clone(obj){ - switch (type(obj)) { - case 'object': - var copy = {}; - for (var key in obj) { - if (obj.hasOwnProperty(key)) { - copy[key] = clone(obj[key]); - } - } - return copy; - - case 'array': - var copy = new Array(obj.length); - for (var i = 0, l = obj.length; i < l; i++) { - copy[i] = clone(obj[i]); - } - return copy; - - case 'regexp': - // from millermedeiros/amd-utils - MIT - var flags = ''; - flags += obj.multiline ? 'm' : ''; - flags += obj.global ? 'g' : ''; - flags += obj.ignoreCase ? 'i' : ''; - return new RegExp(obj.source, flags); - - case 'date': - return new Date(obj.getTime()); - - default: // string, number, boolean, … - return obj; - } -} - -}); -require.register("component-bind/index.js", function(exports, require, module){ - -/** - * Slice reference. - */ - -var slice = [].slice; - -/** - * Bind `obj` to `fn`. - * - * @param {Object} obj - * @param {Function|String} fn or string - * @return {Function} - * @api public - */ - -module.exports = function(obj, fn){ - if ('string' == typeof fn) fn = obj[fn]; - if ('function' != typeof fn) throw new Error('bind() requires a function'); - var args = [].slice.call(arguments, 2); - return function(){ - return fn.apply(obj, args.concat(slice.call(arguments))); - } -}; - -}); -require.register("component-props/index.js", function(exports, require, module){ -/** - * Global Names - */ - -var globals = /\b(this|Array|Date|Object|Math|JSON)\b/g; - -/** - * Return immediate identifiers parsed from `str`. - * - * @param {String} str - * @param {String|Function} map function or prefix - * @return {Array} - * @api public - */ - -module.exports = function(str, fn){ - var p = unique(props(str)); - if (fn && 'string' == typeof fn) fn = prefixed(fn); - if (fn) return map(str, p, fn); - return p; -}; - -/** - * Return immediate identifiers in `str`. - * - * @param {String} str - * @return {Array} - * @api private - */ - -function props(str) { - return str - .replace(/\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\//g, '') - .replace(globals, '') - .match(/[$a-zA-Z_]\w*/g) - || []; -} - -/** - * Return `str` with `props` mapped with `fn`. - * - * @param {String} str - * @param {Array} props - * @param {Function} fn - * @return {String} - * @api private - */ - -function map(str, props, fn) { - var re = /\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^/]+)\/|[a-zA-Z_]\w*/g; - return str.replace(re, function(_){ - if ('(' == _[_.length - 1]) return fn(_); - if (!~props.indexOf(_)) return _; - return fn(_); - }); -} - -/** - * Return unique array. - * - * @param {Array} arr - * @return {Array} - * @api private - */ - -function unique(arr) { - var ret = []; - - for (var i = 0; i < arr.length; i++) { - if (~ret.indexOf(arr[i])) continue; - ret.push(arr[i]); - } - - return ret; -} - -/** - * Map with prefix `str`. - */ - -function prefixed(str) { - return function(_){ - return str + _; - }; -} - -}); -require.register("component-to-function/index.js", function(exports, require, module){ -/** - * Module Dependencies - */ - -var expr = require('props'); - -/** - * Expose `toFunction()`. - */ - -module.exports = toFunction; - -/** - * Convert `obj` to a `Function`. - * - * @param {Mixed} obj - * @return {Function} - * @api private - */ - -function toFunction(obj) { - switch ({}.toString.call(obj)) { - case '[object Object]': - return objectToFunction(obj); - case '[object Function]': - return obj; - case '[object String]': - return stringToFunction(obj); - case '[object RegExp]': - return regexpToFunction(obj); - default: - return defaultToFunction(obj); - } -} - -/** - * Default to strict equality. - * - * @param {Mixed} val - * @return {Function} - * @api private - */ - -function defaultToFunction(val) { - return function(obj){ - return val === obj; - } -} - -/** - * Convert `re` to a function. - * - * @param {RegExp} re - * @return {Function} - * @api private - */ - -function regexpToFunction(re) { - return function(obj){ - return re.test(obj); - } -} - -/** - * Convert property `str` to a function. - * - * @param {String} str - * @return {Function} - * @api private - */ - -function stringToFunction(str) { - // immediate such as "> 20" - if (/^ *\W+/.test(str)) return new Function('_', 'return _ ' + str); - - // properties such as "name.first" or "age > 18" or "age > 18 && age < 36" - return new Function('_', 'return ' + get(str)); -} - -/** - * Convert `object` to a function. - * - * @param {Object} object - * @return {Function} - * @api private - */ - -function objectToFunction(obj) { - var match = {} - for (var key in obj) { - match[key] = typeof obj[key] === 'string' - ? defaultToFunction(obj[key]) - : toFunction(obj[key]) - } - return function(val){ - if (typeof val !== 'object') return false; - for (var key in match) { - if (!(key in val)) return false; - if (!match[key](val[key])) return false; - } - return true; - } -} - -/** - * Built the getter function. Supports getter style functions - * - * @param {String} str - * @return {String} - * @api private - */ - -function get(str) { - var props = expr(str); - if (!props.length) return '_.' + str; - - var val; - for(var i = 0, prop; prop = props[i]; i++) { - val = '_.' + prop; - val = "('function' == typeof " + val + " ? " + val + "() : " + val + ")"; - str = str.replace(new RegExp(prop, 'g'), val); - } - - return str; -} - -}); -require.register("component-each/index.js", function(exports, require, module){ - -/** - * Module dependencies. - */ - -var type = require('type'); -var toFunction = require('to-function'); - -/** - * HOP reference. - */ - -var has = Object.prototype.hasOwnProperty; - -/** - * Iterate the given `obj` and invoke `fn(val, i)` - * in optional context `ctx`. - * - * @param {String|Array|Object} obj - * @param {Function} fn - * @param {Object} [ctx] - * @api public - */ - -module.exports = function(obj, fn, ctx){ - fn = toFunction(fn); - ctx = ctx || this; - switch (type(obj)) { - case 'array': - return array(obj, fn, ctx); - case 'object': - if ('number' == typeof obj.length) return array(obj, fn, ctx); - return object(obj, fn, ctx); - case 'string': - return string(obj, fn, ctx); - } -}; - -/** - * Iterate string chars. - * - * @param {String} obj - * @param {Function} fn - * @param {Object} ctx - * @api private - */ - -function string(obj, fn, ctx) { - for (var i = 0; i < obj.length; ++i) { - fn.call(ctx, obj.charAt(i), i); - } -} - -/** - * Iterate object keys. - * - * @param {Object} obj - * @param {Function} fn - * @param {Object} ctx - * @api private - */ - -function object(obj, fn, ctx) { - for (var key in obj) { - if (has.call(obj, key)) { - fn.call(ctx, key, obj[key]); - } - } -} - -/** - * Iterate array-ish. - * - * @param {Array|Object} obj - * @param {Function} fn - * @param {Object} ctx - * @api private - */ - -function array(obj, fn, ctx) { - for (var i = 0; i < obj.length; ++i) { - fn.call(ctx, obj[i], i); - } -} - -}); -require.register("component-find/index.js", function(exports, require, module){ - -/** - * Module dependencies. - */ - -var toFunction = require('to-function'); - -/** - * Find the first value in `arr` with when `fn(val, i)` is truthy. - * - * @param {Array} arr - * @param {Function} fn - * @return {Array} - * @api public - */ - -module.exports = function(arr, fn){ - // callback - if ('function' != typeof fn) { - if (Object(fn) === fn) fn = objectToFunction(fn); - else fn = toFunction(fn); - } - - // filter - for (var i = 0, len = arr.length; i < len; ++i) { - if (fn(arr[i], i)) return arr[i]; - } -}; - -/** - * Convert `obj` into a match function. - * - * @param {Object} obj - * @return {Function} - * @api private - */ - -function objectToFunction(obj) { - return function(o){ - for (var key in obj) { - if (o[key] != obj[key]) return false; - } - return true; - } -} -}); -require.register("component-json/index.js", function(exports, require, module){ - -module.exports = 'undefined' == typeof JSON - ? require('component-json-fallback') - : JSON; - -}); -require.register("component-type/index.js", function(exports, require, module){ - -/** - * toString ref. - */ - -var toString = Object.prototype.toString; - -/** - * Return the type of `val`. - * - * @param {Mixed} val - * @return {String} - * @api public - */ - -module.exports = function(val){ - switch (toString.call(val)) { - case '[object Function]': return 'function'; - case '[object Date]': return 'date'; - case '[object RegExp]': return 'regexp'; - case '[object Arguments]': return 'arguments'; - case '[object Array]': return 'array'; - case '[object String]': return 'string'; - } - - if (val === null) return 'null'; - if (val === undefined) return 'undefined'; - if (val && val.nodeType === 1) return 'element'; - if (val === Object(val)) return 'object'; - - return typeof val; -}; - -}); -require.register("component-trim/index.js", function(exports, require, module){ - -exports = module.exports = trim; - -function trim(str){ - if (str.trim) return str.trim(); - return str.replace(/^\s*|\s*$/g, ''); -} - -exports.left = function(str){ - if (str.trimLeft) return str.trimLeft(); - return str.replace(/^\s*/, ''); -}; - -exports.right = function(str){ - if (str.trimRight) return str.trimRight(); - return str.replace(/\s*$/, ''); -}; - -}); -require.register("component-map/index.js", function(exports, require, module){ - -/** - * Module dependencies. - */ - -var toFunction = require('to-function'); - -/** - * Map the given `arr` with callback `fn(val, i)`. - * - * @param {Array} arr - * @param {Function} fn - * @return {Array} - * @api public - */ - -module.exports = function(arr, fn){ - var ret = []; - fn = toFunction(fn); - for (var i = 0; i < arr.length; ++i) { - ret.push(fn(arr[i], i)); - } - return ret; -}; -}); -require.register("yields-merge/index.js", function(exports, require, module){ - -/** - * merge `b`'s properties with `a`'s. - * - * example: - * - * var user = {}; - * merge(user, console); - * // > { log: fn, dir: fn ..} - * - * @param {Object} a - * @param {Object} b - * @return {Object} - */ - -module.exports = function (a, b) { - for (var k in b) a[k] = b[k]; - return a; -}; - -}); -require.register("learnboost-jsonp/index.js", function(exports, require, module){ -/** - * Module dependencies - */ - -var debug = require('debug')('jsonp'); - -/** - * Module exports. - */ - -module.exports = jsonp; - -/** - * Callback index. - */ - -var count = 0; - -/** - * Noop function. - */ - -function noop(){} - -/** - * JSONP handler - * - * Options: - * - param {String} qs parameter (`callback`) - * - timeout {Number} how long after a timeout error is emitted (`60000`) - * - * @param {String} url - * @param {Object|Function} optional options / callback - * @param {Function} optional callback - */ - -function jsonp(url, opts, fn){ - if ('function' == typeof opts) { - fn = opts; - opts = {}; - } - if (!opts) opts = {}; - - var prefix = opts.prefix || '__jp'; - var param = opts.param || 'callback'; - var timeout = null != opts.timeout ? opts.timeout : 60000; - var enc = encodeURIComponent; - var target = document.getElementsByTagName('script')[0] || document.head; - var script; - var timer; - - // generate a unique id for this request - var id = prefix + (count++); - - if (timeout) { - timer = setTimeout(function(){ - cleanup(); - if (fn) fn(new Error('Timeout')); - }, timeout); - } - - function cleanup(){ - script.parentNode.removeChild(script); - window[id] = noop; - } - - window[id] = function(data){ - debug('jsonp got', data); - if (timer) clearTimeout(timer); - cleanup(); - if (fn) fn(null, data); - }; - - // add qs component - url += (~url.indexOf('?') ? '&' : '?') + param + '=' + enc(id); - url = url.replace('?&', '?'); - - debug('jsonp req "%s"', url); - - // create script - script = document.createElement('script'); - script.src = url; - target.parentNode.insertBefore(script, target); -} - -}); -require.register("visionmedia-debug/debug.js", function(exports, require, module){ - -/** - * Expose `debug()` as the module. - */ - -module.exports = debug; - -/** - * Create a debugger with the given `name`. - * - * @param {String} name - * @return {Type} - * @api public - */ - -function debug(name) { - if (!debug.enabled(name)) return function(){}; - - return function(fmt){ - fmt = coerce(fmt); - - var curr = new Date; - var ms = curr - (debug[name] || curr); - debug[name] = curr; - - fmt = name - + ' ' - + fmt - + ' +' + debug.humanize(ms); - - // This hackery is required for IE8 - // where `console.log` doesn't have 'apply' - window.console - && console.log - && Function.prototype.apply.call(console.log, console, arguments); - } -} - -/** - * The currently active debug mode names. - */ - -debug.names = []; -debug.skips = []; - -/** - * Enables a debug mode by name. This can include modes - * separated by a colon and wildcards. - * - * @param {String} name - * @api public - */ - -debug.enable = function(name) { - try { - localStorage.debug = name; - } catch(e){} - - var split = (name || '').split(/[\s,]+/) - , len = split.length; - - for (var i = 0; i < len; i++) { - name = split[i].replace('*', '.*?'); - if (name[0] === '-') { - debug.skips.push(new RegExp('^' + name.substr(1) + '$')); - } - else { - debug.names.push(new RegExp('^' + name + '$')); - } - } -}; - -/** - * Disable debug output. - * - * @api public - */ - -debug.disable = function(){ - debug.enable(''); -}; - -/** - * Humanize the given `ms`. - * - * @param {Number} m - * @return {String} - * @api private - */ - -debug.humanize = function(ms) { - var sec = 1000 - , min = 60 * 1000 - , hour = 60 * min; - - if (ms >= hour) return (ms / hour).toFixed(1) + 'h'; - if (ms >= min) return (ms / min).toFixed(1) + 'm'; - if (ms >= sec) return (ms / sec | 0) + 's'; - return ms + 'ms'; -}; - -/** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - -debug.enabled = function(name) { - for (var i = 0, len = debug.skips.length; i < len; i++) { - if (debug.skips[i].test(name)) { - return false; - } - } - for (var i = 0, len = debug.names.length; i < len; i++) { - if (debug.names[i].test(name)) { - return true; - } - } - return false; -}; - -/** - * Coerce `val`. - */ - -function coerce(val) { - if (val instanceof Error) return val.stack || val.message; - return val; -} - -// persist - -try { - if (window.localStorage) debug.enable(localStorage.debug); -} catch(e){} - -}); -require.register("johntron-asap/asap.js", function(exports, require, module){ -"use strict"; - -// Use the fastest possible means to execute a task in a future turn -// of the event loop. - -// linked list of tasks (single, with head node) -var head = {task: void 0, next: null}; -var tail = head; -var flushing = false; -var requestFlush = void 0; -var hasSetImmediate = typeof setImmediate === "function"; -var domain; - -if (typeof global != 'undefined') { - // Avoid shims from browserify. - // The existence of `global` in browsers is guaranteed by browserify. - var process = global.process; -} - -// Note that some fake-Node environments, -// like the Mocha test runner, introduce a `process` global. -var isNodeJS = !!process && ({}).toString.call(process) === "[object process]"; - -function flush() { - /* jshint loopfunc: true */ - - while (head.next) { - head = head.next; - var task = head.task; - head.task = void 0; - - try { - task(); - - } catch (e) { - if (isNodeJS) { - // In node, uncaught exceptions are considered fatal errors. - // Re-throw them to interrupt flushing! - - // Ensure continuation if an uncaught exception is suppressed - // listening process.on("uncaughtException") or domain("error"). - requestFlush(); - - throw e; - - } else { - // In browsers, uncaught exceptions are not fatal. - // Re-throw them asynchronously to avoid slow-downs. - throw e; - } - } - } - - flushing = false; -} - -if (isNodeJS) { - // Node.js - requestFlush = function () { - // Ensure flushing is not bound to any domain. - var currentDomain = process.domain; - if (currentDomain) { - domain = domain || (1,require)("domain"); - domain.active = process.domain = null; - } - - // Avoid tick recursion - use setImmediate if it exists. - if (flushing && hasSetImmediate) { - setImmediate(flush); - } else { - process.nextTick(flush); - } - - if (currentDomain) { - domain.active = process.domain = currentDomain; - } - }; - -} else if (hasSetImmediate) { - // In IE10, or https://github.com/NobleJS/setImmediate - requestFlush = function () { - setImmediate(flush); - }; - -} else if (typeof MessageChannel !== "undefined") { - // modern browsers - // http://www.nonblocking.io/2011/06/windownexttick.html - var channel = new MessageChannel(); - // At least Safari Version 6.0.5 (8536.30.1) intermittently cannot create - // working message ports the first time a page loads. - channel.port1.onmessage = function () { - requestFlush = requestPortFlush; - channel.port1.onmessage = flush; - flush(); - }; - var requestPortFlush = function () { - // Opera requires us to provide a message payload, regardless of - // whether we use it. - channel.port2.postMessage(0); - }; - requestFlush = function () { - setTimeout(flush, 0); - requestPortFlush(); - }; - -} else { - // old browsers - requestFlush = function () { - setTimeout(flush, 0); - }; -} - -function asap(task) { - if (isNodeJS && process.domain) { - task = process.domain.bind(task); - } - - tail = tail.next = {task: task, next: null}; - - if (!flushing) { - requestFlush(); - flushing = true; - } -}; - -module.exports = asap; - -}); -require.register("chrissrogers-promise/index.js", function(exports, require, module){ -'use strict'; - -//This file contains then/promise specific extensions to the core promise API - -var Promise = require('./core.js') -var asap = require('asap') - -module.exports = Promise - -/* Static Functions */ - -function ValuePromise(value) { - this.then = function (onFulfilled) { - if (typeof onFulfilled !== 'function') return this - return new Promise(function (resolve, reject) { - asap(function () { - try { - resolve(onFulfilled(value)) - } catch (ex) { - reject(ex); - } - }) - }) - } -} -ValuePromise.prototype = Promise.prototype - -var TRUE = new ValuePromise(true) -var FALSE = new ValuePromise(false) -var NULL = new ValuePromise(null) -var UNDEFINED = new ValuePromise(undefined) -var ZERO = new ValuePromise(0) -var EMPTYSTRING = new ValuePromise('') - -Promise.resolve = function (value) { - if (value instanceof Promise) return value - - if (value === null) return NULL - if (value === undefined) return UNDEFINED - if (value === true) return TRUE - if (value === false) return FALSE - if (value === 0) return ZERO - if (value === '') return EMPTYSTRING - - if (typeof value === 'object' || typeof value === 'function') { - try { - var then = value.then - if (typeof then === 'function') { - return new Promise(then.bind(value)) - } - } catch (ex) { - return new Promise(function (resolve, reject) { - reject(ex) - }) - } - } - - return new ValuePromise(value) -} - -Promise.from = Promise.cast = function (value) { - var err = new Error('Promise.from and Promise.cast are deprecated, use Promise.resolve instead') - err.name = 'Warning' - console.warn(err.stack) - return Promise.resolve(value) -} - -Promise.denodeify = function (fn, argumentCount) { - argumentCount = argumentCount || Infinity - return function () { - var self = this - var args = Array.prototype.slice.call(arguments) - return new Promise(function (resolve, reject) { - while (args.length && args.length > argumentCount) { - args.pop() - } - args.push(function (err, res) { - if (err) reject(err) - else resolve(res) - }) - fn.apply(self, args) - }) - } -} -Promise.nodeify = function (fn) { - return function () { - var args = Array.prototype.slice.call(arguments) - var callback = typeof args[args.length - 1] === 'function' ? args.pop() : null - try { - return fn.apply(this, arguments).nodeify(callback) - } catch (ex) { - if (callback === null || typeof callback == 'undefined') { - return new Promise(function (resolve, reject) { reject(ex) }) - } else { - asap(function () { - callback(ex) - }) - } - } - } -} - -Promise.all = function () { - var calledWithArray = arguments.length === 1 && Array.isArray(arguments[0]) - var args = Array.prototype.slice.call(calledWithArray ? arguments[0] : arguments) - - if (!calledWithArray) { - var err = new Error('Promise.all should be called with a single array, calling it with multiple arguments is deprecated') - err.name = 'Warning' - console.warn(err.stack) - } - - return new Promise(function (resolve, reject) { - if (args.length === 0) return resolve([]) - var remaining = args.length - function res(i, val) { - try { - if (val && (typeof val === 'object' || typeof val === 'function')) { - var then = val.then - if (typeof then === 'function') { - then.call(val, function (val) { res(i, val) }, reject) - return - } - } - args[i] = val - if (--remaining === 0) { - resolve(args); - } - } catch (ex) { - reject(ex) - } - } - for (var i = 0; i < args.length; i++) { - res(i, args[i]) - } - }) -} - -Promise.reject = function (value) { - return new Promise(function (resolve, reject) { - reject(value); - }); -} - -Promise.race = function (values) { - return new Promise(function (resolve, reject) { - values.forEach(function(value){ - Promise.resolve(value).then(resolve, reject); - }) - }); -} - -/* Prototype Methods */ - -Promise.prototype.done = function (onFulfilled, onRejected) { - var self = arguments.length ? this.then.apply(this, arguments) : this - self.then(null, function (err) { - asap(function () { - throw err - }) - }) -} - -Promise.prototype.nodeify = function (callback) { - if (typeof callback != 'function') return this - - this.then(function (value) { - asap(function () { - callback(null, value) - }) - }, function (err) { - asap(function () { - callback(err) - }) - }) -} - -Promise.prototype['catch'] = function (onRejected) { - return this.then(null, onRejected); -} - -}); -require.register("chrissrogers-promise/core.js", function(exports, require, module){ -'use strict'; - -var asap = require('asap') - -module.exports = Promise -function Promise(fn) { - if (typeof this !== 'object') throw new TypeError('Promises must be constructed via new') - if (typeof fn !== 'function') throw new TypeError('not a function') - var state = null - var value = null - var deferreds = [] - var self = this - - this.then = function(onFulfilled, onRejected) { - return new self.constructor(function(resolve, reject) { - handle(new Handler(onFulfilled, onRejected, resolve, reject)) - }) - } - - function handle(deferred) { - if (state === null) { - deferreds.push(deferred) - return - } - asap(function() { - var cb = state ? deferred.onFulfilled : deferred.onRejected - if (cb === null) { - (state ? deferred.resolve : deferred.reject)(value) - return - } - var ret - try { - ret = cb(value) - } - catch (e) { - deferred.reject(e) - return - } - deferred.resolve(ret) - }) - } - - function resolve(newValue) { - try { //Promise Resolution Procedure: https://github.com/promises-aplus/promises-spec#the-promise-resolution-procedure - if (newValue === self) throw new TypeError('A promise cannot be resolved with itself.') - if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) { - var then = newValue.then - if (typeof then === 'function') { - doResolve(then.bind(newValue), resolve, reject) - return - } - } - state = true - value = newValue - finale() - } catch (e) { reject(e) } - } - - function reject(newValue) { - state = false - value = newValue - finale() - } - - function finale() { - for (var i = 0, len = deferreds.length; i < len; i++) - handle(deferreds[i]) - deferreds = null - } - - doResolve(fn, resolve, reject) -} - - -function Handler(onFulfilled, onRejected, resolve, reject){ - this.onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : null - this.onRejected = typeof onRejected === 'function' ? onRejected : null - this.resolve = resolve - this.reject = reject -} - -/** - * Take a potentially misbehaving resolver function and make sure - * onFulfilled and onRejected are only called once. - * - * Makes no guarantees about asynchrony. - */ -function doResolve(fn, onFulfilled, onRejected) { - var done = false; - try { - fn(function (value) { - if (done) return - done = true - onFulfilled(value) - }, function (reason) { - if (done) return - done = true - onRejected(reason) - }) - } catch (ex) { - if (done) return - done = true - onRejected(ex) - } -} - -}); -require.register("kewah-mixin/index.js", function(exports, require, module){ -if (typeof Object.keys === 'function') { - module.exports = function(to, from) { - Object.keys(from).forEach(function(property) { - Object.defineProperty(to, property, Object.getOwnPropertyDescriptor(from, property)); - }); - }; -} else { - module.exports = function(to, from) { - for (var property in from) { - if (from.hasOwnProperty(property)) { - to[property] = from[property]; - } - } - }; -} - -}); -require.register("pluma-par/dist/par.js", function(exports, require, module){ -/*! par 0.3.0 Original author Alan Plum . Released into the Public Domain under the UNLICENSE. @preserve */ -var slice = Array.prototype.slice; - -function par(fn) { - var args0 = slice.call(arguments, 1); - return function() { - var argsN = slice.call(arguments, 0), - args = []; - args.push.apply(args, args0); - args.push.apply(args, argsN); - return fn.apply(this, args); - }; -} - -function rpartial(fn) { - var argsN = slice.call(arguments, 1); - return function() { - var args = slice.call(arguments, 0); - args.push.apply(args, argsN); - return fn.apply(this, args); - }; -} - -par.rpartial = rpartial; -par.lpartial = par; - -module.exports = par; - -}); -require.register("ianstormtaylor-to-no-case/index.js", function(exports, require, module){ - -/** - * Expose `toNoCase`. - */ - -module.exports = toNoCase; - - -/** - * Test whether a string is camel-case. - */ - -var hasSpace = /\s/; -var hasCamel = /[a-z][A-Z]/; -var hasSeparator = /[\W_]/; - - -/** - * Remove any starting case from a `string`, like camel or snake, but keep - * spaces and punctuation that may be important otherwise. - * - * @param {String} string - * @return {String} - */ - -function toNoCase (string) { - if (hasSpace.test(string)) return string.toLowerCase(); - - if (hasSeparator.test(string)) string = unseparate(string); - if (hasCamel.test(string)) string = uncamelize(string); - return string.toLowerCase(); -} - - -/** - * Separator splitter. - */ - -var separatorSplitter = /[\W_]+(.|$)/g; - - -/** - * Un-separate a `string`. - * - * @param {String} string - * @return {String} - */ - -function unseparate (string) { - return string.replace(separatorSplitter, function (m, next) { - return next ? ' ' + next : ''; - }); -} - - -/** - * Camelcase splitter. - */ - -var camelSplitter = /(.)([A-Z]+)/g; - - -/** - * Un-camelcase a `string`. - * - * @param {String} string - * @return {String} - */ - -function uncamelize (string) { - return string.replace(camelSplitter, function (m, previous, uppers) { - return previous + ' ' + uppers.toLowerCase().split('').join(' '); - }); -} -}); -require.register("ianstormtaylor-to-space-case/index.js", function(exports, require, module){ - -var clean = require('to-no-case'); - - -/** - * Expose `toSpaceCase`. - */ - -module.exports = toSpaceCase; - - -/** - * Convert a `string` to space case. - * - * @param {String} string - * @return {String} - */ - - -function toSpaceCase (string) { - return clean(string).replace(/[\W_]+(.|$)/g, function (matches, match) { - return match ? ' ' + match : ''; - }); -} -}); -require.register("ianstormtaylor-to-slug-case/index.js", function(exports, require, module){ - -var toSpace = require('to-space-case'); - - -/** - * Expose `toSlugCase`. - */ - -module.exports = toSlugCase; - - -/** - * Convert a `string` to slug case. - * - * @param {String} string - * @return {String} - */ - - -function toSlugCase (string) { - return toSpace(string).replace(/\s/g, '-'); -} -}); -require.register("recurly/lib/index.js", function(exports, require, module){ - -/*! - * Module dependencies. - */ - -var Recurly = require('./recurly'); - -/** - * Export a single instance. - */ - -module.exports = exports = new Recurly(); - -/** - * Hack for testing. - */ - -exports.Recurly = Recurly; - -}); -require.register("recurly/lib/recurly.js", function(exports, require, module){ - -/*! - * Module dependencies. - */ - -var bind = require('bind'); -var json = require('json'); -var each = require('each'); -var type = require('type'); -var merge = require('merge'); -var mixin = require('mixin'); -var jsonp = require('jsonp'); -var qs = require('querystring'); -var Emitter = require('emitter'); -var errors = require('./errors'); -var version = require('./version'); -var debug = require('debug')('recurly'); - -/** - * Default configuration values. - * - * @private - * @type {Object} - */ - -var defaults = { - currency: 'USD' - , timeout: 60000 - , publicKey: '' - , api: 'https://api.recurly.com/js/v1' -}; - -/** - * API mixins. - * - * @type {Array} - * @private - */ - -var mixins = [ - 'open' - , 'coupon' - , 'paypal' - , 'plan' - , 'tax' - , 'token' - , 'pricing' - , 'validate' -]; - -/** - * Export `Recurly`. - */ - -module.exports = Recurly; - -/** - * Initialize defaults. - * - * @param {Object} options - * @constructor - * @public - */ - -function Recurly (options) { - this.id = 0; - this.version = version; - this.configured = false; - this.config = merge({}, defaults); - if (options) this.configure(options); -} - -/** - * Inherits `Emitter`. - */ - -Emitter(Recurly.prototype); - -/** - * Configure settings. - * - * @param {String|Object} options Either publicKey or object containing - * publicKey and other optional members - * @param {String} options.publicKey - * @param {String} [options.currency] - * @param {String} [options.api] - * @public - */ - -Recurly.prototype.configure = function configure (options) { - if (this.configured) throw errors('already-configured'); - - debug('configure'); - - if (type(options) === 'string') options = { publicKey: options }; - - if ('publicKey' in options) { - this.config.publicKey = options.publicKey; - } else { - throw errors('missing-public-key'); - } - - if ('api' in options) { - this.config.api = options.api; - } - - if ('currency' in options) { - this.config.currency = options.currency; - } - - this.configured = true; -}; - -/** - * Assembles the API endpoint. - * - * @return {String} route - * @private - */ - -Recurly.prototype.url = function url (route) { - return this.config.api + route; -}; - -/** - * Issues an API request. - * - * @param {String} route - * @param {Object} [data] - * @param {Function} done - * @throws {Error} If `configure` has not been called. - * @private - */ - -Recurly.prototype.request = function request (route, data, done) { - debug('request'); - - if (false === this.configured) { - throw errors('not-configured'); - } - - if ('function' == type(data)) { - done = data; - data = {}; - } - - var url = this.url(route); - var timeout = this.config.timeout; - - data.version = this.version; - data.key = this.config.publicKey; - - url += '?' + qs.stringify(data); - - this.cache(url, function (res, set) { - if (res) return done(null, res); - jsonp(url, { timeout: timeout }, function (err, res) { - if (err) return done(err); - if (res.error) { - done(errors('api-error', res.error)); - } else { - done(null, set(res)); - } - }); - }); -}; - -/** - * Caches an object - * - * TODO: figure out invalidation & expiry - * - * @param {String} url - * @param {Function} done - * @private - */ - -Recurly.prototype.cache = function cache (url, done) { - debug('cache'); - var stored = localStorage.getItem(url); - if (stored) { - debug('cache found ' + url); - return done(json.parse(stored)); - } else { - debug('cache set ' + url); - return done(null, set); - } - function set (obj) { - // disabled for now - // localStorage.setItem(url, json.stringify(obj)); - return obj; - } -}; - -/** - * Load the `mixins` onto Recurly.prototype. - */ - -each(mixins, function (name) { - mixin(Recurly.prototype, require('./recurly/' + name)); -}); - -}); -require.register("recurly/lib/version.js", function(exports, require, module){ - -/** - * Current package/component version. - */ - -module.exports = '3.0.5'; - -}); -require.register("recurly/lib/errors.js", function(exports, require, module){ -/** - * dependencies - */ - -var mixin = require('mixin'); - -/** - * Export `errors`. - */ - -module.exports = exports = errors; - -/** - * Error accessor. - * - * @param {String} name - * @param {Object} options - * @return {Error} - */ - -function errors (name, options) { - return errors.get(name, options); -} - -/** - * Defined errors. - * - * @type {Object} - * @private - */ - -errors.map = {}; - -/** - * Base url for documention. - * - * @type {String} - * @private - */ - -errors.baseURL = ''; - -/** - * Sets the `baseURL` for docs. - * - * @param {String} url - * @public - */ - -errors.doc = function (baseURL) { - errors.baseURL = baseURL; -}; - -/** - * Gets errors defined by `name`. - * - * @param {String} name - * @param {Object} context - * @return {Error} - * @public - */ - -errors.get = function (name, context) { - if (!(name in errors.map)) { - throw new Error('invalid error'); - } else { - return new errors.map[name](context); - } -}; - -/** - * Registers an error defined by `name` with `config`. - * - * @param {String} name - * @param {Object} config - * @return {Error} - * @public - */ - -errors.add = function (name, config) { - config = config || {}; - - function RecurlyError (context) { - Error.call(this); - - this.name = this.code = name; - this.message = config.message; - mixin(this, context || {}); - - if (config.help) { - this.help = errors.baseURL + config.help; - this.message += ' (need help? ' + this.help + ')'; - } - }; - - RecurlyError.prototype = new Error(); - return errors.map[name] = RecurlyError; -}; - -/** - * Internal definations. - * - * TODO(gjohnson): open source this as a component - * and move these out. - */ - -errors.doc('https://docs.recurly.com/js'); - -errors.add('already-configured', { - message: 'Configuration may only be set once.', - help: '#identify-your-site' -}); - -errors.add('not-configured', { - message: 'Not configured. You must first call recurly.configure().', - help: '#identify-your-site' -}); - -errors.add('missing-public-key', { - message: 'The publicKey setting is required.', - help: '#identify-your-site' -}); - -errors.add('api-error', { - message: 'There was an error with your request.' -}); - -errors.add('validation', { - message: 'There was an error validating your request.' -}); - -errors.add('missing-callback', { - message: 'Missing callback' -}); - -errors.add('invalid-options', { - message: 'Options must be an object' -}); - -errors.add('missing-plan', { - message: 'A plan must be specified.' -}); - -errors.add('missing-coupon', { - message: 'A coupon must be specified.' -}); - -errors.add('invalid-item', { - message: 'The given item does not appear to be a valid recurly plan, coupon, addon, or taxable address.' -}); - -errors.add('invalid-addon', { - message: 'The given addon_code is not among the valid addons for the specified plan.' -}); - -errors.add('invalid-currency', { - message: 'The given currency is not among the valid codes for the specified plan.' -}); - -errors.add('unremovable-item', { - message: 'The given item cannot be removed.' -}); - -}); -require.register("recurly/lib/util/dom.js", function(exports, require, module){ -/** - * dependencies - */ - -var slug = require('to-slug-case'); -var type = require('type'); -var each = require('each'); -var map = require('map'); - -/** - * expose - */ - -module.exports = { - element: element, - value: value, - data: data -}; - -/** - * Detects whether an object is an html element. - * - * @param {Mixed} node - * @return {HTMLElement|Boolean} node - */ - -function element (node) { - var isJQuery = window.jQuery && node instanceof jQuery; - var isArray = type(node) === 'array'; - if (isJQuery || isArray) node = node[0]; - - var isElem = typeof HTMLElement !== 'undefined' - ? node instanceof HTMLElement - : node && node.nodeType === 1; - - return isElem && node; -}; - -/** - * Gets or sets the value of a given HTML form element - * - * supports text inputs, radio inputs, and selects - * - * @param {HTMLElement} node - * @return {String} value of the element - */ - -function value (node, value) { - if (!element(node)) return null; - return typeof value !== 'undefined' - ? valueSet(node, value) - : valueGet(node); -} - -/** - * Gets an HTMLElement's value property in the context of a form - * - * @param {HTMLElement} node - * @return {String} node's value - */ - -function valueGet (node) { - node = element(node); - - var nodeType = node && node.type && node.type.toLowerCase(); - var value; - - if (!nodeType) { - value = ''; - } else if ('options' in node) { - value = node.options[node.selectedIndex].value; - } else if (nodeType === 'checkbox') { - if (node.checked) value = node.value; - } else if (nodeType === 'radio') { - var radios = document.querySelectorAll('input[data-recurly="' + data(node, 'recurly') + '"]'); - each(radios, function (radio) { - if (radio.checked) value = radio.value; - }); - } else if ('value' in node) { - value = node.value; - } - - return value; -} - -/** - * Updates an element's value property if - * one exists; else innerText if it exists - * - * @param {Array[HTMLElement]} nodes - * @param {Mixed} value - */ - -function valueSet (nodes, value) { - if (type(nodes) !== 'array') nodes = [nodes]; - each(nodes, function (node) { - if (!node) return; - else if ('value' in node) - node.value = value; - else if ('textContent' in node) - node.textContent = value; - else if ('innerText' in node) - node.innerText = value; - }); -} - -/** - * Gets or sets a node's data attribute - * - * @param {HTMLElement} node - * @param {String} key - * @param {Mixed} [value] - */ - -function data (node, key, value) { - node = element(node); - if (!node) return; - return typeof value !== 'undefined' - ? dataSet(node, key, value) - : dataGet(node, key); -} - -/** - * Gets a node's data attribute - * - * @param {HTMLElement} node - * @param {String} key - */ - -function dataGet (node, key) { - return node.dataset - ? node.dataset[key] - : node.getAttribute('data-' + slug(key)); -} - -/** - * sets a node's data attribute - * - * @param {HTMLElement} node - * @param {String} key - * @param {Mixed} value - */ - -function dataSet (node, key, value) { - if (node.dataset) node.dataset[key] = value; - else node.setAttribute('data-' + slug(key), value); -} - -}); -require.register("recurly/lib/util/parse-card.js", function(exports, require, module){ - -/** - * Removes dashes and spaces from a card number. - * - * @param {Number|String} number - * @return {String} parsed card number - */ - -module.exports = function parseCard (number) { - return number && number.toString().replace(/[-\s]/g, ''); -}; - -}); -require.register("recurly/lib/recurly/open.js", function(exports, require, module){ - -/*! - * Module dependencies. - */ - -var bind = require('bind'); -var type = require('type'); -var json = require('json'); -var events = require('event'); -var qs = require('querystring'); -var errors = require('../errors'); -var debug = require('debug')('recurly:open'); - -/** - * Issues an API request to a popup window. - * - * TODO(*): configurable window name? - * TODO(*): configurable window properties? - * - * @param {String} url - * @param {Object} [data] - * @param {Function} [done] - * @throws {Error} If `configure` has not been called. - * @return {Window} - * @private - */ - -exports.open = function (url, data, done) { - debug('open'); - - if (false === this.configured) { - throw errors('not-configured'); - } - - if ('function' == type(data)) { - done = data; - data = {}; - } - - data = data || {}; - data.version = this.version; - data.event = 'recurly-open-' + this.id++; - data.key = this.config.publicKey; - this.once(data.event, done); - - if (!/^https?:\/\//.test(url)) url = this.url(url); - url += (~url.indexOf('?') ? '&' : '?') + qs.stringify(data); - - this.relay(function () { - window.open(url); - }); -}; - -/** - * Relay mixin. - * - * Inspects the window for intent to relay a message, - * then attempts to send it off. closes the window once - * dispatched. - * - * @param {Function} done - * @private - */ - -exports.relay = function (done) { - var self = this; - - if (false === this.configured) { - throw errors('not-configured'); - } - - events.bind(window, 'message', function listener (event) { - var data = json.parse(event.data); - var name = data.recurly_event; - var body = data.recurly_message; - var err = body.error ? errors('api-error', body.error) : null; - events.unbind(window, 'message', listener); - if (name) self.emit(name, err, body); - if (frame) document.body.removeChild(frame); - }); - - if ('documentMode' in document) { - var frame = document.createElement('iframe'); - frame.width = frame.height = 0; - frame.src = this.url('/relay'); - frame.name = 'recurly-relay'; - frame.style.display = 'none'; - frame.onload = bind(this, done); - document.body.appendChild(frame); - } else { - done(); - } -}; - -}); -require.register("recurly/lib/recurly/coupon.js", function(exports, require, module){ - -/*! - * Module dependencies. - */ - -var type = require('type'); -var debug = require('debug')('recurly:coupon'); -var errors = require('../errors'); - -/** - * Coupon mixin. - * - * Retrieves coupon information for the `plan`. The `callback` signature - * is `err, plan` where `err` may be a request or server error, and `plan` - * is a representation of the requested plan. - * - * @param {Object} options - * @param {Function} callback - */ - -exports.coupon = function (options, callback) { - debug('%j', options); - - if ('function' !== type(callback)) { - throw errors('missing-callback'); - } - - if ('object' !== type(options)) { - throw errors('invalid-options'); - } - - if (!('plan' in options)) { - throw errors('missing-plan'); - } - - if (!('coupon' in options)) { - throw errors('missing-coupon'); - } - - this.request('/plans/' + options.plan + '/coupons/' + options.coupon, options, callback); -}; - -}); -require.register("recurly/lib/recurly/paypal.js", function(exports, require, module){ - -/*! - * Module dependencies. - */ - -var debug = require('debug')('recurly:paypal'); - -/** - * Paypal mixin. - * - * @param {Object} data - * @param {Function} done callback - */ - -exports.paypal = function (data, done) { - debug('start'); - this.open('/paypal/start', data, done); -}; - -}); -require.register("recurly/lib/recurly/plan.js", function(exports, require, module){ - -/*! - * Module dependencies. - */ - -var type = require('type'); -var debug = require('debug')('recurly:plan'); - -/** - * Plan mixin. - * - * Retrieves information for the `plan`. The `callback` signature - * is `err, plan` where `err` may be a request or server error, and `plan` - * is a representation of the requested plan. - * - * @param {String} code - * @param {Function} callback - */ - -exports.plan = function (code, callback) { - debug('%s', code); - - if ('function' != type(callback)) { - throw new Error('Missing callback'); - } - - if ('undefined' == type(code)) { - return callback(new Error('Missing plan code')); - } - - this.request('/plans/' + code, callback); -}; - -}); -require.register("recurly/lib/recurly/tax.js", function(exports, require, module){ - -/*! - * Module dependencies. - */ - -var type = require('type'); -var clone = require('clone'); -var debug = require('debug')('recurly:tax'); - -/** - * Tax mixin. - * - * Provides a tax estiamte for the given address. - * - * @param {Object} options - * @param {Object} options.postal_code - * @param {Object} options.country - * @param {Object} [options.vat_number] Used for VAT exemptions - * @param {Function} callback - */ - -exports.tax = function (options, callback) { - var request = clone(options); - - if ('function' != type(callback)) { - throw new Error('Missing callback'); - } - - if (!('currency' in request)) { - request.currency = this.config.currency; - } - - this.request('/tax', request, callback); -}; - -}); -require.register("recurly/lib/recurly/token.js", function(exports, require, module){ - -/*! - * Module dependencies. - */ - -var bind = require('bind'); -var each = require('each'); -var type = require('type'); -var index = require('indexof'); -var debug = require('debug')('recurly:token'); -var dom = require('../util/dom'); -var parseCard = require('../util/parse-card'); -var errors = require('../errors'); - -/** - * Fields that are sent to API. - * - * @type {Array} - * @private - */ - -var fields = [ - 'first_name' - , 'last_name' - , 'number' - , 'month' - , 'year' - , 'cvv' - , 'address1' - , 'address2' - , 'country' - , 'city' - , 'state' - , 'postal_code' - , 'phone' - , 'vat_number' - , 'token' -]; - -/** - * Generates a token from customer data. - * - * The callback signature: `err, response` where `err` is a - * connection, request, or server error, and `response` is the - * recurly service response. The generated token is accessed - * at `response.token`. - * - * @param {Object|HTMLFormElement} options Billing properties or an HTMLFormElement - * with children corresponding to billing properties via 'data-reurly' attributes. - * @param {String} options.first_name customer first name - * @param {String} options.last_name customer last name - * @param {String|Number} options.number card number - * @param {String|Number} options.month card expiration month - * @param {String|Number} options.year card expiration year - * @param {String|Number} options.cvv card verification value - * @param {String} [options.address1] - * @param {String} [options.address2] - * @param {String} [options.country] - * @param {String} [options.city] - * @param {String} [options.state] - * @param {String|Number} [options.postal_code] - * @param {Function} done callback - */ - -exports.token = function (options, done) { - var open = bind(this, this.open); - var data = normalize(options); - var input = data.values; - var userErrors = validate.call(this, input); - - if ('function' !== type(done)) { - throw errors('missing-callback'); - } - - if (userErrors.length) { - return done(errors('validation', { fields: userErrors })); - } - - this.request('/token', input, function (err, res) { - if (err) return done(err); - if (data.fields.token && res.id) { - data.fields.token.value = res.id; - } - done(null, res); - }); -}; - -/** - * Parses options out of a form element and normalizes according to rules. - * - * @param {Object|HTMLFormElement} options - * @return {Object} - */ - -function normalize (options) { - var el = dom.element(options); - var data = { fields: {}, values: {} }; - - if (el && 'form' === el.nodeName.toLowerCase()) { - each(el.querySelectorAll('[data-recurly]'), function (field) { - var name = dom.data(field, 'recurly'); - if (~index(fields, name)) { - data.fields[name] = field; - data.values[name] = dom.value(field); - } - }); - } else { - data.values = options; - } - - data.values.number = parseCard(data.values.number); - - return data; -} - -/** - * Checks user input on a token call - * - * @param {Object} input - * @return {Array} indicates which fields are not valid - */ - -function validate (input) { - var errors = []; - - if (!this.validate.cardNumber(input.number)) { - errors.push('number'); - } - - if (!this.validate.expiry(input.month, input.year)) { - errors.push('month', 'year'); - } - - if (!input.first_name) { - errors.push('first_name'); - } - - if (!input.last_name) { - errors.push('last_name'); - } - - return errors; -} - -}); -require.register("recurly/lib/recurly/validate.js", function(exports, require, module){ - -/*! - * Module dependencies. - */ - -var find = require('find'); -var trim = require('trim'); -var index = require('indexof'); -var parseCard = require('../util/parse-card'); - -/** - * Card patterns. - * - * @private - */ - -var types = [ - { - type: 'discover', - pattern: /^(6011|622|64[4-9]|65)/, - lengths: [16] - } - , { - type: 'master', - pattern: /^5[0-5]/, - lengths: [16] - } - , { - type: 'american_express', - pattern: /^3[47]/, - lengths: [15] - } - , { - type: 'visa', - pattern: /^4/, - lengths: [13, 16] - } - , { - type: 'jcb', - pattern: /^35[2-8]\d/, - lengths: [16] - } - , { - type: 'diners_club', - pattern: /^(30[0-5]|309|36|3[89]|54|55|2014|2149)/, - lengths: [14] - } -]; - -/** - * Validate mixin. - * - * @public - */ - -exports.validate = { - - /** - * Validates a credit card number via luhn algorithm. - * - * @param {Number|String} number The card number. - * @return {Boolean} - * @see https://sites.google.com/site/abapexamples/javascript/luhn-validation - */ - - cardNumber: function (number) { - var str = parseCard(number); - var ca, sum = 0, mul = 1; - var i = str.length; - - while (i--) { - ca = parseInt(str.charAt(i), 10) * mul; - sum += ca - (ca > 9) * 9; - mul ^= 3; - } - - return sum % 10 === 0 && sum > 0; - }, - - /** - * Returns the type of the card number as a string. - * - * TODO(chrissrogers): Maybe undefined instread of "unknown"? - * - * @param {Number|String} number The card number - * @return {String} card type - */ - - cardType: function (number) { - var str = parseCard(number); - var card = find(types, function (card) { - return card.pattern.test(str) && ~index(card.lengths, str.length); - }); - return card && card.type || 'unknown'; - }, - - /** - * Validates whether an expiry month is present or future. - * - * @param {Numer|String} month The 2 digit month - * @param {Numer|String} year The 2 or 4 digit year - * @return {Boolean} - */ - - expiry: function (month, year) { - month = parseInt(month, 10) - 1; - if (month < 0 || month > 11) return false; - year = parseInt(year, 10); - year += year < 100 ? 2000 : 0; - - var expiry = new Date; - expiry.setYear(year); - expiry.setDate(1); - expiry.setHours(0); - expiry.setMinutes(0); - expiry.setSeconds(0); - expiry.setMonth(month + 1); - return new Date < expiry; - }, - - /** - * Validates whether a number looks like a cvv. - * - * e.g.: '123', '0321' - * - * @param {Number|String} number The card verification value - * @return {Boolean} - */ - - cvv: function (number) { - number = trim(number + ''); - return /^\d+$/.test(number) && (number.length === 3 || number.length === 4); - } - -}; - -}); -require.register("recurly/lib/recurly/pricing/index.js", function(exports, require, module){ -/** - * dependencies - */ - -var Emitter = require('emitter'); -var index = require('indexof'); -var each = require('each'); -var type = require('type'); -var bind = require('bind'); -var find = require('find'); -var mixin = require('mixin'); -var keys = require('object').keys; -var json = require('json'); -var debug = require('debug')('recurly:pricing'); -var PricingPromise = require('./promise'); -var Calculations = require('./calculations'); -var errors = require('../../errors'); - -/** - * expose - */ - -exports.Pricing = Pricing; - -/** - * Pricing - * - * @constructor - * @param {Recurly} recurly - * @public - */ - -function Pricing (recurly) { - if (this instanceof require('../../recurly')) return new Pricing(this); - this.recurly = recurly; - this.reset(); -} - -Emitter(Pricing.prototype); - -/** - * Subscription properties - */ - -Pricing.properties = [ - 'plan' - , 'addon' - , 'coupon' - , 'address' - , 'currency' -]; - -/** - * Resets the pricing calculator - * - * @public - */ - -Pricing.prototype.reset = function () { - this.items = {}; - this.items.addons = []; - this.currency(this.recurly.config.currency); -}; - -/** - * Removes an object from the pricing model - * - * example - * - * .remove({ plan: 'plan_code' }); - * .remove({ addon: 'addon_code' }); - * .remove({ coupon: 'coupon_code' }); - * .remove({ address: true }); // to remove without specifying a code - * - * @param {Object} opts - * @param {Function} [done] callback - * @public - */ - -Pricing.prototype.remove = function (opts, done) { - var self = this; - var item; - debug('remove'); - - return new PricingPromise(function (resolve, reject) { - var prop = keys(opts)[0]; - var id = opts[prop]; - if (!~index(Pricing.properties, prop)) return reject(errors('invalid-item')); - if (prop === 'addon') { - var pos = index(self.items.addons, findAddon(self.items.addons, { code: id })); - if (~pos) { - item = self.items.addons.splice(pos); - } - } else if (self.items[prop] && (id === self.items[prop].code || id === true)) { - item = self.items[prop] - delete self.items[prop]; - } else { - return reject(errors('unremovable-item', { - type: prop - , id: id - , reason: 'does not exist on this pricing instance.' - })); - } - }, this).nodeify(done); -}; - -/** - * Provides a subscription price estimate using current state - * - * @param {Function} [done] callback - * @public - */ - -Pricing.prototype.reprice = function (done) { - var self = this; - debug('reprice'); - - return new PricingPromise(function (resolve, reject) { - if (!self.items.plan) return reject(errors('missing-plan')); - - Calculations(self, function (price) { - if (json.stringify(price) === json.stringify(self.price)) return resolve(price); - self.price = price; - self.emit('change', price); - resolve(price); - }); - }, this).nodeify(done); -}; - -/** - * Updates plan - * - * @param {String} planCode - * @param {Object} [meta] - * @param {Number} [meta.quantity] - * @param {Function} [done] callback - * @public - */ - -Pricing.prototype.plan = function (planCode, meta, done) { - var self = this; - var plan = this.items.plan; - var quantity; - - if (type(meta) === 'function') { - done = meta; - meta = undefined; - } - - meta = meta || {}; - - // meta.quantity, plan.quantity, 1 - if (plan && plan.quantity) quantity = plan.quantity; - if (meta.quantity) quantity = parseInt(meta.quantity, 10); - if (!quantity || quantity < 1) quantity = 1; - - return new PricingPromise(function (resolve, reject) { - if (plan && plan.code === planCode) { - plan.quantity = quantity; - return resolve(plan); - } - - self.recurly.plan(planCode, function (err, plan) { - if (err) return reject(err); - - plan.quantity = quantity; - self.items.plan = plan; - - if (!(self.items.currency in plan.price)) { - self.currency(keys(plan.price)[0]); - } - - debug('set.plan'); - self.emit('set.plan', plan); - resolve(plan); - }); - }, this).nodeify(done); -}; - -/** - * Updates addon - * - * @param {String} addonCode - * @param {Object} [meta] - * @param {Number} [meta.quantity] - * @param {Function} [done] callback - * @public - */ - -Pricing.prototype.addon = function (addonCode, meta, done) { - var self = this; - - if (type(meta) === 'function') { - done = meta; - meta = undefined; - } - - meta = meta || {}; - - return new PricingPromise(function (resolve, reject) { - if (!self.items.plan) return reject(errors('missing-plan')); - - var planAddon = findAddon(self.items.plan.addons, addonCode); - if (!planAddon) { - return reject(errors('invalid-addon', { - planCode: self.items.plan.code - , addonCode: addonCode - })); - } - - var quantity = addonQuantity(meta, planAddon); - var addon = findAddon(self.items.addons, addonCode); - - if (quantity === 0) { - self.remove({ addon: addonCode }); - } - - if (addon) { - addon.quantity = quantity; - } else { - addon = json.parse(json.stringify(planAddon)); - addon.quantity = quantity; - self.items.addons.push(addon); - } - - debug('set.addon'); - self.emit('set.addon', addon); - resolve(addon); - }, this).nodeify(done); -}; - -/** - * Updates coupon - * - * @param {String} couponCode - * @param {Function} [done] callback - * @public - */ - -Pricing.prototype.coupon = function (couponCode, done) { - var self = this; - var coupon = this.items.coupon; - - return new PricingPromise(function (resolve, reject) { - if (!self.items.plan) return reject(errors('missing-plan')); - if (coupon) { - if (coupon.code === couponCode) return resolve(coupon); - else self.remove({ coupon: coupon.code }); - } - if (!couponCode) return resolve(); - - self.recurly.coupon({ plan: self.items.plan.code, coupon: couponCode }, function (err, coupon) { - if (err && err.code !== 'not_found') return reject(err); - - self.items.coupon = coupon; - - debug('set.coupon'); - self.emit('set.coupon', coupon); - resolve(coupon); - }); - }, this).nodeify(done); -}; - -/** - * Updates address - * - * @param {Object} address - * @param {String} address.country - * @param {String|Number} address.postal_code - * @param {String} address.vat_number - * @param {Function} [done] callback - * @public - */ - -Pricing.prototype.address = function (address, done) { - var self = this; - - return new PricingPromise(function (resolve, reject) { - if (json.stringify(address) === json.stringify(self.items.address)) { - return resolve(self.items.address); - } - - self.items.address = address; - - debug('set.address'); - self.emit('set.address', address); - resolve(address); - }, this).nodeify(done); -}; - -/** - * Updates or retrieves currency code - * - * @param {String} code - * @param {Function} [done] callback - * @public - */ - -Pricing.prototype.currency = function (code, done) { - var self = this; - var plan = this.items.plan - var currency = this.items.currency; - - return new PricingPromise(function (resolve, reject) { - if (currency === code) return resolve(currency); - if (plan && !(code in plan.price)) { - return reject(errors('invalid-currency', { - currencyCode: code - , planCurrencies: keys(plan.price) - })); - } - - self.items.currency = code; - - debug('set.currency'); - self.emit('set.currency', code); - resolve(code); - }, this).nodeify(done); -}; - -/** - * DOM attachment mixin - */ - -mixin(Pricing.prototype, require('./attach')); - -/** - * Utility functions - */ - -function addonQuantity (meta, planAddon) { - var qty = 1; - if ('quantity' in planAddon) qty = planAddon.quantity; - if ('quantity' in meta) qty = meta.quantity; - return parseInt(qty, 10) || 0; -} - -function findAddon (addons, code) { - return addons && find(addons, { code: code }); -} - -}); -require.register("recurly/lib/recurly/pricing/promise.js", function(exports, require, module){ -/** - * Dependencies - */ - -var Promise = require('promise'); -var mixin = require('mixin'); -var bind = require('bind'); -var each = require('each'); -var type = require('type'); -var par = require('par'); -var debug = require('debug')('recurly:pricing:promise'); - -/** - * Expose - */ - -module.exports = PricingPromise; - -/** - * PricingPromise - * - * issues repricing when .done - * - * contains .then wrappers for Pricing property methods - * - * Usage - * - * var pricing = recurly.Pricing(); - * - * pricing - * .plan('basic') - * .addon('addon1') - * .then(process) - * .catch(errors) - * .done(); - * - * @param {Function} resolver - * @param {Pricing} pricing bound instance - * @constructor - * @public - */ - -function PricingPromise (resolver, pricing) { - if (!(this instanceof PricingPromise)) return new PricingPromise(resolver, pricing); - - var self = this; - this.pricing = pricing; - this.constructor = par.rpartial(this.constructor, pricing); - - Promise.call(this, resolver); - - // for each pricing method, create a promise wrapper method - each(require('./').Pricing.prototype, function (method) { - self[method] = function () { - var args = arguments; - return self.then(function () { - return self.pricing[method].apply(self.pricing, args); - }); - }; - }); -} - -mixin(PricingPromise.prototype, Promise.prototype); -PricingPromise.prototype.constructor = PricingPromise; - -/** - * Adds a reprice and completes the control flow - * - * @param {Function} onFulfilled - * @param {Function} onRejected - * @return {Pricing} bound pricing instance - * @public - */ - -PricingPromise.prototype.done = function () { - Promise.prototype.done.apply(this.then(this.reprice), arguments); - return this.pricing; -}; - -/** - * Adds a reprice if a callback is passed - * - * @param {Function} [done] callback - * @public - */ - -PricingPromise.prototype.nodeify = function (done) { - if (type(done) === 'function') this.reprice(); - return Promise.prototype.nodeify.apply(this, arguments); -}; - -}); -require.register("recurly/lib/recurly/pricing/calculations.js", function(exports, require, module){ -/** - * dependencies - */ - -var each = require('each'); -var bind = require('bind'); -var find = require('find'); - -/** - * expose - */ - -module.exports = Calculations; - -/** - * Subscription calculation calculation - * - * @param {Pricing} pricing - * @constructor - * @public - */ - -function Calculations (pricing, done) { - if (!(this instanceof Calculations)) { - return new Calculations(pricing, done); - } - - this.pricing = pricing; - this.items = pricing.items; - - this.price = { - now: {}, - next: {}, - addons: {}, - currency: { - code: this.items.currency, - symbol: this.planPrice().symbol - } - }; - - this.subtotal(); - - this.tax(function () { - this.total(); - each(this.price.now, decimal, this.price.now); - each(this.price.next, decimal, this.price.next); - each(this.price.addons, decimal, this.price.addons); - done(this.price); - }); -} - -/** - * Calculates subtotal - * - * @private - */ - -Calculations.prototype.subtotal = function () { - var subtotal = this.planPrice().amount; - - this.price.now.subtotal = subtotal; - this.price.next.subtotal = subtotal; - - if (this.items.plan.trial) this.price.now.subtotal = 0; - - this.addons(); - this.price.now.subtotal += this.price.now.addons; - this.price.next.subtotal += this.price.next.addons; - - this.discount(); - this.price.now.subtotal -= this.price.now.discount; - this.price.next.subtotal -= this.price.next.discount; - - this.setupFee(); - this.price.now.subtotal += this.price.now.setup_fee; -}; - -/** - * Calculates tax - * - * @param {Function} done - * @private - */ - -Calculations.prototype.tax = function (done) { - this.price.now.tax = 0; - this.price.next.tax = 0; - - if (this.items.address) { - var self = this; - this.pricing.recurly.tax(this.items.address, function applyTax (err, taxes) { - if (err) { - self.pricing.emit('error', err); - } else { - each(taxes, function (tax) { - if (tax.type === 'usst' && self.items.plan.tax_exempt) return; - self.price.now.tax += self.price.now.subtotal * tax.rate; - self.price.next.tax += self.price.next.subtotal * tax.rate; - }); - - // tax estimation prefers partial cents to always round up - self.price.now.tax = Math.ceil(self.price.now.tax * 100) / 100; - self.price.next.tax = Math.ceil(self.price.next.tax * 100) / 100; - } - done.call(self); - }); - } else done.call(this); -}; - -/** - * Calculates total - * - * @private - */ - -Calculations.prototype.total = function () { - this.price.now.total = this.price.now.subtotal + this.price.now.tax; - this.price.next.total = this.price.next.subtotal + this.price.next.tax; -}; - -/** - * Computes addon prices and applies addons to the subtotal - * - * @private - */ - -Calculations.prototype.addons = function () { - this.price.now.addons = 0; - this.price.next.addons = 0; - - each(this.items.plan.addons, function (addon) { - var price = addon.price[this.items.currency].unit_amount; - - this.price.addons[addon.code] = price; - - var selected = find(this.items.addons, { code: addon.code }); - if (selected) { - price = price * selected.quantity; - if (!this.items.plan.trial) this.price.now.addons += price; - this.price.next.addons += price; - } - }, this); -}; - -/** - * Applies coupon discount to the subtotal - * - * @private - */ - -Calculations.prototype.discount = function () { - var coupon = this.items.coupon; - - this.price.now.discount = 0; - this.price.next.discount = 0; - - if (coupon) { - if (coupon.discount.rate) { - this.price.now.discount = Math.round(this.price.now.subtotal * coupon.discount.rate * 100) / 100; - this.price.next.discount = Math.round(this.price.next.subtotal * coupon.discount.rate * 100) / 100; - } else { - this.price.now.discount = coupon.discount.amount[this.items.currency]; - this.price.next.discount = coupon.discount.amount[this.items.currency]; - } - } -}; - -/** - * Applies plan setup fee to the subtotal - * - * @private - */ - -Calculations.prototype.setupFee = function () { - this.price.now.setup_fee = this.planPrice().setup_fee; - this.price.next.setup_fee = 0; -}; - -/** - * Get the price structure of a plan based on currency - * - * @return {Object} - * @private - */ - -Calculations.prototype.planPrice = function () { - var plan = this.items.plan; - var price = plan.price[this.items.currency]; - price.amount = price.unit_amount * (plan.quantity || 1); - return price; -}; - -/** - * Applies a decimal transform on an object's member - * - * @param {String} prop Property on {this} to transform - * @this {Object} on which to apply decimal transformation - * @private - */ - -function decimal (prop) { - this[prop] = (Math.round(Math.max(this[prop], 0) * 100) / 100).toFixed(2); -} - -}); -require.register("recurly/lib/recurly/pricing/attach.js", function(exports, require, module){ -/** - * dependencies - */ - -var each = require('each'); -var events = require('event'); -var find = require('find'); -var type = require('type'); -var dom = require('../../util/dom'); -var debug = require('debug')('recurly:pricing:attach'); - -/** - * bind a dom element to pricing values - * - * @param {HTMLElement} el - */ - -exports.attach = function (el) { - var self = this; - var elems = {}; - var el = dom.element(el); - - if (!el) throw new Error('invalid dom element'); - - if (this.attach.detatch) this.attach.detatch(); - - self.on('change', update); - - each(el.querySelectorAll('[data-recurly]'), function (elem) { - // 'zip' -> 'postal_code' - if (dom.data(elem, 'recurly') === 'zip') dom.data(elem, 'recurly', 'postal_code'); - - var name = dom.data(elem, 'recurly'); - if (!elems[name]) elems[name] = []; - elems[name].push(elem); - events.bind(elem, 'change', change); - events.bind(elem, 'propertychange', change); - }); - - this.attach.detatch = detatch; - - change(); - - function change (event) { - debug('change'); - - var targetName = event && event.target && dom.data(event.target, 'recurly'); - targetName = targetName || window.event && window.event.srcElement; - - var pricing = self.plan(dom.value(elems.plan), { quantity: dom.value(elems.plan_quantity) }); - - if (target('currency')) { - pricing = pricing.currency(dom.value(elems.currency)); - } - - if (target('addon') && elems.addon) { - addons(); - } - - if (target('coupon') && elems.coupon) { - pricing = pricing.coupon(dom.value(elems.coupon)).then(null, ignoreBadCoupons); - } - - if (target('country') || target('postal_code') || target('vat_number')) { - pricing = pricing.address({ - country: dom.value(elems.country), - postal_code: dom.value(elems.postal_code), - vat_number: dom.value(elems.vat_number) - }); - } - - pricing.done(); - - function addons () { - each(elems.addon, function (node) { - var plan = self.items.plan; - var addonCode = dom.data(node, 'recurlyAddon'); - if (plan.addons && find(plan.addons, { code: addonCode })) { - pricing = pricing.addon(addonCode, { quantity: dom.value(node) }); - } - }); - } - - function target (name) { - if (!targetName) return true; - if (targetName === name) return true; - return false - } - }; - - function update (price) { - dom.value(elems.currency_code, price.currency.code); - dom.value(elems.currency_symbol, price.currency.symbol); - each(['addons', 'discount', 'setup_fee', 'subtotal', 'tax', 'total'], function (value) { - dom.value(elems[value + '_now'], price.now[value]); - dom.value(elems[value + '_next'], price.next[value]); - }); - if (elems.addonPrice) { - each(elems.addonPrice, function (elem) { - var addonPrice = price.addons[dom.data(elem, 'recurlyAddon')]; - if (addonPrice) dom.value(elem, addonPrice); - }); - } - } - - function detatch () { - each(elems, function (name, elems) { - each(elems, function (elem) { - events.unbind(elem, 'change', change); - events.unbind(elem, 'propertychange', change); - }, this); - }, this); - } -}; - -function ignoreBadCoupons (err) { - if (err.code === 'not-found') return; - else throw err; -} - -/** - * Backward-compatibility - * - * @deprecated - */ - -exports.binding = exports.attach; - -}); - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -require.alias("visionmedia-node-querystring/index.js", "recurly/deps/querystring/index.js"); -require.alias("visionmedia-node-querystring/index.js", "querystring/index.js"); - -require.alias("component-emitter/index.js", "recurly/deps/emitter/index.js"); -require.alias("component-emitter/index.js", "emitter/index.js"); - -require.alias("component-indexof/index.js", "recurly/deps/indexof/index.js"); -require.alias("component-indexof/index.js", "indexof/index.js"); - -require.alias("component-object/index.js", "recurly/deps/object/index.js"); -require.alias("component-object/index.js", "object/index.js"); - -require.alias("component-event/index.js", "recurly/deps/event/index.js"); -require.alias("component-event/index.js", "event/index.js"); - -require.alias("component-clone/index.js", "recurly/deps/clone/index.js"); -require.alias("component-clone/index.js", "clone/index.js"); -require.alias("component-type/index.js", "component-clone/deps/type/index.js"); - -require.alias("component-bind/index.js", "recurly/deps/bind/index.js"); -require.alias("component-bind/index.js", "bind/index.js"); - -require.alias("component-each/index.js", "recurly/deps/each/index.js"); -require.alias("component-each/index.js", "each/index.js"); -require.alias("component-to-function/index.js", "component-each/deps/to-function/index.js"); -require.alias("component-props/index.js", "component-to-function/deps/props/index.js"); - -require.alias("component-type/index.js", "component-each/deps/type/index.js"); - -require.alias("component-find/index.js", "recurly/deps/find/index.js"); -require.alias("component-find/index.js", "find/index.js"); -require.alias("component-to-function/index.js", "component-find/deps/to-function/index.js"); -require.alias("component-props/index.js", "component-to-function/deps/props/index.js"); - -require.alias("component-json/index.js", "recurly/deps/json/index.js"); -require.alias("component-json/index.js", "json/index.js"); - -require.alias("component-type/index.js", "recurly/deps/type/index.js"); -require.alias("component-type/index.js", "type/index.js"); - -require.alias("component-trim/index.js", "recurly/deps/trim/index.js"); -require.alias("component-trim/index.js", "trim/index.js"); - -require.alias("component-map/index.js", "recurly/deps/map/index.js"); -require.alias("component-map/index.js", "map/index.js"); -require.alias("component-to-function/index.js", "component-map/deps/to-function/index.js"); -require.alias("component-props/index.js", "component-to-function/deps/props/index.js"); - -require.alias("yields-merge/index.js", "recurly/deps/merge/index.js"); -require.alias("yields-merge/index.js", "merge/index.js"); - -require.alias("learnboost-jsonp/index.js", "recurly/deps/jsonp/index.js"); -require.alias("learnboost-jsonp/index.js", "recurly/deps/jsonp/index.js"); -require.alias("learnboost-jsonp/index.js", "jsonp/index.js"); -require.alias("visionmedia-debug/debug.js", "learnboost-jsonp/deps/debug/debug.js"); -require.alias("visionmedia-debug/debug.js", "learnboost-jsonp/deps/debug/index.js"); -require.alias("visionmedia-debug/debug.js", "visionmedia-debug/index.js"); -require.alias("learnboost-jsonp/index.js", "learnboost-jsonp/index.js"); -require.alias("visionmedia-debug/debug.js", "recurly/deps/debug/debug.js"); -require.alias("visionmedia-debug/debug.js", "recurly/deps/debug/index.js"); -require.alias("visionmedia-debug/debug.js", "debug/index.js"); -require.alias("visionmedia-debug/debug.js", "visionmedia-debug/index.js"); -require.alias("chrissrogers-promise/index.js", "recurly/deps/promise/index.js"); -require.alias("chrissrogers-promise/core.js", "recurly/deps/promise/core.js"); -require.alias("chrissrogers-promise/index.js", "promise/index.js"); -require.alias("johntron-asap/asap.js", "chrissrogers-promise/deps/asap/asap.js"); -require.alias("johntron-asap/asap.js", "chrissrogers-promise/deps/asap/index.js"); -require.alias("johntron-asap/asap.js", "johntron-asap/index.js"); -require.alias("kewah-mixin/index.js", "recurly/deps/mixin/index.js"); -require.alias("kewah-mixin/index.js", "recurly/deps/mixin/index.js"); -require.alias("kewah-mixin/index.js", "mixin/index.js"); -require.alias("kewah-mixin/index.js", "kewah-mixin/index.js"); -require.alias("pluma-par/dist/par.js", "recurly/deps/par/dist/par.js"); -require.alias("pluma-par/dist/par.js", "recurly/deps/par/index.js"); -require.alias("pluma-par/dist/par.js", "par/index.js"); -require.alias("pluma-par/dist/par.js", "pluma-par/index.js"); -require.alias("ianstormtaylor-to-slug-case/index.js", "recurly/deps/to-slug-case/index.js"); -require.alias("ianstormtaylor-to-slug-case/index.js", "to-slug-case/index.js"); -require.alias("ianstormtaylor-to-space-case/index.js", "ianstormtaylor-to-slug-case/deps/to-space-case/index.js"); -require.alias("ianstormtaylor-to-no-case/index.js", "ianstormtaylor-to-space-case/deps/to-no-case/index.js"); - -require.alias("recurly/lib/index.js", "recurly/index.js");if (typeof exports == "object") { - module.exports = require("recurly"); -} else if (typeof define == "function" && define.amd) { - define([], function(){ return require("recurly"); }); -} else { - this["recurly"] = require("recurly"); -}})(); \ No newline at end of file diff --git a/services/web/public/js/libs/recurly-4.8.5.js b/services/web/public/js/libs/recurly-4.8.5.js new file mode 100644 index 0000000000..f638c14904 --- /dev/null +++ b/services/web/public/js/libs/recurly-4.8.5.js @@ -0,0 +1,6 @@ +var recurly=function(e){function t(r){if(n[r])return n[r].exports;var i=n[r]={i:r,l:!1,exports:{}};return e[r].call(i.exports,i,i.exports,t),i.l=!0,i.exports}var n={};return t.m=e,t.c=n,t.d=function(e,n,r){t.o(e,n)||Object.defineProperty(e,n,{configurable:!1,enumerable:!0,get:r})},t.n=function(e){var n=e&&e.__esModule?function(){return e.default}:function(){return e};return t.d(n,"a",n),n},t.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},t.p="/build/",t(t.s=36)}([function(e,t,n){(function(r){function i(){return!("undefined"==typeof window||!window.process||"renderer"!==window.process.type)||("undefined"==typeof navigator||!navigator.userAgent||!navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/))&&("undefined"!=typeof document&&document.documentElement&&document.documentElement.style&&document.documentElement.style.WebkitAppearance||"undefined"!=typeof window&&window.console&&(window.console.firebug||window.console.exception&&window.console.table)||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)&&parseInt(RegExp.$1,10)>=31||"undefined"!=typeof navigator&&navigator.userAgent&&navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/))}function o(e){var n=this.useColors;if(e[0]=(n?"%c":"")+this.namespace+(n?" %c":" ")+e[0]+(n?"%c ":" ")+"+"+t.humanize(this.diff),n){var r="color: "+this.color;e.splice(1,0,r,"color: inherit");var i=0,o=0;e[0].replace(/%[a-zA-Z%]/g,function(e){"%%"!==e&&(i++,"%c"===e&&(o=i))}),e.splice(o,0,r)}}function a(){return"object"==typeof console&&console.log&&Function.prototype.apply.call(console.log,console,arguments)}function u(e){try{null==e?t.storage.removeItem("debug"):t.storage.debug=e}catch(e){}}function s(){var e;try{e=t.storage.debug}catch(e){}return!e&&void 0!==r&&"env"in r&&(e=r.env.DEBUG),e}t=e.exports=n(40),t.log=a,t.formatArgs=o,t.save=u,t.load=s,t.useColors=i,t.storage="undefined"!=typeof chrome&&void 0!==chrome.storage?chrome.storage.local:function(){try{return window.localStorage}catch(e){}}(),t.colors=["#0000CC","#0000FF","#0033CC","#0033FF","#0066CC","#0066FF","#0099CC","#0099FF","#00CC00","#00CC33","#00CC66","#00CC99","#00CCCC","#00CCFF","#3300CC","#3300FF","#3333CC","#3333FF","#3366CC","#3366FF","#3399CC","#3399FF","#33CC00","#33CC33","#33CC66","#33CC99","#33CCCC","#33CCFF","#6600CC","#6600FF","#6633CC","#6633FF","#66CC00","#66CC33","#9900CC","#9900FF","#9933CC","#9933FF","#99CC00","#99CC33","#CC0000","#CC0033","#CC0066","#CC0099","#CC00CC","#CC00FF","#CC3300","#CC3333","#CC3366","#CC3399","#CC33CC","#CC33FF","#CC6600","#CC6633","#CC9900","#CC9933","#CCCC00","#CCCC33","#FF0000","#FF0033","#FF0066","#FF0099","#FF00CC","#FF00FF","#FF3300","#FF3333","#FF3366","#FF3399","#FF33CC","#FF33FF","#FF6600","#FF6633","#FF9900","#FF9933","#FFCC00","#FFCC33"],t.formatters.j=function(e){try{return JSON.stringify(e)}catch(e){return"[UnexpectedJSONParseError]: "+e.message}},t.enable(s())}).call(t,n(39))},function(e,t,n){"use strict";function r(e,t){return r.get(e,t)}var i=n(23);e.exports=r,r.map={},r.baseURL="",r.doc=function(e){r.baseURL=e},r.get=function(e,t){if(e in r.map)return new r.map[e](t);throw new Error("invalid error: "+e)},r.add=function(e,t){function n(n){Error.call(this),this.name=this.code=e,t.message instanceof Function?this.message=t.message(n):this.message=t.message,i(this,n||{}),t.help&&(this.help=r.baseURL+t.help,this.message+=" (need help? "+this.help+")")}return t=t||{},n.prototype=new Error,r.map[e]=n},r.doc("https://docs.recurly.com/js"),r.add("already-configured",{message:"Configuration may only be set once.",help:"#identify-your-site"}),r.add("config-missing-public-key",{message:"The publicKey setting is required.",help:"#identify-your-site"}),r.add("config-missing-fields",{message:"The fields setting is required."}),r.add("missing-hosted-field-target",{message:function(e){return"Target element not found for "+e.type+" field using selector '"+e.selector+"'"}}),r.add("api-error",{message:"There was an error with your request."}),r.add("api-timeout",{message:"The API request timed out."}),r.add("validation",{message:"There was an error validating your request."}),r.add("missing-callback",{message:"Missing callback"}),r.add("invalid-options",{message:"Options must be an object"}),r.add("invalid-option",{message:function(e){return"Option "+e.name+" must be "+e.expect}}),r.add("missing-plan",{message:"A plan must be specified."}),r.add("missing-coupon",{message:"A coupon must be specified."}),r.add("invalid-item",{message:"The given item does not appear to be a valid recurly plan, coupon, addon, or taxable address."}),r.add("invalid-addon",{message:"The given addon_code is not among the valid addons for the specified plan."}),r.add("invalid-currency",{message:function(e){return"The given currency ("+e.currency+") is not among the valid codes for the specified plan(s): "+e.allowed+"."}}),r.add("invalid-plan-currency",{message:function(e){return"The requested plan ("+e.planCode+") does not support the possible checkout currencies: "+e.currencies+"."}}),r.add("invalid-subscription-currency",{message:"The given subscription does not support the currencies of this Checkout instance's existing subscriptions"}),r.add("unremovable-item",{message:"The given item cannot be removed."}),r.add("fraud-data-collector-request-failed",{message:function(e){return"There was an error getting the data collector fields: "+e.error}}),r.add("fraud-data-collector-missing-form",{message:function(e){return"There was an error finding a form to inject the data collector fields using selector '"+e.selector+"'"}}),r.add("gift-card-currency-mismatch",{message:"The giftcard currency does not match the given currency."}),r.add("apple-pay-not-supported",{message:"Apple Pay is not supported by this device or browser."}),r.add("apple-pay-not-available",{message:"Apple Pay is supported by this device, but the customer has not configured Apple Pay."}),r.add("apple-pay-config-missing",{message:function(e){return"Missing Apple Pay configuration option: '"+e.opt+"'"}}),r.add("apple-pay-config-invalid",{message:function(e){return"Apple Pay configuration option '"+e.opt+"' is not among your available options: "+e.set+".\n Please refer to your site configuration if the available options is incorrect."}}),r.add("apple-pay-factory-only",{message:"Apple Pay must be initialized by calling recurly.ApplePay"}),r.add("apple-pay-init-error",{message:function(e){var t="Apple Pay did not initialize due to a fatal error";return e.err&&(t+=": "+e.err.message),t}}),r.add("apple-pay-payment-failure",{message:"Apply Pay could not charge the customer"}),r.add("paypal-factory-only",{message:"PayPal must be initialized by calling recurly.PayPal"}),r.add("paypal-config-missing",{message:function(e){return"Missing PayPal configuration option: '"+e.opt+"'"}}),r.add("paypal-load-error",{message:"Client libraries failed to load"}),r.add("paypal-client-error",{message:"PayPal encountered an unexpected error"}),r.add("paypal-tokenize-error",{message:"An error occurred while attempting to generate the PayPal token"}),r.add("paypal-tokenize-recurly-error",{message:"An error occurred while attempting to generate the Recurly token"}),r.add("paypal-braintree-not-ready",{message:"Braintree PayPal is not yet ready to create a checkout flow"}),r.add("paypal-braintree-api-error",{message:"Braintree API experienced an error"}),r.add("paypal-braintree-tokenize-braintree-error",{message:"An error occurred while attempting to generate the Braintree token"}),r.add("paypal-braintree-tokenize-recurly-error",{message:"An error occurred while attempting to generate the Braintree token within Recurly"})},function(e,t){function n(e){if(e)return r(e)}function r(e){for(var t in n.prototype)e[t]=n.prototype[t];return e}e.exports=n,n.prototype.on=n.prototype.addEventListener=function(e,t){return this._callbacks=this._callbacks||{},(this._callbacks[e]=this._callbacks[e]||[]).push(t),this},n.prototype.once=function(e,t){function n(){r.off(e,n),t.apply(this,arguments)}var r=this;return this._callbacks=this._callbacks||{},n.fn=t,this.on(e,n),this},n.prototype.off=n.prototype.removeListener=n.prototype.removeAllListeners=n.prototype.removeEventListener=function(e,t){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var n=this._callbacks[e];if(!n)return this;if(1==arguments.length)return delete this._callbacks[e],this;for(var r,i=0;i0&&void 0!==arguments[0]?arguments[0]:12;return Math.random().toString(36).substr(2,e)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=r},function(e,t,n){"use strict";e.exports=n(44)},function(e,t,n){"use strict";function r(){}function i(e){try{return e.then}catch(e){return m=e,b}}function o(e,t){try{return e(t)}catch(e){return m=e,b}}function a(e,t,n){try{e(t,n)}catch(e){return m=e,b}}function u(e){if("object"!=typeof this)throw new TypeError("Promises must be constructed via new");if("function"!=typeof e)throw new TypeError("not a function");this._45=0,this._81=0,this._65=null,this._54=null,e!==r&&y(e,this)}function s(e,t,n){return new e.constructor(function(i,o){var a=new u(r);a.then(i,o),c(e,new h(t,n,a))})}function c(e,t){for(;3===e._81;)e=e._65;if(u._10&&u._10(e),0===e._81)return 0===e._45?(e._45=1,void(e._54=t)):1===e._45?(e._45=2,void(e._54=[e._54,t])):void e._54.push(t);l(e,t)}function l(e,t){v(function(){var n=1===e._81?t.onFulfilled:t.onRejected;if(null===n)return void(1===e._81?f(t.promise,e._65):d(t.promise,e._65));var r=o(n,e._65);r===b?d(t.promise,m):f(t.promise,r)})}function f(e,t){if(t===e)return d(e,new TypeError("A promise cannot be resolved with itself."));if(t&&("object"==typeof t||"function"==typeof t)){var n=i(t);if(n===b)return d(e,m);if(n===e.then&&t instanceof u)return e._81=3,e._65=t,void p(e);if("function"==typeof n)return void y(n.bind(t),e)}e._81=1,e._65=t,p(e)}function d(e,t){e._81=2,e._65=t,u._97&&u._97(e,t),p(e)}function p(e){if(1===e._45&&(c(e,e._54),e._54=null),2===e._45){for(var t=0;t0&&void 0!==arguments[0]?arguments[0]:{},n="input";~["button","select"].indexOf(e.type)&&(n=e.type,delete e.type);var r=t.document.createElement(n);return"type"in e||(e.type="text"),"style"in e||(e.style="position: absolute; top: 0px; left: -1000px; opacity: 0;"),Object.keys(e).forEach(function(t){return r.setAttribute(t,e[t])}),r}var d=n(54),p=n(4);e.exports={createHiddenInput:f,data:u,element:r,findNodeInParents:l,value:i}}).call(t,n(3))},function(e,t,n){function r(e){return function(t){for(var n in e)if(t[n]!=e[n])return!1;return!0}}var i=n(64);e.exports=function(e,t){"function"!=typeof t&&(t=Object(t)===t?r(t):i(t));for(var n=0,o=e.length;n1&&void 0!==arguments[1]?arguments[1]:{},r=n.internal,i=void 0!==r&&r;return this.debug("reprice"),new v.default(function(e,n){new t.Calculations(t,function(n){if(JSON.stringify(n)===JSON.stringify(t.price))return e(n);t.price=n,i||t.emit("change:external",n),t.emit("change",n),e(n)})},this).nodeify(e)}},{key:"remove",value:function(e,t){var n=this,r=void 0;return this.debug("remove"),new v.default(function(t,i){var o=Object.keys(e)[0],a=e[o];if(!~Object.keys(n.items).indexOf(o))return n.error((0,b.default)("invalid-item"),i);if(Array.isArray(n.items[o])){var s=n.items[o].indexOf(u(n.items[o],{code:a}));~s&&(r=n.items[o].splice(s))}else{if(!n.items[o]||a!==n.items[o].code&&!0!==a)return n.error((0,b.default)("unremovable-item",{type:o,id:a,reason:"does not exist on this pricing instance."}),i);r=n.items[o],delete n.items[o]}t()},this).nodeify(t)}},{key:"itemUpdateFactory",value:function(e,t){var n=this;return function(r,i){if(JSON.stringify(t)===JSON.stringify(n.items[e]))return r(n.items[e]);n.items[e]=t,_("set."+e),n.emit("set."+e,t),r(t)}}},{key:"error",value:function(e){var t=this,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:function(){},r=arguments[2];return r&&r.split(".").reduce(function(n,r){return t.emit(n+"."+r,e)},"error"),this.emit("error",e),n(e)}},{key:"Calculations",get:function(){throw new Error("Not implemented")}},{key:"PRICING_METHODS",get:function(){return["reset","remove","reprice"]}},{key:"hasPrice",get:function(){return!!this.price}},{key:"totalNow",get:function(){return(0,w.default)(this.hasPrice?this.price.now.total:0)}},{key:"subtotalPreDiscountNow",get:function(){var e=parseFloat(this.price.now.subtotal)+parseFloat(this.price.now.discount);return(0,w.default)(this.hasPrice?e:0)}},{key:"currencyCode",get:function(){return this.items.currency||""}},{key:"currencySymbol",get:function(){return(0,l.default)(this.currencyCode)}}]),t}(d.default);t.Pricing=O},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var u=function(){function e(e,t){for(var n=0;n1?r-1:0),o=1;o1&&void 0!==arguments[1]?arguments[1]:{},r=n.id,a=void 0===r?(0,w.default)():r;i(this,t);var u=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this,e));return u.id=a,u.debug=_,u}return a(t,e),s(t,[{key:"reset",value:function(){c(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"reset",this).call(this),this.items.addons=[]}},{key:"attach",value:function(e){var t=this;return this.attachment&&this.attachment.detach(),this.attachment=new v.default(this,e),this.attachment.once("ready",function(){return t.emit("attached")}),this.attachment}},{key:"plan",value:function(){var e=this,t=this.resolvePlanOptions.apply(this,arguments),n=t.currentPlan,r=t.quantity,i=t.planCode,o=(t.options,t.done);return new d.default(function(t,o){if(n&&n.code===i)return n.quantity=r,t(n);e.recurly.plan(i,function(n,i){if(n)return e.error(n,o,"plan");var a=function(){_("set.plan"),e.emit("set.plan",i),t(i)};i.quantity=r,e.items.plan=i,e.items.currency in i.price||e.currency(Object.keys(i.price)[0]),e.items.coupon?e.coupon(e.items.coupon.code).then(a,a):a()})},this).nodeify(o)}},{key:"addon",value:function(e,t,n){var r=this;return"function"==typeof t&&(n=t,t=void 0),t=t||{},new d.default(function(n,i){if(!r.items.plan)return r.error((0,b.default)("missing-plan"),i,"addon");var o=(0,l.findByCode)(r.items.plan.addons,e);if(!o)return r.error((0,b.default)("invalid-addon",{planCode:r.items.plan.code,addonCode:e}),i,"addon");var a=u(t,o),s=(0,l.findByCode)(r.items.addons,e);0===a&&r.remove({addon:e}),s?s.quantity=a:(s=JSON.parse(JSON.stringify(o)),s.quantity=a,r.items.addons.push(s)),_("set.addon"),r.emit("set.addon",s),n(s)},this).nodeify(n)}},{key:"giftcard",value:function(e,t){function n(e){_("set.gift_card"),i.items.gift_card=e,i.emit("set.gift_card",e)}function r(e){_("unset.gift_card"),delete i.items.gift_card,i.emit("unset.gift_card")}var i=this;return new d.default(function(t,o){if(r(),!e)return t();i.recurly.giftcard({giftcard:e},function(e,a){return e&&"not-found"===e.code&&r(),e?i.error(e,o,"gift_card"):i.items.currency!==a.currency?(r(),i.error((0,b.default)("gift-card-currency-mismatch"),o,"gift_card")):(n(a),void t(a))})},this).nodeify(t)}},{key:"coupon",value:function(e,t){var n=this,r=function(){_("unset.coupon"),delete n.items.coupon,n.emit("unset.coupon")};return new d.default(function(t,i){if(!n.items.plan)return n.error((0,b.default)("missing-plan"),i,"coupon");if(n.items.coupon&&n.remove({coupon:n.items.coupon.code}),!e)return r(),t();var o=function(e,o){return e&&"not-found"===e.code&&r(),e?n.error(e,i,"coupon"):n.couponIsValidForSubscription(o)?(_("set.coupon"),n.items.coupon=o,n.emit("set.coupon",o),t(o),void 0):n.error("invalid-coupon-for-subscription",i,"coupon")};"string"==typeof e?n.recurly.coupon({plan:n.items.plan.code,coupon:e},o):o(null,e)},this).nodeify(t)}},{key:"address",value:function(e,t){return new d.default(this.itemUpdateFactory("address",e),this).nodeify(t)}},{key:"shippingAddress",value:function(e,t){return new d.default(this.itemUpdateFactory("shipping_address",e),this).nodeify(t)}},{key:"tax",value:function(e,t){return new d.default(this.itemUpdateFactory("tax",e),this).nodeify(t)}},{key:"currency",value:function(e,t){var n=this,r=this.items.plan,i=this.items.currency;return new d.default(function(t,o){return i===e?t(i):!r||e in r.price?(n.items.currency=e,_("set.currency"),n.emit("set.currency",e),void t(e)):n.error((0,b.default)("invalid-currency",{currency:e,allowed:Object.keys(r.price)}),o,"currency")},this).nodeify(t)}},{key:"couponIsValidForSubscription",value:function(e){return!!e&&(!!e.applies_to_plans&&(!!e.applies_to_all_plans||!!~e.plans.indexOf(this.items.plan.code)))}},{key:"resolvePlanOptions",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=arguments[2],r=this.items.plan,i=void 0;return"function"==typeof t&&(n=t,t={}),r&&r.quantity&&(i=r.quantity),t.quantity&&(i=parseInt(t.quantity,10)),(!i||i<1)&&(i=1),{currentPlan:r,quantity:i,planCode:e,options:t,done:n}}},{key:"Calculations",get:function(){return h.default}},{key:"PRICING_METHODS",get:function(){return c(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"PRICING_METHODS",this).concat(["addon","address","coupon","currency","giftcard","plan","shippingAddress","tax"])}},{key:"isValid",get:function(){return!(!this.items.plan||!this.price)}},{key:"taxCode",get:function(){if(this.items.tax)return this.items.tax.taxCode||this.items.tax.tax_code}},{key:"taxExempt",get:function(){return this.items.plan&&this.items.plan.tax_exempt}}]),t}(l.Pricing);t.default=O},function(e,t,n){function r(e){switch(i(e)){case"object":var t={};for(var n in e)e.hasOwnProperty(n)&&(t[n]=r(e[n]));return t;case"array":for(var t=new Array(e.length),o=0,a=e.length;o-1&&e%1==0&&e<=w}function m(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function b(e){return!!e&&"object"==typeof e}function g(){return!1}var w=9007199254740991,_="[object Arguments]",O="[object Function]",j="[object GeneratorFunction]",x="[object Map]",k="[object Set]",C=/[\\^$.*+?()[\]{}|]/g,P=/^\[object .+?Constructor\]$/,S="object"==typeof e&&e&&e.Object===Object&&e,E="object"==typeof self&&self&&self.Object===Object&&self,F=S||E||Function("return this")(),A="object"==typeof t&&t&&!t.nodeType&&t,T=A&&"object"==typeof n&&n&&!n.nodeType&&n,M=T&&T.exports===A,I=Function.prototype,N=Object.prototype,R=F["__core-js_shared__"],L=function(){var e=/[^.]+$/.exec(R&&R.keys&&R.keys.IE_PROTO||"");return e?"Symbol(src)_1."+e:""}(),D=I.toString,q=N.hasOwnProperty,B=N.toString,z=RegExp("^"+D.call(q).replace(C,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),U=M?F.Buffer:void 0,V=N.propertyIsEnumerable,$=U?U.isBuffer:void 0,H=function(e,t){return function(n){return e(t(n))}}(Object.keys,Object),K=u(F,"DataView"),G=u(F,"Map"),W=u(F,"Promise"),J=u(F,"Set"),Z=u(F,"WeakMap"),Y=!V.call({valueOf:1},"valueOf"),X=l(K),Q=l(G),ee=l(W),te=l(J),ne=l(Z),re=o;(K&&"[object DataView]"!=re(new K(new ArrayBuffer(1)))||G&&re(new G)!=x||W&&"[object Promise]"!=re(W.resolve())||J&&re(new J)!=k||Z&&"[object WeakMap]"!=re(new Z))&&(re=function(e){var t=B.call(e),n="[object Object]"==t?e.constructor:void 0,r=n?l(n):void 0;if(r)switch(r){case X:return"[object DataView]";case Q:return x;case ee:return"[object Promise]";case te:return k;case ne:return"[object WeakMap]"}return t});var ie=Array.isArray,oe=$||g;n.exports=h}).call(t,n(3),n(81)(e))},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function o(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t1?e():this.once("ready",e)}},{key:"configure",value:function(e){if(te("configure"),e=(0,h.default)(e),"string"==typeof e&&(e={publicKey:e}),e=c(e),e.publicKey)this.config.publicKey=e.publicKey;else if(!this.config.publicKey)throw(0,C.default)("config-missing-public-key");e.api&&(this.config.api=e.api),e.currency&&(this.config.currency=e.currency),"cors"in e&&(this.config.cors=e.cors),"fraud"in e&&(0,Y.default)(this.config.fraud,e.fraud),"parent"in e&&(this.config.parent=e.parent),"object"===f(e.fields)&&(0,Y.default)(this.config.fields,e.fields),this.config.required=e.required||this.config.required||[],this.config.parent?this.parent():e.parentVersion&&(this.config.parentVersion=e.parentVersion),this.configured=!0}},{key:"parent",value:function(){var e=this,t=!1;if(this.hostedFields&&this.readyState>1&&!this.hostedFields.integrityCheck(this.config.fields)&&(t=!0),t&&(this.readyState=0,this.hostedFields.reset()),this.readyState>0)return void this.bus.send("hostedFields:configure",{recurly:this.config});this.fraud||(this.fraud=new $.Fraud(this)),this.bus&&this.bus.stop(),this.bus=new V.Bus({api:this.config.api}),this.bus.add(this),this.hostedFields&&!t||(this.hostedFields=new H.HostedFields({recurly:this.config})),0===this.hostedFields.errors.length?(this.bus.add(this.hostedFields),this.once("hostedFields:ready",function(t){e.readyState=2,e.emit("ready")}),this.on("hostedFields:state:change",function(t){return e.emit("change",{fields:t})}),this.on("hostedField:submit",function(){return e.emit("field:submit")}),this.readyState=1):(this.readyState=3,this.emit("ready"))}},{key:"url",value:function(e){return this.config.api+e}},{key:"request",value:function(e,t,n,r){if(te("request",e,t,n),!this.configured)throw(0,C.default)("not-configured");return"function"==typeof n&&(r=n,n={}),this.config.parent?n.version=this.version:n.version=this.config.parentVersion,n.key=this.config.publicKey,this.config.cors?this.xhr(e,t,n,r):this.jsonp(t,n,r)}},{key:"cachedRequest",value:function(e,t,n,r){var i=this._cachedRequests=this._cachedRequests||{},a=[e,t,JSON.stringify(n)].join("-");i[a]?r.apply(void 0,[null].concat(o(i[a]))):this.request(e,t,n,function(e){for(var t=arguments.length,n=Array(t>1?t-1:0),o=1;o0?e(f.reduce(function(e,t){return e.concat(t)})):n(d[0]))};p.forEach(function(e){return t.request(r,o,e,i)})})}},{key:"xhr",value:function(e,t,n,r){function i(){"post"===e?(a.setRequestHeader&&a.setRequestHeader("Content-type","application/x-www-form-urlencoded"),a.send(s)):a.send()}te("xhr");var o=function(){var e=window.XMLHttpRequest,t=window.XDomainRequest;return e&&"withCredentials"in new e?e:t||void 0}(),a=new o,u=this.url(t),s=w.default.stringify(n,{encodeValuesOnly:!0});"get"===e&&(u+="?"+s),a.open(e,u),a.timeout=this.config.timeout,a.ontimeout=function(){r((0,C.default)("api-timeout"))},a.onerror=function(){r((0,C.default)("api-error"))},a.onprogress=function(){},a.onload=function(){var e;try{e=JSON.parse(this.responseText)}catch(e){return te(this.responseText,e),r((0,C.default)("api-error",{message:"There was a problem parsing the API response with: "+this.responseText}))}e&&e.error?r((0,C.default)("api-error",e.error)):r(null,e)},setTimeout(i,0)}},{key:"jsonp",value:function(e,t,n){te("jsonp");var r=this.url(e)+"?"+w.default.stringify(t,{encodeValuesOnly:!0});(0,b.default)(r,{timeout:this.config.timeout,prefix:"__rjs"},function(e,t){if(e)return n(e);t.error?n((0,C.default)("api-error",t.error)):n(null,t)})}}]),t}(O.default);re.prototype.Frame=D.factory,re.prototype.coupon=T.default,re.prototype.giftCard=I.default,re.prototype.giftcard=I.default,re.prototype.ApplePay=q.factory,re.prototype.PayPal=B.factory,re.prototype.paypal=U.deprecated,re.prototype.Adyen=z.factory,re.prototype.plan=n(89),re.prototype.tax=R.default,re.prototype.token=L.token,re.prototype.validate=n(90)},function(e,t,n){"use strict";var r=n(42),i=n(43),o=n(21);e.exports={formats:o,parse:i,stringify:r}},function(e,t,n){"use strict";var r=Object.prototype.hasOwnProperty,i=function(){for(var e=[],t=0;t<256;++t)e.push("%"+((t<16?"0":"")+t.toString(16)).toUpperCase());return e}(),o=function(e){for(var t;e.length;){var n=e.pop();if(t=n.obj[n.prop],Array.isArray(t)){for(var r=[],i=0;i=48&&o<=57||o>=65&&o<=90||o>=97&&o<=122?n+=t.charAt(r):o<128?n+=i[o]:o<2048?n+=i[192|o>>6]+i[128|63&o]:o<55296||o>=57344?n+=i[224|o>>12]+i[128|o>>6&63]+i[128|63&o]:(r+=1,o=65536+((1023&o)<<10|1023&t.charCodeAt(r)),n+=i[240|o>>18]+i[128|o>>12&63]+i[128|o>>6&63]+i[128|63&o])}return n},t.compact=function(e){for(var t=[{obj:{o:e},prop:"o"}],n=[],r=0;rc){for(var t=0,n=a.length-s;t0)throw this.hostedFields.errors[0];var a=(0,l.default)();this.bus.send("token:init",{id:a,inputs:i}),this.once("token:done:"+a,function(e){return n(e.err,e.token)})}else{var u=o.call(this,i);if(u.length)return t((0,d.default)("validation",{fields:u}));this.request("post","/token",i,n)}}function o(e){var t=[];return this.validate.cardNumber(e.number)||t.push("number"),this.validate.expiry(e.month,e.year)||t.push("month","year"),e.first_name||t.push("first_name"),e.last_name||t.push("last_name"),!~this.config.required.indexOf("cvv")&&!e.cvv||this.validate.cvv(e.cvv)||t.push("cvv"),(0,u.default)(this.config.required,function(n){!e[n]&&~h.indexOf(n)&&t.push(n)}),p("validate errors",t),t}Object.defineProperty(t,"__esModule",{value:!0}),t.FIELDS=void 0,t.token=i;var a=n(4),u=r(a),s=n(15),c=n(5),l=r(c),f=n(1),d=r(f),p=n(0)("recurly:token"),h=t.FIELDS=["first_name","last_name","address1","address2","company","country","city","state","postal_code","phone","vat_number","fraud_session_id","token"]},function(e,t){function n(e){return e.replace(/\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^\/]+)\//g,"").replace(a,"").match(/[a-zA-Z_]\w*/g)||[]}function r(e,t,n){var r=/\.\w+|\w+ *\(|"[^"]*"|'[^']*'|\/([^\/]+)\/|[a-zA-Z_]\w*/g;return e.replace(r,function(e){return"("==e[e.length-1]?n(e):~t.indexOf(e)?n(e):e})}function i(e){for(var t=[],n=0;n1?t-1:0),a=1;a1&&void 0!==arguments[1]?arguments[1]:function(e){return e};return e.reduce(function(e,n){var r=t(n);return(e[r]=e[r]||[]).push(n),e},{})}Object.defineProperty(t,"__esModule",{value:!0}),t.default=r},function(e,t,n){"use strict";var r=n(18);e.exports=t=new r.Recurly,t.Recurly=r.Recurly},function(e,t){function n(e,t,n){var r=-1,i=e.length;t<0&&(t=-t>i?0:i+t),n=n>i?i:n,n<0&&(n+=i),i=t>n?0:n-t>>>0,t>>>=0;for(var o=Array(i);++r-1&&e%1==0&&e-1&&e%1==0&&e<=m}function l(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function f(e){return!!e&&"object"==typeof e}function d(e){return"symbol"==typeof e||f(e)&&F.call(e)==O}function p(e){if(!e)return 0===e?e:0;if((e=y(e))===v||e===-v){return(e<0?-1:1)*b}return e===e?e:0}function h(e){var t=p(e),n=t%1;return t===t?n?t-n:t:0}function y(e){if("number"==typeof e)return e;if(d(e))return g;if(l(e)){var t="function"==typeof e.valueOf?e.valueOf():e;e=l(t)?t+"":t}if("string"!=typeof e)return 0===e?e:+e;e=e.replace(j,"");var n=k.test(e);return n||C.test(e)?S(e.slice(2),n?2:8):x.test(e)?g:+e}var v=1/0,m=9007199254740991,b=1.7976931348623157e308,g=NaN,w="[object Function]",_="[object GeneratorFunction]",O="[object Symbol]",j=/^\s+|\s+$/g,x=/^[-+]0x[0-9a-f]+$/i,k=/^0b[01]+$/i,C=/^0o[0-7]+$/i,P=/^(?:0|[1-9]\d*)$/,S=parseInt,E=Object.prototype,F=E.toString,A=Math.ceil,T=Math.max;e.exports=o},function(e,t,n){function r(){}function i(e,t,n){function i(){u.parentNode.removeChild(u),window[h]=r}"function"==typeof t&&(n=t,t={}),t||(t={});var u,s,c=t.prefix||"__jp",l=t.param||"callback",f=null!=t.timeout?t.timeout:6e4,d=encodeURIComponent,p=document.getElementsByTagName("script")[0]||document.head,h=c+a++;f&&(s=setTimeout(function(){i(),n&&n(new Error("Timeout"))},f)),window[h]=function(e){o("jsonp got",e),s&&clearTimeout(s),i(),n&&n(null,e)},e+=(~e.indexOf("?")?"&":"?")+l+"="+d(h),e=e.replace("?&","?"),o('jsonp req "%s"',e),u=document.createElement("script"),u.src=e,p.parentNode.insertBefore(u,p)}var o=n(0)("jsonp");e.exports=i;var a=0},function(e,t){function n(){throw new Error("setTimeout has not been defined")}function r(){throw new Error("clearTimeout has not been defined")}function i(e){if(l===setTimeout)return setTimeout(e,0);if((l===n||!l)&&setTimeout)return l=setTimeout,setTimeout(e,0);try{return l(e,0)}catch(t){try{return l.call(null,e,0)}catch(t){return l.call(this,e,0)}}}function o(e){if(f===clearTimeout)return clearTimeout(e);if((f===r||!f)&&clearTimeout)return f=clearTimeout,clearTimeout(e);try{return f(e)}catch(t){try{return f.call(null,e)}catch(t){return f.call(this,e)}}}function a(){y&&p&&(y=!1,p.length?h=p.concat(h):v=-1,h.length&&u())}function u(){if(!y){var e=i(a);y=!0;for(var t=h.length;t;){for(p=h,h=[];++v1)for(var n=1;n100)){var t=/^((?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|years?|yrs?|y)?$/i.exec(e);if(t){var n=parseFloat(t[1]);switch((t[2]||"ms").toLowerCase()){case"years":case"year":case"yrs":case"yr":case"y":return n*l;case"days":case"day":case"d":return n*c;case"hours":case"hour":case"hrs":case"hr":case"h":return n*s;case"minutes":case"minute":case"mins":case"min":case"m":return n*u;case"seconds":case"second":case"secs":case"sec":case"s":return n*a;case"milliseconds":case"millisecond":case"msecs":case"msec":case"ms":return n;default:return}}}}function r(e){return e>=c?Math.round(e/c)+"d":e>=s?Math.round(e/s)+"h":e>=u?Math.round(e/u)+"m":e>=a?Math.round(e/a)+"s":e+"ms"}function i(e){return o(e,c,"day")||o(e,s,"hour")||o(e,u,"minute")||o(e,a,"second")||e+" ms"}function o(e,t,n){if(!(e0)return n(e);if("number"===o&&!1===isNaN(e))return t.long?i(e):r(e);throw new Error("val is not a non-empty string or a valid number. val="+JSON.stringify(e))}},function(e,t,n){"use strict";var r=n(20),i=n(21),o={brackets:function(e){return e+"[]"},indices:function(e,t){return e+"["+t+"]"},repeat:function(e){return e}},a=Date.prototype.toISOString,u={delimiter:"&",encode:!0,encoder:r.encode,encodeValuesOnly:!1,serializeDate:function(e){return a.call(e)},skipNulls:!1,strictNullHandling:!1},s=function e(t,n,i,o,a,s,c,l,f,d,p,h){var y=t;if("function"==typeof c)y=c(n,y);else if(y instanceof Date)y=d(y);else if(null===y){if(o)return s&&!h?s(n,u.encoder):n;y=""}if("string"==typeof y||"number"==typeof y||"boolean"==typeof y||r.isBuffer(y)){if(s){return[p(h?n:s(n,u.encoder))+"="+p(s(y,u.encoder))]}return[p(n)+"="+p(String(y))]}var v=[];if(void 0===y)return v;var m;if(Array.isArray(c))m=c;else{var b=Object.keys(y);m=l?b.sort(l):b}for(var g=0;g0?P+C:""}},function(e,t,n){"use strict";var r=n(20),i=Object.prototype.hasOwnProperty,o={allowDots:!1,allowPrototypes:!1,arrayLimit:20,decoder:r.decode,delimiter:"&",depth:5,parameterLimit:1e3,plainObjects:!1,strictNullHandling:!1},a=function(e,t){for(var n={},r=t.ignoreQueryPrefix?e.replace(/^\?/,""):e,a=t.parameterLimit===1/0?void 0:t.parameterLimit,u=r.split(t.delimiter,a),s=0;s=0;--i){var o,a=e[i];if("[]"===a)o=[],o=o.concat(r);else{o=n.plainObjects?Object.create(null):{};var u="["===a.charAt(0)&&"]"===a.charAt(a.length-1)?a.slice(1,-1):a,s=parseInt(u,10);!isNaN(s)&&a!==u&&String(s)===u&&s>=0&&n.parseArrays&&s<=n.arrayLimit?(o=[],o[s]=r):o[u]=r}r=o}return r},s=function(e,t,n){if(e){var r=n.allowDots?e.replace(/\.([^.[]+)/g,"[$1]"):e,o=/(\[[^[\]]*])/,a=/(\[[^[\]]*])/g,s=o.exec(r),c=s?r.slice(0,s.index):r,l=[];if(c){if(!n.plainObjects&&i.call(Object.prototype,c)&&!n.allowPrototypes)return;l.push(c)}for(var f=0;null!==(s=a.exec(r))&&f "+t+") {","args = new Array(arguments.length + 1);","for (var i = 0; i < arguments.length; i++) {","args[i] = arguments[i];","}","}","return new Promise(function (rs, rj) {","var cb = "+u+";","var res;","switch (argLength) {",n.concat(["extra"]).map(function(e,t){return"case "+t+":res = fn.call("+["self"].concat(n.slice(0,t)).concat("cb").join(",")+");break;"}).join(""),"default:","args[argLength] = cb;","res = fn.apply(self, args);","}","if (res &&",'(typeof res === "object" || typeof res === "function") &&','typeof res.then === "function"',") {rs(res);}","});","};"].join("");return Function(["Promise","fn"],i)(o,e)}var o=n(7),a=n(49);e.exports=o,o.denodeify=function(e,t){return"number"==typeof t&&t!==1/0?r(e,t):i(e)};var u="function (err, res) {if (err) { rj(err); } else { rs(res); }}";o.nodeify=function(e){return function(){var t=Array.prototype.slice.call(arguments),n="function"==typeof t[t.length-1]?t.pop():null,r=this;try{return e.apply(this,arguments).nodeify(n,r)}catch(e){if(null===n||void 0===n)return new o(function(t,n){n(e)});a(function(){n.call(r,e)})}}},o.prototype.nodeify=function(e,t){if("function"!=typeof e)return this;this.then(function(n){a(function(){e.call(t,null,n)})},function(n){a(function(){e.call(t,n)})})}},function(e,t,n){"use strict";function r(){if(s.length)throw s.shift()}function i(e){var t;t=u.length?u.pop():new o,t.task=e,a(t)}function o(){this.task=null}var a=n(22),u=[],s=[],c=a.makeRequestCallFromTimer(r);e.exports=i,o.prototype.call=function(){try{this.task.call()}catch(e){i.onerror?i.onerror(e):(s.push(e),c())}finally{this.task=null,u[u.length]=this}}},function(e,t,n){"use strict";var r=n(7);e.exports=r,r.enableSynchronous=function(){r.prototype.isPending=function(){return 0==this.getState()},r.prototype.isFulfilled=function(){return 1==this.getState()},r.prototype.isRejected=function(){return 2==this.getState()},r.prototype.getValue=function(){if(3===this._81)return this._65.getValue();if(!this.isFulfilled())throw new Error("Cannot get a value of an unfulfilled promise.");return this._65},r.prototype.getReason=function(){if(3===this._81)return this._65.getReason();if(!this.isRejected())throw new Error("Cannot get a rejection reason of a non-rejected promise.");return this._65},r.prototype.getState=function(){return 3===this._81?this._65.getState():-1===this._81||-2===this._81?0:this._81}},r.disableSynchronous=function(){r.prototype.isPending=void 0,r.prototype.isFulfilled=void 0,r.prototype.isRejected=void 0,r.prototype.getValue=void 0,r.prototype.getReason=void 0,r.prototype.getState=void 0}},function(e,t,n){"use strict";e.exports="4.8.5"},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){d("token");var n=(0,c.normalize)(p,e),r=n.values,i=a(r);if("function"!=typeof t)throw(0,f.default)("missing-callback");if(i.length)return t((0,f.default)("validation",{fields:i}));this.request("post","/token",r,function(e,r){if(e)return t(e);n.fields.token&&r.id&&(n.fields.token.value=r.id),t(null,r)})}function o(e,t){if(d("bankInfo"),"function"!=typeof t)throw(0,f.default)("missing-callback");var n=e&&e.routingNumber;if(!n||"string"!=typeof n)return t((0,f.default)("validation",{fields:["routingNumber"]}));this.request("get","/bank",{routing_number:n},function(e,n){if(e)return t(e);t(null,n)})}function a(e){var t=[];return(0,s.default)(h,function(n){e[n]&&"string"==typeof e[n]||t.push(n)}),e.account_number!==e.account_number_confirmation&&t.push("account_number_confirmation"),d("validate errors",t),t}var u=n(4),s=r(u),c=n(15),l=n(1),f=r(l),d=n(0)("recurly:bankAccount");e.exports={token:i,bankInfo:o};var p=["account_number","account_number_confirmation","routing_number","name_on_account","account_type","address1","address2","company","country","city","state","postal_code","phone","vat_number","token"],h=["account_number","account_number_confirmation","routing_number","account_type","name_on_account","country"]},function(e,t,n){function r(e){switch({}.toString.call(e)){case"[object Object]":return u(e);case"[object Function]":return e;case"[object String]":return a(e);case"[object RegExp]":return o(e);default:return i(e)}}function i(e){return function(t){return e===t}}function o(e){return function(t){return e.test(t)}}function a(e){return/^ *\W+/.test(e)?new Function("_","return _ "+e):new Function("_","return "+s(e))}function u(e){var t={};for(var n in e)t[n]="string"==typeof e[n]?i(e[n]):r(e[n]);return function(e){if("object"!=typeof e)return!1;for(var n in t){if(!(n in e))return!1;if(!t[n](e[n]))return!1}return!0}}function s(e){var t=l(e);if(!t.length)return"_."+e;var n,r,i;for(r=0;r0&&void 0!==arguments[0]?arguments[0]:{},t=arguments[1];a("%j",e);var n=e.coupon,r=e.plans,i=e.plan,u=e.currency;if(!n)throw(0,o.default)("missing-coupon");if("function"!=typeof t)throw(0,o.default)("missing-callback");!r&&i&&(r=[i]),this.pipedRequest({route:"/coupons/"+n,data:{plan_codes:r,currency:u},by:"plan_codes"}).nodeify(t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(1),o=function(e){return e&&e.__esModule?e:{default:e}}(i),a=n(0)("recurly:coupon")},function(e,t,n){"use strict";function r(e,t){if(u("%j",e),"function"!=typeof t)throw(0,a.default)("missing-callback");if("object"!==(void 0===e?"undefined":i(e)))throw(0,a.default)("invalid-options");if(!("giftcard"in e))throw(0,a.default)("missing-giftcard");this.request("get","/gift_cards/"+e.giftcard,e,t)}Object.defineProperty(t,"__esModule",{value:!0});var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e};t.default=r;var o=n(1),a=function(e){return e&&e.__esModule?e:{default:e}}(o),u=n(0)("recurly:giftcard")},function(e,t,n){"use strict";function r(e,t){if(e=i({},e),"function"!=typeof t)throw new Error("Missing callback");this.cachedRequest("get","/tax",{country:e.country,postal_code:e.postalCode||e.postal_code,tax_code:e.taxCode||e.tax_code,vat_number:e.vatNumber||e.vat_number},t)}Object.defineProperty(t,"__esModule",{value:!0});var i=Object.assign||function(e){for(var t=1;t1&&void 0!==arguments[1]?arguments[1]:{};b("creating request frame"),r.version=this.recurly.version,r.event=this.name,r.key=this.recurly.config.publicKey,this.once(r.event,function(t){n.relay&&e.document.body.removeChild(n.relay),t.error?n.emit("error",t.error):n.emit("done",t)}),this.url=this.recurly.url(t),this.url+=(~this.url.indexOf("?")?"&":"?")+d.default.stringify(r,{encodeValuesOnly:!0})}},{key:"listen",value:function(t){var n=this;if(this.recurly.bus.add(this),"documentMode"in document){b("Creating relay");var r=document.createElement("iframe");r.width=r.height=0,r.src=this.recurly.url("/relay"),r.name="recurly-relay-"+this.recurly.id+"-"+this.id,r.style.display="none",r.onload=function(){return n.create()},e.document.body.appendChild(r),this.relay=r,b("Created relay",r)}else this.create()}},{key:"create",value:function(){b("opening frame window",this.url,this.name,this.attributes),e.open(this.url,this.name,this.attributes)}},{key:"attributes",get:function(){return"\n resizable,scrollbars,\n width="+this.width+",\n height="+this.height+",\n top="+this.top+",\n left="+this.left+"\n "}},{key:"top",get:function(){var t=e.outerHeight||e.document.documentElement.clientHeight,n=null===e.screenY?e.screenTop:e.screenY;return s(t,this.height,n)}},{key:"left",get:function(){var t=e.outerWidth||e.document.documentElement.clientWidth,n=null===e.screenX?e.screenLeft:e.screenX;return s(t,this.width,n)}}]),n}(h.default)}).call(t,n(3))},function(e,t,n){"use strict";(function(e){function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function u(e){return new j(l({},e,{recurly:this}))}function s(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"",t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:0;return{label:e,amount:(0,b.default)(t)}}Object.defineProperty(t,"__esModule",{value:!0});var c=function(){function e(e,t){for(var n=0;n0||(this.config.lineItems.push(this.authorizationLineItem),this.config.total=this.authorizationLineItem.amount)}},{key:"onPricingChange",value:function(){var e=this.config.pricing,t=this.config.lineItems=[];if(this.config.total=e.totalNow,e.hasPrice){var n=e.price.now.taxes||e.price.now.tax;t.push(s(this.config.i18n.subtotalLineItemLabel,e.subtotalPreDiscountNow)),+e.price.now.discount&&t.push(s(this.config.i18n.discountLineItemLabel,-e.price.now.discount)),+n&&t.push(s(this.config.i18n.taxLineItemLabel,n)),+e.price.now.giftCard&&t.push(s(this.config.i18n.giftCardLineItemLabel,-e.price.now.giftCard)),this.config.lineItems=t}}},{key:"onValidateMerchant",value:function(e){var t=this;w("Validating Apple Pay merchant session",e);var n=e.validationURL;this.recurly.request("post","/apple_pay/start",{validationURL:n},function(e,n){if(e)return t.error(e);t.session.completeMerchantValidation(n)})}},{key:"onPaymentMethodSelected",value:function(e){w("Payment method selected",e),this.session.completePaymentMethodSelection(this.finalTotalLineItem,this.lineItems)}},{key:"onShippingContactSelected",value:function(e){var t=this.session.STATUS_SUCCESS,n=[];this.session.completeShippingContactSelection(t,n,this.finalTotalLineItem,this.lineItems)}},{key:"onShippingMethodSelected",value:function(e){this.session.completeShippingMethodSelection(this.finalTotalLineItem,this.lineItems)}},{key:"onPaymentAuthorized",value:function(e){var t=this;w("Payment authorization received",e);var n={};this.config.form&&(n=(0,v.normalize)(g.FIELDS,this.config.form,{parseCard:!1}).values),this.mapPaymentData(n,e.payment),this.recurly.request("post","/apple_pay/token",n,function(e,n){if(e)return t.session.completePayment(t.session.STATUS_FAILURE),t.error("apple-pay-payment-failure",e);w("Token received",n),t.session.completePayment(t.session.STATUS_SUCCESS),t.emit("token",n)})}},{key:"onCancel",value:function(e){w("User canceled Apple Pay payment",e),this.emit("cancel",e)}},{key:"error",value:function(){var e=(arguments.length<=0?void 0:arguments[0])instanceof Error?arguments.length<=0?void 0:arguments[0]:h.default.apply(void 0,arguments);return this.emit("error",e),e}},{key:"mapPaymentData",value:function(e,t){e.paymentData=t.token.paymentData,e.paymentMethod=t.token.paymentMethod,t.billingContact&&(g.FIELDS.some(function(t){return e[t]})||g.FIELDS.forEach(function(n){if(_[n]){var r=t.billingContact[_[n]];"address1"===n?r=r[0]:"address2"===n&&(r=r[1]),e[n]=r}}))}},{key:"session",get:function(){if(this._session)return this._session;w("Creating new Apple Pay session"),this.addAuthorizationLineItem();var t=new e.ApplePaySession(2,{countryCode:this.config.country,currencyCode:this.config.currency,supportedNetworks:this.config.supportedNetworks,merchantCapabilities:this.config.merchantCapabilities,requiredBillingContactFields:["postalAddress"],total:this.totalLineItem});return t.onvalidatemerchant=this.onValidateMerchant.bind(this),t.onshippingcontactselected=this.onShippingContactSelected.bind(this),t.onshippingmethodselected=this.onShippingMethodSelected.bind(this),t.onpaymentmethodselected=this.onPaymentMethodSelected.bind(this),t.onpaymentauthorized=this.onPaymentAuthorized.bind(this),t.oncancel=this.onCancel.bind(this),this._session=t}},{key:"lineItems",get:function(){return[].concat(this.config.lineItems)}},{key:"totalLineItem",get:function(){return s(this.config.label,this.config.total)}},{key:"finalTotalLineItem",get:function(){return l({},this.totalLineItem,{type:"final"})}},{key:"authorizationLineItem",get:function(){return s(this.config.i18n.authorizationLineItemLabel,1)}}]),n}(d.default)}).call(t,n(3))},function(e,t,n){var r=n(63);e.exports=function(e){if("string"==typeof e){var t=e.toUpperCase();if(r.hasOwnProperty(t))return r[t]}},e.exports.currencySymbolMap=r},function(e,t){e.exports={AED:"د.Ø¥",AFN:"Ø‹",ALL:"L",AMD:"֏",ANG:"Æ’",AOA:"Kz",ARS:"$",AUD:"$",AWG:"Æ’",AZN:"ман",BAM:"KM",BBD:"$",BDT:"à§³",BGN:"лв",BHD:".د.ب",BIF:"FBu",BMD:"$",BND:"$",BOB:"$b",BRL:"R$",BSD:"$",BTC:"฿",BTN:"Nu.",BWP:"P",BYR:"p.",BZD:"BZ$",CAD:"$",CDF:"FC",CHF:"CHF",CLP:"$",CNY:"Â¥",COP:"$",CRC:"â‚¡",CUC:"$",CUP:"₱",CVE:"$",CZK:"Kč",DJF:"Fdj",DKK:"kr",DOP:"RD$",DZD:"دج",EEK:"kr",EGP:"£",ERN:"Nfk",ETB:"Br",ETH:"Ξ",EUR:"€",FJD:"$",FKP:"£",GBP:"£",GEL:"₾",GGP:"£",GHC:"₵",GHS:"GH₵",GIP:"£",GMD:"D",GNF:"FG",GTQ:"Q",GYD:"$",HKD:"$",HNL:"L",HRK:"kn",HTG:"G",HUF:"Ft",IDR:"Rp",ILS:"₪",IMP:"£",INR:"₹",IQD:"ع.د",IRR:"ï·¼",ISK:"kr",JEP:"£",JMD:"J$",JOD:"JD",JPY:"Â¥",KES:"KSh",KGS:"лв",KHR:"៛",KMF:"CF",KPW:"â‚©",KRW:"â‚©",KWD:"KD",KYD:"$",KZT:"лв",LAK:"â‚­",LBP:"£",LKR:"₨",LRD:"$",LSL:"M",LTC:"Ł",LTL:"Lt",LVL:"Ls",LYD:"LD",MAD:"MAD",MDL:"lei",MGA:"Ar",MKD:"ден",MMK:"K",MNT:"â‚®",MOP:"MOP$",MUR:"₨",MVR:"Rf",MWK:"MK",MXN:"$",MYR:"RM",MZN:"MT",NAD:"$",NGN:"₦",NIO:"C$",NOK:"kr",NPR:"₨",NZD:"$",OMR:"ï·¼",PAB:"B/.",PEN:"S/.",PGK:"K",PHP:"₱",PKR:"₨",PLN:"zÅ‚",PYG:"Gs",QAR:"ï·¼",RMB:"ï¿¥",RON:"lei",RSD:"Дин.",RUB:"₽",RWF:"Râ‚£",SAR:"ï·¼",SBD:"$",SCR:"₨",SDG:"ج.س.",SEK:"kr",SGD:"$",SHP:"£",SLL:"Le",SOS:"S",SRD:"$",SSP:"£",STD:"Db",SVC:"$",SYP:"£",SZL:"E",THB:"฿",TJS:"SM",TMT:"T",TND:"د.ت",TOP:"T$",TRL:"₤",TRY:"₺",TTD:"TT$",TVD:"$",TWD:"NT$",TZS:"TSh",UAH:"â‚´",UGX:"USh",USD:"$",UYU:"$U",UZS:"лв",VEF:"Bs",VND:"â‚«",VUV:"VT",WST:"WS$",XAF:"FCFA",XBT:"Ƀ",XCD:"$",XOF:"CFA",XPF:"â‚£",YER:"ï·¼",ZAR:"R",ZWD:"Z$"}},function(e,t,n){function r(e){switch({}.toString.call(e)){case"[object Object]":return u(e);case"[object Function]":return e;case"[object String]":return a(e);case"[object RegExp]":return o(e);default:return i(e)}}function i(e){return function(t){return e===t}}function o(e){return function(t){return e.test(t)}}function a(e){return/^ *\W+/.test(e)?new Function("_","return _ "+e):new Function("_","return "+s(e))}function u(e){var t={};for(var n in e)t[n]="string"==typeof e[n]?i(e[n]):r(e[n]);return function(e){if("object"!=typeof e)return!1;for(var n in t){if(!(n in e))return!1;if(!t[n](e[n]))return!1}return!0}}function s(e){var t=l(e);if(!t.length)return"_."+e;var n,r,i;for(r=0;r0&&n(l)?t>1?o(l,t-1,n,r,a):i(a,l):r||(a[a.length]=l)}return a}function a(e,t){return e=Object(e),u(e,t,function(t,n){return n in e})}function u(e,t,n){for(var r=-1,i=t.length,o={};++r-1&&e%1==0&&e<=g}function y(e){var t=typeof e;return!!e&&("object"==t||"function"==t)}function v(e){return!!e&&"object"==typeof e}function m(e){return"symbol"==typeof e||v(e)&&E.call(e)==j}var b=1/0,g=9007199254740991,w="[object Arguments]",_="[object Function]",O="[object GeneratorFunction]",j="[object Symbol]",x="object"==typeof t&&t&&t.Object===Object&&t,k="object"==typeof self&&self&&self.Object===Object&&self,C=x||k||Function("return this")(),P=Object.prototype,S=P.hasOwnProperty,E=P.toString,F=C.Symbol,A=P.propertyIsEnumerable,T=F?F.isConcatSpreadable:void 0,M=Math.max,I=Array.isArray,N=function(e,t){return t=M(void 0===t?e.length-1:t,0),function(){for(var r=arguments,i=-1,o=M(r.length-t,0),a=Array(o);++i1&&void 0!==arguments[1]?arguments[1]:function(){},r="https://js.braintreegateway.com/web/"+v+"/js/"+t+".min.js";(0,f.default)(r,function(t){t?e.error("paypal-load-error",{cause:t}):n()})},r=function(){e.braintreeClientAvailable("paypal")?t():n("paypal",t),e.braintreeClientAvailable("dataCollector")?t():n("data-collector",t)};this.braintreeClientAvailable()?r():n("client",r)}},{key:"initialize",value:function(){var t=this;y("Initializing Braintree client");var n=this.config.clientAuthorization,r=e.braintree;r.client.create({authorization:n},function(e,n){if(e)return t.fail("paypal-braintree-api-error",{cause:e});y("Braintree client created"),r.dataCollector.create({client:n,paypal:!0},function(e,i){if(e)return t.fail("paypal-braintree-api-error",{cause:e});y("Device data collector created"),t.deviceFingerprint=i.deviceData,r.paypal.create({client:n},function(e,n){if(e)return t.fail("paypal-braintree-api-error",{cause:e});y("PayPal client created"),t.paypal=n,t.emit("ready")})})})}},{key:"start",value:function(){var e=this,t=u({},this.config.display,{flow:"vault"});this.paypal.tokenize(t,function(t,n){if(t)return"PAYPAL_POPUP_CLOSED"===t.code?e.emit("cancel"):e.error("paypal-braintree-tokenize-braintree-error",{cause:t});y("Token payload received",n),e.deviceFingerprint&&(n.deviceFingerprint=e.deviceFingerprint),e.recurly.request("post","/paypal/token",{type:"braintree",payload:n},function(t,n){if(t)return e.error("paypal-braintree-tokenize-recurly-error",{cause:t});e.emit("token",n)})})}},{key:"braintreeClientAvailable",value:function(t){var n=e.braintree;return n&&n.client&&n.client.VERSION===v&&(!t||t in n)}}]),n}(h.PayPalStrategy)}).call(t,n(3))},function(e,t){function n(e,t){for(var n in t)e.setAttribute(n,t[n])}function r(e,t){e.onload=function(){this.onerror=this.onload=null,t(null,e)},e.onerror=function(){this.onerror=this.onload=null,t(new Error("Failed to load "+this.src),e)}}function i(e,t){e.onreadystatechange=function(){"complete"!=this.readyState&&"loaded"!=this.readyState||(this.onreadystatechange=null,t(null,e))}}e.exports=function(e,t,o){var a=document.head||document.getElementsByTagName("head")[0],u=document.createElement("script");"function"==typeof t&&(o=t,t={}),t=t||{},o=o||function(){},u.type=t.type||"text/javascript",u.charset=t.charset||"utf8",u.async=!("async"in t)||!!t.async,u.src=e,t.attrs&&n(u,t.attrs),t.text&&(u.text=""+t.text),("onload"in u?r:i)(u,o),u.onload||r(u,o),a.appendChild(u)}},function(e,t,n){"use strict";function r(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:0,t=arguments[1],n=0;return function(){return n++&&n===e&&t()}}Object.defineProperty(t,"__esModule",{value:!0}),t.default=r},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function u(e){return new v(l({},e,{recurly:this}))}function s(e){var t=[];return e.skinCode&&8!=e.skinCode.length&&t.push("skinCode should be 8 characters"),e.countryCode&&2!=e.countryCode.length&&t.push("countryCode should be 2 characters"),e.shopperLocale&&5!=e.shopperLocale.length&&t.push("shopperLocale should be 5 characters"),e.invoiceUuid||t.push("invoiceUuid cannot be blank"),y("validate errors",t),t}Object.defineProperty(t,"__esModule",{value:!0});var c=function(){function e(e,t){for(var n=0;n0)return this.error("validation",{fields:r});var i=this.recurly.Frame({height:600,path:"/adyen/start",payload:n});i.once("error",function(e){return t.error("adyen-error",{cause:e})}),i.once("done",function(e){return t.emit("token",e)})}},{key:"error",value:function(){var e=(arguments.length<=0?void 0:arguments[0])instanceof Error?arguments.length<=0?void 0:arguments[0]:h.default.apply(void 0,arguments);return this.emit("error",e),e}}]),t}(d.default)},function(e,t,n){"use strict";(function(e){function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}function u(t){var n=e.document.createElement("a");return n.href=t,n.protocol+"//"+n.host}Object.defineProperty(t,"__esModule",{value:!0}),t.Bus=void 0;var s=function(){function e(e,t){for(var n=0;n0&&void 0!==arguments[0]?arguments[0]:{};i(this,t);var r=o(this,(t.__proto__||Object.getPrototypeOf(t)).call(this));return r.id=(0,p.default)(),r.debug=n(0)("recurly:bus:"+r.id),r.emitters=[],r.recipients=[],r.receive=r.receive.bind(r),r.config={api:e.api},r.connect(),r.send=r.send.bind(r),r}return a(t,e),s(t,[{key:"connect",value:function(){window.addEventListener("message",this.receive,!1),this.debug("listening for postMessage events on",window)}},{key:"receive",value:function(e){if(this.originMatches(e.origin))return"string"==typeof e.data?this.receiveFromRelay(e):void(e.data.event&&(this.debug("message received",e.data),this.send(new f.Message(e),null,{exclude:[e.srcElement]})))}},{key:"receiveFromRelay",value:function(e){var t=void 0;try{t=JSON.parse(e.data)}catch(t){this.debug("failed to parse a string message",t,e)}t&&this.send(new f.Message(t.recurly_event,t.recurly_message))}},{key:"add",value:function(e){~this.recipients.indexOf(e)||(this.recipients.push(e),"function"==typeof e.emit&&e.emit("bus:added",this),this.debug("added recipient",e))}},{key:"send",value:function(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{exclude:[]},r=new f.Message(e,t);n.exclude;this.debug("sending message to "+this.recipients.length+" recipients",r),this.recipients.forEach(function(e){e.postMessage?"function"==typeof e.postMessage&&e.postMessage(r,"*"):"function"==typeof e.emit&&e.emit(r.event,r.body)})}},{key:"stop",value:function(){this.recipients=[],window.removeEventListener("message",this.receive,!1)}},{key:"originMatches",value:function(e){return u(e)===u(this.config.api)}}]),t}(l.default)}).call(t,n(3))},function(e,t,n){"use strict";function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(t,"__esModule",{value:!0});var i=function(){function e(e,t){for(var n=0;n1&&void 0!==arguments[1]?arguments[1]:{};if(r(this,e),t instanceof e)return t;t instanceof window.MessageEvent&&(n=t.data.body,t=t.data.event),this.event=t,this.body=n}return i(e,[{key:"format",value:function(){return{event:this.event,body:this.body}}}]),e}()},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0}),t.Fraud=void 0;var u=function(){function e(e,t){for(var n=0;n\n \n \n \n ',this.container=this.target.children[0],this.iframe=this.container.querySelector("iframe"),this.window=this.iframe.contentWindow,u()&&(this.tabbingProxy=y.default.createHiddenInput(),this.tabbingProxy.addEventListener("focus",this.focus),this.container.insertBefore(this.tabbingProxy,this.iframe))}},{key:"bindDeferredFocus",value:function(){var t=this;if(this.container.addEventListener("click",this.focus),this.target.id){var n=e.document.querySelectorAll("label[for="+this.target.id+"]");[].slice.apply(n).forEach(function(e){e.addEventListener("click",t.focus)})}}},{key:"reset",value:function(){this.off(),this.target.innerHTML="",delete this.target,delete this.iframe,delete this.window}},{key:"update",value:function(){this.container.className=this.classList,this.iframe.setAttribute("tabindex",this.tabIndex)}},{key:"onReady",value:function(e){e.type===this.type&&(this.ready=!0,this.off("hostedField:ready",this.onReady),this.update())}},{key:"onStateChange",value:function(e){if(e.type===this.type){var t=s({},e);delete t.type,this.state=t,this.update()}}},{key:"onChange",value:function(e){e.type===this.type&&this.update()}},{key:"onConfigure",value:function(e){e.type===this.type&&(this.configure(e),this.update())}},{key:"focus",value:function(){this.bus&&this.bus.send("hostedField:"+this.type+":focus!")}},{key:"type",get:function(){return this.config.type}},{key:"url",get:function(){var e=encodeURIComponent(JSON.stringify(this.config));return this.config.recurly.api+"/field.html#config="+e}},{key:"classList",get:function(){var e="recurly-hosted-field",t=[e];return this.ready&&(t.push(e+"-"+this.config.type),this.state.focus&&(t.push(e+"-focus"),t.push(e+"-"+this.config.type+"-focus")),this.state.valid?(t.push(e+"-valid"),t.push(e+"-"+this.config.type+"-valid")):this.state.focus||this.state.empty||(t.push(e+"-invalid"),t.push(e+"-"+this.config.type+"-invalid"))),t.join(" ")}},{key:"tabIndex",get:function(){var e=parseInt(this.config.tabIndex,10);return isNaN(e)?0:e}}]),n}(f.default)}).call(t,n(3))},function(e,t,n){/*! + * Bowser - a browser detector + * https://github.com/ded/bowser + * MIT License | (c) Dustin Diaz 2015 + */ +!function(t,r,i){void 0!==e&&e.exports?e.exports=i():n(78)("bowser",i)}(0,0,function(){function e(e){function t(t){var n=e.match(t);return n&&n.length>1&&n[1]||""}function n(t){var n=e.match(t);return n&&n.length>1&&n[2]||""}var r,i=t(/(ipod|iphone|ipad)/i).toLowerCase(),o=/like android/i.test(e),u=!o&&/android/i.test(e),s=/nexus\s*[0-6]\s*/i.test(e),c=!s&&/nexus\s*[0-9]+/i.test(e),l=/CrOS/.test(e),f=/silk/i.test(e),d=/sailfish/i.test(e),p=/tizen/i.test(e),h=/(web|hpw)os/i.test(e),y=/windows phone/i.test(e),v=(/SamsungBrowser/i.test(e),!y&&/windows/i.test(e)),m=!i&&!f&&/macintosh/i.test(e),b=!u&&!d&&!p&&!h&&/linux/i.test(e),g=n(/edg([ea]|ios)\/(\d+(\.\d+)?)/i),w=t(/version\/(\d+(\.\d+)?)/i),_=/tablet/i.test(e)&&!/tablet pc/i.test(e),O=!_&&/[^-]mobi/i.test(e),j=/xbox/i.test(e);/opera/i.test(e)?r={name:"Opera",opera:a,version:w||t(/(?:opera|opr|opios)[\s\/](\d+(\.\d+)?)/i)}:/opr\/|opios/i.test(e)?r={name:"Opera",opera:a,version:t(/(?:opr|opios)[\s\/](\d+(\.\d+)?)/i)||w}:/SamsungBrowser/i.test(e)?r={name:"Samsung Internet for Android",samsungBrowser:a,version:w||t(/(?:SamsungBrowser)[\s\/](\d+(\.\d+)?)/i)}:/coast/i.test(e)?r={name:"Opera Coast",coast:a,version:w||t(/(?:coast)[\s\/](\d+(\.\d+)?)/i)}:/yabrowser/i.test(e)?r={name:"Yandex Browser",yandexbrowser:a,version:w||t(/(?:yabrowser)[\s\/](\d+(\.\d+)?)/i)}:/ucbrowser/i.test(e)?r={name:"UC Browser",ucbrowser:a,version:t(/(?:ucbrowser)[\s\/](\d+(?:\.\d+)+)/i)}:/mxios/i.test(e)?r={name:"Maxthon",maxthon:a,version:t(/(?:mxios)[\s\/](\d+(?:\.\d+)+)/i)}:/epiphany/i.test(e)?r={name:"Epiphany",epiphany:a,version:t(/(?:epiphany)[\s\/](\d+(?:\.\d+)+)/i)}:/puffin/i.test(e)?r={name:"Puffin",puffin:a,version:t(/(?:puffin)[\s\/](\d+(?:\.\d+)?)/i)}:/sleipnir/i.test(e)?r={name:"Sleipnir",sleipnir:a,version:t(/(?:sleipnir)[\s\/](\d+(?:\.\d+)+)/i)}:/k-meleon/i.test(e)?r={name:"K-Meleon",kMeleon:a,version:t(/(?:k-meleon)[\s\/](\d+(?:\.\d+)+)/i)}:y?(r={name:"Windows Phone",osname:"Windows Phone",windowsphone:a},g?(r.msedge=a,r.version=g):(r.msie=a,r.version=t(/iemobile\/(\d+(\.\d+)?)/i))):/msie|trident/i.test(e)?r={name:"Internet Explorer",msie:a,version:t(/(?:msie |rv:)(\d+(\.\d+)?)/i)}:l?r={name:"Chrome",osname:"Chrome OS",chromeos:a,chromeBook:a,chrome:a,version:t(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)}:/edg([ea]|ios)/i.test(e)?r={name:"Microsoft Edge",msedge:a,version:g}:/vivaldi/i.test(e)?r={name:"Vivaldi",vivaldi:a,version:t(/vivaldi\/(\d+(\.\d+)?)/i)||w}:d?r={name:"Sailfish",osname:"Sailfish OS",sailfish:a,version:t(/sailfish\s?browser\/(\d+(\.\d+)?)/i)}:/seamonkey\//i.test(e)?r={name:"SeaMonkey",seamonkey:a,version:t(/seamonkey\/(\d+(\.\d+)?)/i)}:/firefox|iceweasel|fxios/i.test(e)?(r={name:"Firefox",firefox:a,version:t(/(?:firefox|iceweasel|fxios)[ \/](\d+(\.\d+)?)/i)},/\((mobile|tablet);[^\)]*rv:[\d\.]+\)/i.test(e)&&(r.firefoxos=a,r.osname="Firefox OS")):f?r={name:"Amazon Silk",silk:a,version:t(/silk\/(\d+(\.\d+)?)/i)}:/phantom/i.test(e)?r={name:"PhantomJS",phantom:a,version:t(/phantomjs\/(\d+(\.\d+)?)/i)}:/slimerjs/i.test(e)?r={name:"SlimerJS",slimer:a,version:t(/slimerjs\/(\d+(\.\d+)?)/i)}:/blackberry|\bbb\d+/i.test(e)||/rim\stablet/i.test(e)?r={name:"BlackBerry",osname:"BlackBerry OS",blackberry:a,version:w||t(/blackberry[\d]+\/(\d+(\.\d+)?)/i)}:h?(r={name:"WebOS",osname:"WebOS",webos:a,version:w||t(/w(?:eb)?osbrowser\/(\d+(\.\d+)?)/i)},/touchpad\//i.test(e)&&(r.touchpad=a)):/bada/i.test(e)?r={name:"Bada",osname:"Bada",bada:a,version:t(/dolfin\/(\d+(\.\d+)?)/i)}:p?r={name:"Tizen",osname:"Tizen",tizen:a,version:t(/(?:tizen\s?)?browser\/(\d+(\.\d+)?)/i)||w}:/qupzilla/i.test(e)?r={name:"QupZilla",qupzilla:a,version:t(/(?:qupzilla)[\s\/](\d+(?:\.\d+)+)/i)||w}:/chromium/i.test(e)?r={name:"Chromium",chromium:a,version:t(/(?:chromium)[\s\/](\d+(?:\.\d+)?)/i)||w}:/chrome|crios|crmo/i.test(e)?r={name:"Chrome",chrome:a,version:t(/(?:chrome|crios|crmo)\/(\d+(\.\d+)?)/i)}:u?r={name:"Android",version:w}:/safari|applewebkit/i.test(e)?(r={name:"Safari",safari:a},w&&(r.version=w)):i?(r={name:"iphone"==i?"iPhone":"ipad"==i?"iPad":"iPod"},w&&(r.version=w)):r=/googlebot/i.test(e)?{name:"Googlebot",googlebot:a,version:t(/googlebot\/(\d+(\.\d+))/i)||w}:{name:t(/^(.*)\/(.*) /),version:n(/^(.*)\/(.*) /)},!r.msedge&&/(apple)?webkit/i.test(e)?(/(apple)?webkit\/537\.36/i.test(e)?(r.name=r.name||"Blink",r.blink=a):(r.name=r.name||"Webkit",r.webkit=a),!r.version&&w&&(r.version=w)):!r.opera&&/gecko\//i.test(e)&&(r.name=r.name||"Gecko",r.gecko=a,r.version=r.version||t(/gecko\/(\d+(\.\d+)?)/i)),r.windowsphone||!u&&!r.silk?!r.windowsphone&&i?(r[i]=a,r.ios=a,r.osname="iOS"):m?(r.mac=a,r.osname="macOS"):j?(r.xbox=a,r.osname="Xbox"):v?(r.windows=a,r.osname="Windows"):b&&(r.linux=a,r.osname="Linux"):(r.android=a,r.osname="Android");var x="";r.windows?x=function(e){switch(e){case"NT":return"NT";case"XP":return"XP";case"NT 5.0":return"2000";case"NT 5.1":return"XP";case"NT 5.2":return"2003";case"NT 6.0":return"Vista";case"NT 6.1":return"7";case"NT 6.2":return"8";case"NT 6.3":return"8.1";case"NT 10.0":return"10";default:return}}(t(/Windows ((NT|XP)( \d\d?.\d)?)/i)):r.windowsphone?x=t(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i):r.mac?(x=t(/Mac OS X (\d+([_\.\s]\d+)*)/i),x=x.replace(/[_\s]/g,".")):i?(x=t(/os (\d+([_\s]\d+)*) like mac os x/i),x=x.replace(/[_\s]/g,".")):u?x=t(/android[ \/-](\d+(\.\d+)*)/i):r.webos?x=t(/(?:web|hpw)os\/(\d+(\.\d+)*)/i):r.blackberry?x=t(/rim\stablet\sos\s(\d+(\.\d+)*)/i):r.bada?x=t(/bada\/(\d+(\.\d+)*)/i):r.tizen&&(x=t(/tizen[\/\s](\d+(\.\d+)*)/i)),x&&(r.osversion=x);var k=!r.windows&&x.split(".")[0];return _||c||"ipad"==i||u&&(3==k||k>=4&&!O)||r.silk?r.tablet=a:(O||"iphone"==i||"ipod"==i||u||s||r.blackberry||r.webos||r.bada)&&(r.mobile=a),r.msedge||r.msie&&r.version>=10||r.yandexbrowser&&r.version>=15||r.vivaldi&&r.version>=1||r.chrome&&r.version>=20||r.samsungBrowser&&r.version>=4||r.firefox&&r.version>=20||r.safari&&r.version>=6||r.opera&&r.version>=10||r.ios&&r.osversion&&r.osversion.split(".")[0]>=6||r.blackberry&&r.version>=10.1||r.chromium&&r.version>=20?r.a=a:r.msie&&r.version<10||r.chrome&&r.version<20||r.firefox&&r.version<20||r.safari&&r.version<6||r.opera&&r.version<10||r.ios&&r.osversion&&r.osversion.split(".")[0]<6||r.chromium&&r.version<20?r.c=a:r.x=a,r}function t(e){return e.split(".").length}function n(e,t){var n,r=[];if(Array.prototype.map)return Array.prototype.map.call(e,t);for(n=0;n=0;){if(i[0][r]>i[1][r])return 1;if(i[0][r]!==i[1][r])return-1;if(0===r)return 0}}function i(t,n,i){var o=u;"string"==typeof n&&(i=n,n=void 0),void 0===n&&(n=!1),i&&(o=e(i));var a=""+o.version;for(var s in t)if(t.hasOwnProperty(s)&&o[s]){if("string"!=typeof t[s])throw new Error("Browser version in the minVersion map should be a string: "+s+": "+String(t));return r([a,t[s]])<0}return n}function o(e,t,n){return!i(e,t,n)}var a=!0,u=e("undefined"!=typeof navigator?navigator.userAgent||"":"");return u.test=function(e){for(var t=0;t1&&void 0!==arguments[1]?arguments[1]:{},n=t.commit,r=void 0===n||n,i=void 0;if(i=(0,v.default)(this.validSubscriptions)?e:(0,h.default)(this.subscriptionCurrencies,e),(0,v.default)(i))throw new Error("unresolvable");if(r)return this.currency(i[0])}},{key:"Calculations",get:function(){return N.default}},{key:"PRICING_METHODS",get:function(){return l(t.prototype.__proto__||Object.getPrototypeOf(t.prototype),"PRICING_METHODS",this).concat(["address","adjustment","coupon","currency","giftCard","shippingAddress","subscription","tax"])}},{key:"validAdjustments",get:function(){var e=this;return this.items.adjustments.filter(function(t){return t.currency===e.items.currency})}},{key:"validSubscriptions",get:function(){return this.items.subscriptions.filter(function(e){return e.isValid})}},{key:"subscriptionCurrencies",get:function(){return(0,h.default)(this.validSubscriptions.map(function(e){return Object.keys(e.items.plan.price)}))}},{key:"subscriptionPlanCodes",get:function(){return(0,O.default)(this.validSubscriptions.map(function(e){return e.items.plan.code}))}}]),t}(P.Pricing);t.default=q},function(e,t){function n(e){var t={},n=e.length-1,r=e[0],i=e[n];for(var o in r)t[r[o]]=0;for(var o=1;o<=n;o++){var a=e[o];for(var u in a){var s=a[u];t[s]===o-1&&(t[s]=o)}}var c=[];for(var o in i){var s=i[o];t[s]===n&&c.push(s)}return c}function r(e,t){if(!t)return n(e);for(var r=[],o=0;o-1&&r.push(e[o]);return r}function i(e,t){for(var n=0;ne?(n=e,r=t-e):n=t,{used:n,remains:r}}if(this.pricing.items.gift_card){var t=this.price.now.total,n=this.price.next.total,r=e(t,this.pricing.items.gift_card.unit_amount),i=e(n,r.remains);this.price.now.gift_card=r.used,this.price.next.gift_card=i.used,this.price.now.total=t-r.used,this.price.next.total=n-i.used}}},{key:"planPrice",value:function(){var e=this.items.plan;if(!e)return{amount:0,setup_fee:0};var t=e.price[this.items.currency];return t.amount=t.unit_amount*(e.quantity||1),t}},{key:"isTrial",value:function(){var e=this.items.coupon;return!(!e||"free_trial"!==e.discount.type)||this.items.plan.trial}}]),e}();t.default=m},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){return function(){return new l.default(function(e,t){return e()},t)}}Object.defineProperty(t,"__esModule",{value:!0}),t.DEFERRED_METHODS=t.DISABLED_METHODS=void 0;var a=function(){function e(e,t){for(var n=0;n1)try{t.checkout.resolveCurrency(Object.keys(o.price),{commit:!1})}catch(r){return n((0,s.default)("invalid-plan-currency",{planCode:o.code,currencies:t.checkout.subscriptionCurrencies}))}o.quantity=i,t.subscription.items.plan=o;var a=function(){t.subscription.emit("set.plan",o),e(o)};if(t.checkout.currencyCode in o.price)a();else try{t.checkout.resolveCurrency(Object.keys(o.price)).then(a)}catch(e){n(r)}})},this.subscription).nodeify(a)}},{key:"id",get:function(){return this.subscription.id}},{key:"isValid",get:function(){return this.subscription.isValid}},{key:"items",get:function(){return this.subscription.items}},{key:"price",get:function(){return this.subscription.price}},{key:"taxCode",get:function(){return this.subscription.taxCode}},{key:"taxExempt",get:function(){return this.subscription.taxExempt}}]),e}();t.default=h},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){var n=t.id,r=t.price;return{type:"subscription",id:n,amount:r[e].total,setupFee:r[e].setup_fee,addons:r[e].addons,plan:r[e].plan}}function a(e){if(!e.items.plan.trial)return 0;var t={days:86400,months:2678400}[e.items.plan.trial.interval]||0;return e.items.plan.trial.length*t}function u(e,t){var n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=n.setupFees,i=void 0===r||r,o=t.price[e],a=parseFloat(o.subtotal)+parseFloat(o.discount);return i||(a-=parseFloat(o.setup_fee)),a}function s(e,t){return 0===e.length?e:t.applies_to_all_plans?e:e.filter(function(e){return e.couponIsValidForSubscription(t)})}function c(e){return parseFloat((Math.round(100*e)/100).toFixed(6))}Object.defineProperty(t,"__esModule",{value:!0});var l=Object.assign||function(e){for(var t=1;te?(n=e,r=t-e):n=t,{used:n,remains:r}}if(this.price.now.giftCard=0,this.price.next.giftCard=0,this.items.giftCard){var t=this.price.now.subtotal+this.price.now.taxes,n=this.price.next.subtotal+this.price.next.taxes,r=e(t,this.items.giftCard.unit_amount),i=r.used,o=r.remains,a=e(n,o),u=a.used;return this.price.now.giftCard=i,this.price.next.giftCard=u,m.default.resolve()}}},{key:"totals",value:function(){return this.price.now.total=this.price.now.subtotal+this.price.now.taxes-this.price.now.giftCard,this.price.next.total=this.price.next.subtotal+this.price.next.taxes-this.price.next.giftCard,m.default.resolve()}},{key:"itemizedSets",value:function(){return this.price.now.items=this._itemizedSets.now.subscriptions.concat(this._itemizedSets.now.adjustments),this.price.next.items=this._itemizedSets.next.subscriptions,m.default.resolve()}},{key:"applyFreeTrialCoupon",value:function(){var e=this,t=this.items.coupon;return this.hasValidSubscriptions?"subscription"===t.redemption_resource?this.mostValuableSubscriptionForFreeTrial().coupon(t).reprice(null,{internal:!0}).then(function(){return e.subscriptions()}):m.default.all(this.validSubscriptions.map(function(e){return e.coupon(t).reprice(null,{internal:!0})})).then(function(){return e.subscriptions()}):m.default.resolve()}},{key:"discountAmounts",value:function(){var e=(arguments.length>0&&void 0!==arguments[0]&&arguments[0],this.items.coupon),t=0,n=0;if(e)if("free_trial"===e.discount.type);else if(e.discount.rate){var r=this.discountableSubtotals(e,{setupFees:!1}),i=r.discountableNow,o=r.discountableNext;t=c(i*e.discount.rate),n=c(o*e.discount.rate)}else if(e.discount.amount){var a=this.discountableSubtotals(e),u=a.discountableNow,s=a.discountableNext,l=e.discount.amount[this.items.currency]||0;t=Math.min(u,l),n=Math.min(s,l)}return{discountNow:t,discountNext:n}}},{key:"discountableSubtotals",value:function(e){var t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},n=t.setupFees,r=void 0===n||n,i=t.taxExempt,o=void 0===i||i,a=0,c=0;if(e.applies_to_non_plan_charges&&(a+=o?this.price.now.adjustments:this.validAdjustments.reduce(function(e,t){return e+(t.taxExempt?0:t.amount*t.quantity)},0)),e.applies_to_plans&&this.hasValidSubscriptions){var l=void 0;l="subscription"===e.redemption_resource?[this.mostValuableSubscriptionForDiscount()]:this.validSubscriptions,l=s(l,e),l.forEach(function(e){!o&&e.taxExempt||(a+=u("now",e,{setupFees:r}),c+=u("next",e,{setupFees:r}))})}return{discountableNow:a,discountableNext:c}}},{key:"mostValuableSubscriptionForDiscount",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:this.validSubscriptions;return e=s(e,this.items.coupon),e.sort(function(e,t){var n=parseFloat(e.price.now.subtotal),r=parseFloat(t.price.now.subtotal);return n>r?-1:n0}},{key:"taxableAdjustments",get:function(){return this.validAdjustments.filter(function(e){return!e.taxExempt&&e.amount>0})}},{key:"taxableSubscriptions",get:function(){return this.validSubscriptions.filter(function(e){return!e.items.plan.tax_exempt})}},{key:"taxCodes",get:function(){var e=this.taxableAdjustments.concat(this.taxableSubscriptions);return(0,g.default)(e.map(function(e){return e.taxCode}))}}]),e}();t.default=C},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function o(e,t){if(!e)throw new ReferenceError("this hasn't been initialised - super() hasn't been called");return!t||"object"!=typeof t&&"function"!=typeof t?e:t}function a(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function, not "+typeof t);e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,enumerable:!1,writable:!0,configurable:!0}}),t&&(Object.setPrototypeOf?Object.setPrototypeOf(e,t):e.__proto__=t)}Object.defineProperty(t,"__esModule",{value:!0});var u=function(){function e(e,t){for(var n=0;n19)return!1;for(;o--;)n=parseInt(t.charAt(o),10)*i,r+=n-9*(n>9),i^=3;return r%10==0&&r>0}function i(e){var t=arguments.length>1&&void 0!==arguments[1]&&arguments[1],n=(0,c.parseCard)(e),r=n.length,i=(0,s.default)(l,function(e){return e.pattern.test(n)&&(t||~e.lengths.indexOf(r))});return i&&i.type||"unknown"}function o(e,t){if((e=Number(e)-1)<0||e>11)return!1;t=Number(t),t+=t<100?2e3:0;var n=new Date;return n.setYear(t),n.setDate(1),n.setHours(0),n.setMinutes(0),n.setSeconds(0),n.setMonth(e+1),new Date=222100&&t<=272099}},lengths:[16]},{type:"american_express",pattern:/^3[47]/,lengths:[15]},{type:"visa",pattern:/^4/,lengths:[13,16]},{type:"jcb",pattern:/^35[2-8]\d/,lengths:[16]},{type:"diners_club",pattern:/^(30[0-5]|309|36|3[89]|54|55|2014|2149)/,lengths:[14]}]}]); \ No newline at end of file diff --git a/services/web/public/js/libs/recurly.min.js b/services/web/public/js/libs/recurly.min.js deleted file mode 100755 index 8d1833a20c..0000000000 --- a/services/web/public/js/libs/recurly.min.js +++ /dev/null @@ -1 +0,0 @@ -(function(f){function v(D){function C(){}C.prototype=D||this;return new C()}var h={};h.settings={enableGeoIP:true,acceptedCards:["visa","mastercard","discover","american_express"],oneErrorPerField:true};h.version="2.2.9";h.dom={};h.Error={toString:function(){return"RecurlyJS Error: "+this.message}};h.raiseError=function(C){var D=v(h.Error);D.message=C;throw D};h.config=function(C){f.extend(true,h.settings,C);if(!C.baseURL){var D=h.settings.subdomain||h.raiseError("company subdomain not configured");h.settings.baseURL="https://"+D+".recurly.com/jsonp/"+D+"/"}};function u(D,C){if(D==1){return C.substr(0,C.length-1)}return""+D+" "+C}(h.Cost=function(C){this._cents=C||0}).prototype={toString:function(){return h.formatCurrency(this.dollars())},cents:function(C){if(C===undefined){return this._cents}return new h.Cost(C)},dollars:function(C){if(C===undefined){return this._cents/100}return new h.Cost(C*100)},mult:function(C){return new h.Cost(this._cents*C)},add:function(C){if(C.cents){C=C.cents()}return new h.Cost(this._cents+C)},sub:function(C){if(C.cents){C=C.cents()}return new h.Cost(this._cents-C)}};h.Cost.FREE=new h.Cost(0);(h.TimePeriod=function(D,C){this.length=D;this.unit=C}).prototype={toString:function(){return""+u(this.length,this.unit)},toDate:function(){var C=new Date();switch(this.unit){case"month":C.setMonth(C.getMonth()+this.length);break;case"day":C.setDay(C.getDay()+this.length);break}return C},clone:function(){return new h.TimePeriod(this.length,this.unit)}};(h.RecurringCost=function(D,C){this.cost=D;this.interval=C}).prototype={toString:function(){return""+this.cost+" every "+this.interval},clone:function(){return new h.TimePeriod(this.length,this.unit)}};h.RecurringCost.FREE=new h.RecurringCost(0,null);(h.RecurringCostStage=function(C,D){this.recurringCost=C;this.duration=D}).prototype={toString:function(){this.recurringCost.toString()+" for "+this.duration.toString()}};h.locale={};h.locale.errors={emptyField:"Required field",missingFullAddress:"Please enter your full address.",invalidEmail:"Invalid",invalidCC:"Invalid",invalidCVV:"Invalid",invalidCoupon:"Invalid",cardDeclined:"Transaction declined",acceptTOS:"Please accept the Terms of Service.",invalidQuantity:"Invalid quantity"};h.locale.currencies={};h.locale.currency={format:"%u%n",separator:".",delimiter:",",precision:2};function s(C,D){var F=h.locale.currencies[C]=v(h.locale.currency);for(var E in D){F[E]=D[E]}}s("USD",{symbol:"$"});s("AUD",{symbol:"$"});s("CAD",{symbol:"$"});s("EUR",{symbol:"\u20ac"});s("GBP",{symbol:"\u00a3"});s("CZK",{symbol:"\u004b"});s("DKK",{symbol:"\u006b\u0072"});s("HUF",{symbol:"Ft"});s("JPY",{symbol:"\u00a5"});s("NOK",{symbol:"kr"});s("NZD",{symbol:"$"});s("PLN",{symbol:"\u007a"});s("SGD",{symbol:"$"});s("SEK",{symbol:"kr"});s("CHF",{symbol:"Fr"});s("ZAR",{symbol:"R"});h.settings.locale=h.locale;h.knownCards={visa:{prefixes:[4],name:"Visa"},mastercard:{prefixes:[51,52,53,54,55],name:"MasterCard"},american_express:{prefixes:[34,37],name:"American Express"},discover:{prefixes:[6011,62,64,65],name:"Discover"},diners_club:{prefixes:[305,36,38],name:"Diners Club"},carte_blanche:{prefixes:[300,301,302,303,304,305]},jcb:{prefixes:[35],name:"JCB"},enroute:{prefixes:[2014,2149],name:"EnRoute"},maestro:{prefixes:[5018,5020,5038,6304,6759,6761],name:"Maestro"},laser:{prefixes:[6304,6706,6771,6709],name:"Laser"}};h.detectCardType=function(E){E=E.replace(/\D/g,"");var H=h.knownCards;for(var C in H){if(H.hasOwnProperty(C)){var I=H[C];for(var G=0,D=I.prefixes.length;G").hide();F.attr("action",D).attr("method","POST").attr("enctype","application/x-www-form-urlencoded");f('').attr({name:"recurly_token",value:E}).appendTo(F);f("body").append(F);F.submit()};function k(F){var E=f("
');if(!P.displayQuantity){J.find(".quantity").remove()}J.data("add_on",P);J.appendTo(K)}K.delegate(".quantity input","change keyup recalculate",function(ac){var Y=f(this);var Z=Y.closest(".add_on");var ab=Z.data("add_on");var aa=Y.val()===""?1:parseInt(Y.val(),10);V.findAddOnByCode(ab.code).quantity=aa>0?aa:0;M()});K.delegate(".quantity input","blur",function(ab){var Y=f(this);var Z=Y.closest(".add_on");var aa=parseInt(Y.val(),10);if(aa<1){Y.trigger("recalculate")}if(aa===0){Z.trigger("actuate")}});K.bind("selectstart",function(Y){if(f(Y.target).is(".add_on")){Y.preventDefault()}});K.delegate(".add_on","click actuate",function(ac){if(f(ac.target).closest(".quantity").length){return}var aa=!f(this).hasClass("selected");f(this).toggleClass("selected",aa);var ab=f(this).data("add_on");if(aa){var Y=V.redeemAddOn(ab);var Z=f(this).find(".quantity input");var ad=parseInt(Z.val(),10);if(ad<1||isNaN(ad)){ad=1;Z.val(ad)}Y.quantity=ad;Z.focus()}else{V.removeAddOn(ab.code)}M()});K.find("input").trigger("init")}}else{K.remove()}var N=C.find(".coupon");var R=null;function W(){var Y=N.find("input").val();if(Y==R){return}R=Y;if(!Y){N.removeClass("invalid").removeClass("valid");N.find(".description").text("");V.coupon=undefined;M();return}N.addClass("checking");V.getCoupon(Y,function(Z){N.removeClass("checking");V.coupon=Z;N.removeClass("invalid").addClass("valid");N.find(".description").text(Z.description);M()},function(){V.coupon=undefined;N.removeClass("checking");N.removeClass("valid").addClass("invalid");N.find(".description").text(h.locale.errors.invalidCoupon);M()})}if(D.enableCoupons){N.find("input").bind("keyup change",function(Y){});N.find("input").keypress(function(Y){if(Y.charCode==13){Y.preventDefault();W()}});N.find(".check").click(function(){W()});N.find("input").blur(function(){N.find(".check").click()})}else{N.remove()}var I=C.find(".vat");var X=C.find(".vat_number");var Q=X.find("input");I.find(".title").text("VAT at "+h.settings.VATPercent+"%");function L(){var Z=C.find(".country select").val();var ab=h.isVATNumberApplicable(Z);X.toggleClass("applicable",ab);X.toggleClass("inapplicable",!ab);var aa=Q.val();var Y=h.isVATChargeApplicable(Z,aa);I.toggleClass("applicable",Y);I.toggleClass("inapplicable",!Y)}C.find(".country select").change(function(){O.country=f(this).val();M();L()}).change();Q.bind("keyup change",function(){O.vatNumber=f(this).val();M();L()});C.submit(function(Y){Y.preventDefault();a(C);C.find(".error").remove();C.find(".invalid").removeClass("invalid");B(function(Z){q(C,V.plan,D,Z);i(C,H,D,Z);d(C,O,D,Z);y(C,Z)},function(){C.addClass("submitting");var Z=C.find("button.submit").text();C.find("button.submit").attr("disabled",true).text("Please Wait");V.save({signature:D.signature,success:function(aa){if(D.successHandler){D.successHandler(h.getToken(aa))}if(D.successURL){var ab=D.successURL;h.postResult(ab,aa,D)}},error:function(aa){if(!D.onError||!D.onError(aa)){p(C,aa)}},complete:function(){C.removeClass("submitting");C.find("button.submit").removeAttr("disabled").text(Z)}})})});W();M();if(D.beforeInject){D.beforeInject(C.get(0))}f(function(){var Y=f(D.target);Y.html(C);if(D.afterInject){D.afterInject(C.get(0))}})}};h.paypal={start:function(G){var J=window.name;if(J.indexOf("recurly_result")>-1){window.name="";J=""}var H=f.extend(G.data,{post_message:true,referer:window.location.href}),E=G.url+"?"+f.param(H),C=window.open(E,"recurly_paypal","menubar=1,resizable=1");window.popup=C;f(window).on("message",I);var D=setInterval(function(){var L=decodeURIComponent(window.name),M=L.match(/recurly_result=(.*)[&$]?/),K=M&&f.parseJSON(M[1]);if(K){F(K);window.name=J}},1000);function F(K){try{C.close()}finally{G.success(K);G.complete();f(window).unbind("message",I);clearInterval(D)}}function I(M){var L=document.createElement("a");L.href=h.settings.baseURL;var K=L.protocol+"//"+L.host.replace(/:\d+$/,"");if(M.originalEvent.origin==K){F(M.originalEvent.data)}}}};h.states={};h.states.US={"-":"Select State","--":"------------",AL:"Alabama",AK:"Alaska",AS:"American Samoa",AZ:"Arizona",AR:"Arkansas",AA:"Armed Forces Americas",AE:"Armed Forces Europe, Middle East, & Canada",AP:"Armed Forces Pacific",CA:"California",CO:"Colorado",CT:"Connecticut",DE:"Delaware",DC:"District of Columbia",FM:"Federated States of Micronesia",FL:"Florida",GA:"Georgia",GU:"Guam",HI:"Hawaii",ID:"Idaho",IL:"Illinois",IN:"Indiana",IA:"Iowa",KS:"Kansas",KY:"Kentucky",LA:"Louisiana",ME:"Maine",MH:"Marshall Islands",MD:"Maryland",MA:"Massachusetts",MI:"Michigan",MN:"Minnesota",MS:"Mississippi",MO:"Missouri",MT:"Montana",NE:"Nebraska",NV:"Nevada",NH:"New Hampshire",NJ:"New Jersey",NM:"New Mexico",NY:"New York",NC:"North Carolina",ND:"North Dakota",MP:"Northern Mariana Islands",OH:"Ohio",OK:"Oklahoma",OR:"Oregon",PW:"Palau",PA:"Pennsylvania",PR:"Puerto Rico",RI:"Rhode Island",SC:"South Carolina",SD:"South Dakota",TN:"Tennessee",TX:"Texas",UT:"Utah",VT:"Vermont",VI:"Virgin Islands",VA:"Virginia",WA:"Washington",WV:"West Virginia",WI:"Wisconsin",WY:"Wyoming"};h.states.CA={"-":"Select State","--":"------------",AB:"Alberta",BC:"British Columbia",MB:"Manitoba",NB:"New Brunswick",NL:"Newfoundland",NT:"Northwest Territories",NS:"Nova Scotia",NU:"Nunavut",ON:"Ontario",PE:"Prince Edward Island",QC:"Quebec",SK:"Saskatchewan",YT:"Yukon Territory"};h.dom.contact_info_fields='
Contact Info
First Name
Last Name
Phone Number
Company/Organization Name
';h.dom.billing_info_fields='
Billing Info
First Name
Last Name
Credit Card Number
CVV
Expires
Address
Apt/Suite
City
State/Province
Zip/Postal
VAT Number
You will be taken to PayPal to authorize a billing agreement.

Please make sure pop-ups aren\'t blocked.
';h.dom.subscribe_form='';h.dom.update_billing_info_form='
';h.dom.one_time_transaction_form='
';h.dom.terms_of_service='
Terms of Service and Privacy Policy';window.Recurly=h})(jQuery); \ No newline at end of file diff --git a/services/web/public/stylesheets/_style_includes.less b/services/web/public/stylesheets/_style_includes.less index de6c4ff7fe..7f6070e69c 100644 --- a/services/web/public/stylesheets/_style_includes.less +++ b/services/web/public/stylesheets/_style_includes.less @@ -84,8 +84,4 @@ @import "../js/libs/pdfListView/TextLayer.css"; @import "../js/libs/pdfListView/AnnotationsLayer.css"; @import "../js/libs/pdfListView/HighlightsLayer.css"; - -// CodeMirror -& when (@show-rich-text) { - @import "vendor/codemirror.css"; -} +@import "vendor/codemirror.css"; diff --git a/services/web/public/stylesheets/app/editor.less b/services/web/public/stylesheets/app/editor.less index 9a8bdc5594..0936baee1b 100644 --- a/services/web/public/stylesheets/app/editor.less +++ b/services/web/public/stylesheets/app/editor.less @@ -12,9 +12,7 @@ @import "./editor/online-users.less"; @import "./editor/hotkeys.less"; @import "./editor/review-panel.less"; -& when (@show-rich-text) { - @import "./editor/rich-text.less"; -} +@import "./editor/rich-text.less"; @ui-layout-toggler-def-height: 50px; @ui-resizer-size: 7px; @@ -82,10 +80,7 @@ .full-size; } -#editor when (@show-rich-text = true) { - top: 40px; // TODO: replace with toolbar height var -} -#editor-rich-text when (@show-rich-text = true) { +#editor-rich-text { top: 40px; // TODO: replace with toolbar height var } @@ -305,7 +300,6 @@ } .ui-layout-resizer when (@is-overleaf = true) { - z-index: 5 !important; width: @ui-resizer-size !important; background-color: @editor-resizer-bg-color; &.ui-layout-resizer-closed { diff --git a/services/web/public/stylesheets/app/recurly.less b/services/web/public/stylesheets/app/recurly.less index c953f34bd1..2e88d6742d 100644 --- a/services/web/public/stylesheets/app/recurly.less +++ b/services/web/public/stylesheets/app/recurly.less @@ -1,3 +1,8 @@ +.recurly-hosted-field { + &:extend(.form-control); +} + + .recurly { display: block; position: relative; diff --git a/services/web/public/stylesheets/components/forms.less b/services/web/public/stylesheets/components/forms.less index 4e32779d7b..8eb92411cd 100755 --- a/services/web/public/stylesheets/components/forms.less +++ b/services/web/public/stylesheets/components/forms.less @@ -309,8 +309,9 @@ input[type="checkbox"], .has-warning { .form-control-validation(@state-warning-text; @state-warning-text; @state-warning-bg); } -.has-error { +.has-external-error { .form-control-validation(@state-danger-text; @state-danger-text; @state-danger-bg); + color:@red; } .form-control.ng-dirty.ng-invalid:not(:focus) { diff --git a/services/web/test/acceptance/coffee/LinkedFilesTests.coffee b/services/web/test/acceptance/coffee/LinkedFilesTests.coffee index 54a7f7f562..9ca7ecae42 100644 --- a/services/web/test/acceptance/coffee/LinkedFilesTests.coffee +++ b/services/web/test/acceptance/coffee/LinkedFilesTests.coffee @@ -1,7 +1,9 @@ async = require "async" expect = require("chai").expect _ = require 'underscore' +mkdirp = require "mkdirp" +Settings = require "settings-sharelatex" MockFileStoreApi = require './helpers/MockFileStoreApi' request = require "./helpers/request" User = require "./helpers/User" @@ -22,7 +24,8 @@ describe "LinkedFiles", -> LinkedUrlProxy.listen 6543, (error) => return done(error) if error? @owner = new User() - @owner.login done + @owner.login -> + mkdirp Settings.path.dumpFolder, done describe "creating a URL based linked file", -> before (done) -> @@ -181,4 +184,4 @@ describe "LinkedFiles", -> done() # TODO: Add test for asking for host that return ENOTFOUND - # (This will probably end up handled by the proxy) \ No newline at end of file + # (This will probably end up handled by the proxy) diff --git a/services/web/test/acceptance/coffee/ProjectDuplicateNameTests.coffee b/services/web/test/acceptance/coffee/ProjectDuplicateNameTests.coffee index 5da94856fc..b4742eefec 100644 --- a/services/web/test/acceptance/coffee/ProjectDuplicateNameTests.coffee +++ b/services/web/test/acceptance/coffee/ProjectDuplicateNameTests.coffee @@ -186,10 +186,13 @@ describe "ProjectDuplicateNames", -> contentType: 'image/jpeg' , (err, res, body) => @body = body + # update the image id because we have replaced the file + @imageFile._id = @body.entity_id done() it "should succeed (overwriting the file)", -> expect(@body.success).to.equal true + # at this point the @imageFile._id has changed describe "for an existing folder", -> describe "trying to add a doc with the same name", -> diff --git a/services/web/test/acceptance/coffee/ProjectStructureTests.coffee b/services/web/test/acceptance/coffee/ProjectStructureTests.coffee index 51bd4a26c7..30fbccfb8d 100644 --- a/services/web/test/acceptance/coffee/ProjectStructureTests.coffee +++ b/services/web/test/acceptance/coffee/ProjectStructureTests.coffee @@ -56,7 +56,6 @@ describe "ProjectStructureChanges", -> describe "duplicating a project", -> before (done) -> MockDocUpdaterApi.clearProjectStructureUpdates() - console.log(example_project_id) @owner.request.post { uri: "/Project/#{example_project_id}/clone", json: @@ -219,11 +218,17 @@ describe "ProjectStructureChanges", -> if res.statusCode < 200 || res.statusCode >= 300 throw new Error("failed to upload file #{res.statusCode}") + example_file_id = JSON.parse(body).entity_id + {fileUpdates:updates, version} = MockDocUpdaterApi.getProjectStructureUpdates(example_project_id) - expect(updates.length).to.equal(1) + expect(updates.length).to.equal(2) update = updates[0] expect(update.userId).to.equal(@owner._id) expect(update.pathname).to.equal("/1pixel.png") + #expect(update.url).to.be.a('string'); + update = updates[1] + expect(update.userId).to.equal(@owner._id) + expect(update.pathname).to.equal("/1pixel.png") expect(update.url).to.be.a('string'); expect(version).to.equal(@project_0.version + 1) @@ -591,10 +596,14 @@ describe "ProjectStructureChanges", -> throw new Error("failed to upload file #{res.statusCode}") {fileUpdates:updates, version} = MockDocUpdaterApi.getProjectStructureUpdates(@tpds_project_id) - expect(updates.length).to.equal(1) + expect(updates.length).to.equal(2) update = updates[0] expect(update.userId).to.equal(@owner._id) expect(update.pathname).to.equal("/1pixel.png") + #expect(update.url).to.be.a('string'); + update = updates[1] + expect(update.userId).to.equal(@owner._id) + expect(update.pathname).to.equal("/1pixel.png") expect(update.url).to.be.a('string'); expect(version).to.equal(@project_0.version + 1) diff --git a/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee b/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee index 9e30ef3fd1..703a03d14f 100644 --- a/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee +++ b/services/web/test/unit/coffee/Editor/EditorControllerTests.coffee @@ -20,6 +20,8 @@ describe "EditorController", -> @fsPath = "/folder/file.png" @linkedFileData = {provider: 'url'} + @newFile = _id: "new-file-id" + @folder_id = "123ksajdn" @folder = _id: @folder_id @folderName = "folder" @@ -107,7 +109,7 @@ describe "EditorController", -> describe 'upsertFile', -> beforeEach -> - @ProjectEntityUpdateHandler.upsertFile = sinon.stub().yields(null, @file, false) + @ProjectEntityUpdateHandler.upsertFile = sinon.stub().yields(null, @newFile, false, @file) @EditorController.upsertFile @project_id, @folder_id, @fileName, @fsPath, @linkedFileData, @source, @user_id, @callback it 'upserts the file using the project entity handler', -> @@ -116,7 +118,7 @@ describe "EditorController", -> .should.equal true it 'returns the file', -> - @callback.calledWith(null, @file).should.equal true + @callback.calledWith(null, @newFile).should.equal true describe 'file does not exist', -> beforeEach -> @@ -171,7 +173,7 @@ describe "EditorController", -> beforeEach -> @filePath = '/folder/file' - @ProjectEntityUpdateHandler.upsertFileWithPath = sinon.stub().yields(null, @file, false, [], @folder) + @ProjectEntityUpdateHandler.upsertFileWithPath = sinon.stub().yields(null, @newFile, false, @file, [], @folder) @EditorController.upsertFileWithPath @project_id, @filePath, @fsPath, @linkedFileData, @source, @user_id, @callback it 'upserts the file using the project entity handler', -> @@ -181,7 +183,7 @@ describe "EditorController", -> describe 'file does not exist', -> beforeEach -> - @ProjectEntityUpdateHandler.upsertFileWithPath = sinon.stub().yields(null, @file, true, [], @folder) + @ProjectEntityUpdateHandler.upsertFileWithPath = sinon.stub().yields(null, @file, true, undefined, [], @folder) @EditorController.upsertFileWithPath @project_id, @filePath, @fsPath, @linkedFileData, @source, @user_id, @callback it 'should send the update for the file out to users in the project', -> @@ -195,7 +197,7 @@ describe "EditorController", -> @folderA = { _id: 2, parentFolder_id: 1} @folderB = { _id: 3, parentFolder_id: 2} ] - @ProjectEntityUpdateHandler.upsertFileWithPath = sinon.stub().yields(null, @file, true, folders, @folderB) + @ProjectEntityUpdateHandler.upsertFileWithPath = sinon.stub().yields(null, @file, true, undefined, folders, @folderB) @EditorController.upsertFileWithPath @project_id, @filePath, @fsPath, @linkedFileData, @source, @user_id, @callback it 'should send the update for each folder to users in the project', -> diff --git a/services/web/test/unit/coffee/Project/ProjectEntityMongoUpdateHandlerTests.coffee b/services/web/test/unit/coffee/Project/ProjectEntityMongoUpdateHandlerTests.coffee index e05d5bc38a..57911bd7d8 100644 --- a/services/web/test/unit/coffee/Project/ProjectEntityMongoUpdateHandlerTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectEntityMongoUpdateHandlerTests.coffee @@ -93,33 +93,50 @@ describe 'ProjectEntityMongoUpdateHandler', -> .calledWith(@project, folder_id, @file, 'file', @callback) .should.equal true - describe 'replaceFile', -> + describe 'replaceFileWithNew', -> beforeEach -> @file = _id: file_id @path = mongo: 'file.png' - @linkedFileData = {provider: 'url'} + @newFile = _id: 'new-file-id' + @newFile.linkedFileData = @linkedFileData = {provider: 'url'} @ProjectLocator.findElement = sinon.stub().yields(null, @file, @path) @ProjectModel.update = sinon.stub().yields() - @subject.replaceFile project_id, file_id, @linkedFileData, @callback + @subject.replaceFileWithNew project_id, file_id, @newFile, @callback it 'gets the project', -> @ProjectGetter.getProjectWithoutLock .calledWith(project_id, {rootFolder:true, name: true}) .should.equal true - it 'finds the element', -> + it 'finds the existing element', -> @ProjectLocator.findElement .calledWith({ @project, element_id: file_id, type: 'file' }) .should.equal true - it 'increments the rev and sets the created_at', -> + it 'inserts a deletedFile reference for the old file', -> + @ProjectModel.update + .calledWith({ _id: project_id }, + { + $push: { + deletedFiles: { + _id: file_id + name: @file.name + linkedFileData: @file.linkedFileData + deletedAt: new Date() + } + } + } + ) + .should.equal true + + it 'increments the project version and sets the rev and created_at', -> @ProjectModel.update .calledWith( { _id: project_id }, { - '$inc': { 'file.png.rev': 1, 'version': 1 } - '$set': { 'file.png.created': new Date(), 'file.png.linkedFileData': @linkedFileData } + '$inc': { 'version': 1 } + '$set': { 'file.png._id': @newFile._id, 'file.png.created': new Date(), 'file.png.linkedFileData': @linkedFileData, 'file.png.rev': 1 } } {} ) @@ -596,3 +613,29 @@ describe 'ProjectEntityMongoUpdateHandler', -> folder = name: 'folder_name' @subject._checkValidMove @project, 'folder', folder, fileSystem: '/foo', @destFolder._id, (err) => expect(err).to.deep.equal new Errors.InvalidNameError("destination folder is a child folder of me") + + describe "_insertDeletedDocReference", -> + beforeEach -> + @doc = + _id: ObjectId() + name: "test.tex" + @callback = sinon.stub() + @ProjectModel.update = sinon.stub().yields() + @subject._insertDeletedDocReference project_id, @doc, @callback + + it "should insert the doc into deletedDocs", -> + @ProjectModel.update + .calledWith({ + _id: project_id + }, { + $push: { + deletedDocs: { + _id: @doc._id + name: @doc.name + } + } + }) + .should.equal true + + it "should call the callback", -> + @callback.called.should.equal true \ No newline at end of file diff --git a/services/web/test/unit/coffee/Project/ProjectEntityUpdateHandlerTests.coffee b/services/web/test/unit/coffee/Project/ProjectEntityUpdateHandlerTests.coffee index 4d038d9683..788a32cd3b 100644 --- a/services/web/test/unit/coffee/Project/ProjectEntityUpdateHandlerTests.coffee +++ b/services/web/test/unit/coffee/Project/ProjectEntityUpdateHandlerTests.coffee @@ -14,6 +14,7 @@ describe 'ProjectEntityUpdateHandler', -> file_id = "4eecaffcbffa66588e000009" folder_id = "4eecaffcbffa66588e000008" rootFolderId = "4eecaffcbffa66588e000007" + new_file_id = "4eecaffcbffa66588e000099" userId = 1234 beforeEach -> @@ -31,7 +32,11 @@ describe 'ProjectEntityUpdateHandler', -> @FileModel = class File constructor:(options)-> {@name} = options - @._id = file_id + # use a new id for replacement files + if @name is 'dummy-upload-filename' + @._id = new_file_id + else + @._id = file_id @rev = 0 @docName = "doc-name" @@ -274,14 +279,33 @@ describe 'ProjectEntityUpdateHandler', -> beforeEach -> @path = "/path/to/file" - @newFile = _id: file_id - @ProjectEntityUpdateHandler.addFileWithoutUpdatingHistory = - withoutLock: sinon.stub().yields(null, @newFile, folder_id, @path, @fileUrl) - @ProjectEntityUpdateHandler.addFile project_id, folder_id, @docName, @fileSystemPath, @linkedFileData, userId, @callback + @newFile = {_id: file_id, rev: 0, name: @fileName} + @TpdsUpdateSender.addFile = sinon.stub().yields() + @ProjectEntityMongoUpdateHandler.addFile = sinon.stub().yields(null, {path: fileSystem: @path}, @project) + @ProjectEntityUpdateHandler.addFile project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId, @callback - it "creates the doc without history", () -> - @ProjectEntityUpdateHandler.addFileWithoutUpdatingHistory.withoutLock - .calledWith(project_id, folder_id, @docName, @fileSystemPath, @linkedFileData, userId) + it "updates the file in the filestore", () -> + @FileStoreHandler.uploadFileFromDisk + .calledWith(project_id, file_id, @fileSystemPath) + .should.equal true + + it "updates the file in mongo", () -> + fileMatcher = sinon.match (file) => + file.name == @fileName + + @ProjectEntityMongoUpdateHandler.addFile + .calledWithMatch(project_id, folder_id, fileMatcher) + .should.equal true + + it "notifies the tpds", () -> + @TpdsUpdateSender.addFile + .calledWith({ + project_id: project_id + project_name: @project.name + file_id: file_id + rev: 0 + path: @path + }) .should.equal true it "sends the change in project structure to the doc updater", () -> @@ -296,21 +320,26 @@ describe 'ProjectEntityUpdateHandler', -> describe 'replaceFile', -> beforeEach -> - @newFile = _id: file_id, rev: 0 + # replacement file now creates a new file object + @newFileUrl = "new-file-url" + @FileStoreHandler.uploadFileFromDisk = sinon.stub().yields(null, @newFileUrl) + + @newFile = _id: new_file_id, name: "dummy-upload-filename", rev: 0 + @oldFile = _id: file_id @path = "/path/to/file" @project = _id: project_id, name: 'some project' - @ProjectEntityMongoUpdateHandler.replaceFile = sinon.stub().yields(null, @newFile, @project, fileSystem: @path) - + @ProjectEntityMongoUpdateHandler._insertDeletedFileReference = sinon.stub().yields() + @ProjectEntityMongoUpdateHandler.replaceFileWithNew = sinon.stub().yields(null, @oldFile, @project, fileSystem: @path) @ProjectEntityUpdateHandler.replaceFile project_id, file_id, @fileSystemPath, @linkedFileData, userId, @callback it 'uploads a new version of the file', -> @FileStoreHandler.uploadFileFromDisk - .calledWith(project_id, file_id, @fileSystemPath) + .calledWith(project_id, new_file_id, @fileSystemPath) .should.equal true it 'replaces the file in mongo', -> - @ProjectEntityMongoUpdateHandler.replaceFile - .calledWith(project_id, file_id, @linkedFileData) + @ProjectEntityMongoUpdateHandler.replaceFileWithNew + .calledWith(project_id, file_id, @newFile) .should.equal true it 'notifies the tpds', -> @@ -318,20 +347,24 @@ describe 'ProjectEntityUpdateHandler', -> .calledWith({ project_id: project_id project_name: @project.name - file_id: file_id + file_id: new_file_id rev: @newFile.rev + 1 path: @path }) .should.equal true it 'updates the project structure in the doc updater', -> + oldFiles = [ + file: @oldFile + path: @path + ] newFiles = [ file: @newFile path: @path - url: @fileUrl + url: @newFileUrl ] @DocumentUpdaterHandler.updateProjectStructure - .calledWith(project_id, userId, {newFiles}) + .calledWith(project_id, userId, {oldFiles, newFiles}) .should.equal true describe 'addDocWithoutUpdatingHistory', -> @@ -490,12 +523,12 @@ describe 'ProjectEntityUpdateHandler', -> @existingFile = _id: file_id, name: @fileName @folder = _id: folder_id, fileRefs: [@existingFile] @ProjectLocator.findElement = sinon.stub().yields(null, @folder) - @ProjectEntityUpdateHandler.replaceFile = withoutLock: sinon.stub().yields(null, @newFile) + @ProjectEntityUpdateHandler.replaceFile = mainTask: sinon.stub().yields(null, @newFile) @ProjectEntityUpdateHandler.upsertFile project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId, @callback it 'replaces the file', -> - @ProjectEntityUpdateHandler.replaceFile.withoutLock + @ProjectEntityUpdateHandler.replaceFile.mainTask .calledWith(project_id, file_id, @fileSystemPath, @linkedFileData, userId) .should.equal true @@ -507,7 +540,7 @@ describe 'ProjectEntityUpdateHandler', -> @folder = _id: folder_id, fileRefs: [] @newFile = _id: file_id @ProjectLocator.findElement = sinon.stub().yields(null, @folder) - @ProjectEntityUpdateHandler.addFile = withoutLock: sinon.stub().yields(null, @newFile) + @ProjectEntityUpdateHandler.addFile = mainTask: sinon.stub().yields(null, @newFile) @ProjectEntityUpdateHandler.upsertFile project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId, @callback @@ -517,7 +550,7 @@ describe 'ProjectEntityUpdateHandler', -> .should.equal true it 'adds the file', -> - @ProjectEntityUpdateHandler.addFile.withoutLock + @ProjectEntityUpdateHandler.addFile.mainTask .calledWith(project_id, folder_id, @fileName, @fileSystemPath, @linkedFileData, userId) .should.equal true @@ -563,7 +596,7 @@ describe 'ProjectEntityUpdateHandler', -> @ProjectEntityUpdateHandler.mkdirp = withoutLock: sinon.stub().yields(null, @newFolders, @folder) @ProjectEntityUpdateHandler.upsertFile = - withoutLock: sinon.stub().yields(null, @file, @isNewFile) + mainTask: sinon.stub().yields(null, @file, @isNewFile) @ProjectEntityUpdateHandler.upsertFileWithPath project_id, @path, @fileSystemPath, @linkedFileData, userId, @callback @@ -573,13 +606,13 @@ describe 'ProjectEntityUpdateHandler', -> .should.equal true it 'upserts the file', -> - @ProjectEntityUpdateHandler.upsertFile.withoutLock + @ProjectEntityUpdateHandler.upsertFile.mainTask .calledWith(project_id, @folder._id, 'file.png', @fileSystemPath, @linkedFileData, userId) .should.equal true it 'calls the callback', -> @callback - .calledWith(null, @file, @isNewFile, @newFolders, @folder) + .calledWith(null, @file, @isNewFile, undefined, @newFolders, @folder) .should.equal true describe 'deleteEntity', -> @@ -798,6 +831,7 @@ describe 'ProjectEntityUpdateHandler', -> @FileStoreHandler.deleteFile = sinon.stub().yields() @DocumentUpdaterHandler.deleteDoc = sinon.stub().yields() @ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields() + @ProjectEntityMongoUpdateHandler._insertDeletedFileReference = sinon.stub().yields() describe "a file", -> beforeEach (done) -> @@ -805,8 +839,13 @@ describe 'ProjectEntityUpdateHandler', -> @entity = _id: @entity_id @ProjectEntityUpdateHandler._cleanUpEntity @project, @entity, 'file', @path, userId, done - it "should delete the file from FileStoreHandler", -> - @FileStoreHandler.deleteFile.calledWith(project_id, @entity_id).should.equal true + it "should insert the file into the deletedFiles array", -> + @ProjectEntityMongoUpdateHandler._insertDeletedFileReference + .calledWith(@project._id, @entity) + .should.equal true + + it "should not delete the file from FileStoreHandler", -> + @FileStoreHandler.deleteFile.calledWith(project_id, @entity_id).should.equal false it "should not attempt to delete from the document updater", -> @DocumentUpdaterHandler.deleteDoc.called.should.equal false @@ -871,7 +910,7 @@ describe 'ProjectEntityUpdateHandler', -> name: "test.tex" @path = "/path/to/doc" @ProjectEntityUpdateHandler.unsetRootDoc = sinon.stub().yields() - @ProjectEntityUpdateHandler._insertDeletedDocReference = sinon.stub().yields() + @ProjectEntityMongoUpdateHandler._insertDeletedDocReference = sinon.stub().yields() @DocumentUpdaterHandler.deleteDoc = sinon.stub().yields() @DocstoreManager.deleteDoc = sinon.stub().yields() @callback = sinon.stub() @@ -891,7 +930,7 @@ describe 'ProjectEntityUpdateHandler', -> .calledWith(project_id, @doc._id.toString()) it "should insert the doc into the deletedDocs array", -> - @ProjectEntityUpdateHandler._insertDeletedDocReference + @ProjectEntityMongoUpdateHandler._insertDeletedDocReference .calledWith(@project._id, @doc) .should.equal true @@ -920,28 +959,4 @@ describe 'ProjectEntityUpdateHandler', -> it "should call the callback", -> @callback.called.should.equal true - describe "_insertDeletedDocReference", -> - beforeEach -> - @doc = - _id: ObjectId() - name: "test.tex" - @callback = sinon.stub() - @ProjectModel.update = sinon.stub().yields() - @ProjectEntityUpdateHandler._insertDeletedDocReference project_id, @doc, @callback - - it "should insert the doc into deletedDocs", -> - @ProjectModel.update - .calledWith({ - _id: project_id - }, { - $push: { - deletedDocs: { - _id: @doc._id - name: @doc.name - } - } - }) - .should.equal true - - it "should call the callback", -> - @callback.called.should.equal true + diff --git a/services/web/webpack.config.js b/services/web/webpack.config.js index 50dc47efb4..da0dc8257e 100644 --- a/services/web/webpack.config.js +++ b/services/web/webpack.config.js @@ -52,6 +52,7 @@ module.exports = { loader: 'babel-loader', options: { presets: [ + 'react', ['env', { modules: false }] ], // Configure babel-loader to cache compiled output so that subsequent @@ -64,4 +65,5 @@ module.exports = { // TODO // plugins: {} -} \ No newline at end of file +} + diff --git a/services/web/webpack.config.prod.js b/services/web/webpack.config.prod.js index 70287d543e..530af759fe 100644 --- a/services/web/webpack.config.prod.js +++ b/services/web/webpack.config.prod.js @@ -5,8 +5,7 @@ const merge = require('webpack-merge') const base = require('./webpack.config') module.exports = merge(base, { - // Enable a full source map. - devtool: 'source-map', + devtool: false, output: { // Override output path to minjs dir