diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index bdb0cab..0000000 --- a/.gitattributes +++ /dev/null @@ -1,17 +0,0 @@ -# Auto detect text files and perform LF normalization -* text=auto - -# Custom for Visual Studio -*.cs diff=csharp - -# Standard to msysgit -*.doc diff=astextplain -*.DOC diff=astextplain -*.docx diff=astextplain -*.DOCX diff=astextplain -*.dot diff=astextplain -*.DOT diff=astextplain -*.pdf diff=astextplain -*.PDF diff=astextplain -*.rtf diff=astextplain -*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore index cd2946a..09eac40 100644 Binary files a/.gitignore and b/.gitignore differ diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..702634f --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,157 @@ +module.exports = function (grunt) { + grunt.loadNpmTasks('grunt-contrib-copy'); + grunt.loadNpmTasks('grunt-contrib-uglify'); + grunt.loadNpmTasks('grunt-search'); + grunt.loadNpmTasks('grunt-comment-toggler'); + + var packager = require('electron-packager'); + + const BUILD_DIR = 'build/'; + const DIST_DIR = 'dist/'; + const APP_NAME = 'APP Name'; + const PLATFORM = 'all'; + const ARCH = 'all'; + const ELECTRON_VERSION = '1.2.3'; + const USE_ASAR = true; + + var toggleCommentsFiles = {files:{}}; + toggleCommentsFiles.files[BUILD_DIR + 'index.html'] = 'index.html'; + + // Project configuration. + grunt.initConfig( + { + pkg: grunt.file.readJSON('package.json'), + uglify: { + production: { + files: [ + { + src : 'scripts/**/*.js', + dest : BUILD_DIR + '/scripts/app.angular.min.js' + }, + { + expand: true, + src: BUILD_DIR + 'main.js' + } + ] + } + }, + copy: { + electron_app: { + files: [ + { + expand: true, + src: ['index.html', 'main.js', 'package.json'], + dest: BUILD_DIR + } + ] + }, + angular_app_html: { + files: [ + { + expand: true, + src: ['scripts/**/*.html'], + dest: BUILD_DIR + } + ] + } + }, + search: { + node_modules_dependencies: { + files: { + src: ['index.html'] + }, + options: { + searchString: /="node_modules\/.*"/g, + logFormat: "custom", + onMatch: function (match) { + var filePath = match.match.substring( + 2, + match.match.length - 1 + ); + grunt.file.copy(filePath, BUILD_DIR + filePath); + console.log('Found dependency: ' + filePath) + }, + customLogFormatCallback: function (params) { + } + } + } + }, + toggleComments: { + customOptions: { + padding: 1, + removeCommands: false + }, + files: toggleCommentsFiles + } + } + ); + + grunt.registerTask( + 'build', + [ + 'clean', + 'copy:electron_app', + 'copy:angular_app_html', + 'toggleComments', + 'search:node_modules_dependencies', + 'uglify:production', + 'package', + 'fix_default_app' + ] + ); + + // Clean the build directory + grunt.registerTask( + 'clean', + function () { + if (grunt.file.isDir(BUILD_DIR)) { + grunt.file.delete(BUILD_DIR, {force: true}); + } + } + ); + + grunt.registerTask( + 'package', + function () { + var done = this.async(); + packager( + { + dir: BUILD_DIR, + out: DIST_DIR, + name: APP_NAME, + platform: PLATFORM, + arch: ARCH, + version: ELECTRON_VERSION, + asar: USE_ASAR + }, + function (err) { + if (err) { + grunt.warn(err); + return; + } + + done(); + } + ); + } + ); + + // Used as a temporary fix for: + // https://github.com/maxogden/electron-packager/issues/49 + grunt.registerTask( + 'fix_default_app', + function () { + + var default_apps = grunt.file.expand( + DIST_DIR + APP_NAME + '*/resources/default_app' + ); + + default_apps.forEach( + function (folder) { + console.log('Removing ' + folder); + grunt.file.delete(folder); + } + ); + } + ); +}; \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c29c75a --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# FTP file manager built on Electron & Node + +_There are still a few issues_ + +## Building & running it locally: +- Clone the repo. +- cd to the directory +- make sure electron is installed `npm install electron` +- install and run with `npm install && npm start` + +## Packaging it (.app for mac, .exe for windows) +- Install [Electron Packager Interactive](https://github.com/Urucas/electron-packager-interactive) with `npm install -g electron-packager-interactive` +- run `epi` +- Follow steps + - Icon is `./icon.ico' diff --git a/app/.DS_Store b/app/.DS_Store new file mode 100644 index 0000000..038510b Binary files /dev/null and b/app/.DS_Store differ diff --git a/app/app.js b/app/app.js new file mode 100644 index 0000000..b37dc39 --- /dev/null +++ b/app/app.js @@ -0,0 +1,36 @@ +var dirTree = require('directory-tree'); +var JSFtp = require("jsftp"); +JSFtp = require('jsftp-rmr')(JSFtp); +var storage = require('electron-json-storage'); + +var app = angular.module('app', ['ngRoute', 'ngMaterial', 'ngAnimate', 'ngDraggable']); +app.config(['$routeProvider', function ($routeProvider) { + $routeProvider + .when('/', { + templateUrl : './app/pages/main.html', + controller : 'homeCtrl' + }) + .otherwise({redirectTo: '/'}); +}]); + + +app.filter('filesize', function() { + return function(bytes, precision) { + if (bytes == 0 || isNaN(parseFloat(bytes)) || !isFinite(bytes)) return '-'; + if (typeof precision === 'undefined') precision = 1; + var units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'], + number = Math.floor(Math.log(bytes) / Math.log(1024)); + return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number]; + } +}); + +app.directive('showFocus', function($timeout) { + return function(scope, element, attrs) { + scope.$watch(attrs.showFocus, + function (newValue) { + $timeout(function() { + newValue && element.focus(); + }); + },true); + }; +}); diff --git a/app/controllers/base.js b/app/controllers/base.js new file mode 100644 index 0000000..985446b --- /dev/null +++ b/app/controllers/base.js @@ -0,0 +1,754 @@ +app.controller('homeCtrl', ['$scope', '$timeout', '$filter', '$interval', 'ngDraggable', '$http', +function($scope, $timeout, $filter, $interval, ngDraggable, $http){ + + // Analytics setup (https://github.com/peaksandpies/universal-analytics) + var ua = require('universal-analytics'); + var visitor = ua('UA-88669012-1'); + visitor.pageview("/").send() + console.log("Tracking " + visitor); + + // Initialize Variables + $scope.fs = require('fs'); + + // Get computer OS + var os = require('os'); + $scope.isWindowsOS = false; + if(os.platform() == 'win32'){$scope.isWindowsOS = true;} + + // $scope.remote = require('electron').remote; + // $scope.dialog = remote.require('dialog'); + var remote = require('electron').remote; + var dialog = require('electron').dialog; + + $scope.path = "."; + $scope.emptyMessage = "Loading..."; + $scope.fullConsole = false; + $scope.showingMenu = true; + $scope.consoleMessage = "Click to expand console."; + + $scope.editingFavorites = false; + + $scope.fileSelected = false; + $scope.saveFavorite = false; + + + var shell = require('electron').shell; + var pjson = require('./package.json'); + $scope.appVersion = pjson.version; + // console.log(require('electron').remote.app.getVersion()); + + // Get notifications from ffftp.site + $http({method: 'GET', url: 'http://www.ffftp.site/appupdate.json' + }).success(function(data){ + $timeout(function() { + console.log(data); + if(data.version != $scope.appVersion.toString()){ + $scope.showUpdate = true; + } + }, 0); + }).error(function(){ + console.log("Error getting notification"); + }); + $scope.updateApp = function(){ + shell.openExternal('http://ffftp.site/download/'+$scope.appVersion); + } + + + + + // Load Favorites + $scope.favorites = []; + storage.has('favorites', function(error, hasKey) { + if (error) throw error; + if (hasKey) { + storage.get('favorites', function(error, data) { + if (error) throw error; + $timeout(function() { + $scope.favorites = data; + console.log("FAVORITES"); + console.log(data); + }, 0); + + }); + }else{ + console.log("No favs"); + } + }); + + // On favorite click + $scope.loadFavorite = function(index){ + $scope.ftpHost = $scope.favorites[index].host; + $scope.ftpPort = $scope.favorites[index].port; + $scope.ftpUsername = $scope.favorites[index].user; + $scope.ftpPassword = $scope.favorites[index].pass; + $scope.favoriteName = $scope.favorites[index].name; + $scope.connect(); + } + $scope.deleteFavorite = function(index){ + $scope.favorites.splice(index, 1); + $scope.saveFavoritesToStorage(); + } + + + + + // + // Connect to ftp + // + $scope.connect = function(){ + $scope.showingMenu = false; + + if($scope.saveFavorite){ + $scope.newFavorite = { + name: $scope.favoriteName, + host: $scope.ftpHost, + port: $scope.ftpPort, + user: $scope.ftpUsername, + pass: $scope.ftpPassword + } + $scope.favorites.push($scope.newFavorite); + $scope.saveFavoritesToStorage(); + } + + $scope.saveFavorite = false; + + + // Connect + $scope.ftp = new JSFtp({ + host: $scope.ftpHost, + port: $scope.ftpPort, + user: $scope.ftpUsername, + pass: $scope.ftpPassword + }); + $scope.console("white", "Connected to " + $scope.ftpHost); + + // Start Scripts + $scope.changeDir(); + $scope.splitPath(); + } + + $scope.saveFavoritesToStorage = function(){ + storage.set('favorites', $scope.favorites, function(error) { + if (error) throw error; + }); + } + $scope.deleteFavs = function(){ + storage.clear(function(error) { + if (error) throw error; + }); + } + + + + + // + // Change directory + // + $scope.changeDir = function(){ + $scope.searchFiles = ""; + if($scope.showCancelOperation){ + return; + }else{ + $scope.fileSelected = false; + $scope.ftp.ls($scope.path, function(err, res) { + $timeout(function() { + $scope.files = res.sort(function(a, b){ + //type: + // 1 - dir + // 0 - file + // return b.type - a.type // works too + return parseInt(b.type) - parseInt(a.type) + }) + $scope.splitPath(); + $scope.emptyMessage = "There's nothin' here"; + if($scope.path != '.'){ + $scope.console("white", "Navigated to " + $scope.path); + } + }, 0); + }); + } + } + + + + // + // Go into a directory (double click folder); + // + $scope.intoDir = function(dir){ + if($scope.selectedFileType == 0){ // If file, do nothing but select + return; + }else{ + $scope.emptyMessage = "Loading..."; + $scope.path = $scope.path + "/" + dir; + $scope.changeDir(); + } + } + + // Go up a directory - button on nav + $scope.upDir = function(){ + $scope.path = $scope.path.substring(0, $scope.path.lastIndexOf("/")); + $scope.changeDir(); + } + + + // + // Click a breadcrumb to go up multiple directories + // + $scope.breadCrumb = function(index){ + $scope.path = "."; + for (var i = 1; i <= index; i++) { + $scope.path = $scope.path + "/" + $scope.pathArray[i]; + } + console.log($scope.path); + $scope.changeDir(); + } + + // + // Split paths for use in breadcrumbs + // + $scope.splitPath = function(){ + $scope.pathArray = new Array(); + $scope.pathArray = $scope.path.split("/"); + } + + + // + // Select a file to modify + // + $scope.selectTimer = function(){ + $scope.fileToFile = true; + $timeout(function() { + $scope.fileToFile = false; + }, 200); + } + $scope.selectFile = function(name, filetype){ + $scope.fileSelected = true; + $scope.selectedFileName = name; + $scope.selectedFileType = filetype; + $scope.selectedFilePath = $scope.path + "/" + name; + console.log($scope.selectedFileName); + } + $scope.clearSelected = function(){ + $timeout(function() { + if(!$scope.fileToFile){ + $scope.fileSelected = false; + } + }, 200); + } + + + // + // Create a new folder + // + $scope.showingNewFolder = false; + $scope.newFolder = function(){ + $scope.showingNewFolder = false; + $scope.ftp.raw('mkd', $scope.path + "/" + $scope.newFolderName, function(err, data) { + $scope.changeDir(); + $scope.newFolderName = ""; + if(err){$scope.console("red", err)} + else{$scope.console("white", data.text)} + }); + } + + + // + // Delete a file or folder depending on file type + // + $scope.deleteFile = function(){ + console.log("TYPE: " + $scope.selectedFileType); + console.log("NAME: " + $scope.selectedFileName); + console.log("PATH: " + $scope.path); + $scope.showingConfirmDelete = false; + console.log("DELETING " + $scope.path + "/" + $scope.selectedFileName); + if($scope.selectedFileType == 0){ // 0 is file + $scope.ftp.raw('dele', $scope.path + "/" + $scope.selectedFileName, function(err, data) { + if(err){return $scope.console("red", err);} + $scope.changeDir(); + $scope.console("green", data.text); + }); + }else if($scope.selectedFileType == 1){ // Everything else is folder + $scope.ftp.rmr($scope.path + "/" + $scope.selectedFileName, function (err) { + $scope.ftp.raw('rmd', $scope.path + "/" + $scope.selectedFileName, function(err, data) { + if(err){return $scope.console("red", err);} + $scope.changeDir(); + $scope.console("green", data.text); + }); + }); + } + } + + + // + // Rename a file or folder + // + $scope.renameFile = function(){ + if(!$scope.showingRename){ + $scope.fileRenameInput = $scope.selectedFileName; + $scope.showingRename = true; + }else{ + $scope.ftp.rename($scope.path + "/" + $scope.selectedFileName, $scope.path + "/" + $scope.fileRenameInput, function(err, res) { + if (!err){ + $scope.showingRename = false; + $scope.console("green", "Renamed " + $scope.selectedFileName + " to " + $scope.fileRenameInput); + $scope.changeDir(); + }else{ + $scope.console("red", err); + } + }); + } + } + + + // + // Download a file + // + $scope.chooseDownloadDirectory = function(){ + document.getElementById('chooseDownloadDirectory').click() + } + $scope.saveDownloadPath = function(){ + $scope.downloadPath = document.getElementById('chooseDownloadDirectory').files[0].path; + console.log($scope.downloadPath); + $scope.downloadFiles(); + } + $scope.downloadFiles = function(){ + console.log("downloadFiles"); + + if($scope.selectedFileType == 0){ // If file, download right away + $scope.saveFileToDisk($scope.selectedFilePath, $scope.selectedFileName); + }else if($scope.selectedFileType == 1){ // if folder, index folders and files + $scope.foldersToCreate = []; + $scope.filesToDownload = []; + $scope.getDownloadTree($scope.selectedFilePath); + $scope.downloadTime = 0; + $scope.showCancelOperation = true; + $scope.downloadInterval = $interval(function () {$scope.downloadTime++;}, 1000); // Download Timer + $scope.gettingDownloadReady = true; + $scope.watchDownloadProcess(); + }else{ // else unknown file type + $scope.console("red", "Unable to download file " + $scope.selectedFileName + ". Unknown file type.") + } + } + + // Checks every 400ms if download tree is still processing + $scope.watchDownloadProcess = function(){ + $timeout(function() { + if($scope.gettingDownloadReady){$scope.watchDownloadProcess;} + else{$scope.processFiles();} + }, 400); + } + + // Get download tree loops through all folders and files, and adds them to arrays. + // Current directory folders are added to the tempfolders array + $scope.getDownloadTree = function(path){ + $scope.tempFolders = []; + $scope.tempPath = path; + $scope.gettingDownloadReady = true; // Reset because still working + + $scope.ftp.ls(path, function(err, res) { + console.log(res); + for (var i = 0, item; item = res[i]; i++) { + if(item.type==1){ // if folder, push to full array and temp + $scope.foldersToCreate.push({"path": path, "name": item.name}); + $scope.tempFolders.push({"path": path, "name": item.name}); + }else if(item.type==0){ // if file, push to file array + $scope.filesToDownload.push({"path": path, "name": item.name}); + } + } + $scope.gettingDownloadReady = false; + for (var x = 0, folder; folder = $scope.tempFolders[x]; x++) { // for each folder, getDownloadTree again and index those. Same process + console.log("FOLDER PATH: " + folder.path); + $scope.getDownloadTree(folder.path + "/" + folder.name); + } + }); + } + + // Once getDownloadTree is finished, this is called + $scope.processFiles = function(){ + + //First create base folder + if($scope.isWindowsOS){ + $scope.fs.mkdir($scope.downloadPath + "\\" + $scope.selectedFileName); + }else{ + $scope.fs.mkdir($scope.downloadPath + "/" + $scope.selectedFileName); + } + //Then create all folders within + for (var i = 0, folder; folder = $scope.foldersToCreate[i]; i++) { // Create all empty folders + if($scope.isWindowsOS){ + var newfolderpath = folder.path + "\\" + folder.name; + $scope.fs.mkdir($scope.downloadPath + "\\" + $scope.selectedFileName + newfolderpath.replace($scope.selectedFilePath, "")); + }else{ + var newfolderpath = folder.path + "/" + folder.name; + $scope.fs.mkdir($scope.downloadPath + "/" + $scope.selectedFileName + newfolderpath.replace($scope.selectedFilePath, "")); + } + } + + + // Then begin downloading files individually + $scope.downloadFileZero = 0; + $scope.saveAllFilesToDisk(); + } + + $scope.saveAllFilesToDisk = function(){ + if($scope.filesToDownload[$scope.downloadFileZero]){ + var filepath = $scope.filesToDownload[$scope.downloadFileZero].path; + var filename = $scope.filesToDownload[$scope.downloadFileZero].name; + var absoluteFilePath = filepath.substring(filepath.indexOf("/") + 1) + "/" + filename; + + var from = filepath + "/" + filename; + + if($scope.isWindowsOS){ + var newfilepath = filepath + "\\" + filename; + var to = $scope.downloadPath + "\\" + $scope.selectedFileName + newfilepath.replace($scope.selectedFilePath, ""); + $scope.console("white", "Downloading " + filename + " to " + $scope.downloadPath + "\\" + $scope.selectedFileName + newfilepath.replace($scope.selectedFilePath, "")); + }else{ + var newfilepath = filepath + "/" + filename; + var to = $scope.downloadPath + "/" + $scope.selectedFileName + newfilepath.replace($scope.selectedFilePath, ""); + $scope.console("white", "Downloading " + filename + " to " + $scope.downloadPath + "/" + $scope.selectedFileName + newfilepath.replace($scope.selectedFilePath, "")); + } + + $scope.ftp.get(from, to, function(hadErr) { + if (hadErr){ + $scope.console("red", "Error downloading " + filename + "... " + hadErr); + }else{ + $scope.console("white", "Done."); + } + $scope.downloadFileZero++; + $scope.changeDir(); + $scope.saveAllFilesToDisk(); // do it again until all files are downloaded + }); + + }else{ // once finished + $timeout(function() { + $scope.changeDir(); + $interval.cancel($scope.downloadInterval); + $scope.showCancelOperation = false; + $scope.console("blue", "Downloaded " + $scope.filesToDownload.length + " files in " + $scope.foldersToCreate.length + " directories in " + $scope.downloadTime + " seconds."); + }, 200); + } + } + + + // Download file if single file - not folder + $scope.saveFileToDisk = function(filepath, filename){ + var from = filepath; + if($scope.isWindowsOS){ + var to = $scope.downloadPath + "\\" + filename; + }else{ + var to = $scope.downloadPath + "/" + filename; + } + console.log("DOWNLOADING: " + from + " TO: " + to); + $scope.ftp.get(from, to, function(hadErr) { + if (hadErr){ + $scope.console("red", "Error downloading " + filename); + }else{ + $scope.console("green", "Successfully downloaded " + filename); + } + }); + } + + + + + // + // File Uploading + // + document.ondragover = document.ondrop = (ev) => { + ev.preventDefault() + } + document.body.ondrop = (ev) => { + $scope.dragged = ev.dataTransfer.files; + + $scope.console("white", "Getting file tree...") + $scope.folderTree = []; + $scope.baseUploadPath = $scope.path; + + $scope.foldersArray = []; + $scope.filesArray = []; + + $scope.uploadTime = 0; + $scope.uploadInterval = $interval(function () {$scope.uploadTime++;}, 1000); + $scope.showCancelOperation = true; + + + for (var i = 0, f; f = $scope.dragged[i]; i++) { + $scope.folderTree.push(dirTree($scope.dragged[i].path)); + } + + if($scope.isWindowsOS){ + $scope.baselocalpath = $scope.dragged[0].path.substring(0, $scope.dragged[0].path.lastIndexOf('\\')); + }else{ + $scope.baselocalpath = $scope.dragged[0].path.substring(0, $scope.dragged[0].path.lastIndexOf('/')); + } + + $scope.gatherFiles($scope.folderTree); + $timeout(function() { + $scope.uploadEverything(); + }, 1000); + + + ev.preventDefault(); + } + + $scope.gatherFiles = function(tree){ + if(!tree.length){ + console.log("No folders") + } + $scope.nestedTree = []; + for (var i = 0, f; f = tree[i]; i++) { + if(tree[i].extension){ // if file + $scope.filesArray.push({"name": tree[i].name, "path": tree[i].path}); + }else{ // if folder + $scope.foldersArray.push({"name": tree[i].name, "path": tree[i].path}); + if(tree[i].children.length){ + console.log("HAS CHILDREN: " + tree[i].name) + for (var x = 0, y; y = tree[i].children[x]; x++) { + $scope.nestedTree.push(dirTree(y.path)); + } + $scope.gatherFiles($scope.nestedTree); + } + } + } + } + + $scope.uploadEverything = function(){ + console.log($scope.foldersArray); + console.log($scope.filesArray); + $scope.console("white", "Uploading " + $scope.foldersArray.length + " folders and " + $scope.filesArray.length + " files..."); + $scope.filezero = 0; + $scope.folderzero = 0; + $scope.mkDirs(); + } + $scope.mkDirs = function(){ + if($scope.foldersArray[$scope.folderzero]){ + var localpath = $scope.foldersArray[$scope.folderzero].path; + var uploadpath = $scope.baseUploadPath; + + $scope.dirToCreate = uploadpath + localpath.replace($scope.baselocalpath, "").replace(/\\/g,"/"); + $scope.console("white", "Creating folder " + $scope.dirToCreate + "...") + + $scope.ftp.raw('mkd', $scope.dirToCreate, function(err, data) { + // $scope.changeDir(); + if(err){$scope.console(err);} + else{$scope.console(data.text);} + $scope.folderzero++; + $scope.mkDirs(); + }); + + }else{ + $timeout(function() { + $scope.changeDir(); + $scope.upFiles(); + }, 200); + } + } + $scope.upFiles = function(){ + if($scope.filesArray[$scope.filezero]){ + var localpath = $scope.filesArray[$scope.filezero].path; + var uploadpath = $scope.baseUploadPath; + $scope.fileToUpload = uploadpath + localpath.replace($scope.baselocalpath, "").replace(/\\/g,"/"); + $scope.console("white", "Uploading " + $scope.fileToUpload + "..."); + + $scope.ftp.put(localpath, $scope.fileToUpload, function(hadError) { + if (!hadError){ + $scope.console("white", "Successfully uploaded " + localpath + " to " + $scope.fileToUpload); + }else{ + $scope.console("red", "Error Uploading " + $scope.fileToUpload); + } + $scope.filezero++; + $scope.changeDir(); + $scope.upFiles(); + }); + }else{ + $timeout(function() { + $interval.cancel($scope.uploadInterval); + $scope.showCancelOperation = false; + $scope.changeDir(); + $scope.console("blue", "File transfer completed in " + $scope.uploadTime + " seconds."); + }, 200); + } + } + + + + + // Drag to move files + // Unused for now + $scope.onDragComplete=function(path){ + console.log("MOVING: " + path); + } + $scope.onDropComplete=function(path){ + console.log("MOVE TO: " + path); + } + + + + + + + // + // Console controls + // + $scope.consoleMessages = []; + $scope.consoleUnread = 0; + $scope.console = function(color, msg){ + $timeout(function() { + $scope.consoleMessageClass = color; + $scope.consoleMessage = msg; + $scope.consoleMessages.push({"color":color, "message":msg}); + $scope.consoleUnread++; + }, 0); + } + $scope.openConsole = function(){ + $scope.consoleUnread = 0; + $scope.fullConsole = true + } + + + // + // Cancel Operations + // + // $scope.cancelFTPOperation = function(){ + // $scope.ftp.raw('abor', function() { + // $scope.console("red", "Process aborted.") + // }); + // } + + + + + + + // + // Keyboard Shortcuts + // + window.document.onkeydown = function (e){ + if (!e) e = event; + if (e.keyCode == 27){ // esc + $timeout(function() { + console.log("esc pressed"); + if(!$scope.showingRename && !$scope.showingNewFolder && !$scope.showingMenu){$scope.fullConsole = false;} + $scope.showingRename = false; + $scope.showingMenu = false; + $scope.showingNewFolder = false; + }, 0); + } + if (e.keyCode == 8 || e.keyCode == 46){ // esc + $timeout(function() { + if($scope.fileSelected){ + $scope.showingConfirmDelete = true; + } + }, 0); + } + } + + + + + // + // Electron Menu + // + var remote = require('electron').remote; + var Menu = remote.Menu; + + var template = [ + {label: 'ffftp', + submenu: [{ + label: 'About', + accelerator: 'CmdOrCtrl+H', + click: function(item, focusedWindow) { + shell.openExternal('http://ffftp.site'); + } + },{ + label: 'Close', + accelerator: 'CmdOrCtrl+Q', + role: 'close' + } + ] + },{ + label: 'Action', + submenu: [{ + label: 'Connect', + accelerator: 'CmdOrCtrl+R', + click: function() { + $timeout(function() {$scope.showingMenu = true;}, 0); + } + },{ + label: 'Up directory', + accelerator: 'CmdOrCtrl+U', + click: function() { + if($scope.path == '.'){$scope.console("red", "You are in the root directory.")} + else{$scope.upDir();} + } + },{ + label: 'New folder', + accelerator: 'CmdOrCtrl+N', + click: function() { + $timeout(function() {$scope.showingNewFolder = true;}, 0); + } + } + ] + },{ + label: 'View', + submenu: [{ + label: 'Reload', + accelerator: 'CmdOrCtrl+R', + click: function(item, focusedWindow) { + if (focusedWindow) + focusedWindow.reload(); + } + },{ + label: 'Full screen', + accelerator: (function() { + if (process.platform == 'darwin') + return 'Ctrl+Command+F'; + else + return 'F11'; + })(), + click: function(item, focusedWindow) { + if (focusedWindow) + focusedWindow.setFullScreen(!focusedWindow.isFullScreen()); + } + } + ] + },{ + label: "Edit", + submenu: [ + { label: "Undo", accelerator: "CmdOrCtrl+Z", selector: "undo:" }, + { label: "Redo", accelerator: "Shift+CmdOrCtrl+Z", selector: "redo:" }, + { type: "separator" }, + { label: "Cut", accelerator: "CmdOrCtrl+X", selector: "cut:" }, + { label: "Copy", accelerator: "CmdOrCtrl+C", selector: "copy:" }, + { label: "Paste", accelerator: "CmdOrCtrl+V", selector: "paste:" }, + { label: "Select All", accelerator: "CmdOrCtrl+A", selector: "selectAll:" } + ]},{ + label: 'Dev', + submenu: [ + { + label: 'Dev tools', + accelerator: (function() { + if (process.platform == 'darwin') + return 'Alt+Command+I'; + else + return 'Ctrl+Shift+I'; + })(), + click: function(item, focusedWindow) { + if (focusedWindow) + focusedWindow.toggleDevTools(); + } + },{ + label: 'Github', + click: function(item, focusedWindow) { + shell.openExternal('http://github.com/mitchas/ffftp'); + } + } + ] + } + ]; + + + var menu = Menu.buildFromTemplate(template); + Menu.setApplicationMenu(menu); + + +}]); diff --git a/app/pages/main.html b/app/pages/main.html new file mode 100644 index 0000000..1faf151 --- /dev/null +++ b/app/pages/main.html @@ -0,0 +1,188 @@ +
+ +
+ + + + + +
+ + + root + + + + {{level}} + + +
+ +
+ + + + + + +
+
+ +
+ + +
+ Name + Size + Date +
+
+ + {{file.name}} + {{file.size | filesize}} + {{file.time | date}} +
+
+ {{emptyMessage}} +
+
+
+ + + + + + + + +
+
+ + +
+
+ +
+
+ + +
+
+ +
+
+

are you sure?

+ +
+
+ + + + + + +
+ {{consoleMessage}} + {{consoleUnread}} +
+
+
+
+
{{line.message}}
+
+
diff --git a/app/stylesheets/less/animations.less b/app/stylesheets/less/animations.less new file mode 100644 index 0000000..c7d386a --- /dev/null +++ b/app/stylesheets/less/animations.less @@ -0,0 +1,63 @@ +// "main": "main.less" + +// .ng-hide-add { animation:0.2s fadeOut ease; } // Hide +// .ng-hide-remove { animation:0.2s fadeIn ease; } // Show + +// .ng-fade +.ng-fade{ + &.ng-hide-add{animation: 0.15s fadeOut ease !important;} + &.ng-hide-remove{ animation: 0.15s fadeIn ease !important; } +} +// .slideTop +.slideTop{ + &.ng-hide-add{animation: 0.2s slideOutToTop ease !important;} + &.ng-hide-remove{ animation: 0.2s slideInFromTop ease !important; } +} +// .slideBottom +.slideBottom{ + &.ng-hide-add{animation: 0.2s slideOutToBottom ease !important;} + &.ng-hide-remove{ animation: 0.2s slideInFromBottom ease !important; } +} +// .slideBottom +.zoom{ + &.ng-hide-add{animation: 0.2s zoomOut ease !important;} + &.ng-hide-remove{ animation: 0.2s zoomIn ease !important; } +} + +// From Top +@keyframes slideInFromTop { + 0% {top: -100%; opacity: 1;} + 100% {top: 0; opacity: 1} +} +@keyframes slideOutToTop { + 0% {top: 0; opacity: 1;} + 100% {top: -100%; opacity: 1;} +} +// From Bottom +@keyframes slideInFromBottom { + 0% {bottom: -100%; opacity: 0;} + 100% {bottom: 0; opacity: 1} +} +@keyframes slideOutToBottom { + 0% {bottom: 0; opacity: 1;} + 100% {bottom: -100%; opacity: 0;} +} +// Zoom From Center +@keyframes zoomIn { + 0% {transform: scale(0.85); opacity: 0;} + 100% {transform: scale(1); opacity: 1} +} +@keyframes zoomOut { + 0% {transform: scale(1); opacity: 1;} + 100% {transform: scale(0.85); opacity: 0;} +} + + +@keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1} +} +@keyframes fadeOut { + 0% {opacity: 1;} + 100% {opacity: 0;} +} diff --git a/app/stylesheets/less/console.less b/app/stylesheets/less/console.less new file mode 100644 index 0000000..233d5d5 --- /dev/null +++ b/app/stylesheets/less/console.less @@ -0,0 +1,155 @@ +// "main": "main.less" +div.cancel-operation{ + position: fixed; + right: 0; + bottom: 32px; + box-sizing: border-box; + padding: 5px 8px; + background-color: fade(@red, 80%); + color: @white; + font-family: @code; + font-size: 10pt; + transition: @transition; + + &:hover{ + cursor: pointer; + background-color: @red; + transition: @transition; + } + + &.withconsole{ + bottom: 220px; + } +} +div.console-preview{ + display: block; + position: fixed; + bottom: 0; + left: 0; + z-index: 2000; + box-sizing: border-box; + padding: 7px 35px 8px 35px; + color: @white; + font-family: @code; + font-size: 10pt; + background-color: fade(@black, 90%); + width: 100%; + transition: @transition; + font-weight: bolder; + letter-spacing: 0.5px; + height: 32px; + .message{ + line-height: 2em; + position: relative; + top: -5px; + } + + &.red{color: @red;} + &.green{color: @green;} + &.white{color: @white;} + &.blue{color: lighten(@blue, 10%);} + + i{ + font-size: 12pt; + margin-right: 10px; + position: relative; + top: 1px; + } + + &:hover{ + cursor: pointer; + background-color: fade(@black, 100%); + transition: @transition; + } + + .console-unread{ + position: absolute; + float: right; + right: 10px; + bottom: 7px; + background-color: fade(@white, 90%); + display: flex; + height: 17px; + width: auto; + box-sizing: border-box; + text-align: center; + box-sizing: border-box; + padding: 1px 5px 0 5px; + justify-content: center; + flex-direction: column; + transition: @transition; + color: @black; + } + .cancel-operation{ + display: none; + background-color: @red; + color: @white; + transition: @transition; + cursor: pointer; + } +} + + +div.console{ + display: block; + position: fixed; + background-color: fade(@black, 90%); + box-sizing: border-box; + padding: 0 0 35px 0; + z-index: 2000; + width: 100%; + color: @white; + box-shadow: 0px -4px 10px 0px fade(@black, 16%); + height: 220px; + font-family: @code; + font-size: 10pt; + bottom: 0; + left: 0; + + div.console-minimize{ + display: block; + width: 100%; + text-align: center; + font-size: 14pt; + box-sizing: border-box; + padding: 5px 0 5px 0; + margin-bottom: 15px; + transition: @transition; + position: absolute; + z-index: 250; + background-color: transparent; + &:hover{ + cursor: pointer; + transition: @transition; + background-color: fade(@black, 50%); + } + } + + div.console-messages{ + display: block; + height: auto; + height: 185px; + overflow: auto; + padding: 35px 20px 0 0; + width: 100%; + + div.line{ + display: block; + width: 100%; + box-sizing: border-box; + box-sizing: border-box; + padding: 2px 35px; + letter-spacing: 0.5px; + + + &:last-child{ + margin-bottom: 25px; + } + + &.red{color: @red;} + &.green{color: @green;} + &.white{color: @white;} + &.blue{color: @blue;} + } + } +} diff --git a/app/stylesheets/less/main.less b/app/stylesheets/less/main.less new file mode 100644 index 0000000..e5b27a7 --- /dev/null +++ b/app/stylesheets/less/main.less @@ -0,0 +1,350 @@ +//"out": "../main.css", "compress": true + +@import 'reset.less'; +@import 'variables.less'; +@import 'modals.less'; +@import 'menu.less'; +@import 'console.less'; +@import 'animations.less'; + +*, *:hover, *:active, *:focus{ + outline: none; + text-decoration: none; +} + + +html, +body{ + font-family: @sans; + -webkit-font-smoothing: subpixel-antialiased; + color: @black; + background-color: @white; +} + +// +// Universal Elements +// +h1{ + display: block; + font-size: 26pt; + font-weight: 100; +} + + +div.app-wrapper{ + display: block; + width: 100%; + height: 100%; + max-height: 100%; + box-sizing: border-box; +} + + + + + + +div.workspace{ + box-sizing: border-box; + // padding: 140px 280px 25px 5%; + padding: 110px 5% 25px 5%; + max-width: 1200px; + margin: 0 auto; + display: flex; + + &.consolePadding{ + padding-bottom: 220px !important; + } + + div.workspace-nav{ + display: block; + box-sizing: border-box; + padding: 25px 0 25px 0; + width: 100%; + top: 0; + left: 0; + z-index: 250; + position: fixed; + background-color: fade(@white, 80%); + + .ion-navicon{ + display: flex; + width: 40px; + height: 40px; + justify-content: center; + flex-direction: column; + font-size: 24pt; + text-align: center; + background-color: @white; + color: @black; + position: fixed; + top: 15px; + left: 15px; + transition: @transition; + + &:hover{ + cursor: pointer; + color: fade(@black, 50%); + transition: @transition; + } + } + + div.dir{ + display: block; + font-size: 13pt; + font-weight: 500; + width: 100%; + text-align: center; + box-sizing: border-box; + padding: 0 0 15px 0; + + .item{ + display: inline-block; + box-sizing: border-box; + padding: 0 5px; + + span{ + color: @red; + transition: @transition; + + i{ + font-size: 10pt; + color: fade(@red, 50%); + display: inline-block; + box-sizing: border-box; + padding: 0 8px 0 5px; + position: relative; + top: 0.5px; + } + + &:hover{ + cursor: pointer; + transition: @transition; + text-decoration: underline; + } + + + &.current{ + color: @black; + &:hover{ + cursor: default; + color: @black; + text-decoration: none; + } + } + + } + } + } + + div.workspace-buttons{ + display: block; + box-sizing: border-box; + padding: 0 5%; + text-align: center; + + input[type='file']{ + display: none; + } + + button{ + font-size: 11pt; + color: @black; + margin: 0 6px; + background-color: transparent; + border: none; + font-weight: 600; + text-transform: lowercase; + vertical-align: middle; + transition: @transition; + + i{ + font-size: 13pt; + position: relative; + padding-right: 4px; + display: inline-block; + top: 1.25px; + } + + &:hover{ + color: @red; + transition: @transition; + } + + &:disabled{ + color: fade(@black, 40%); + font-weight: 400; + &:hover{ + cursor: default; + color: fade(@black, 40%); + + } + } + } + } + } + + div.workspace-sidebar{ + display: block; + flex-wrap: wrap; + width: 280px; + box-sizing: border-box; + // background-color: red; + padding: 0 0px 0 55px; + + div.inner{ + position: fixed; + + .search{ + display: block; + width: 100%; + margin: 0 auto; + box-sizing: border-box; + padding: 12px 10px; + font-weight: 300; + border: none; + border-bottom: 1px solid fade(@black, 50%); + // border-radius: 4px; + } + + div.sidebar-preview{ + display: block; + width: 100%; + height: 200px; + background-repeat: no-repeat; + background-color: fade(@black, 5%); + margin-top: 35px; + + &.folder{ + background-image: url('../visuals/icons/folder.png'); + background-size: 70%; + background-position: center center; + } + } + + div.sidebar-file-details{ + color: @black; + box-sizing: border-box; + padding: 35px 0 0 0; + font-family: @sans; + + span.name{ + font-weight: 600; + font-size: 14pt; + color: @black; + } + span.path, + span.type{ + display: block; + box-sizing: border-box; + padding: 10px 0 0 0; + font-size: 10pt; + } + } + } + } + + div.file-list{ + display: block; + height: 100%; + box-sizing: border-box; + font-weight: 400; + color: @black; + padding: 0 0 40px 0; + width: 100%; + + .searchInput{ + display: block; + margin: 0 auto; + text-align: center; + background-color: fade(@grey, 50%); + box-sizing: border-box; + padding: 8px 12px; + border: none; + } + + div.header{ + display: flex; + box-sizing: border-box; + padding: 14px; + border-bottom: 1px solid fade(@black, 40%); + font-family: @sans; + font-size: 12pt; + color: fade(@black, 80%); + .name{flex-grow: 4;} + .size, + .time{ + width: 18%; + text-align: right; + } + } + div.file{ + display: flex; + box-sizing: border-box; + padding: 14px; + border-bottom: 1px solid fade(@black, 7.5%); + font-family: @code; + .name{ + flex-grow: 4; + i{ + font-size: 14pt; + margin-right: 10px; + position: relative; + top: 1px; + } + } + .size, + .time{ + width: 18%; + text-align: right; + } + &:hover{ + background-color: fade(@black, 6%); + transition: @transition; + cursor: pointer; + } + &:focus{ + background-color: fade(@black, 12%); + } + } + .empty-message{ + display: block; + width: 100%; + text-align: center; + font-size: 16pt; + color: fade(@black, 30%); + font-weight: 400; + box-sizing: border-box; + padding: 75px 0; + } + } +} + +button:disabled{ + background-color: red; +} + + + +// +// Universal elements +// +.button(){ + font-size: 12pt; + text-transform: lowercase; + letter-spacing: -0.5px; + font-weight: 400; + color: @white; + background-color: darken(@green, 6%); + border: none; + box-sizing: border-box; + padding: 8px 16px; + transition: @transition; + border-radius: 4px; + margin-right: 10px; + &:hover{ + transition: @transition; + cursor: pointer; + background-color: darken(@green, 12%); + } +} diff --git a/app/stylesheets/less/menu.less b/app/stylesheets/less/menu.less new file mode 100644 index 0000000..f20ee6f --- /dev/null +++ b/app/stylesheets/less/menu.less @@ -0,0 +1,292 @@ +// "main": "main.less" + + +.menu{ + display: flex; + position: fixed; + justify-content: space-between; + flex-direction: column; + text-align: center; + top: 0; + left: 0; + box-sizing: border-box; + padding: 30px 0; + height: 100%; + width: 100%; + box-sizing: border-box; + z-index: 5000; + background-color: @white; + + .menu-branding{ + display: block; + margin: 0 auto; + height: auto; + width: 80px; + img{ + width: 50px; + height: auto; + } + } + + + i.close-menu, + i.menu-open-notifications{ + display: flex; + width: 40px; + height: 40px; + justify-content: center; + flex-direction: column; + font-size: 38pt; + text-align: center; + color: @black; + position: absolute; + left: 15px; + top: 15px; + transition: @transition; + + &:hover{ + cursor: pointer; + color: fade(@black, 50%); + transition: @transition; + } + } + + div.menu-notifications{ + display: flex; + justify-content: space-between; + position: absolute; + bottom: 0px; + left: 0; + background-color: @grey; + width: 100%; + color: @black; + font-size: 10pt; + text-align: center; + line-height: 1.4em; + letter-spacing: 1px; + font-weight: 500; + + div.message{ + flex-grow: 3; + box-sizing: border-box; + transition: @transition; + padding: 8px 15px 8px 45px; + &:hover{ + background-color: darken(@grey, 5%); + cursor: pointer; + transition: @transition; + } + } + div.ignore{ + padding: 8px 15px; + box-sizing: border-box; + transition: @transition; + color: @black; + &:hover{ + transition: @transition; + cursor: pointer; + color: @red; + } + } + } + + + // Connection form + form.connection-form{ + display: block; + width: 100%; + + div.form-row{ + display: flex; + width: 100%; + justify-content: center; + box-sizing: border-box; + padding: 10px 35px; + max-width: 850px; + margin: 0 auto; + + &.flex-end{ + justify-content: flex-end; + } + + div.form-item{ + label{ + display: block; + box-sizing: border-box; + padding: 4px 0 0 0; + font-weight: 400; + width: 90%; + margin: 0 auto; + font-size: 10pt; + text-transform: lowercase; + color: fade(@black, 50%); + } + input{ + display: block; + width: 90%; + max-width: 280px; + margin: 0 auto; + box-sizing: border-box; + text-align: center; + padding: 12px 8px; + font-weight: 300; + border: 1px solid fade(@black, 50%); + border-radius: 4px; + + &[type='number']{ + padding-left: 35px; + } + } + + button{ + .button(); + + &.favorites{ + background-color: fade(@black, 10%); + color: fade(@black, 50%); + margin-right: 20px; + + i{ + position: relative; + vertical-align: middle; + padding-right: 5px; + width: 14px; + display: inline-block; + } + &:hover{ + background-color: fade(@blue, 30%); + } + &.active{ + background-color: @blue; + color: @white; + &:hover{ + background-color: darken(@blue, 5%); + color: @white; + } + } + } + + &:disabled{ + background-color: @red; + &:hover{ + background-color: @red; + cursor: default; + } + } + + } + } + } + } + + // Favorites + .favorites-list{ + display: block; + text-align: left; + width: 100%; + box-sizing: border-box; + padding: 10px 45px; + max-width: 850px; + margin: 0 auto; + + .favorites-header{ + display: block; + box-sizing: border-box; + padding: 4px 0 15px 0; + font-weight: 400; + width: 100%; + margin: 0 auto; + font-size: 14pt; + text-transform: lowercase; + color: fade(@black, 50%); + } + + .favorite{ + display: inline-block; + box-sizing: border-box; + letter-spacing: 1px; + color: @white; + border-radius: 4px; + font-size: 12pt; + text-transform: lowercase; + letter-spacing: -0.5px; + margin: 0 15px 25px 0; + transition: @transition; + height: 35px; + + .name{ + background-color: @blue; + display: block; + border-radius: 4px; + width: auto; + height: auto; + box-sizing: border-box; + padding: 12px 25px 12px 25px; + &:hover{ + cursor: pointer; + background-color: darken(@blue, 5%); + color: @white; + transition: @transition; + } + } + .delete{ + width: 100%; + display: block; + color: fade(@black, 30%); + font-size: 10pt; + box-sizing: border-box; + padding: 2px 0 0 0; + text-align: center; + transition: @transition; + text-align: center; + &:hover{ + cursor: pointer; + color: @red; + transition: @transition; + } + } + + } + + .edit-favorites{ + display: block; + box-sizing: border-box; + padding: 25px 0 0 0; + + button{ + .button(); + background-color: fade(@black, 10%); + color: fade(@black, 50%); + &:hover{ + background-color: fade(@black, 15%); + } + } + } + } + + + + div.menu-footer{ + display: block; + width: 100%; + text-align: center; + box-sizing: border-box; + padding: 6px 0; + font-size: 8pt; + font-weight: 600; + color: fade(@black, 30%); + text-transform: lowercase; + background-color: @white; + a{ + transition: @transition; + color: fade(@blue, 50%); + &:hover{ + color: @blue; + transition: @transition; + } + } + } + + + +} diff --git a/app/stylesheets/less/modals.less b/app/stylesheets/less/modals.less new file mode 100644 index 0000000..fb459cb --- /dev/null +++ b/app/stylesheets/less/modals.less @@ -0,0 +1,89 @@ +// "main": "main.less" + +.modal(){ + display: flex; + width: 100%; + height: 100%; + position: fixed; + top: 0; + left: 0; + background-color: @white; + z-index: 5000; +} +.modal-buttons{ + display: block; + width: 90%; + max-width: 280px; + margin: 0 auto; + text-align: right; + box-sizing: border-box; + padding: 15px 0 0 0; + + &.center{ + text-align: center; + } + + button{ + font-size: 12pt; + text-transform: lowercase; + letter-spacing: -0.5px; + font-weight: 400; + color: @white; + background-color: @black; + border: none; + box-sizing: border-box; + padding: 8px 16px; + border-radius: 4px; + &:hover{ + cursor: pointer; + transition: @transition; + } + + &.cancel{ + background-color: fade(@black, 20%); + margin-right: 10px; + &:hover{ + background-color: fade(@black, 40%); + } + } + &.proceed{ + background-color: darken(@green, 6%); + &:hover{ + background-color: darken(@green, 12%); + } + } + &.danger{ + background-color: @red; + &:hover{ + background-color: darken(@red, 5%); + } + } + } +} + +div.rename, +div.newfolder, +div.confirmdelete{ + .modal(); + flex-direction: column; + justify-content: center; + text-align: center; + + h1{ + font-size: 22pt; + box-sizing: border-box; + padding: 0 0 15px 0; + } + + input{ + display: block; + width: 90%; + max-width: 280px; + margin: 0 auto; + box-sizing: border-box; + padding: 12px 20px; + font-weight: 300; + border: 1px solid fade(@black, 50%); + border-radius: 4px; + } +} diff --git a/app/stylesheets/less/reset.less b/app/stylesheets/less/reset.less new file mode 100644 index 0000000..e8e4384 --- /dev/null +++ b/app/stylesheets/less/reset.less @@ -0,0 +1,45 @@ +//"main": "main.less" + +html, body, div, span, applet, object, iframe, +h1, h2, h3, h4, h5, h6, p, blockquote, pre, +a, abbr, acronym, address, big, cite, code, +del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, +b, u, i, center, +dl, dt, dd, ol, ul, li, +fieldset, form, label, legend, +table, caption, tbody, tfoot, thead, tr, th, td, +article, aside, canvas, details, embed, +figure, figcaption, footer, header, hgroup, +menu, nav, output, ruby, section, summary, +time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline; +} +/* HTML5 display-role reset for older browsers */ +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block; +} +body { + line-height: 1; +} +ol, ul { + list-style: none; +} +blockquote, q { + quotes: none; +} +blockquote:before, blockquote:after, +q:before, q:after { + content: ''; + content: none; +} +table { + border-collapse: collapse; + border-spacing: 0; +} diff --git a/app/stylesheets/less/variables.less b/app/stylesheets/less/variables.less new file mode 100644 index 0000000..194de03 --- /dev/null +++ b/app/stylesheets/less/variables.less @@ -0,0 +1,24 @@ +// "main": "main.less" +@import url('https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700'); +@import 'http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css'; +@import url('https://fonts.googleapis.com/css?family=Inconsolata:400,700'); + +// Fonts +@ionicons: 'Ionicons'; +@sans: 'Roboto', 'Helvetica', 'Arial' sans-serif; +@code: 'Inconsolata', monospace; + +// Colors +@black: #081D38; +@mediumblack: #32384A; +@lightblack: #474E65; +@white: #FFF; +@blue: #0080FF; +@grey: #E9EEF2; +@blue-text: #939EBC; + +@red: #FF4F5E; +@green: #25E552; + +// UI +@transition: 0.2s; diff --git a/app/stylesheets/main.css b/app/stylesheets/main.css new file mode 100644 index 0000000..4ac7900 --- /dev/null +++ b/app/stylesheets/main.css @@ -0,0 +1 @@ +@import url('https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700');@import 'http://code.ionicframework.com/ionicons/2.0.1/css/ionicons.min.css';@import url('https://fonts.googleapis.com/css?family=Inconsolata:400,700');html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:'';content:none}table{border-collapse:collapse;border-spacing:0}.modal-buttons{display:block;width:90%;max-width:280px;margin:0 auto;text-align:right;box-sizing:border-box;padding:15px 0 0 0}.modal-buttons.center{text-align:center}.modal-buttons button{font-size:12pt;text-transform:lowercase;letter-spacing:-0.5px;font-weight:400;color:#FFF;background-color:#081D38;border:none;box-sizing:border-box;padding:8px 16px;border-radius:4px}.modal-buttons button:hover{cursor:pointer;transition:.2s}.modal-buttons button.cancel{background-color:rgba(8,29,56,0.2);margin-right:10px}.modal-buttons button.cancel:hover{background-color:rgba(8,29,56,0.4)}.modal-buttons button.proceed{background-color:#19d244}.modal-buttons button.proceed:hover{background-color:#16b73c}.modal-buttons button.danger{background-color:#FF4F5E}.modal-buttons button.danger:hover{background-color:#ff3547}div.rename,div.newfolder,div.confirmdelete{display:flex;width:100%;height:100%;position:fixed;top:0;left:0;background-color:#FFF;z-index:5000;flex-direction:column;justify-content:center;text-align:center}div.rename h1,div.newfolder h1,div.confirmdelete h1{font-size:22pt;box-sizing:border-box;padding:0 0 15px 0}div.rename input,div.newfolder input,div.confirmdelete input{display:block;width:90%;max-width:280px;margin:0 auto;box-sizing:border-box;padding:12px 20px;font-weight:300;border:1px solid rgba(8,29,56,0.5);border-radius:4px}.menu{display:flex;position:fixed;justify-content:space-between;flex-direction:column;text-align:center;top:0;left:0;padding:30px 0;height:100%;width:100%;box-sizing:border-box;z-index:5000;background-color:#FFF}.menu .menu-branding{display:block;margin:0 auto;height:auto;width:80px}.menu .menu-branding img{width:50px;height:auto}.menu i.close-menu,.menu i.menu-open-notifications{display:flex;width:40px;height:40px;justify-content:center;flex-direction:column;font-size:38pt;text-align:center;color:#081D38;position:absolute;left:15px;top:15px;transition:.2s}.menu i.close-menu:hover,.menu i.menu-open-notifications:hover{cursor:pointer;color:rgba(8,29,56,0.5);transition:.2s}.menu div.menu-notifications{display:flex;justify-content:space-between;position:absolute;bottom:0;left:0;background-color:#E9EEF2;width:100%;color:#081D38;font-size:10pt;text-align:center;line-height:1.4em;letter-spacing:1px;font-weight:500}.menu div.menu-notifications div.message{flex-grow:3;box-sizing:border-box;transition:.2s;padding:8px 15px 8px 45px}.menu div.menu-notifications div.message:hover{background-color:#d9e2e9;cursor:pointer;transition:.2s}.menu div.menu-notifications div.ignore{padding:8px 15px;box-sizing:border-box;transition:.2s;color:#081D38}.menu div.menu-notifications div.ignore:hover{transition:.2s;cursor:pointer;color:#FF4F5E}.menu form.connection-form{display:block;width:100%}.menu form.connection-form div.form-row{display:flex;width:100%;justify-content:center;box-sizing:border-box;padding:10px 35px;max-width:850px;margin:0 auto}.menu form.connection-form div.form-row.flex-end{justify-content:flex-end}.menu form.connection-form div.form-row div.form-item label{display:block;box-sizing:border-box;padding:4px 0 0 0;font-weight:400;width:90%;margin:0 auto;font-size:10pt;text-transform:lowercase;color:rgba(8,29,56,0.5)}.menu form.connection-form div.form-row div.form-item input{display:block;width:90%;max-width:280px;margin:0 auto;box-sizing:border-box;text-align:center;padding:12px 8px;font-weight:300;border:1px solid rgba(8,29,56,0.5);border-radius:4px}.menu form.connection-form div.form-row div.form-item input[type='number']{padding-left:35px}.menu form.connection-form div.form-row div.form-item button{font-size:12pt;text-transform:lowercase;letter-spacing:-0.5px;font-weight:400;color:#FFF;background-color:#19d244;border:none;box-sizing:border-box;padding:8px 16px;transition:.2s;border-radius:4px;margin-right:10px}.menu form.connection-form div.form-row div.form-item button:hover{transition:.2s;cursor:pointer;background-color:#16b73c}.menu form.connection-form div.form-row div.form-item button.favorites{background-color:rgba(8,29,56,0.1);color:rgba(8,29,56,0.5);margin-right:20px}.menu form.connection-form div.form-row div.form-item button.favorites i{position:relative;vertical-align:middle;padding-right:5px;width:14px;display:inline-block}.menu form.connection-form div.form-row div.form-item button.favorites:hover{background-color:rgba(0,128,255,0.3)}.menu form.connection-form div.form-row div.form-item button.favorites.active{background-color:#0080FF;color:#FFF}.menu form.connection-form div.form-row div.form-item button.favorites.active:hover{background-color:#0073e6;color:#FFF}.menu form.connection-form div.form-row div.form-item button:disabled{background-color:#FF4F5E}.menu form.connection-form div.form-row div.form-item button:disabled:hover{background-color:#FF4F5E;cursor:default}.menu .favorites-list{display:block;text-align:left;width:100%;box-sizing:border-box;padding:10px 45px;max-width:850px;margin:0 auto}.menu .favorites-list .favorites-header{display:block;box-sizing:border-box;padding:4px 0 15px 0;font-weight:400;width:100%;margin:0 auto;font-size:14pt;text-transform:lowercase;color:rgba(8,29,56,0.5)}.menu .favorites-list .favorite{display:inline-block;box-sizing:border-box;letter-spacing:1px;color:#FFF;border-radius:4px;font-size:12pt;text-transform:lowercase;letter-spacing:-0.5px;margin:0 15px 25px 0;transition:.2s;height:35px}.menu .favorites-list .favorite .name{background-color:#0080FF;display:block;border-radius:4px;width:auto;height:auto;box-sizing:border-box;padding:12px 25px 12px 25px}.menu .favorites-list .favorite .name:hover{cursor:pointer;background-color:#0073e6;color:#FFF;transition:.2s}.menu .favorites-list .favorite .delete{width:100%;display:block;color:rgba(8,29,56,0.3);font-size:10pt;box-sizing:border-box;padding:2px 0 0 0;transition:.2s;text-align:center}.menu .favorites-list .favorite .delete:hover{cursor:pointer;color:#FF4F5E;transition:.2s}.menu .favorites-list .edit-favorites{display:block;box-sizing:border-box;padding:25px 0 0 0}.menu .favorites-list .edit-favorites button{font-size:12pt;text-transform:lowercase;letter-spacing:-0.5px;font-weight:400;color:#FFF;background-color:#19d244;border:none;box-sizing:border-box;padding:8px 16px;transition:.2s;border-radius:4px;margin-right:10px;background-color:rgba(8,29,56,0.1);color:rgba(8,29,56,0.5)}.menu .favorites-list .edit-favorites button:hover{transition:.2s;cursor:pointer;background-color:#16b73c}.menu .favorites-list .edit-favorites button:hover{background-color:rgba(8,29,56,0.15)}.menu div.menu-footer{display:block;width:100%;text-align:center;box-sizing:border-box;padding:6px 0;font-size:8pt;font-weight:600;color:rgba(8,29,56,0.3);text-transform:lowercase;background-color:#FFF}.menu div.menu-footer a{transition:.2s;color:rgba(0,128,255,0.5)}.menu div.menu-footer a:hover{color:#0080FF;transition:.2s}div.cancel-operation{position:fixed;right:0;bottom:32px;box-sizing:border-box;padding:5px 8px;background-color:rgba(255,79,94,0.8);color:#FFF;font-family:'Inconsolata',monospace;font-size:10pt;transition:.2s}div.cancel-operation:hover{cursor:pointer;background-color:#FF4F5E;transition:.2s}div.cancel-operation.withconsole{bottom:220px}div.console-preview{display:block;position:fixed;bottom:0;left:0;z-index:2000;box-sizing:border-box;padding:7px 35px 8px 35px;color:#FFF;font-family:'Inconsolata',monospace;font-size:10pt;background-color:rgba(8,29,56,0.9);width:100%;transition:.2s;font-weight:bolder;letter-spacing:.5px;height:32px}div.console-preview .message{line-height:2em;position:relative;top:-5px}div.console-preview.red{color:#FF4F5E}div.console-preview.green{color:#25E552}div.console-preview.white{color:#FFF}div.console-preview.blue{color:#39f}div.console-preview i{font-size:12pt;margin-right:10px;position:relative;top:1px}div.console-preview:hover{cursor:pointer;background-color:#081d38;transition:.2s}div.console-preview .console-unread{position:absolute;float:right;right:10px;bottom:7px;background-color:rgba(255,255,255,0.9);display:flex;height:17px;width:auto;text-align:center;box-sizing:border-box;padding:1px 5px 0 5px;justify-content:center;flex-direction:column;transition:.2s;color:#081D38}div.console-preview .cancel-operation{display:none;background-color:#FF4F5E;color:#FFF;transition:.2s;cursor:pointer}div.console{display:block;position:fixed;background-color:rgba(8,29,56,0.9);box-sizing:border-box;padding:0 0 35px 0;z-index:2000;width:100%;color:#FFF;box-shadow:0 -4px 10px 0 rgba(8,29,56,0.16);height:220px;font-family:'Inconsolata',monospace;font-size:10pt;bottom:0;left:0}div.console div.console-minimize{display:block;width:100%;text-align:center;font-size:14pt;box-sizing:border-box;padding:5px 0 5px 0;margin-bottom:15px;transition:.2s;position:absolute;z-index:250;background-color:transparent}div.console div.console-minimize:hover{cursor:pointer;transition:.2s;background-color:rgba(8,29,56,0.5)}div.console div.console-messages{display:block;height:auto;height:185px;overflow:auto;padding:35px 20px 0 0;width:100%}div.console div.console-messages div.line{display:block;width:100%;box-sizing:border-box;padding:2px 35px;letter-spacing:.5px}div.console div.console-messages div.line:last-child{margin-bottom:25px}div.console div.console-messages div.line.red{color:#FF4F5E}div.console div.console-messages div.line.green{color:#25E552}div.console div.console-messages div.line.white{color:#FFF}div.console div.console-messages div.line.blue{color:#0080FF}.ng-fade.ng-hide-add{animation:.15s fadeOut ease !important}.ng-fade.ng-hide-remove{animation:.15s fadeIn ease !important}.slideTop.ng-hide-add{animation:.2s slideOutToTop ease !important}.slideTop.ng-hide-remove{animation:.2s slideInFromTop ease !important}.slideBottom.ng-hide-add{animation:.2s slideOutToBottom ease !important}.slideBottom.ng-hide-remove{animation:.2s slideInFromBottom ease !important}.zoom.ng-hide-add{animation:.2s zoomOut ease !important}.zoom.ng-hide-remove{animation:.2s zoomIn ease !important}@keyframes slideInFromTop{0%{top:-100%;opacity:1}100%{top:0;opacity:1}}@keyframes slideOutToTop{0%{top:0;opacity:1}100%{top:-100%;opacity:1}}@keyframes slideInFromBottom{0%{bottom:-100%;opacity:0}100%{bottom:0;opacity:1}}@keyframes slideOutToBottom{0%{bottom:0;opacity:1}100%{bottom:-100%;opacity:0}}@keyframes zoomIn{0%{transform:scale(.85);opacity:0}100%{transform:scale(1);opacity:1}}@keyframes zoomOut{0%{transform:scale(1);opacity:1}100%{transform:scale(.85);opacity:0}}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes fadeOut{0%{opacity:1}100%{opacity:0}}*,*:hover,*:active,*:focus{outline:none;text-decoration:none}html,body{font-family:'Roboto','Helvetica','Arial' sans-serif;-webkit-font-smoothing:subpixel-antialiased;color:#081D38;background-color:#FFF}h1{display:block;font-size:26pt;font-weight:100}div.app-wrapper{display:block;width:100%;height:100%;max-height:100%;box-sizing:border-box}div.workspace{box-sizing:border-box;padding:110px 5% 25px 5%;max-width:1200px;margin:0 auto;display:flex}div.workspace.consolePadding{padding-bottom:220px !important}div.workspace div.workspace-nav{display:block;box-sizing:border-box;padding:25px 0 25px 0;width:100%;top:0;left:0;z-index:250;position:fixed;background-color:rgba(255,255,255,0.8)}div.workspace div.workspace-nav .ion-navicon{display:flex;width:40px;height:40px;justify-content:center;flex-direction:column;font-size:24pt;text-align:center;background-color:#FFF;color:#081D38;position:fixed;top:15px;left:15px;transition:.2s}div.workspace div.workspace-nav .ion-navicon:hover{cursor:pointer;color:rgba(8,29,56,0.5);transition:.2s}div.workspace div.workspace-nav div.dir{display:block;font-size:13pt;font-weight:500;width:100%;text-align:center;box-sizing:border-box;padding:0 0 15px 0}div.workspace div.workspace-nav div.dir .item{display:inline-block;box-sizing:border-box;padding:0 5px}div.workspace div.workspace-nav div.dir .item span{color:#FF4F5E;transition:.2s}div.workspace div.workspace-nav div.dir .item span i{font-size:10pt;color:rgba(255,79,94,0.5);display:inline-block;box-sizing:border-box;padding:0 8px 0 5px;position:relative;top:.5px}div.workspace div.workspace-nav div.dir .item span:hover{cursor:pointer;transition:.2s;text-decoration:underline}div.workspace div.workspace-nav div.dir .item span.current{color:#081D38}div.workspace div.workspace-nav div.dir .item span.current:hover{cursor:default;color:#081D38;text-decoration:none}div.workspace div.workspace-nav div.workspace-buttons{display:block;box-sizing:border-box;padding:0 5%;text-align:center}div.workspace div.workspace-nav div.workspace-buttons input[type='file']{display:none}div.workspace div.workspace-nav div.workspace-buttons button{font-size:11pt;color:#081D38;margin:0 6px;background-color:transparent;border:none;font-weight:600;text-transform:lowercase;vertical-align:middle;transition:.2s}div.workspace div.workspace-nav div.workspace-buttons button i{font-size:13pt;position:relative;padding-right:4px;display:inline-block;top:1.25px}div.workspace div.workspace-nav div.workspace-buttons button:hover{color:#FF4F5E;transition:.2s}div.workspace div.workspace-nav div.workspace-buttons button:disabled{color:rgba(8,29,56,0.4);font-weight:400}div.workspace div.workspace-nav div.workspace-buttons button:disabled:hover{cursor:default;color:rgba(8,29,56,0.4)}div.workspace div.workspace-sidebar{display:block;flex-wrap:wrap;width:280px;box-sizing:border-box;padding:0 0 0 55px}div.workspace div.workspace-sidebar div.inner{position:fixed}div.workspace div.workspace-sidebar div.inner .search{display:block;width:100%;margin:0 auto;box-sizing:border-box;padding:12px 10px;font-weight:300;border:none;border-bottom:1px solid rgba(8,29,56,0.5)}div.workspace div.workspace-sidebar div.inner div.sidebar-preview{display:block;width:100%;height:200px;background-repeat:no-repeat;background-color:rgba(8,29,56,0.05);margin-top:35px}div.workspace div.workspace-sidebar div.inner div.sidebar-preview.folder{background-image:url('../visuals/icons/folder.png');background-size:70%;background-position:center center}div.workspace div.workspace-sidebar div.inner div.sidebar-file-details{color:#081D38;box-sizing:border-box;padding:35px 0 0 0;font-family:'Roboto','Helvetica','Arial' sans-serif}div.workspace div.workspace-sidebar div.inner div.sidebar-file-details span.name{font-weight:600;font-size:14pt;color:#081D38}div.workspace div.workspace-sidebar div.inner div.sidebar-file-details span.path,div.workspace div.workspace-sidebar div.inner div.sidebar-file-details span.type{display:block;box-sizing:border-box;padding:10px 0 0 0;font-size:10pt}div.workspace div.file-list{display:block;height:100%;box-sizing:border-box;font-weight:400;color:#081D38;padding:0 0 40px 0;width:100%}div.workspace div.file-list .searchInput{display:block;margin:0 auto;text-align:center;background-color:rgba(233,238,242,0.5);box-sizing:border-box;padding:8px 12px;border:none}div.workspace div.file-list div.header{display:flex;box-sizing:border-box;padding:14px;border-bottom:1px solid rgba(8,29,56,0.4);font-family:'Roboto','Helvetica','Arial' sans-serif;font-size:12pt;color:rgba(8,29,56,0.8)}div.workspace div.file-list div.header .name{flex-grow:4}div.workspace div.file-list div.header .size,div.workspace div.file-list div.header .time{width:18%;text-align:right}div.workspace div.file-list div.file{display:flex;box-sizing:border-box;padding:14px;border-bottom:1px solid rgba(8,29,56,0.075);font-family:'Inconsolata',monospace}div.workspace div.file-list div.file .name{flex-grow:4}div.workspace div.file-list div.file .name i{font-size:14pt;margin-right:10px;position:relative;top:1px}div.workspace div.file-list div.file .size,div.workspace div.file-list div.file .time{width:18%;text-align:right}div.workspace div.file-list div.file:hover{background-color:rgba(8,29,56,0.06);transition:.2s;cursor:pointer}div.workspace div.file-list div.file:focus{background-color:rgba(8,29,56,0.12)}div.workspace div.file-list .empty-message{display:block;width:100%;text-align:center;font-size:16pt;color:rgba(8,29,56,0.3);font-weight:400;box-sizing:border-box;padding:75px 0}button:disabled{background-color:red} \ No newline at end of file diff --git a/app/visuals/ffftp-512.png b/app/visuals/ffftp-512.png new file mode 100644 index 0000000..9b5ae53 Binary files /dev/null and b/app/visuals/ffftp-512.png differ diff --git a/app/visuals/ffftp200w.png b/app/visuals/ffftp200w.png new file mode 100644 index 0000000..c6a4852 Binary files /dev/null and b/app/visuals/ffftp200w.png differ diff --git a/app/visuals/icon200.png b/app/visuals/icon200.png new file mode 100644 index 0000000..5ac1c47 Binary files /dev/null and b/app/visuals/icon200.png differ diff --git a/app/visuals/icons/file.png b/app/visuals/icons/file.png new file mode 100644 index 0000000..65d2af5 Binary files /dev/null and b/app/visuals/icons/file.png differ diff --git a/app/visuals/icons/folder.png b/app/visuals/icons/folder.png new file mode 100644 index 0000000..3f143c2 Binary files /dev/null and b/app/visuals/icons/folder.png differ diff --git a/app/visuals/icons/folder.svg b/app/visuals/icons/folder.svg new file mode 100644 index 0000000..61e5e3e --- /dev/null +++ b/app/visuals/icons/folder.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/visuals/icons/new-connection.svg b/app/visuals/icons/new-connection.svg new file mode 100644 index 0000000..381e339 --- /dev/null +++ b/app/visuals/icons/new-connection.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/icon.icns b/icon.icns new file mode 100644 index 0000000..6e9563f Binary files /dev/null and b/icon.icns differ diff --git a/icon.ico b/icon.ico new file mode 100644 index 0000000..4c331d6 Binary files /dev/null and b/icon.ico differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..0eea009 --- /dev/null +++ b/index.html @@ -0,0 +1,40 @@ + + + + ffftp + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

Loading...

+
+
+ + + + diff --git a/main.js b/main.js new file mode 100644 index 0000000..7e350a4 --- /dev/null +++ b/main.js @@ -0,0 +1,38 @@ +var app = require('electron').app; // Module to control application life. +var BrowserWindow = require('electron').BrowserWindow; // Module to create native browser window. + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +var mainWindow = null; + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On OS X it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform != 'darwin') { + app.quit(); + } +}); + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +app.on('ready', function () { + // Create the browser window. + + + mainWindow = new BrowserWindow({width: 1000, height: 600, icon: __dirname + '/icon.ico'}); + + // and load the index.html of the app. + mainWindow.loadURL('file://' + __dirname + '/index.html'); + + // Open the DevTools. + // mainWindow.openDevTools(); + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null; + }); +}); diff --git a/node_modules/.bin/asar b/node_modules/.bin/asar new file mode 100644 index 0000000..45ff677 --- /dev/null +++ b/node_modules/.bin/asar @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../asar/bin/asar" "$@" + ret=$? +else + node "$basedir/../asar/bin/asar" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/asar.cmd b/node_modules/.bin/asar.cmd new file mode 100644 index 0000000..ecaebb6 --- /dev/null +++ b/node_modules/.bin/asar.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\asar\bin\asar" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\asar\bin\asar" %* +) \ No newline at end of file diff --git a/node_modules/.bin/cake b/node_modules/.bin/cake new file mode 100644 index 0000000..481d2e9 --- /dev/null +++ b/node_modules/.bin/cake @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../coffee-script/bin/cake" "$@" + ret=$? +else + node "$basedir/../coffee-script/bin/cake" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/cake.cmd b/node_modules/.bin/cake.cmd new file mode 100644 index 0000000..5053d93 --- /dev/null +++ b/node_modules/.bin/cake.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\coffee-script\bin\cake" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\coffee-script\bin\cake" %* +) \ No newline at end of file diff --git a/node_modules/.bin/coffee b/node_modules/.bin/coffee new file mode 100644 index 0000000..0118e4c --- /dev/null +++ b/node_modules/.bin/coffee @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../coffee-script/bin/coffee" "$@" + ret=$? +else + node "$basedir/../coffee-script/bin/coffee" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/coffee.cmd b/node_modules/.bin/coffee.cmd new file mode 100644 index 0000000..72881d8 --- /dev/null +++ b/node_modules/.bin/coffee.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\coffee-script\bin\coffee" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\coffee-script\bin\coffee" %* +) \ No newline at end of file diff --git a/node_modules/.bin/dateformat b/node_modules/.bin/dateformat new file mode 100644 index 0000000..710c858 --- /dev/null +++ b/node_modules/.bin/dateformat @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../dateformat/bin/cli.js" "$@" + ret=$? +else + node "$basedir/../dateformat/bin/cli.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/dateformat.cmd b/node_modules/.bin/dateformat.cmd new file mode 100644 index 0000000..b78cb07 --- /dev/null +++ b/node_modules/.bin/dateformat.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\dateformat\bin\cli.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\dateformat\bin\cli.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/decompress-zip b/node_modules/.bin/decompress-zip new file mode 100644 index 0000000..45a9094 --- /dev/null +++ b/node_modules/.bin/decompress-zip @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../decompress-zip/bin/decompress-zip" "$@" + ret=$? +else + node "$basedir/../decompress-zip/bin/decompress-zip" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/decompress-zip.cmd b/node_modules/.bin/decompress-zip.cmd new file mode 100644 index 0000000..c61d045 --- /dev/null +++ b/node_modules/.bin/decompress-zip.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\decompress-zip\bin\decompress-zip" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\decompress-zip\bin\decompress-zip" %* +) \ No newline at end of file diff --git a/node_modules/.bin/electron b/node_modules/.bin/electron new file mode 100644 index 0000000..f223b67 --- /dev/null +++ b/node_modules/.bin/electron @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../electron/cli.js" "$@" + ret=$? +else + node "$basedir/../electron/cli.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/electron-download b/node_modules/.bin/electron-download new file mode 100644 index 0000000..f692710 --- /dev/null +++ b/node_modules/.bin/electron-download @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../electron-download/build/cli.js" "$@" + ret=$? +else + node "$basedir/../electron-download/build/cli.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/electron-download.cmd b/node_modules/.bin/electron-download.cmd new file mode 100644 index 0000000..05b8dfc --- /dev/null +++ b/node_modules/.bin/electron-download.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\electron-download\build\cli.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\electron-download\build\cli.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/electron-osx-flat b/node_modules/.bin/electron-osx-flat new file mode 100644 index 0000000..bda89d6 --- /dev/null +++ b/node_modules/.bin/electron-osx-flat @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../electron-osx-sign/bin/electron-osx-flat.js" "$@" + ret=$? +else + node "$basedir/../electron-osx-sign/bin/electron-osx-flat.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/electron-osx-flat.cmd b/node_modules/.bin/electron-osx-flat.cmd new file mode 100644 index 0000000..a4d4c81 --- /dev/null +++ b/node_modules/.bin/electron-osx-flat.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\electron-osx-sign\bin\electron-osx-flat.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\electron-osx-sign\bin\electron-osx-flat.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/electron-osx-sign b/node_modules/.bin/electron-osx-sign new file mode 100644 index 0000000..38f7949 --- /dev/null +++ b/node_modules/.bin/electron-osx-sign @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../electron-osx-sign/bin/electron-osx-sign.js" "$@" + ret=$? +else + node "$basedir/../electron-osx-sign/bin/electron-osx-sign.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/electron-osx-sign.cmd b/node_modules/.bin/electron-osx-sign.cmd new file mode 100644 index 0000000..0273fbd --- /dev/null +++ b/node_modules/.bin/electron-osx-sign.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\electron-osx-sign\bin\electron-osx-sign.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\electron-osx-sign\bin\electron-osx-sign.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/electron-packager b/node_modules/.bin/electron-packager new file mode 100644 index 0000000..e3dc46d --- /dev/null +++ b/node_modules/.bin/electron-packager @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../electron-packager/cli.js" "$@" + ret=$? +else + node "$basedir/../electron-packager/cli.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/electron-packager.cmd b/node_modules/.bin/electron-packager.cmd new file mode 100644 index 0000000..c8657ff --- /dev/null +++ b/node_modules/.bin/electron-packager.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\electron-packager\cli.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\electron-packager\cli.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/electron.cmd b/node_modules/.bin/electron.cmd new file mode 100644 index 0000000..0a14fcf --- /dev/null +++ b/node_modules/.bin/electron.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\electron\cli.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\electron\cli.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/esparse b/node_modules/.bin/esparse new file mode 100644 index 0000000..2525527 --- /dev/null +++ b/node_modules/.bin/esparse @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../esprima/bin/esparse.js" "$@" + ret=$? +else + node "$basedir/../esprima/bin/esparse.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/esparse.cmd b/node_modules/.bin/esparse.cmd new file mode 100644 index 0000000..064f58e --- /dev/null +++ b/node_modules/.bin/esparse.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\esprima\bin\esparse.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\esprima\bin\esparse.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/esvalidate b/node_modules/.bin/esvalidate new file mode 100644 index 0000000..2137cd5 --- /dev/null +++ b/node_modules/.bin/esvalidate @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../esprima/bin/esvalidate.js" "$@" + ret=$? +else + node "$basedir/../esprima/bin/esvalidate.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/esvalidate.cmd b/node_modules/.bin/esvalidate.cmd new file mode 100644 index 0000000..8dfaec0 --- /dev/null +++ b/node_modules/.bin/esvalidate.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\esprima\bin\esvalidate.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\esprima\bin\esvalidate.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/extract-zip b/node_modules/.bin/extract-zip new file mode 100644 index 0000000..401e914 --- /dev/null +++ b/node_modules/.bin/extract-zip @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../extract-zip/cli.js" "$@" + ret=$? +else + node "$basedir/../extract-zip/cli.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/extract-zip.cmd b/node_modules/.bin/extract-zip.cmd new file mode 100644 index 0000000..87bf4a1 --- /dev/null +++ b/node_modules/.bin/extract-zip.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\extract-zip\cli.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\extract-zip\cli.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/grunt b/node_modules/.bin/grunt new file mode 100644 index 0000000..e78c5db --- /dev/null +++ b/node_modules/.bin/grunt @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../grunt/bin/grunt" "$@" + ret=$? +else + node "$basedir/../grunt/bin/grunt" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/grunt.cmd b/node_modules/.bin/grunt.cmd new file mode 100644 index 0000000..edd95d5 --- /dev/null +++ b/node_modules/.bin/grunt.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\grunt\bin\grunt" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\grunt\bin\grunt" %* +) \ No newline at end of file diff --git a/node_modules/.bin/gzip-size b/node_modules/.bin/gzip-size new file mode 100644 index 0000000..76b02e0 --- /dev/null +++ b/node_modules/.bin/gzip-size @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../gzip-size/cli.js" "$@" + ret=$? +else + node "$basedir/../gzip-size/cli.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/gzip-size.cmd b/node_modules/.bin/gzip-size.cmd new file mode 100644 index 0000000..1cd9fda --- /dev/null +++ b/node_modules/.bin/gzip-size.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\gzip-size\cli.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\gzip-size\cli.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/har-validator b/node_modules/.bin/har-validator new file mode 100644 index 0000000..e5f2300 --- /dev/null +++ b/node_modules/.bin/har-validator @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../har-validator/bin/har-validator" "$@" + ret=$? +else + node "$basedir/../har-validator/bin/har-validator" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/har-validator.cmd b/node_modules/.bin/har-validator.cmd new file mode 100644 index 0000000..45e3fae --- /dev/null +++ b/node_modules/.bin/har-validator.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\har-validator\bin\har-validator" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\har-validator\bin\har-validator" %* +) \ No newline at end of file diff --git a/node_modules/.bin/js-yaml b/node_modules/.bin/js-yaml new file mode 100644 index 0000000..460d9df --- /dev/null +++ b/node_modules/.bin/js-yaml @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../js-yaml/bin/js-yaml.js" "$@" + ret=$? +else + node "$basedir/../js-yaml/bin/js-yaml.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/js-yaml.cmd b/node_modules/.bin/js-yaml.cmd new file mode 100644 index 0000000..ae8e7bc --- /dev/null +++ b/node_modules/.bin/js-yaml.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\js-yaml\bin\js-yaml.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\js-yaml\bin\js-yaml.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/mkdirp b/node_modules/.bin/mkdirp new file mode 100644 index 0000000..4b00467 --- /dev/null +++ b/node_modules/.bin/mkdirp @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../mkdirp/bin/cmd.js" "$@" + ret=$? +else + node "$basedir/../mkdirp/bin/cmd.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/mkdirp.cmd b/node_modules/.bin/mkdirp.cmd new file mode 100644 index 0000000..0d2cdd7 --- /dev/null +++ b/node_modules/.bin/mkdirp.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\mkdirp\bin\cmd.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\mkdirp\bin\cmd.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/ncp b/node_modules/.bin/ncp new file mode 100644 index 0000000..96ff01e --- /dev/null +++ b/node_modules/.bin/ncp @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../ncp/bin/ncp" "$@" + ret=$? +else + node "$basedir/../ncp/bin/ncp" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/ncp.cmd b/node_modules/.bin/ncp.cmd new file mode 100644 index 0000000..afbf6d8 --- /dev/null +++ b/node_modules/.bin/ncp.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\ncp\bin\ncp" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\ncp\bin\ncp" %* +) \ No newline at end of file diff --git a/node_modules/.bin/nopt b/node_modules/.bin/nopt new file mode 100644 index 0000000..714334e --- /dev/null +++ b/node_modules/.bin/nopt @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../nopt/bin/nopt.js" "$@" + ret=$? +else + node "$basedir/../nopt/bin/nopt.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/nopt.cmd b/node_modules/.bin/nopt.cmd new file mode 100644 index 0000000..1626454 --- /dev/null +++ b/node_modules/.bin/nopt.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\nopt\bin\nopt.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\nopt\bin\nopt.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/nugget b/node_modules/.bin/nugget new file mode 100644 index 0000000..a74d7b7 --- /dev/null +++ b/node_modules/.bin/nugget @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../nugget/bin.js" "$@" + ret=$? +else + node "$basedir/../nugget/bin.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/nugget.cmd b/node_modules/.bin/nugget.cmd new file mode 100644 index 0000000..f2cc3e8 --- /dev/null +++ b/node_modules/.bin/nugget.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\nugget\bin.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\nugget\bin.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/pretty-bytes b/node_modules/.bin/pretty-bytes new file mode 100644 index 0000000..a3d1320 --- /dev/null +++ b/node_modules/.bin/pretty-bytes @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../pretty-bytes/cli.js" "$@" + ret=$? +else + node "$basedir/../pretty-bytes/cli.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/pretty-bytes.cmd b/node_modules/.bin/pretty-bytes.cmd new file mode 100644 index 0000000..f91d69b --- /dev/null +++ b/node_modules/.bin/pretty-bytes.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\pretty-bytes\cli.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\pretty-bytes\cli.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/rc b/node_modules/.bin/rc new file mode 100644 index 0000000..22d17e5 --- /dev/null +++ b/node_modules/.bin/rc @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../rc/index.js" "$@" + ret=$? +else + node "$basedir/../rc/index.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/rc.cmd b/node_modules/.bin/rc.cmd new file mode 100644 index 0000000..8f7475e --- /dev/null +++ b/node_modules/.bin/rc.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\rc\index.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\rc\index.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/rimraf b/node_modules/.bin/rimraf new file mode 100644 index 0000000..3cebd6e --- /dev/null +++ b/node_modules/.bin/rimraf @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../rimraf/bin.js" "$@" + ret=$? +else + node "$basedir/../rimraf/bin.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/rimraf.cmd b/node_modules/.bin/rimraf.cmd new file mode 100644 index 0000000..9333ec6 --- /dev/null +++ b/node_modules/.bin/rimraf.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\rimraf\bin.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\rimraf\bin.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/semver b/node_modules/.bin/semver new file mode 100644 index 0000000..d592e69 --- /dev/null +++ b/node_modules/.bin/semver @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../semver/bin/semver" "$@" + ret=$? +else + node "$basedir/../semver/bin/semver" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/semver.cmd b/node_modules/.bin/semver.cmd new file mode 100644 index 0000000..37c00a4 --- /dev/null +++ b/node_modules/.bin/semver.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\semver\bin\semver" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\semver\bin\semver" %* +) \ No newline at end of file diff --git a/node_modules/.bin/sshpk-conv b/node_modules/.bin/sshpk-conv new file mode 100644 index 0000000..c9c2987 --- /dev/null +++ b/node_modules/.bin/sshpk-conv @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../sshpk/bin/sshpk-conv" "$@" + ret=$? +else + node "$basedir/../sshpk/bin/sshpk-conv" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/sshpk-conv.cmd b/node_modules/.bin/sshpk-conv.cmd new file mode 100644 index 0000000..dde70b0 --- /dev/null +++ b/node_modules/.bin/sshpk-conv.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\sshpk\bin\sshpk-conv" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\sshpk\bin\sshpk-conv" %* +) \ No newline at end of file diff --git a/node_modules/.bin/sshpk-sign b/node_modules/.bin/sshpk-sign new file mode 100644 index 0000000..1a92124 --- /dev/null +++ b/node_modules/.bin/sshpk-sign @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../sshpk/bin/sshpk-sign" "$@" + ret=$? +else + node "$basedir/../sshpk/bin/sshpk-sign" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/sshpk-sign.cmd b/node_modules/.bin/sshpk-sign.cmd new file mode 100644 index 0000000..45025ec --- /dev/null +++ b/node_modules/.bin/sshpk-sign.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\sshpk\bin\sshpk-sign" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\sshpk\bin\sshpk-sign" %* +) \ No newline at end of file diff --git a/node_modules/.bin/sshpk-verify b/node_modules/.bin/sshpk-verify new file mode 100644 index 0000000..597a66b --- /dev/null +++ b/node_modules/.bin/sshpk-verify @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../sshpk/bin/sshpk-verify" "$@" + ret=$? +else + node "$basedir/../sshpk/bin/sshpk-verify" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/sshpk-verify.cmd b/node_modules/.bin/sshpk-verify.cmd new file mode 100644 index 0000000..1b5fc0c --- /dev/null +++ b/node_modules/.bin/sshpk-verify.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\sshpk\bin\sshpk-verify" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\sshpk\bin\sshpk-verify" %* +) \ No newline at end of file diff --git a/node_modules/.bin/strip-indent b/node_modules/.bin/strip-indent new file mode 100644 index 0000000..dbb5259 --- /dev/null +++ b/node_modules/.bin/strip-indent @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../strip-indent/cli.js" "$@" + ret=$? +else + node "$basedir/../strip-indent/cli.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/strip-indent.cmd b/node_modules/.bin/strip-indent.cmd new file mode 100644 index 0000000..8f675e8 --- /dev/null +++ b/node_modules/.bin/strip-indent.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\strip-indent\cli.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\strip-indent\cli.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/strip-json-comments b/node_modules/.bin/strip-json-comments new file mode 100644 index 0000000..b41804c --- /dev/null +++ b/node_modules/.bin/strip-json-comments @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../strip-json-comments/cli.js" "$@" + ret=$? +else + node "$basedir/../strip-json-comments/cli.js" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/strip-json-comments.cmd b/node_modules/.bin/strip-json-comments.cmd new file mode 100644 index 0000000..16825d1 --- /dev/null +++ b/node_modules/.bin/strip-json-comments.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\strip-json-comments\cli.js" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\strip-json-comments\cli.js" %* +) \ No newline at end of file diff --git a/node_modules/.bin/uglifyjs b/node_modules/.bin/uglifyjs new file mode 100644 index 0000000..de7b74f --- /dev/null +++ b/node_modules/.bin/uglifyjs @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../uglify-js/bin/uglifyjs" "$@" + ret=$? +else + node "$basedir/../uglify-js/bin/uglifyjs" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/uglifyjs.cmd b/node_modules/.bin/uglifyjs.cmd new file mode 100644 index 0000000..3306013 --- /dev/null +++ b/node_modules/.bin/uglifyjs.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\uglify-js\bin\uglifyjs" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\uglify-js\bin\uglifyjs" %* +) \ No newline at end of file diff --git a/node_modules/.bin/uuid b/node_modules/.bin/uuid new file mode 100644 index 0000000..f3bfcf4 --- /dev/null +++ b/node_modules/.bin/uuid @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../uuid/bin/uuid" "$@" + ret=$? +else + node "$basedir/../uuid/bin/uuid" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/uuid.cmd b/node_modules/.bin/uuid.cmd new file mode 100644 index 0000000..da52d68 --- /dev/null +++ b/node_modules/.bin/uuid.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\uuid\bin\uuid" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\uuid\bin\uuid" %* +) \ No newline at end of file diff --git a/node_modules/.bin/which b/node_modules/.bin/which new file mode 100644 index 0000000..cbe872c --- /dev/null +++ b/node_modules/.bin/which @@ -0,0 +1,15 @@ +#!/bin/sh +basedir=$(dirname "$(echo "$0" | sed -e 's,\\,/,g')") + +case `uname` in + *CYGWIN*) basedir=`cygpath -w "$basedir"`;; +esac + +if [ -x "$basedir/node" ]; then + "$basedir/node" "$basedir/../which/bin/which" "$@" + ret=$? +else + node "$basedir/../which/bin/which" "$@" + ret=$? +fi +exit $ret diff --git a/node_modules/.bin/which.cmd b/node_modules/.bin/which.cmd new file mode 100644 index 0000000..588f44d --- /dev/null +++ b/node_modules/.bin/which.cmd @@ -0,0 +1,7 @@ +@IF EXIST "%~dp0\node.exe" ( + "%~dp0\node.exe" "%~dp0\..\which\bin\which" %* +) ELSE ( + @SETLOCAL + @SET PATHEXT=%PATHEXT:;.JS;=;% + node "%~dp0\..\which\bin\which" %* +) \ No newline at end of file diff --git a/node_modules/abbrev/LICENSE b/node_modules/abbrev/LICENSE new file mode 100644 index 0000000..19129e3 --- /dev/null +++ b/node_modules/abbrev/LICENSE @@ -0,0 +1,15 @@ +The ISC License + +Copyright (c) Isaac Z. Schlueter and Contributors + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR +IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/node_modules/abbrev/README.md b/node_modules/abbrev/README.md new file mode 100644 index 0000000..99746fe --- /dev/null +++ b/node_modules/abbrev/README.md @@ -0,0 +1,23 @@ +# abbrev-js + +Just like [ruby's Abbrev](http://apidock.com/ruby/Abbrev). + +Usage: + + var abbrev = require("abbrev"); + abbrev("foo", "fool", "folding", "flop"); + + // returns: + { fl: 'flop' + , flo: 'flop' + , flop: 'flop' + , fol: 'folding' + , fold: 'folding' + , foldi: 'folding' + , foldin: 'folding' + , folding: 'folding' + , foo: 'foo' + , fool: 'fool' + } + +This is handy for command-line scripts, or other cases where you want to be able to accept shorthands. diff --git a/node_modules/abbrev/abbrev.js b/node_modules/abbrev/abbrev.js new file mode 100644 index 0000000..69cfeac --- /dev/null +++ b/node_modules/abbrev/abbrev.js @@ -0,0 +1,62 @@ + +module.exports = exports = abbrev.abbrev = abbrev + +abbrev.monkeyPatch = monkeyPatch + +function monkeyPatch () { + Object.defineProperty(Array.prototype, 'abbrev', { + value: function () { return abbrev(this) }, + enumerable: false, configurable: true, writable: true + }) + + Object.defineProperty(Object.prototype, 'abbrev', { + value: function () { return abbrev(Object.keys(this)) }, + enumerable: false, configurable: true, writable: true + }) +} + +function abbrev (list) { + if (arguments.length !== 1 || !Array.isArray(list)) { + list = Array.prototype.slice.call(arguments, 0) + } + for (var i = 0, l = list.length, args = [] ; i < l ; i ++) { + args[i] = typeof list[i] === "string" ? list[i] : String(list[i]) + } + + // sort them lexicographically, so that they're next to their nearest kin + args = args.sort(lexSort) + + // walk through each, seeing how much it has in common with the next and previous + var abbrevs = {} + , prev = "" + for (var i = 0, l = args.length ; i < l ; i ++) { + var current = args[i] + , next = args[i + 1] || "" + , nextMatches = true + , prevMatches = true + if (current === next) continue + for (var j = 0, cl = current.length ; j < cl ; j ++) { + var curChar = current.charAt(j) + nextMatches = nextMatches && curChar === next.charAt(j) + prevMatches = prevMatches && curChar === prev.charAt(j) + if (!nextMatches && !prevMatches) { + j ++ + break + } + } + prev = current + if (j === cl) { + abbrevs[current] = current + continue + } + for (var a = current.substr(0, j) ; j <= cl ; j ++) { + abbrevs[a] = current + a += current.charAt(j) + } + } + return abbrevs +} + +function lexSort (a, b) { + return a === b ? 0 : a > b ? 1 : -1 +} diff --git a/node_modules/abbrev/package.json b/node_modules/abbrev/package.json new file mode 100644 index 0000000..eef9d36 --- /dev/null +++ b/node_modules/abbrev/package.json @@ -0,0 +1,90 @@ +{ + "_args": [ + [ + { + "raw": "abbrev@1", + "scope": null, + "escapedName": "abbrev", + "name": "abbrev", + "rawSpec": "1", + "spec": ">=1.0.0 <2.0.0", + "type": "range" + }, + "C:\\Users\\samue\\documents\\github\\ffftp\\node_modules\\nopt" + ] + ], + "_from": "abbrev@>=1.0.0 <2.0.0", + "_id": "abbrev@1.0.9", + "_inCache": true, + "_location": "/abbrev", + "_nodeVersion": "4.4.4", + "_npmOperationalInternal": { + "host": "packages-16-east.internal.npmjs.com", + "tmp": "tmp/abbrev-1.0.9.tgz_1466016055839_0.7825860097073019" + }, + "_npmUser": { + "name": "isaacs", + "email": "i@izs.me" + }, + "_npmVersion": "3.9.1", + "_phantomChildren": {}, + "_requested": { + "raw": "abbrev@1", + "scope": null, + "escapedName": "abbrev", + "name": "abbrev", + "rawSpec": "1", + "spec": ">=1.0.0 <2.0.0", + "type": "range" + }, + "_requiredBy": [ + "/nopt", + "/touch/nopt" + ], + "_resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "_shasum": "91b4792588a7738c25f35dd6f63752a2f8776135", + "_shrinkwrap": null, + "_spec": "abbrev@1", + "_where": "C:\\Users\\samue\\documents\\github\\ffftp\\node_modules\\nopt", + "author": { + "name": "Isaac Z. Schlueter", + "email": "i@izs.me" + }, + "bugs": { + "url": "https://github.com/isaacs/abbrev-js/issues" + }, + "dependencies": {}, + "description": "Like ruby's abbrev module, but in js", + "devDependencies": { + "tap": "^5.7.2" + }, + "directories": {}, + "dist": { + "shasum": "91b4792588a7738c25f35dd6f63752a2f8776135", + "tarball": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz" + }, + "files": [ + "abbrev.js" + ], + "gitHead": "c386cd9dbb1d8d7581718c54d4ba944cc9298d6f", + "homepage": "https://github.com/isaacs/abbrev-js#readme", + "license": "ISC", + "main": "abbrev.js", + "maintainers": [ + { + "name": "isaacs", + "email": "i@izs.me" + } + ], + "name": "abbrev", + "optionalDependencies": {}, + "readme": "ERROR: No README data found!", + "repository": { + "type": "git", + "url": "git+ssh://git@github.com/isaacs/abbrev-js.git" + }, + "scripts": { + "test": "tap test.js --cov" + }, + "version": "1.0.9" +} diff --git a/node_modules/align-text/LICENSE b/node_modules/align-text/LICENSE new file mode 100644 index 0000000..65f90ac --- /dev/null +++ b/node_modules/align-text/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015, Jon Schlinkert. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/align-text/README.md b/node_modules/align-text/README.md new file mode 100644 index 0000000..476b97f --- /dev/null +++ b/node_modules/align-text/README.md @@ -0,0 +1,236 @@ +# align-text [![NPM version](https://badge.fury.io/js/align-text.svg)](http://badge.fury.io/js/align-text) [![Build Status](https://travis-ci.org/jonschlinkert/align-text.svg)](https://travis-ci.org/jonschlinkert/align-text) + +> Align the text in a string. + +**Examples** + +Align text values in an array: + +```js +align([1, 2, 3, 100]); +//=> [' 1', ' 2', ' 3', '100'] +``` + +Or [do stuff like this](./example.js): + +[![screen shot 2015-06-09 at 2 08 34 am](https://cloud.githubusercontent.com/assets/383994/8051597/7b716fbc-0e4c-11e5-9aef-4493fd22db58.png)](./example.js) + +Visit [the example](./example.js) to see how this works. + +## Install + +Install with [npm](https://www.npmjs.com/) + +```sh +$ npm i align-text --save +``` + +## Usage + +```js +var align = require('align-text'); +align(text, callback_function_or_integer); +``` + +**Params** + +* `text` can be a **string or array**. If a string is passed, a string will be returned. If an array is passed, an array will be returned. +* `callback|integer`: if an integer, the text will be indented by that amount. If a function, it must return an integer representing the amount of leading indentation to use as `align` loops over each line. + +**Example** + +```js +align(text, 4); +``` + +Would align: + +``` +abc +abc +abc +``` + +To: + +``` + abc + abc + abc +``` + +## callback + +### params + +The callback is used to determine the indentation of each line and gets the following params: + +* `len` the length of the "current" line +* `longest` the length of the longest line +* `line` the current line (string) being aligned +* `lines` the array of all lines + +### return + +The callback may return: + +* an integer that represents the number of spaces to use for padding, +* or an object with the following properties: + - `indent`: **{Number}** the amount of indentation to use. Default is `0` when an object is returned. + - `character`: **{String}** the character to use for indentation. Default is `''` (empty string) when an object is returned. + - `prefix`: **{String}** leading characters to use at the beginning of each line. `''` (empty string) when an object is returned. + +**Integer example:** + +```js +// calculate half the difference between the length +// of the current line and the longest line +function centerAlign(len, longest, line, lines) { + return Math.floor((longest - len) / 2); +} +``` + +**Object example:** + +```js +function centerAlign(len, longest, line, lines) { + return { + character: '\t', + indent: Math.floor((longest - len) / 2), + prefix: '~ ', + } +} +``` + +## Usage examples + +### Center align + +Using the `centerAlign` function from above: + +```js +align(text, centerAlign); +``` + +Would align this text: + +```js +Lorem ipsum dolor sit amet +consectetur adipiscin +elit, sed do eiusmod tempor incididun +ut labore et dolor +magna aliqua. Ut enim ad mini +veniam, quis +``` + +Resulting in this: + +``` + Lorem ipsum dolor sit amet, + consectetur adipiscing +elit, sed do eiusmod tempor incididunt + ut labore et dolore + magna aliqua. Ut enim ad minim + veniam, quis +``` + +**Customize** + +If you wanted to add more padding on the left, just pass the number in the callback. + +For example, to add 4 spaces before every line: + +```js +function centerAlign(len, longest, line, lines) { + return 4 + Math.floor((longest - len) / 2); +} +``` + +Would result in: + +``` + Lorem ipsum dolor sit amet, + consectetur adipiscing + elit, sed do eiusmod tempor incididunt + ut labore et dolore + magna aliqua. Ut enim ad minim + veniam, quis +``` + +### Bullets + +```js +align(text, function (len, max, line, lines) { + return {prefix: ' - '}; +}); +``` + +Would return: + +``` +- Lorem ipsum dolor sit amet, +- consectetur adipiscing +- elit, sed do eiusmod tempor incididunt +- ut labore et dolore +- magna aliqua. Ut enim ad minim +- veniam, quis +``` + +### Different indent character + +```js +align(text, function (len, max, line, lines) { + return { + indent: Math.floor((max - len) / 2), + character: '~', + }; +}); +``` + +Would return + +``` +~~~~~Lorem ipsum dolor sit amet, +~~~~~~~~consectetur adipiscing +elit, sed do eiusmod tempor incididunt +~~~~~~~~~ut labore et dolore +~~~~magna aliqua. Ut enim ad minim +~~~~~~~~~~~~~veniam, quis +``` + +## Related projects + +* [center-align](https://github.com/jonschlinkert/center-align): Center-align the text in a string. +* [justify](https://github.com/bahamas10/node-justify): Left or right (or both) justify text using a custom width and character +* [longest](https://github.com/jonschlinkert/longest): Get the longest item in an array. +* [right-align](https://github.com/jonschlinkert/right-align): Right-align the text in a string. +* [repeat-string](https://github.com/jonschlinkert/repeat-string): Repeat the given string n times. Fastest implementation for repeating a string. +* [word-wrap](https://github.com/jonschlinkert/word-wrap): Wrap words to a specified length. + +## Running tests + +Install dev dependencies: + +```sh +$ npm i -d && npm test +``` + +## Contributing + +Pull requests and stars are always welcome. For bugs and feature requests, [please create an issue](https://github.com/jonschlinkert/align-text/issues/new) + +## Author + +**Jon Schlinkert** + ++ [github/jonschlinkert](https://github.com/jonschlinkert) ++ [twitter/jonschlinkert](http://twitter.com/jonschlinkert) + +## License + +Copyright © 2015 [Jon Schlinkert](https://github.com/jonschlinkert) +Released under the MIT license. + +*** + +_This file was generated by [verb-cli](https://github.com/assemble/verb-cli) on June 09, 2015._ diff --git a/node_modules/align-text/index.js b/node_modules/align-text/index.js new file mode 100644 index 0000000..75902a3 --- /dev/null +++ b/node_modules/align-text/index.js @@ -0,0 +1,52 @@ +/*! + * align-text + * + * Copyright (c) 2015, Jon Schlinkert. + * Licensed under the MIT License. + */ + +'use strict'; + +var typeOf = require('kind-of'); +var repeat = require('repeat-string'); +var longest = require('longest'); + +module.exports = function alignText(val, fn) { + var lines, type = typeOf(val); + + if (type === 'array') { + lines = val; + } else if (type === 'string') { + lines = val.split(/(?:\r\n|\n)/); + } else { + throw new TypeError('align-text expects a string or array.'); + } + + var fnType = typeOf(fn); + var len = lines.length; + var max = longest(lines); + var res = [], i = 0; + + while (len--) { + var line = String(lines[i++]); + var diff; + + if (fnType === 'function') { + diff = fn(line.length, max.length, line, lines, i); + } else if (fnType === 'number') { + diff = fn; + } else { + diff = max.length - line.length; + } + + if (typeOf(diff) === 'number') { + res.push(repeat(' ', diff) + line); + } else if (typeOf(diff) === 'object') { + var result = repeat(diff.character || ' ', diff.indent || 0); + res.push((diff.prefix || '') + result + line); + } + } + + if (type === 'array') return res; + return res.join('\n'); +}; diff --git a/node_modules/align-text/package.json b/node_modules/align-text/package.json new file mode 100644 index 0000000..144915d --- /dev/null +++ b/node_modules/align-text/package.json @@ -0,0 +1,117 @@ +{ + "_args": [ + [ + { + "raw": "align-text@^0.1.3", + "scope": null, + "escapedName": "align-text", + "name": "align-text", + "rawSpec": "^0.1.3", + "spec": ">=0.1.3 <0.2.0", + "type": "range" + }, + "C:\\Users\\samue\\documents\\github\\ffftp\\node_modules\\center-align" + ] + ], + "_from": "align-text@>=0.1.3 <0.2.0", + "_id": "align-text@0.1.4", + "_inCache": true, + "_location": "/align-text", + "_nodeVersion": "5.5.0", + "_npmOperationalInternal": { + "host": "packages-9-west.internal.npmjs.com", + "tmp": "tmp/align-text-0.1.4.tgz_1454377856920_0.9624228512402624" + }, + "_npmUser": { + "name": "shinnn", + "email": "snnskwtnb@gmail.com" + }, + "_npmVersion": "3.6.0", + "_phantomChildren": {}, + "_requested": { + "raw": "align-text@^0.1.3", + "scope": null, + "escapedName": "align-text", + "name": "align-text", + "rawSpec": "^0.1.3", + "spec": ">=0.1.3 <0.2.0", + "type": "range" + }, + "_requiredBy": [ + "/center-align", + "/right-align" + ], + "_resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "_shasum": "0cd90a561093f35d0a99256c22b7069433fad117", + "_shrinkwrap": null, + "_spec": "align-text@^0.1.3", + "_where": "C:\\Users\\samue\\documents\\github\\ffftp\\node_modules\\center-align", + "author": { + "name": "Jon Schlinkert", + "url": "https://github.com/jonschlinkert" + }, + "bugs": { + "url": "https://github.com/jonschlinkert/align-text/issues" + }, + "dependencies": { + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" + }, + "description": "Align the text in a string.", + "devDependencies": { + "mocha": "*", + "should": "*", + "word-wrap": "^1.0.3" + }, + "directories": {}, + "dist": { + "shasum": "0cd90a561093f35d0a99256c22b7069433fad117", + "tarball": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz" + }, + "engines": { + "node": ">=0.10.0" + }, + "files": [ + "index.js" + ], + "gitHead": "7f08e823a54c6bda319d875895813537a66a4c5e", + "homepage": "https://github.com/jonschlinkert/align-text", + "keywords": [ + "align", + "align-center", + "alignment", + "center", + "center-align", + "indent", + "pad", + "padding", + "right", + "right-align", + "text", + "typography" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "name": "jonschlinkert", + "email": "github@sellside.com" + }, + { + "name": "shinnn", + "email": "snnskwtnb@gmail.com" + } + ], + "name": "align-text", + "optionalDependencies": {}, + "readme": "ERROR: No README data found!", + "repository": { + "type": "git", + "url": "git://github.com/jonschlinkert/align-text.git" + }, + "scripts": { + "test": "mocha" + }, + "version": "0.1.4" +} diff --git a/node_modules/angular-animate/LICENSE.md b/node_modules/angular-animate/LICENSE.md new file mode 100644 index 0000000..2c395ee --- /dev/null +++ b/node_modules/angular-animate/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Angular + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/angular-animate/README.md b/node_modules/angular-animate/README.md new file mode 100644 index 0000000..8313da6 --- /dev/null +++ b/node_modules/angular-animate/README.md @@ -0,0 +1,68 @@ +# packaged angular-animate + +This repo is for distribution on `npm` and `bower`. The source for this module is in the +[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngAnimate). +Please file issues and pull requests against that repo. + +## Install + +You can install this package either with `npm` or with `bower`. + +### npm + +```shell +npm install angular-animate +``` + +Then add `ngAnimate` as a dependency for your app: + +```javascript +angular.module('myApp', [require('angular-animate')]); +``` + +### bower + +```shell +bower install angular-animate +``` + +Then add a ` +``` + +Then add `ngAnimate` as a dependency for your app: + +```javascript +angular.module('myApp', ['ngAnimate']); +``` + +## Documentation + +Documentation is available on the +[AngularJS docs site](http://docs.angularjs.org/api/ngAnimate). + +## License + +The MIT License + +Copyright (c) 2010-2015 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/angular-animate/angular-animate.js b/node_modules/angular-animate/angular-animate.js new file mode 100644 index 0000000..ccf98b7 --- /dev/null +++ b/node_modules/angular-animate/angular-animate.js @@ -0,0 +1,4152 @@ +/** + * @license AngularJS v1.5.9 + * (c) 2010-2016 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular) {'use strict'; + +var ELEMENT_NODE = 1; +var COMMENT_NODE = 8; + +var ADD_CLASS_SUFFIX = '-add'; +var REMOVE_CLASS_SUFFIX = '-remove'; +var EVENT_CLASS_PREFIX = 'ng-'; +var ACTIVE_CLASS_SUFFIX = '-active'; +var PREPARE_CLASS_SUFFIX = '-prepare'; + +var NG_ANIMATE_CLASSNAME = 'ng-animate'; +var NG_ANIMATE_CHILDREN_DATA = '$$ngAnimateChildren'; + +// Detect proper transitionend/animationend event names. +var CSS_PREFIX = '', TRANSITION_PROP, TRANSITIONEND_EVENT, ANIMATION_PROP, ANIMATIONEND_EVENT; + +// If unprefixed events are not supported but webkit-prefixed are, use the latter. +// Otherwise, just use W3C names, browsers not supporting them at all will just ignore them. +// Note: Chrome implements `window.onwebkitanimationend` and doesn't implement `window.onanimationend` +// but at the same time dispatches the `animationend` event and not `webkitAnimationEnd`. +// Register both events in case `window.onanimationend` is not supported because of that, +// do the same for `transitionend` as Safari is likely to exhibit similar behavior. +// Also, the only modern browser that uses vendor prefixes for transitions/keyframes is webkit +// therefore there is no reason to test anymore for other vendor prefixes: +// http://caniuse.com/#search=transition +if ((window.ontransitionend === undefined) && (window.onwebkittransitionend !== undefined)) { + CSS_PREFIX = '-webkit-'; + TRANSITION_PROP = 'WebkitTransition'; + TRANSITIONEND_EVENT = 'webkitTransitionEnd transitionend'; +} else { + TRANSITION_PROP = 'transition'; + TRANSITIONEND_EVENT = 'transitionend'; +} + +if ((window.onanimationend === undefined) && (window.onwebkitanimationend !== undefined)) { + CSS_PREFIX = '-webkit-'; + ANIMATION_PROP = 'WebkitAnimation'; + ANIMATIONEND_EVENT = 'webkitAnimationEnd animationend'; +} else { + ANIMATION_PROP = 'animation'; + ANIMATIONEND_EVENT = 'animationend'; +} + +var DURATION_KEY = 'Duration'; +var PROPERTY_KEY = 'Property'; +var DELAY_KEY = 'Delay'; +var TIMING_KEY = 'TimingFunction'; +var ANIMATION_ITERATION_COUNT_KEY = 'IterationCount'; +var ANIMATION_PLAYSTATE_KEY = 'PlayState'; +var SAFE_FAST_FORWARD_DURATION_VALUE = 9999; + +var ANIMATION_DELAY_PROP = ANIMATION_PROP + DELAY_KEY; +var ANIMATION_DURATION_PROP = ANIMATION_PROP + DURATION_KEY; +var TRANSITION_DELAY_PROP = TRANSITION_PROP + DELAY_KEY; +var TRANSITION_DURATION_PROP = TRANSITION_PROP + DURATION_KEY; + +var ngMinErr = angular.$$minErr('ng'); +function assertArg(arg, name, reason) { + if (!arg) { + throw ngMinErr('areq', 'Argument \'{0}\' is {1}', (name || '?'), (reason || 'required')); + } + return arg; +} + +function mergeClasses(a,b) { + if (!a && !b) return ''; + if (!a) return b; + if (!b) return a; + if (isArray(a)) a = a.join(' '); + if (isArray(b)) b = b.join(' '); + return a + ' ' + b; +} + +function packageStyles(options) { + var styles = {}; + if (options && (options.to || options.from)) { + styles.to = options.to; + styles.from = options.from; + } + return styles; +} + +function pendClasses(classes, fix, isPrefix) { + var className = ''; + classes = isArray(classes) + ? classes + : classes && isString(classes) && classes.length + ? classes.split(/\s+/) + : []; + forEach(classes, function(klass, i) { + if (klass && klass.length > 0) { + className += (i > 0) ? ' ' : ''; + className += isPrefix ? fix + klass + : klass + fix; + } + }); + return className; +} + +function removeFromArray(arr, val) { + var index = arr.indexOf(val); + if (val >= 0) { + arr.splice(index, 1); + } +} + +function stripCommentsFromElement(element) { + if (element instanceof jqLite) { + switch (element.length) { + case 0: + return element; + + case 1: + // there is no point of stripping anything if the element + // is the only element within the jqLite wrapper. + // (it's important that we retain the element instance.) + if (element[0].nodeType === ELEMENT_NODE) { + return element; + } + break; + + default: + return jqLite(extractElementNode(element)); + } + } + + if (element.nodeType === ELEMENT_NODE) { + return jqLite(element); + } +} + +function extractElementNode(element) { + if (!element[0]) return element; + for (var i = 0; i < element.length; i++) { + var elm = element[i]; + if (elm.nodeType === ELEMENT_NODE) { + return elm; + } + } +} + +function $$addClass($$jqLite, element, className) { + forEach(element, function(elm) { + $$jqLite.addClass(elm, className); + }); +} + +function $$removeClass($$jqLite, element, className) { + forEach(element, function(elm) { + $$jqLite.removeClass(elm, className); + }); +} + +function applyAnimationClassesFactory($$jqLite) { + return function(element, options) { + if (options.addClass) { + $$addClass($$jqLite, element, options.addClass); + options.addClass = null; + } + if (options.removeClass) { + $$removeClass($$jqLite, element, options.removeClass); + options.removeClass = null; + } + }; +} + +function prepareAnimationOptions(options) { + options = options || {}; + if (!options.$$prepared) { + var domOperation = options.domOperation || noop; + options.domOperation = function() { + options.$$domOperationFired = true; + domOperation(); + domOperation = noop; + }; + options.$$prepared = true; + } + return options; +} + +function applyAnimationStyles(element, options) { + applyAnimationFromStyles(element, options); + applyAnimationToStyles(element, options); +} + +function applyAnimationFromStyles(element, options) { + if (options.from) { + element.css(options.from); + options.from = null; + } +} + +function applyAnimationToStyles(element, options) { + if (options.to) { + element.css(options.to); + options.to = null; + } +} + +function mergeAnimationDetails(element, oldAnimation, newAnimation) { + var target = oldAnimation.options || {}; + var newOptions = newAnimation.options || {}; + + var toAdd = (target.addClass || '') + ' ' + (newOptions.addClass || ''); + var toRemove = (target.removeClass || '') + ' ' + (newOptions.removeClass || ''); + var classes = resolveElementClasses(element.attr('class'), toAdd, toRemove); + + if (newOptions.preparationClasses) { + target.preparationClasses = concatWithSpace(newOptions.preparationClasses, target.preparationClasses); + delete newOptions.preparationClasses; + } + + // noop is basically when there is no callback; otherwise something has been set + var realDomOperation = target.domOperation !== noop ? target.domOperation : null; + + extend(target, newOptions); + + // TODO(matsko or sreeramu): proper fix is to maintain all animation callback in array and call at last,but now only leave has the callback so no issue with this. + if (realDomOperation) { + target.domOperation = realDomOperation; + } + + if (classes.addClass) { + target.addClass = classes.addClass; + } else { + target.addClass = null; + } + + if (classes.removeClass) { + target.removeClass = classes.removeClass; + } else { + target.removeClass = null; + } + + oldAnimation.addClass = target.addClass; + oldAnimation.removeClass = target.removeClass; + + return target; +} + +function resolveElementClasses(existing, toAdd, toRemove) { + var ADD_CLASS = 1; + var REMOVE_CLASS = -1; + + var flags = {}; + existing = splitClassesToLookup(existing); + + toAdd = splitClassesToLookup(toAdd); + forEach(toAdd, function(value, key) { + flags[key] = ADD_CLASS; + }); + + toRemove = splitClassesToLookup(toRemove); + forEach(toRemove, function(value, key) { + flags[key] = flags[key] === ADD_CLASS ? null : REMOVE_CLASS; + }); + + var classes = { + addClass: '', + removeClass: '' + }; + + forEach(flags, function(val, klass) { + var prop, allow; + if (val === ADD_CLASS) { + prop = 'addClass'; + allow = !existing[klass] || existing[klass + REMOVE_CLASS_SUFFIX]; + } else if (val === REMOVE_CLASS) { + prop = 'removeClass'; + allow = existing[klass] || existing[klass + ADD_CLASS_SUFFIX]; + } + if (allow) { + if (classes[prop].length) { + classes[prop] += ' '; + } + classes[prop] += klass; + } + }); + + function splitClassesToLookup(classes) { + if (isString(classes)) { + classes = classes.split(' '); + } + + var obj = {}; + forEach(classes, function(klass) { + // sometimes the split leaves empty string values + // incase extra spaces were applied to the options + if (klass.length) { + obj[klass] = true; + } + }); + return obj; + } + + return classes; +} + +function getDomNode(element) { + return (element instanceof jqLite) ? element[0] : element; +} + +function applyGeneratedPreparationClasses(element, event, options) { + var classes = ''; + if (event) { + classes = pendClasses(event, EVENT_CLASS_PREFIX, true); + } + if (options.addClass) { + classes = concatWithSpace(classes, pendClasses(options.addClass, ADD_CLASS_SUFFIX)); + } + if (options.removeClass) { + classes = concatWithSpace(classes, pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX)); + } + if (classes.length) { + options.preparationClasses = classes; + element.addClass(classes); + } +} + +function clearGeneratedClasses(element, options) { + if (options.preparationClasses) { + element.removeClass(options.preparationClasses); + options.preparationClasses = null; + } + if (options.activeClasses) { + element.removeClass(options.activeClasses); + options.activeClasses = null; + } +} + +function blockTransitions(node, duration) { + // we use a negative delay value since it performs blocking + // yet it doesn't kill any existing transitions running on the + // same element which makes this safe for class-based animations + var value = duration ? '-' + duration + 's' : ''; + applyInlineStyle(node, [TRANSITION_DELAY_PROP, value]); + return [TRANSITION_DELAY_PROP, value]; +} + +function blockKeyframeAnimations(node, applyBlock) { + var value = applyBlock ? 'paused' : ''; + var key = ANIMATION_PROP + ANIMATION_PLAYSTATE_KEY; + applyInlineStyle(node, [key, value]); + return [key, value]; +} + +function applyInlineStyle(node, styleTuple) { + var prop = styleTuple[0]; + var value = styleTuple[1]; + node.style[prop] = value; +} + +function concatWithSpace(a,b) { + if (!a) return b; + if (!b) return a; + return a + ' ' + b; +} + +var $$rAFSchedulerFactory = ['$$rAF', function($$rAF) { + var queue, cancelFn; + + function scheduler(tasks) { + // we make a copy since RAFScheduler mutates the state + // of the passed in array variable and this would be difficult + // to track down on the outside code + queue = queue.concat(tasks); + nextTick(); + } + + queue = scheduler.queue = []; + + /* waitUntilQuiet does two things: + * 1. It will run the FINAL `fn` value only when an uncanceled RAF has passed through + * 2. It will delay the next wave of tasks from running until the quiet `fn` has run. + * + * The motivation here is that animation code can request more time from the scheduler + * before the next wave runs. This allows for certain DOM properties such as classes to + * be resolved in time for the next animation to run. + */ + scheduler.waitUntilQuiet = function(fn) { + if (cancelFn) cancelFn(); + + cancelFn = $$rAF(function() { + cancelFn = null; + fn(); + nextTick(); + }); + }; + + return scheduler; + + function nextTick() { + if (!queue.length) return; + + var items = queue.shift(); + for (var i = 0; i < items.length; i++) { + items[i](); + } + + if (!cancelFn) { + $$rAF(function() { + if (!cancelFn) nextTick(); + }); + } + } +}]; + +/** + * @ngdoc directive + * @name ngAnimateChildren + * @restrict AE + * @element ANY + * + * @description + * + * ngAnimateChildren allows you to specify that children of this element should animate even if any + * of the children's parents are currently animating. By default, when an element has an active `enter`, `leave`, or `move` + * (structural) animation, child elements that also have an active structural animation are not animated. + * + * Note that even if `ngAnimteChildren` is set, no child animations will run when the parent element is removed from the DOM (`leave` animation). + * + * + * @param {string} ngAnimateChildren If the value is empty, `true` or `on`, + * then child animations are allowed. If the value is `false`, child animations are not allowed. + * + * @example + * + +
+ + +
+
+
+ List of items: +
Item {{item}}
+
+
+
+
+ + + .container.ng-enter, + .container.ng-leave { + transition: all ease 1.5s; + } + + .container.ng-enter, + .container.ng-leave-active { + opacity: 0; + } + + .container.ng-leave, + .container.ng-enter-active { + opacity: 1; + } + + .item { + background: firebrick; + color: #FFF; + margin-bottom: 10px; + } + + .item.ng-enter, + .item.ng-leave { + transition: transform 1.5s ease; + } + + .item.ng-enter { + transform: translateX(50px); + } + + .item.ng-enter-active { + transform: translateX(0); + } + + + angular.module('ngAnimateChildren', ['ngAnimate']) + .controller('MainController', function MainController() { + this.animateChildren = false; + this.enterElement = false; + }); + +
+ */ +var $$AnimateChildrenDirective = ['$interpolate', function($interpolate) { + return { + link: function(scope, element, attrs) { + var val = attrs.ngAnimateChildren; + if (isString(val) && val.length === 0) { //empty attribute + element.data(NG_ANIMATE_CHILDREN_DATA, true); + } else { + // Interpolate and set the value, so that it is available to + // animations that run right after compilation + setData($interpolate(val)(scope)); + attrs.$observe('ngAnimateChildren', setData); + } + + function setData(value) { + value = value === 'on' || value === 'true'; + element.data(NG_ANIMATE_CHILDREN_DATA, value); + } + } + }; +}]; + +/* exported $AnimateCssProvider */ + +var ANIMATE_TIMER_KEY = '$$animateCss'; + +/** + * @ngdoc service + * @name $animateCss + * @kind object + * + * @description + * The `$animateCss` service is a useful utility to trigger customized CSS-based transitions/keyframes + * from a JavaScript-based animation or directly from a directive. The purpose of `$animateCss` is NOT + * to side-step how `$animate` and ngAnimate work, but the goal is to allow pre-existing animations or + * directives to create more complex animations that can be purely driven using CSS code. + * + * Note that only browsers that support CSS transitions and/or keyframe animations are capable of + * rendering animations triggered via `$animateCss` (bad news for IE9 and lower). + * + * ## Usage + * Once again, `$animateCss` is designed to be used inside of a registered JavaScript animation that + * is powered by ngAnimate. It is possible to use `$animateCss` directly inside of a directive, however, + * any automatic control over cancelling animations and/or preventing animations from being run on + * child elements will not be handled by Angular. For this to work as expected, please use `$animate` to + * trigger the animation and then setup a JavaScript animation that injects `$animateCss` to trigger + * the CSS animation. + * + * The example below shows how we can create a folding animation on an element using `ng-if`: + * + * ```html + * + *
+ * This element will go BOOM + *
+ * + * ``` + * + * Now we create the **JavaScript animation** that will trigger the CSS transition: + * + * ```js + * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) { + * return { + * enter: function(element, doneFn) { + * var height = element[0].offsetHeight; + * return $animateCss(element, { + * from: { height:'0px' }, + * to: { height:height + 'px' }, + * duration: 1 // one second + * }); + * } + * } + * }]); + * ``` + * + * ## More Advanced Uses + * + * `$animateCss` is the underlying code that ngAnimate uses to power **CSS-based animations** behind the scenes. Therefore CSS hooks + * like `.ng-EVENT`, `.ng-EVENT-active`, `.ng-EVENT-stagger` are all features that can be triggered using `$animateCss` via JavaScript code. + * + * This also means that just about any combination of adding classes, removing classes, setting styles, dynamically setting a keyframe animation, + * applying a hardcoded duration or delay value, changing the animation easing or applying a stagger animation are all options that work with + * `$animateCss`. The service itself is smart enough to figure out the combination of options and examine the element styling properties in order + * to provide a working animation that will run in CSS. + * + * The example below showcases a more advanced version of the `.fold-animation` from the example above: + * + * ```js + * ngModule.animation('.fold-animation', ['$animateCss', function($animateCss) { + * return { + * enter: function(element, doneFn) { + * var height = element[0].offsetHeight; + * return $animateCss(element, { + * addClass: 'red large-text pulse-twice', + * easing: 'ease-out', + * from: { height:'0px' }, + * to: { height:height + 'px' }, + * duration: 1 // one second + * }); + * } + * } + * }]); + * ``` + * + * Since we're adding/removing CSS classes then the CSS transition will also pick those up: + * + * ```css + * /* since a hardcoded duration value of 1 was provided in the JavaScript animation code, + * the CSS classes below will be transitioned despite them being defined as regular CSS classes */ + * .red { background:red; } + * .large-text { font-size:20px; } + * + * /* we can also use a keyframe animation and $animateCss will make it work alongside the transition */ + * .pulse-twice { + * animation: 0.5s pulse linear 2; + * -webkit-animation: 0.5s pulse linear 2; + * } + * + * @keyframes pulse { + * from { transform: scale(0.5); } + * to { transform: scale(1.5); } + * } + * + * @-webkit-keyframes pulse { + * from { -webkit-transform: scale(0.5); } + * to { -webkit-transform: scale(1.5); } + * } + * ``` + * + * Given this complex combination of CSS classes, styles and options, `$animateCss` will figure everything out and make the animation happen. + * + * ## How the Options are handled + * + * `$animateCss` is very versatile and intelligent when it comes to figuring out what configurations to apply to the element to ensure the animation + * works with the options provided. Say for example we were adding a class that contained a keyframe value and we wanted to also animate some inline + * styles using the `from` and `to` properties. + * + * ```js + * var animator = $animateCss(element, { + * from: { background:'red' }, + * to: { background:'blue' } + * }); + * animator.start(); + * ``` + * + * ```css + * .rotating-animation { + * animation:0.5s rotate linear; + * -webkit-animation:0.5s rotate linear; + * } + * + * @keyframes rotate { + * from { transform: rotate(0deg); } + * to { transform: rotate(360deg); } + * } + * + * @-webkit-keyframes rotate { + * from { -webkit-transform: rotate(0deg); } + * to { -webkit-transform: rotate(360deg); } + * } + * ``` + * + * The missing pieces here are that we do not have a transition set (within the CSS code nor within the `$animateCss` options) and the duration of the animation is + * going to be detected from what the keyframe styles on the CSS class are. In this event, `$animateCss` will automatically create an inline transition + * style matching the duration detected from the keyframe style (which is present in the CSS class that is being added) and then prepare both the transition + * and keyframe animations to run in parallel on the element. Then when the animation is underway the provided `from` and `to` CSS styles will be applied + * and spread across the transition and keyframe animation. + * + * ## What is returned + * + * `$animateCss` works in two stages: a preparation phase and an animation phase. Therefore when `$animateCss` is first called it will NOT actually + * start the animation. All that is going on here is that the element is being prepared for the animation (which means that the generated CSS classes are + * added and removed on the element). Once `$animateCss` is called it will return an object with the following properties: + * + * ```js + * var animator = $animateCss(element, { ... }); + * ``` + * + * Now what do the contents of our `animator` variable look like: + * + * ```js + * { + * // starts the animation + * start: Function, + * + * // ends (aborts) the animation + * end: Function + * } + * ``` + * + * To actually start the animation we need to run `animation.start()` which will then return a promise that we can hook into to detect when the animation ends. + * If we choose not to run the animation then we MUST run `animation.end()` to perform a cleanup on the element (since some CSS classes and styles may have been + * applied to the element during the preparation phase). Note that all other properties such as duration, delay, transitions and keyframes are just properties + * and that changing them will not reconfigure the parameters of the animation. + * + * ### runner.done() vs runner.then() + * It is documented that `animation.start()` will return a promise object and this is true, however, there is also an additional method available on the + * runner called `.done(callbackFn)`. The done method works the same as `.finally(callbackFn)`, however, it does **not trigger a digest to occur**. + * Therefore, for performance reasons, it's always best to use `runner.done(callback)` instead of `runner.then()`, `runner.catch()` or `runner.finally()` + * unless you really need a digest to kick off afterwards. + * + * Keep in mind that, to make this easier, ngAnimate has tweaked the JS animations API to recognize when a runner instance is returned from $animateCss + * (so there is no need to call `runner.done(doneFn)` inside of your JavaScript animation code). + * Check the {@link ngAnimate.$animateCss#usage animation code above} to see how this works. + * + * @param {DOMElement} element the element that will be animated + * @param {object} options the animation-related options that will be applied during the animation + * + * * `event` - The DOM event (e.g. enter, leave, move). When used, a generated CSS class of `ng-EVENT` and `ng-EVENT-active` will be applied + * to the element during the animation. Multiple events can be provided when spaces are used as a separator. (Note that this will not perform any DOM operation.) + * * `structural` - Indicates that the `ng-` prefix will be added to the event class. Setting to `false` or omitting will turn `ng-EVENT` and + * `ng-EVENT-active` in `EVENT` and `EVENT-active`. Unused if `event` is omitted. + * * `easing` - The CSS easing value that will be applied to the transition or keyframe animation (or both). + * * `transitionStyle` - The raw CSS transition style that will be used (e.g. `1s linear all`). + * * `keyframeStyle` - The raw CSS keyframe animation style that will be used (e.g. `1s my_animation linear`). + * * `from` - The starting CSS styles (a key/value object) that will be applied at the start of the animation. + * * `to` - The ending CSS styles (a key/value object) that will be applied across the animation via a CSS transition. + * * `addClass` - A space separated list of CSS classes that will be added to the element and spread across the animation. + * * `removeClass` - A space separated list of CSS classes that will be removed from the element and spread across the animation. + * * `duration` - A number value representing the total duration of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `0` + * is provided then the animation will be skipped entirely. + * * `delay` - A number value representing the total delay of the transition and/or keyframe (note that a value of 1 is 1000ms). If a value of `true` is + * used then whatever delay value is detected from the CSS classes will be mirrored on the elements styles (e.g. by setting delay true then the style value + * of the element will be `transition-delay: DETECTED_VALUE`). Using `true` is useful when you want the CSS classes and inline styles to all share the same + * CSS delay value. + * * `stagger` - A numeric time value representing the delay between successively animated elements + * ({@link ngAnimate#css-staggering-animations Click here to learn how CSS-based staggering works in ngAnimate.}) + * * `staggerIndex` - The numeric index representing the stagger item (e.g. a value of 5 is equal to the sixth item in the stagger; therefore when a + * `stagger` option value of `0.1` is used then there will be a stagger delay of `600ms`) + * * `applyClassesEarly` - Whether or not the classes being added or removed will be used when detecting the animation. This is set by `$animate` when enter/leave/move animations are fired to ensure that the CSS classes are resolved in time. (Note that this will prevent any transitions from occurring on the classes being added and removed.) + * * `cleanupStyles` - Whether or not the provided `from` and `to` styles will be removed once + * the animation is closed. This is useful for when the styles are used purely for the sake of + * the animation and do not have a lasting visual effect on the element (e.g. a collapse and open animation). + * By default this value is set to `false`. + * + * @return {object} an object with start and end methods and details about the animation. + * + * * `start` - The method to start the animation. This will return a `Promise` when called. + * * `end` - This method will cancel the animation and remove all applied CSS classes and styles. + */ +var ONE_SECOND = 1000; + +var ELAPSED_TIME_MAX_DECIMAL_PLACES = 3; +var CLOSING_TIME_BUFFER = 1.5; + +var DETECT_CSS_PROPERTIES = { + transitionDuration: TRANSITION_DURATION_PROP, + transitionDelay: TRANSITION_DELAY_PROP, + transitionProperty: TRANSITION_PROP + PROPERTY_KEY, + animationDuration: ANIMATION_DURATION_PROP, + animationDelay: ANIMATION_DELAY_PROP, + animationIterationCount: ANIMATION_PROP + ANIMATION_ITERATION_COUNT_KEY +}; + +var DETECT_STAGGER_CSS_PROPERTIES = { + transitionDuration: TRANSITION_DURATION_PROP, + transitionDelay: TRANSITION_DELAY_PROP, + animationDuration: ANIMATION_DURATION_PROP, + animationDelay: ANIMATION_DELAY_PROP +}; + +function getCssKeyframeDurationStyle(duration) { + return [ANIMATION_DURATION_PROP, duration + 's']; +} + +function getCssDelayStyle(delay, isKeyframeAnimation) { + var prop = isKeyframeAnimation ? ANIMATION_DELAY_PROP : TRANSITION_DELAY_PROP; + return [prop, delay + 's']; +} + +function computeCssStyles($window, element, properties) { + var styles = Object.create(null); + var detectedStyles = $window.getComputedStyle(element) || {}; + forEach(properties, function(formalStyleName, actualStyleName) { + var val = detectedStyles[formalStyleName]; + if (val) { + var c = val.charAt(0); + + // only numerical-based values have a negative sign or digit as the first value + if (c === '-' || c === '+' || c >= 0) { + val = parseMaxTime(val); + } + + // by setting this to null in the event that the delay is not set or is set directly as 0 + // then we can still allow for negative values to be used later on and not mistake this + // value for being greater than any other negative value. + if (val === 0) { + val = null; + } + styles[actualStyleName] = val; + } + }); + + return styles; +} + +function parseMaxTime(str) { + var maxValue = 0; + var values = str.split(/\s*,\s*/); + forEach(values, function(value) { + // it's always safe to consider only second values and omit `ms` values since + // getComputedStyle will always handle the conversion for us + if (value.charAt(value.length - 1) === 's') { + value = value.substring(0, value.length - 1); + } + value = parseFloat(value) || 0; + maxValue = maxValue ? Math.max(value, maxValue) : value; + }); + return maxValue; +} + +function truthyTimingValue(val) { + return val === 0 || val != null; +} + +function getCssTransitionDurationStyle(duration, applyOnlyDuration) { + var style = TRANSITION_PROP; + var value = duration + 's'; + if (applyOnlyDuration) { + style += DURATION_KEY; + } else { + value += ' linear all'; + } + return [style, value]; +} + +function createLocalCacheLookup() { + var cache = Object.create(null); + return { + flush: function() { + cache = Object.create(null); + }, + + count: function(key) { + var entry = cache[key]; + return entry ? entry.total : 0; + }, + + get: function(key) { + var entry = cache[key]; + return entry && entry.value; + }, + + put: function(key, value) { + if (!cache[key]) { + cache[key] = { total: 1, value: value }; + } else { + cache[key].total++; + } + } + }; +} + +// we do not reassign an already present style value since +// if we detect the style property value again we may be +// detecting styles that were added via the `from` styles. +// We make use of `isDefined` here since an empty string +// or null value (which is what getPropertyValue will return +// for a non-existing style) will still be marked as a valid +// value for the style (a falsy value implies that the style +// is to be removed at the end of the animation). If we had a simple +// "OR" statement then it would not be enough to catch that. +function registerRestorableStyles(backup, node, properties) { + forEach(properties, function(prop) { + backup[prop] = isDefined(backup[prop]) + ? backup[prop] + : node.style.getPropertyValue(prop); + }); +} + +var $AnimateCssProvider = ['$animateProvider', /** @this */ function($animateProvider) { + var gcsLookup = createLocalCacheLookup(); + var gcsStaggerLookup = createLocalCacheLookup(); + + this.$get = ['$window', '$$jqLite', '$$AnimateRunner', '$timeout', + '$$forceReflow', '$sniffer', '$$rAFScheduler', '$$animateQueue', + function($window, $$jqLite, $$AnimateRunner, $timeout, + $$forceReflow, $sniffer, $$rAFScheduler, $$animateQueue) { + + var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); + + var parentCounter = 0; + function gcsHashFn(node, extraClasses) { + var KEY = '$$ngAnimateParentKey'; + var parentNode = node.parentNode; + var parentID = parentNode[KEY] || (parentNode[KEY] = ++parentCounter); + return parentID + '-' + node.getAttribute('class') + '-' + extraClasses; + } + + function computeCachedCssStyles(node, className, cacheKey, properties) { + var timings = gcsLookup.get(cacheKey); + + if (!timings) { + timings = computeCssStyles($window, node, properties); + if (timings.animationIterationCount === 'infinite') { + timings.animationIterationCount = 1; + } + } + + // we keep putting this in multiple times even though the value and the cacheKey are the same + // because we're keeping an internal tally of how many duplicate animations are detected. + gcsLookup.put(cacheKey, timings); + return timings; + } + + function computeCachedCssStaggerStyles(node, className, cacheKey, properties) { + var stagger; + + // if we have one or more existing matches of matching elements + // containing the same parent + CSS styles (which is how cacheKey works) + // then staggering is possible + if (gcsLookup.count(cacheKey) > 0) { + stagger = gcsStaggerLookup.get(cacheKey); + + if (!stagger) { + var staggerClassName = pendClasses(className, '-stagger'); + + $$jqLite.addClass(node, staggerClassName); + + stagger = computeCssStyles($window, node, properties); + + // force the conversion of a null value to zero incase not set + stagger.animationDuration = Math.max(stagger.animationDuration, 0); + stagger.transitionDuration = Math.max(stagger.transitionDuration, 0); + + $$jqLite.removeClass(node, staggerClassName); + + gcsStaggerLookup.put(cacheKey, stagger); + } + } + + return stagger || {}; + } + + var rafWaitQueue = []; + function waitUntilQuiet(callback) { + rafWaitQueue.push(callback); + $$rAFScheduler.waitUntilQuiet(function() { + gcsLookup.flush(); + gcsStaggerLookup.flush(); + + // DO NOT REMOVE THIS LINE OR REFACTOR OUT THE `pageWidth` variable. + // PLEASE EXAMINE THE `$$forceReflow` service to understand why. + var pageWidth = $$forceReflow(); + + // we use a for loop to ensure that if the queue is changed + // during this looping then it will consider new requests + for (var i = 0; i < rafWaitQueue.length; i++) { + rafWaitQueue[i](pageWidth); + } + rafWaitQueue.length = 0; + }); + } + + function computeTimings(node, className, cacheKey) { + var timings = computeCachedCssStyles(node, className, cacheKey, DETECT_CSS_PROPERTIES); + var aD = timings.animationDelay; + var tD = timings.transitionDelay; + timings.maxDelay = aD && tD + ? Math.max(aD, tD) + : (aD || tD); + timings.maxDuration = Math.max( + timings.animationDuration * timings.animationIterationCount, + timings.transitionDuration); + + return timings; + } + + return function init(element, initialOptions) { + // all of the animation functions should create + // a copy of the options data, however, if a + // parent service has already created a copy then + // we should stick to using that + var options = initialOptions || {}; + if (!options.$$prepared) { + options = prepareAnimationOptions(copy(options)); + } + + var restoreStyles = {}; + var node = getDomNode(element); + if (!node + || !node.parentNode + || !$$animateQueue.enabled()) { + return closeAndReturnNoopAnimator(); + } + + var temporaryStyles = []; + var classes = element.attr('class'); + var styles = packageStyles(options); + var animationClosed; + var animationPaused; + var animationCompleted; + var runner; + var runnerHost; + var maxDelay; + var maxDelayTime; + var maxDuration; + var maxDurationTime; + var startTime; + var events = []; + + if (options.duration === 0 || (!$sniffer.animations && !$sniffer.transitions)) { + return closeAndReturnNoopAnimator(); + } + + var method = options.event && isArray(options.event) + ? options.event.join(' ') + : options.event; + + var isStructural = method && options.structural; + var structuralClassName = ''; + var addRemoveClassName = ''; + + if (isStructural) { + structuralClassName = pendClasses(method, EVENT_CLASS_PREFIX, true); + } else if (method) { + structuralClassName = method; + } + + if (options.addClass) { + addRemoveClassName += pendClasses(options.addClass, ADD_CLASS_SUFFIX); + } + + if (options.removeClass) { + if (addRemoveClassName.length) { + addRemoveClassName += ' '; + } + addRemoveClassName += pendClasses(options.removeClass, REMOVE_CLASS_SUFFIX); + } + + // there may be a situation where a structural animation is combined together + // with CSS classes that need to resolve before the animation is computed. + // However this means that there is no explicit CSS code to block the animation + // from happening (by setting 0s none in the class name). If this is the case + // we need to apply the classes before the first rAF so we know to continue if + // there actually is a detected transition or keyframe animation + if (options.applyClassesEarly && addRemoveClassName.length) { + applyAnimationClasses(element, options); + } + + var preparationClasses = [structuralClassName, addRemoveClassName].join(' ').trim(); + var fullClassName = classes + ' ' + preparationClasses; + var activeClasses = pendClasses(preparationClasses, ACTIVE_CLASS_SUFFIX); + var hasToStyles = styles.to && Object.keys(styles.to).length > 0; + var containsKeyframeAnimation = (options.keyframeStyle || '').length > 0; + + // there is no way we can trigger an animation if no styles and + // no classes are being applied which would then trigger a transition, + // unless there a is raw keyframe value that is applied to the element. + if (!containsKeyframeAnimation + && !hasToStyles + && !preparationClasses) { + return closeAndReturnNoopAnimator(); + } + + var cacheKey, stagger; + if (options.stagger > 0) { + var staggerVal = parseFloat(options.stagger); + stagger = { + transitionDelay: staggerVal, + animationDelay: staggerVal, + transitionDuration: 0, + animationDuration: 0 + }; + } else { + cacheKey = gcsHashFn(node, fullClassName); + stagger = computeCachedCssStaggerStyles(node, preparationClasses, cacheKey, DETECT_STAGGER_CSS_PROPERTIES); + } + + if (!options.$$skipPreparationClasses) { + $$jqLite.addClass(element, preparationClasses); + } + + var applyOnlyDuration; + + if (options.transitionStyle) { + var transitionStyle = [TRANSITION_PROP, options.transitionStyle]; + applyInlineStyle(node, transitionStyle); + temporaryStyles.push(transitionStyle); + } + + if (options.duration >= 0) { + applyOnlyDuration = node.style[TRANSITION_PROP].length > 0; + var durationStyle = getCssTransitionDurationStyle(options.duration, applyOnlyDuration); + + // we set the duration so that it will be picked up by getComputedStyle later + applyInlineStyle(node, durationStyle); + temporaryStyles.push(durationStyle); + } + + if (options.keyframeStyle) { + var keyframeStyle = [ANIMATION_PROP, options.keyframeStyle]; + applyInlineStyle(node, keyframeStyle); + temporaryStyles.push(keyframeStyle); + } + + var itemIndex = stagger + ? options.staggerIndex >= 0 + ? options.staggerIndex + : gcsLookup.count(cacheKey) + : 0; + + var isFirst = itemIndex === 0; + + // this is a pre-emptive way of forcing the setup classes to be added and applied INSTANTLY + // without causing any combination of transitions to kick in. By adding a negative delay value + // it forces the setup class' transition to end immediately. We later then remove the negative + // transition delay to allow for the transition to naturally do it's thing. The beauty here is + // that if there is no transition defined then nothing will happen and this will also allow + // other transitions to be stacked on top of each other without any chopping them out. + if (isFirst && !options.skipBlocking) { + blockTransitions(node, SAFE_FAST_FORWARD_DURATION_VALUE); + } + + var timings = computeTimings(node, fullClassName, cacheKey); + var relativeDelay = timings.maxDelay; + maxDelay = Math.max(relativeDelay, 0); + maxDuration = timings.maxDuration; + + var flags = {}; + flags.hasTransitions = timings.transitionDuration > 0; + flags.hasAnimations = timings.animationDuration > 0; + flags.hasTransitionAll = flags.hasTransitions && timings.transitionProperty === 'all'; + flags.applyTransitionDuration = hasToStyles && ( + (flags.hasTransitions && !flags.hasTransitionAll) + || (flags.hasAnimations && !flags.hasTransitions)); + flags.applyAnimationDuration = options.duration && flags.hasAnimations; + flags.applyTransitionDelay = truthyTimingValue(options.delay) && (flags.applyTransitionDuration || flags.hasTransitions); + flags.applyAnimationDelay = truthyTimingValue(options.delay) && flags.hasAnimations; + flags.recalculateTimingStyles = addRemoveClassName.length > 0; + + if (flags.applyTransitionDuration || flags.applyAnimationDuration) { + maxDuration = options.duration ? parseFloat(options.duration) : maxDuration; + + if (flags.applyTransitionDuration) { + flags.hasTransitions = true; + timings.transitionDuration = maxDuration; + applyOnlyDuration = node.style[TRANSITION_PROP + PROPERTY_KEY].length > 0; + temporaryStyles.push(getCssTransitionDurationStyle(maxDuration, applyOnlyDuration)); + } + + if (flags.applyAnimationDuration) { + flags.hasAnimations = true; + timings.animationDuration = maxDuration; + temporaryStyles.push(getCssKeyframeDurationStyle(maxDuration)); + } + } + + if (maxDuration === 0 && !flags.recalculateTimingStyles) { + return closeAndReturnNoopAnimator(); + } + + if (options.delay != null) { + var delayStyle; + if (typeof options.delay !== 'boolean') { + delayStyle = parseFloat(options.delay); + // number in options.delay means we have to recalculate the delay for the closing timeout + maxDelay = Math.max(delayStyle, 0); + } + + if (flags.applyTransitionDelay) { + temporaryStyles.push(getCssDelayStyle(delayStyle)); + } + + if (flags.applyAnimationDelay) { + temporaryStyles.push(getCssDelayStyle(delayStyle, true)); + } + } + + // we need to recalculate the delay value since we used a pre-emptive negative + // delay value and the delay value is required for the final event checking. This + // property will ensure that this will happen after the RAF phase has passed. + if (options.duration == null && timings.transitionDuration > 0) { + flags.recalculateTimingStyles = flags.recalculateTimingStyles || isFirst; + } + + maxDelayTime = maxDelay * ONE_SECOND; + maxDurationTime = maxDuration * ONE_SECOND; + if (!options.skipBlocking) { + flags.blockTransition = timings.transitionDuration > 0; + flags.blockKeyframeAnimation = timings.animationDuration > 0 && + stagger.animationDelay > 0 && + stagger.animationDuration === 0; + } + + if (options.from) { + if (options.cleanupStyles) { + registerRestorableStyles(restoreStyles, node, Object.keys(options.from)); + } + applyAnimationFromStyles(element, options); + } + + if (flags.blockTransition || flags.blockKeyframeAnimation) { + applyBlocking(maxDuration); + } else if (!options.skipBlocking) { + blockTransitions(node, false); + } + + // TODO(matsko): for 1.5 change this code to have an animator object for better debugging + return { + $$willAnimate: true, + end: endFn, + start: function() { + if (animationClosed) return; + + runnerHost = { + end: endFn, + cancel: cancelFn, + resume: null, //this will be set during the start() phase + pause: null + }; + + runner = new $$AnimateRunner(runnerHost); + + waitUntilQuiet(start); + + // we don't have access to pause/resume the animation + // since it hasn't run yet. AnimateRunner will therefore + // set noop functions for resume and pause and they will + // later be overridden once the animation is triggered + return runner; + } + }; + + function endFn() { + close(); + } + + function cancelFn() { + close(true); + } + + function close(rejected) { + // if the promise has been called already then we shouldn't close + // the animation again + if (animationClosed || (animationCompleted && animationPaused)) return; + animationClosed = true; + animationPaused = false; + + if (!options.$$skipPreparationClasses) { + $$jqLite.removeClass(element, preparationClasses); + } + $$jqLite.removeClass(element, activeClasses); + + blockKeyframeAnimations(node, false); + blockTransitions(node, false); + + forEach(temporaryStyles, function(entry) { + // There is only one way to remove inline style properties entirely from elements. + // By using `removeProperty` this works, but we need to convert camel-cased CSS + // styles down to hyphenated values. + node.style[entry[0]] = ''; + }); + + applyAnimationClasses(element, options); + applyAnimationStyles(element, options); + + if (Object.keys(restoreStyles).length) { + forEach(restoreStyles, function(value, prop) { + if (value) { + node.style.setProperty(prop, value); + } else { + node.style.removeProperty(prop); + } + }); + } + + // the reason why we have this option is to allow a synchronous closing callback + // that is fired as SOON as the animation ends (when the CSS is removed) or if + // the animation never takes off at all. A good example is a leave animation since + // the element must be removed just after the animation is over or else the element + // will appear on screen for one animation frame causing an overbearing flicker. + if (options.onDone) { + options.onDone(); + } + + if (events && events.length) { + // Remove the transitionend / animationend listener(s) + element.off(events.join(' '), onAnimationProgress); + } + + //Cancel the fallback closing timeout and remove the timer data + var animationTimerData = element.data(ANIMATE_TIMER_KEY); + if (animationTimerData) { + $timeout.cancel(animationTimerData[0].timer); + element.removeData(ANIMATE_TIMER_KEY); + } + + // if the preparation function fails then the promise is not setup + if (runner) { + runner.complete(!rejected); + } + } + + function applyBlocking(duration) { + if (flags.blockTransition) { + blockTransitions(node, duration); + } + + if (flags.blockKeyframeAnimation) { + blockKeyframeAnimations(node, !!duration); + } + } + + function closeAndReturnNoopAnimator() { + runner = new $$AnimateRunner({ + end: endFn, + cancel: cancelFn + }); + + // should flush the cache animation + waitUntilQuiet(noop); + close(); + + return { + $$willAnimate: false, + start: function() { + return runner; + }, + end: endFn + }; + } + + function onAnimationProgress(event) { + event.stopPropagation(); + var ev = event.originalEvent || event; + + // we now always use `Date.now()` due to the recent changes with + // event.timeStamp in Firefox, Webkit and Chrome (see #13494 for more info) + var timeStamp = ev.$manualTimeStamp || Date.now(); + + /* Firefox (or possibly just Gecko) likes to not round values up + * when a ms measurement is used for the animation */ + var elapsedTime = parseFloat(ev.elapsedTime.toFixed(ELAPSED_TIME_MAX_DECIMAL_PLACES)); + + /* $manualTimeStamp is a mocked timeStamp value which is set + * within browserTrigger(). This is only here so that tests can + * mock animations properly. Real events fallback to event.timeStamp, + * or, if they don't, then a timeStamp is automatically created for them. + * We're checking to see if the timeStamp surpasses the expected delay, + * but we're using elapsedTime instead of the timeStamp on the 2nd + * pre-condition since animationPauseds sometimes close off early */ + if (Math.max(timeStamp - startTime, 0) >= maxDelayTime && elapsedTime >= maxDuration) { + // we set this flag to ensure that if the transition is paused then, when resumed, + // the animation will automatically close itself since transitions cannot be paused. + animationCompleted = true; + close(); + } + } + + function start() { + if (animationClosed) return; + if (!node.parentNode) { + close(); + return; + } + + // even though we only pause keyframe animations here the pause flag + // will still happen when transitions are used. Only the transition will + // not be paused since that is not possible. If the animation ends when + // paused then it will not complete until unpaused or cancelled. + var playPause = function(playAnimation) { + if (!animationCompleted) { + animationPaused = !playAnimation; + if (timings.animationDuration) { + var value = blockKeyframeAnimations(node, animationPaused); + if (animationPaused) { + temporaryStyles.push(value); + } else { + removeFromArray(temporaryStyles, value); + } + } + } else if (animationPaused && playAnimation) { + animationPaused = false; + close(); + } + }; + + // checking the stagger duration prevents an accidentally cascade of the CSS delay style + // being inherited from the parent. If the transition duration is zero then we can safely + // rely that the delay value is an intentional stagger delay style. + var maxStagger = itemIndex > 0 + && ((timings.transitionDuration && stagger.transitionDuration === 0) || + (timings.animationDuration && stagger.animationDuration === 0)) + && Math.max(stagger.animationDelay, stagger.transitionDelay); + if (maxStagger) { + $timeout(triggerAnimationStart, + Math.floor(maxStagger * itemIndex * ONE_SECOND), + false); + } else { + triggerAnimationStart(); + } + + // this will decorate the existing promise runner with pause/resume methods + runnerHost.resume = function() { + playPause(true); + }; + + runnerHost.pause = function() { + playPause(false); + }; + + function triggerAnimationStart() { + // just incase a stagger animation kicks in when the animation + // itself was cancelled entirely + if (animationClosed) return; + + applyBlocking(false); + + forEach(temporaryStyles, function(entry) { + var key = entry[0]; + var value = entry[1]; + node.style[key] = value; + }); + + applyAnimationClasses(element, options); + $$jqLite.addClass(element, activeClasses); + + if (flags.recalculateTimingStyles) { + fullClassName = node.className + ' ' + preparationClasses; + cacheKey = gcsHashFn(node, fullClassName); + + timings = computeTimings(node, fullClassName, cacheKey); + relativeDelay = timings.maxDelay; + maxDelay = Math.max(relativeDelay, 0); + maxDuration = timings.maxDuration; + + if (maxDuration === 0) { + close(); + return; + } + + flags.hasTransitions = timings.transitionDuration > 0; + flags.hasAnimations = timings.animationDuration > 0; + } + + if (flags.applyAnimationDelay) { + relativeDelay = typeof options.delay !== 'boolean' && truthyTimingValue(options.delay) + ? parseFloat(options.delay) + : relativeDelay; + + maxDelay = Math.max(relativeDelay, 0); + timings.animationDelay = relativeDelay; + delayStyle = getCssDelayStyle(relativeDelay, true); + temporaryStyles.push(delayStyle); + node.style[delayStyle[0]] = delayStyle[1]; + } + + maxDelayTime = maxDelay * ONE_SECOND; + maxDurationTime = maxDuration * ONE_SECOND; + + if (options.easing) { + var easeProp, easeVal = options.easing; + if (flags.hasTransitions) { + easeProp = TRANSITION_PROP + TIMING_KEY; + temporaryStyles.push([easeProp, easeVal]); + node.style[easeProp] = easeVal; + } + if (flags.hasAnimations) { + easeProp = ANIMATION_PROP + TIMING_KEY; + temporaryStyles.push([easeProp, easeVal]); + node.style[easeProp] = easeVal; + } + } + + if (timings.transitionDuration) { + events.push(TRANSITIONEND_EVENT); + } + + if (timings.animationDuration) { + events.push(ANIMATIONEND_EVENT); + } + + startTime = Date.now(); + var timerTime = maxDelayTime + CLOSING_TIME_BUFFER * maxDurationTime; + var endTime = startTime + timerTime; + + var animationsData = element.data(ANIMATE_TIMER_KEY) || []; + var setupFallbackTimer = true; + if (animationsData.length) { + var currentTimerData = animationsData[0]; + setupFallbackTimer = endTime > currentTimerData.expectedEndTime; + if (setupFallbackTimer) { + $timeout.cancel(currentTimerData.timer); + } else { + animationsData.push(close); + } + } + + if (setupFallbackTimer) { + var timer = $timeout(onAnimationExpired, timerTime, false); + animationsData[0] = { + timer: timer, + expectedEndTime: endTime + }; + animationsData.push(close); + element.data(ANIMATE_TIMER_KEY, animationsData); + } + + if (events.length) { + element.on(events.join(' '), onAnimationProgress); + } + + if (options.to) { + if (options.cleanupStyles) { + registerRestorableStyles(restoreStyles, node, Object.keys(options.to)); + } + applyAnimationToStyles(element, options); + } + } + + function onAnimationExpired() { + var animationsData = element.data(ANIMATE_TIMER_KEY); + + // this will be false in the event that the element was + // removed from the DOM (via a leave animation or something + // similar) + if (animationsData) { + for (var i = 1; i < animationsData.length; i++) { + animationsData[i](); + } + element.removeData(ANIMATE_TIMER_KEY); + } + } + } + }; + }]; +}]; + +var $$AnimateCssDriverProvider = ['$$animationProvider', /** @this */ function($$animationProvider) { + $$animationProvider.drivers.push('$$animateCssDriver'); + + var NG_ANIMATE_SHIM_CLASS_NAME = 'ng-animate-shim'; + var NG_ANIMATE_ANCHOR_CLASS_NAME = 'ng-anchor'; + + var NG_OUT_ANCHOR_CLASS_NAME = 'ng-anchor-out'; + var NG_IN_ANCHOR_CLASS_NAME = 'ng-anchor-in'; + + function isDocumentFragment(node) { + return node.parentNode && node.parentNode.nodeType === 11; + } + + this.$get = ['$animateCss', '$rootScope', '$$AnimateRunner', '$rootElement', '$sniffer', '$$jqLite', '$document', + function($animateCss, $rootScope, $$AnimateRunner, $rootElement, $sniffer, $$jqLite, $document) { + + // only browsers that support these properties can render animations + if (!$sniffer.animations && !$sniffer.transitions) return noop; + + var bodyNode = $document[0].body; + var rootNode = getDomNode($rootElement); + + var rootBodyElement = jqLite( + // this is to avoid using something that exists outside of the body + // we also special case the doc fragment case because our unit test code + // appends the $rootElement to the body after the app has been bootstrapped + isDocumentFragment(rootNode) || bodyNode.contains(rootNode) ? rootNode : bodyNode + ); + + return function initDriverFn(animationDetails) { + return animationDetails.from && animationDetails.to + ? prepareFromToAnchorAnimation(animationDetails.from, + animationDetails.to, + animationDetails.classes, + animationDetails.anchors) + : prepareRegularAnimation(animationDetails); + }; + + function filterCssClasses(classes) { + //remove all the `ng-` stuff + return classes.replace(/\bng-\S+\b/g, ''); + } + + function getUniqueValues(a, b) { + if (isString(a)) a = a.split(' '); + if (isString(b)) b = b.split(' '); + return a.filter(function(val) { + return b.indexOf(val) === -1; + }).join(' '); + } + + function prepareAnchoredAnimation(classes, outAnchor, inAnchor) { + var clone = jqLite(getDomNode(outAnchor).cloneNode(true)); + var startingClasses = filterCssClasses(getClassVal(clone)); + + outAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME); + inAnchor.addClass(NG_ANIMATE_SHIM_CLASS_NAME); + + clone.addClass(NG_ANIMATE_ANCHOR_CLASS_NAME); + + rootBodyElement.append(clone); + + var animatorIn, animatorOut = prepareOutAnimation(); + + // the user may not end up using the `out` animation and + // only making use of the `in` animation or vice-versa. + // In either case we should allow this and not assume the + // animation is over unless both animations are not used. + if (!animatorOut) { + animatorIn = prepareInAnimation(); + if (!animatorIn) { + return end(); + } + } + + var startingAnimator = animatorOut || animatorIn; + + return { + start: function() { + var runner; + + var currentAnimation = startingAnimator.start(); + currentAnimation.done(function() { + currentAnimation = null; + if (!animatorIn) { + animatorIn = prepareInAnimation(); + if (animatorIn) { + currentAnimation = animatorIn.start(); + currentAnimation.done(function() { + currentAnimation = null; + end(); + runner.complete(); + }); + return currentAnimation; + } + } + // in the event that there is no `in` animation + end(); + runner.complete(); + }); + + runner = new $$AnimateRunner({ + end: endFn, + cancel: endFn + }); + + return runner; + + function endFn() { + if (currentAnimation) { + currentAnimation.end(); + } + } + } + }; + + function calculateAnchorStyles(anchor) { + var styles = {}; + + var coords = getDomNode(anchor).getBoundingClientRect(); + + // we iterate directly since safari messes up and doesn't return + // all the keys for the coords object when iterated + forEach(['width','height','top','left'], function(key) { + var value = coords[key]; + switch (key) { + case 'top': + value += bodyNode.scrollTop; + break; + case 'left': + value += bodyNode.scrollLeft; + break; + } + styles[key] = Math.floor(value) + 'px'; + }); + return styles; + } + + function prepareOutAnimation() { + var animator = $animateCss(clone, { + addClass: NG_OUT_ANCHOR_CLASS_NAME, + delay: true, + from: calculateAnchorStyles(outAnchor) + }); + + // read the comment within `prepareRegularAnimation` to understand + // why this check is necessary + return animator.$$willAnimate ? animator : null; + } + + function getClassVal(element) { + return element.attr('class') || ''; + } + + function prepareInAnimation() { + var endingClasses = filterCssClasses(getClassVal(inAnchor)); + var toAdd = getUniqueValues(endingClasses, startingClasses); + var toRemove = getUniqueValues(startingClasses, endingClasses); + + var animator = $animateCss(clone, { + to: calculateAnchorStyles(inAnchor), + addClass: NG_IN_ANCHOR_CLASS_NAME + ' ' + toAdd, + removeClass: NG_OUT_ANCHOR_CLASS_NAME + ' ' + toRemove, + delay: true + }); + + // read the comment within `prepareRegularAnimation` to understand + // why this check is necessary + return animator.$$willAnimate ? animator : null; + } + + function end() { + clone.remove(); + outAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME); + inAnchor.removeClass(NG_ANIMATE_SHIM_CLASS_NAME); + } + } + + function prepareFromToAnchorAnimation(from, to, classes, anchors) { + var fromAnimation = prepareRegularAnimation(from, noop); + var toAnimation = prepareRegularAnimation(to, noop); + + var anchorAnimations = []; + forEach(anchors, function(anchor) { + var outElement = anchor['out']; + var inElement = anchor['in']; + var animator = prepareAnchoredAnimation(classes, outElement, inElement); + if (animator) { + anchorAnimations.push(animator); + } + }); + + // no point in doing anything when there are no elements to animate + if (!fromAnimation && !toAnimation && anchorAnimations.length === 0) return; + + return { + start: function() { + var animationRunners = []; + + if (fromAnimation) { + animationRunners.push(fromAnimation.start()); + } + + if (toAnimation) { + animationRunners.push(toAnimation.start()); + } + + forEach(anchorAnimations, function(animation) { + animationRunners.push(animation.start()); + }); + + var runner = new $$AnimateRunner({ + end: endFn, + cancel: endFn // CSS-driven animations cannot be cancelled, only ended + }); + + $$AnimateRunner.all(animationRunners, function(status) { + runner.complete(status); + }); + + return runner; + + function endFn() { + forEach(animationRunners, function(runner) { + runner.end(); + }); + } + } + }; + } + + function prepareRegularAnimation(animationDetails) { + var element = animationDetails.element; + var options = animationDetails.options || {}; + + if (animationDetails.structural) { + options.event = animationDetails.event; + options.structural = true; + options.applyClassesEarly = true; + + // we special case the leave animation since we want to ensure that + // the element is removed as soon as the animation is over. Otherwise + // a flicker might appear or the element may not be removed at all + if (animationDetails.event === 'leave') { + options.onDone = options.domOperation; + } + } + + // We assign the preparationClasses as the actual animation event since + // the internals of $animateCss will just suffix the event token values + // with `-active` to trigger the animation. + if (options.preparationClasses) { + options.event = concatWithSpace(options.event, options.preparationClasses); + } + + var animator = $animateCss(element, options); + + // the driver lookup code inside of $$animation attempts to spawn a + // driver one by one until a driver returns a.$$willAnimate animator object. + // $animateCss will always return an object, however, it will pass in + // a flag as a hint as to whether an animation was detected or not + return animator.$$willAnimate ? animator : null; + } + }]; +}]; + +// TODO(matsko): use caching here to speed things up for detection +// TODO(matsko): add documentation +// by the time... + +var $$AnimateJsProvider = ['$animateProvider', /** @this */ function($animateProvider) { + this.$get = ['$injector', '$$AnimateRunner', '$$jqLite', + function($injector, $$AnimateRunner, $$jqLite) { + + var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); + // $animateJs(element, 'enter'); + return function(element, event, classes, options) { + var animationClosed = false; + + // the `classes` argument is optional and if it is not used + // then the classes will be resolved from the element's className + // property as well as options.addClass/options.removeClass. + if (arguments.length === 3 && isObject(classes)) { + options = classes; + classes = null; + } + + options = prepareAnimationOptions(options); + if (!classes) { + classes = element.attr('class') || ''; + if (options.addClass) { + classes += ' ' + options.addClass; + } + if (options.removeClass) { + classes += ' ' + options.removeClass; + } + } + + var classesToAdd = options.addClass; + var classesToRemove = options.removeClass; + + // the lookupAnimations function returns a series of animation objects that are + // matched up with one or more of the CSS classes. These animation objects are + // defined via the module.animation factory function. If nothing is detected then + // we don't return anything which then makes $animation query the next driver. + var animations = lookupAnimations(classes); + var before, after; + if (animations.length) { + var afterFn, beforeFn; + if (event === 'leave') { + beforeFn = 'leave'; + afterFn = 'afterLeave'; // TODO(matsko): get rid of this + } else { + beforeFn = 'before' + event.charAt(0).toUpperCase() + event.substr(1); + afterFn = event; + } + + if (event !== 'enter' && event !== 'move') { + before = packageAnimations(element, event, options, animations, beforeFn); + } + after = packageAnimations(element, event, options, animations, afterFn); + } + + // no matching animations + if (!before && !after) return; + + function applyOptions() { + options.domOperation(); + applyAnimationClasses(element, options); + } + + function close() { + animationClosed = true; + applyOptions(); + applyAnimationStyles(element, options); + } + + var runner; + + return { + $$willAnimate: true, + end: function() { + if (runner) { + runner.end(); + } else { + close(); + runner = new $$AnimateRunner(); + runner.complete(true); + } + return runner; + }, + start: function() { + if (runner) { + return runner; + } + + runner = new $$AnimateRunner(); + var closeActiveAnimations; + var chain = []; + + if (before) { + chain.push(function(fn) { + closeActiveAnimations = before(fn); + }); + } + + if (chain.length) { + chain.push(function(fn) { + applyOptions(); + fn(true); + }); + } else { + applyOptions(); + } + + if (after) { + chain.push(function(fn) { + closeActiveAnimations = after(fn); + }); + } + + runner.setHost({ + end: function() { + endAnimations(); + }, + cancel: function() { + endAnimations(true); + } + }); + + $$AnimateRunner.chain(chain, onComplete); + return runner; + + function onComplete(success) { + close(success); + runner.complete(success); + } + + function endAnimations(cancelled) { + if (!animationClosed) { + (closeActiveAnimations || noop)(cancelled); + onComplete(cancelled); + } + } + } + }; + + function executeAnimationFn(fn, element, event, options, onDone) { + var args; + switch (event) { + case 'animate': + args = [element, options.from, options.to, onDone]; + break; + + case 'setClass': + args = [element, classesToAdd, classesToRemove, onDone]; + break; + + case 'addClass': + args = [element, classesToAdd, onDone]; + break; + + case 'removeClass': + args = [element, classesToRemove, onDone]; + break; + + default: + args = [element, onDone]; + break; + } + + args.push(options); + + var value = fn.apply(fn, args); + if (value) { + if (isFunction(value.start)) { + value = value.start(); + } + + if (value instanceof $$AnimateRunner) { + value.done(onDone); + } else if (isFunction(value)) { + // optional onEnd / onCancel callback + return value; + } + } + + return noop; + } + + function groupEventedAnimations(element, event, options, animations, fnName) { + var operations = []; + forEach(animations, function(ani) { + var animation = ani[fnName]; + if (!animation) return; + + // note that all of these animations will run in parallel + operations.push(function() { + var runner; + var endProgressCb; + + var resolved = false; + var onAnimationComplete = function(rejected) { + if (!resolved) { + resolved = true; + (endProgressCb || noop)(rejected); + runner.complete(!rejected); + } + }; + + runner = new $$AnimateRunner({ + end: function() { + onAnimationComplete(); + }, + cancel: function() { + onAnimationComplete(true); + } + }); + + endProgressCb = executeAnimationFn(animation, element, event, options, function(result) { + var cancelled = result === false; + onAnimationComplete(cancelled); + }); + + return runner; + }); + }); + + return operations; + } + + function packageAnimations(element, event, options, animations, fnName) { + var operations = groupEventedAnimations(element, event, options, animations, fnName); + if (operations.length === 0) { + var a, b; + if (fnName === 'beforeSetClass') { + a = groupEventedAnimations(element, 'removeClass', options, animations, 'beforeRemoveClass'); + b = groupEventedAnimations(element, 'addClass', options, animations, 'beforeAddClass'); + } else if (fnName === 'setClass') { + a = groupEventedAnimations(element, 'removeClass', options, animations, 'removeClass'); + b = groupEventedAnimations(element, 'addClass', options, animations, 'addClass'); + } + + if (a) { + operations = operations.concat(a); + } + if (b) { + operations = operations.concat(b); + } + } + + if (operations.length === 0) return; + + // TODO(matsko): add documentation + return function startAnimation(callback) { + var runners = []; + if (operations.length) { + forEach(operations, function(animateFn) { + runners.push(animateFn()); + }); + } + + if (runners.length) { + $$AnimateRunner.all(runners, callback); + } else { + callback(); + } + + return function endFn(reject) { + forEach(runners, function(runner) { + if (reject) { + runner.cancel(); + } else { + runner.end(); + } + }); + }; + }; + } + }; + + function lookupAnimations(classes) { + classes = isArray(classes) ? classes : classes.split(' '); + var matches = [], flagMap = {}; + for (var i = 0; i < classes.length; i++) { + var klass = classes[i], + animationFactory = $animateProvider.$$registeredAnimations[klass]; + if (animationFactory && !flagMap[klass]) { + matches.push($injector.get(animationFactory)); + flagMap[klass] = true; + } + } + return matches; + } + }]; +}]; + +var $$AnimateJsDriverProvider = ['$$animationProvider', /** @this */ function($$animationProvider) { + $$animationProvider.drivers.push('$$animateJsDriver'); + this.$get = ['$$animateJs', '$$AnimateRunner', function($$animateJs, $$AnimateRunner) { + return function initDriverFn(animationDetails) { + if (animationDetails.from && animationDetails.to) { + var fromAnimation = prepareAnimation(animationDetails.from); + var toAnimation = prepareAnimation(animationDetails.to); + if (!fromAnimation && !toAnimation) return; + + return { + start: function() { + var animationRunners = []; + + if (fromAnimation) { + animationRunners.push(fromAnimation.start()); + } + + if (toAnimation) { + animationRunners.push(toAnimation.start()); + } + + $$AnimateRunner.all(animationRunners, done); + + var runner = new $$AnimateRunner({ + end: endFnFactory(), + cancel: endFnFactory() + }); + + return runner; + + function endFnFactory() { + return function() { + forEach(animationRunners, function(runner) { + // at this point we cannot cancel animations for groups just yet. 1.5+ + runner.end(); + }); + }; + } + + function done(status) { + runner.complete(status); + } + } + }; + } else { + return prepareAnimation(animationDetails); + } + }; + + function prepareAnimation(animationDetails) { + // TODO(matsko): make sure to check for grouped animations and delegate down to normal animations + var element = animationDetails.element; + var event = animationDetails.event; + var options = animationDetails.options; + var classes = animationDetails.classes; + return $$animateJs(element, event, classes, options); + } + }]; +}]; + +var NG_ANIMATE_ATTR_NAME = 'data-ng-animate'; +var NG_ANIMATE_PIN_DATA = '$ngAnimatePin'; +var $$AnimateQueueProvider = ['$animateProvider', /** @this */ function($animateProvider) { + var PRE_DIGEST_STATE = 1; + var RUNNING_STATE = 2; + var ONE_SPACE = ' '; + + var rules = this.rules = { + skip: [], + cancel: [], + join: [] + }; + + function makeTruthyCssClassMap(classString) { + if (!classString) { + return null; + } + + var keys = classString.split(ONE_SPACE); + var map = Object.create(null); + + forEach(keys, function(key) { + map[key] = true; + }); + return map; + } + + function hasMatchingClasses(newClassString, currentClassString) { + if (newClassString && currentClassString) { + var currentClassMap = makeTruthyCssClassMap(currentClassString); + return newClassString.split(ONE_SPACE).some(function(className) { + return currentClassMap[className]; + }); + } + } + + function isAllowed(ruleType, element, currentAnimation, previousAnimation) { + return rules[ruleType].some(function(fn) { + return fn(element, currentAnimation, previousAnimation); + }); + } + + function hasAnimationClasses(animation, and) { + var a = (animation.addClass || '').length > 0; + var b = (animation.removeClass || '').length > 0; + return and ? a && b : a || b; + } + + rules.join.push(function(element, newAnimation, currentAnimation) { + // if the new animation is class-based then we can just tack that on + return !newAnimation.structural && hasAnimationClasses(newAnimation); + }); + + rules.skip.push(function(element, newAnimation, currentAnimation) { + // there is no need to animate anything if no classes are being added and + // there is no structural animation that will be triggered + return !newAnimation.structural && !hasAnimationClasses(newAnimation); + }); + + rules.skip.push(function(element, newAnimation, currentAnimation) { + // why should we trigger a new structural animation if the element will + // be removed from the DOM anyway? + return currentAnimation.event === 'leave' && newAnimation.structural; + }); + + rules.skip.push(function(element, newAnimation, currentAnimation) { + // if there is an ongoing current animation then don't even bother running the class-based animation + return currentAnimation.structural && currentAnimation.state === RUNNING_STATE && !newAnimation.structural; + }); + + rules.cancel.push(function(element, newAnimation, currentAnimation) { + // there can never be two structural animations running at the same time + return currentAnimation.structural && newAnimation.structural; + }); + + rules.cancel.push(function(element, newAnimation, currentAnimation) { + // if the previous animation is already running, but the new animation will + // be triggered, but the new animation is structural + return currentAnimation.state === RUNNING_STATE && newAnimation.structural; + }); + + rules.cancel.push(function(element, newAnimation, currentAnimation) { + // cancel the animation if classes added / removed in both animation cancel each other out, + // but only if the current animation isn't structural + + if (currentAnimation.structural) return false; + + var nA = newAnimation.addClass; + var nR = newAnimation.removeClass; + var cA = currentAnimation.addClass; + var cR = currentAnimation.removeClass; + + // early detection to save the global CPU shortage :) + if ((isUndefined(nA) && isUndefined(nR)) || (isUndefined(cA) && isUndefined(cR))) { + return false; + } + + return hasMatchingClasses(nA, cR) || hasMatchingClasses(nR, cA); + }); + + this.$get = ['$$rAF', '$rootScope', '$rootElement', '$document', '$$HashMap', + '$$animation', '$$AnimateRunner', '$templateRequest', '$$jqLite', '$$forceReflow', + function($$rAF, $rootScope, $rootElement, $document, $$HashMap, + $$animation, $$AnimateRunner, $templateRequest, $$jqLite, $$forceReflow) { + + var activeAnimationsLookup = new $$HashMap(); + var disabledElementsLookup = new $$HashMap(); + var animationsEnabled = null; + + function postDigestTaskFactory() { + var postDigestCalled = false; + return function(fn) { + // we only issue a call to postDigest before + // it has first passed. This prevents any callbacks + // from not firing once the animation has completed + // since it will be out of the digest cycle. + if (postDigestCalled) { + fn(); + } else { + $rootScope.$$postDigest(function() { + postDigestCalled = true; + fn(); + }); + } + }; + } + + // Wait until all directive and route-related templates are downloaded and + // compiled. The $templateRequest.totalPendingRequests variable keeps track of + // all of the remote templates being currently downloaded. If there are no + // templates currently downloading then the watcher will still fire anyway. + var deregisterWatch = $rootScope.$watch( + function() { return $templateRequest.totalPendingRequests === 0; }, + function(isEmpty) { + if (!isEmpty) return; + deregisterWatch(); + + // Now that all templates have been downloaded, $animate will wait until + // the post digest queue is empty before enabling animations. By having two + // calls to $postDigest calls we can ensure that the flag is enabled at the + // very end of the post digest queue. Since all of the animations in $animate + // use $postDigest, it's important that the code below executes at the end. + // This basically means that the page is fully downloaded and compiled before + // any animations are triggered. + $rootScope.$$postDigest(function() { + $rootScope.$$postDigest(function() { + // we check for null directly in the event that the application already called + // .enabled() with whatever arguments that it provided it with + if (animationsEnabled === null) { + animationsEnabled = true; + } + }); + }); + } + ); + + var callbackRegistry = Object.create(null); + + // remember that the classNameFilter is set during the provider/config + // stage therefore we can optimize here and setup a helper function + var classNameFilter = $animateProvider.classNameFilter(); + var isAnimatableClassName = !classNameFilter + ? function() { return true; } + : function(className) { + return classNameFilter.test(className); + }; + + var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); + + function normalizeAnimationDetails(element, animation) { + return mergeAnimationDetails(element, animation, {}); + } + + // IE9-11 has no method "contains" in SVG element and in Node.prototype. Bug #10259. + var contains = window.Node.prototype.contains || /** @this */ function(arg) { + // eslint-disable-next-line no-bitwise + return this === arg || !!(this.compareDocumentPosition(arg) & 16); + }; + + function findCallbacks(parent, element, event) { + var targetNode = getDomNode(element); + var targetParentNode = getDomNode(parent); + + var matches = []; + var entries = callbackRegistry[event]; + if (entries) { + forEach(entries, function(entry) { + if (contains.call(entry.node, targetNode)) { + matches.push(entry.callback); + } else if (event === 'leave' && contains.call(entry.node, targetParentNode)) { + matches.push(entry.callback); + } + }); + } + + return matches; + } + + function filterFromRegistry(list, matchContainer, matchCallback) { + var containerNode = extractElementNode(matchContainer); + return list.filter(function(entry) { + var isMatch = entry.node === containerNode && + (!matchCallback || entry.callback === matchCallback); + return !isMatch; + }); + } + + function cleanupEventListeners(phase, element) { + if (phase === 'close' && !element[0].parentNode) { + // If the element is not attached to a parentNode, it has been removed by + // the domOperation, and we can safely remove the event callbacks + $animate.off(element); + } + } + + var $animate = { + on: function(event, container, callback) { + var node = extractElementNode(container); + callbackRegistry[event] = callbackRegistry[event] || []; + callbackRegistry[event].push({ + node: node, + callback: callback + }); + + // Remove the callback when the element is removed from the DOM + jqLite(container).on('$destroy', function() { + var animationDetails = activeAnimationsLookup.get(node); + + if (!animationDetails) { + // If there's an animation ongoing, the callback calling code will remove + // the event listeners. If we'd remove here, the callbacks would be removed + // before the animation ends + $animate.off(event, container, callback); + } + }); + }, + + off: function(event, container, callback) { + if (arguments.length === 1 && !isString(arguments[0])) { + container = arguments[0]; + for (var eventType in callbackRegistry) { + callbackRegistry[eventType] = filterFromRegistry(callbackRegistry[eventType], container); + } + + return; + } + + var entries = callbackRegistry[event]; + if (!entries) return; + + callbackRegistry[event] = arguments.length === 1 + ? null + : filterFromRegistry(entries, container, callback); + }, + + pin: function(element, parentElement) { + assertArg(isElement(element), 'element', 'not an element'); + assertArg(isElement(parentElement), 'parentElement', 'not an element'); + element.data(NG_ANIMATE_PIN_DATA, parentElement); + }, + + push: function(element, event, options, domOperation) { + options = options || {}; + options.domOperation = domOperation; + return queueAnimation(element, event, options); + }, + + // this method has four signatures: + // () - global getter + // (bool) - global setter + // (element) - element getter + // (element, bool) - element setter + enabled: function(element, bool) { + var argCount = arguments.length; + + if (argCount === 0) { + // () - Global getter + bool = !!animationsEnabled; + } else { + var hasElement = isElement(element); + + if (!hasElement) { + // (bool) - Global setter + bool = animationsEnabled = !!element; + } else { + var node = getDomNode(element); + + if (argCount === 1) { + // (element) - Element getter + bool = !disabledElementsLookup.get(node); + } else { + // (element, bool) - Element setter + disabledElementsLookup.put(node, !bool); + } + } + } + + return bool; + } + }; + + return $animate; + + function queueAnimation(element, event, initialOptions) { + // we always make a copy of the options since + // there should never be any side effects on + // the input data when running `$animateCss`. + var options = copy(initialOptions); + + var node, parent; + element = stripCommentsFromElement(element); + if (element) { + node = getDomNode(element); + parent = element.parent(); + } + + options = prepareAnimationOptions(options); + + // we create a fake runner with a working promise. + // These methods will become available after the digest has passed + var runner = new $$AnimateRunner(); + + // this is used to trigger callbacks in postDigest mode + var runInNextPostDigestOrNow = postDigestTaskFactory(); + + if (isArray(options.addClass)) { + options.addClass = options.addClass.join(' '); + } + + if (options.addClass && !isString(options.addClass)) { + options.addClass = null; + } + + if (isArray(options.removeClass)) { + options.removeClass = options.removeClass.join(' '); + } + + if (options.removeClass && !isString(options.removeClass)) { + options.removeClass = null; + } + + if (options.from && !isObject(options.from)) { + options.from = null; + } + + if (options.to && !isObject(options.to)) { + options.to = null; + } + + // there are situations where a directive issues an animation for + // a jqLite wrapper that contains only comment nodes... If this + // happens then there is no way we can perform an animation + if (!node) { + close(); + return runner; + } + + var className = [node.className, options.addClass, options.removeClass].join(' '); + if (!isAnimatableClassName(className)) { + close(); + return runner; + } + + var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; + + var documentHidden = $document[0].hidden; + + // this is a hard disable of all animations for the application or on + // the element itself, therefore there is no need to continue further + // past this point if not enabled + // Animations are also disabled if the document is currently hidden (page is not visible + // to the user), because browsers slow down or do not flush calls to requestAnimationFrame + var skipAnimations = !animationsEnabled || documentHidden || disabledElementsLookup.get(node); + var existingAnimation = (!skipAnimations && activeAnimationsLookup.get(node)) || {}; + var hasExistingAnimation = !!existingAnimation.state; + + // there is no point in traversing the same collection of parent ancestors if a followup + // animation will be run on the same element that already did all that checking work + if (!skipAnimations && (!hasExistingAnimation || existingAnimation.state !== PRE_DIGEST_STATE)) { + skipAnimations = !areAnimationsAllowed(element, parent, event); + } + + if (skipAnimations) { + // Callbacks should fire even if the document is hidden (regression fix for issue #14120) + if (documentHidden) notifyProgress(runner, event, 'start'); + close(); + if (documentHidden) notifyProgress(runner, event, 'close'); + return runner; + } + + if (isStructural) { + closeChildAnimations(element); + } + + var newAnimation = { + structural: isStructural, + element: element, + event: event, + addClass: options.addClass, + removeClass: options.removeClass, + close: close, + options: options, + runner: runner + }; + + if (hasExistingAnimation) { + var skipAnimationFlag = isAllowed('skip', element, newAnimation, existingAnimation); + if (skipAnimationFlag) { + if (existingAnimation.state === RUNNING_STATE) { + close(); + return runner; + } else { + mergeAnimationDetails(element, existingAnimation, newAnimation); + return existingAnimation.runner; + } + } + var cancelAnimationFlag = isAllowed('cancel', element, newAnimation, existingAnimation); + if (cancelAnimationFlag) { + if (existingAnimation.state === RUNNING_STATE) { + // this will end the animation right away and it is safe + // to do so since the animation is already running and the + // runner callback code will run in async + existingAnimation.runner.end(); + } else if (existingAnimation.structural) { + // this means that the animation is queued into a digest, but + // hasn't started yet. Therefore it is safe to run the close + // method which will call the runner methods in async. + existingAnimation.close(); + } else { + // this will merge the new animation options into existing animation options + mergeAnimationDetails(element, existingAnimation, newAnimation); + + return existingAnimation.runner; + } + } else { + // a joined animation means that this animation will take over the existing one + // so an example would involve a leave animation taking over an enter. Then when + // the postDigest kicks in the enter will be ignored. + var joinAnimationFlag = isAllowed('join', element, newAnimation, existingAnimation); + if (joinAnimationFlag) { + if (existingAnimation.state === RUNNING_STATE) { + normalizeAnimationDetails(element, newAnimation); + } else { + applyGeneratedPreparationClasses(element, isStructural ? event : null, options); + + event = newAnimation.event = existingAnimation.event; + options = mergeAnimationDetails(element, existingAnimation, newAnimation); + + //we return the same runner since only the option values of this animation will + //be fed into the `existingAnimation`. + return existingAnimation.runner; + } + } + } + } else { + // normalization in this case means that it removes redundant CSS classes that + // already exist (addClass) or do not exist (removeClass) on the element + normalizeAnimationDetails(element, newAnimation); + } + + // when the options are merged and cleaned up we may end up not having to do + // an animation at all, therefore we should check this before issuing a post + // digest callback. Structural animations will always run no matter what. + var isValidAnimation = newAnimation.structural; + if (!isValidAnimation) { + // animate (from/to) can be quickly checked first, otherwise we check if any classes are present + isValidAnimation = (newAnimation.event === 'animate' && Object.keys(newAnimation.options.to || {}).length > 0) + || hasAnimationClasses(newAnimation); + } + + if (!isValidAnimation) { + close(); + clearElementAnimationState(element); + return runner; + } + + // the counter keeps track of cancelled animations + var counter = (existingAnimation.counter || 0) + 1; + newAnimation.counter = counter; + + markElementAnimationState(element, PRE_DIGEST_STATE, newAnimation); + + $rootScope.$$postDigest(function() { + var animationDetails = activeAnimationsLookup.get(node); + var animationCancelled = !animationDetails; + animationDetails = animationDetails || {}; + + // if addClass/removeClass is called before something like enter then the + // registered parent element may not be present. The code below will ensure + // that a final value for parent element is obtained + var parentElement = element.parent() || []; + + // animate/structural/class-based animations all have requirements. Otherwise there + // is no point in performing an animation. The parent node must also be set. + var isValidAnimation = parentElement.length > 0 + && (animationDetails.event === 'animate' + || animationDetails.structural + || hasAnimationClasses(animationDetails)); + + // this means that the previous animation was cancelled + // even if the follow-up animation is the same event + if (animationCancelled || animationDetails.counter !== counter || !isValidAnimation) { + // if another animation did not take over then we need + // to make sure that the domOperation and options are + // handled accordingly + if (animationCancelled) { + applyAnimationClasses(element, options); + applyAnimationStyles(element, options); + } + + // if the event changed from something like enter to leave then we do + // it, otherwise if it's the same then the end result will be the same too + if (animationCancelled || (isStructural && animationDetails.event !== event)) { + options.domOperation(); + runner.end(); + } + + // in the event that the element animation was not cancelled or a follow-up animation + // isn't allowed to animate from here then we need to clear the state of the element + // so that any future animations won't read the expired animation data. + if (!isValidAnimation) { + clearElementAnimationState(element); + } + + return; + } + + // this combined multiple class to addClass / removeClass into a setClass event + // so long as a structural event did not take over the animation + event = !animationDetails.structural && hasAnimationClasses(animationDetails, true) + ? 'setClass' + : animationDetails.event; + + markElementAnimationState(element, RUNNING_STATE); + var realRunner = $$animation(element, event, animationDetails.options); + + // this will update the runner's flow-control events based on + // the `realRunner` object. + runner.setHost(realRunner); + notifyProgress(runner, event, 'start', {}); + + realRunner.done(function(status) { + close(!status); + var animationDetails = activeAnimationsLookup.get(node); + if (animationDetails && animationDetails.counter === counter) { + clearElementAnimationState(getDomNode(element)); + } + notifyProgress(runner, event, 'close', {}); + }); + }); + + return runner; + + function notifyProgress(runner, event, phase, data) { + runInNextPostDigestOrNow(function() { + var callbacks = findCallbacks(parent, element, event); + if (callbacks.length) { + // do not optimize this call here to RAF because + // we don't know how heavy the callback code here will + // be and if this code is buffered then this can + // lead to a performance regression. + $$rAF(function() { + forEach(callbacks, function(callback) { + callback(element, phase, data); + }); + cleanupEventListeners(phase, element); + }); + } else { + cleanupEventListeners(phase, element); + } + }); + runner.progress(event, phase, data); + } + + function close(reject) { + clearGeneratedClasses(element, options); + applyAnimationClasses(element, options); + applyAnimationStyles(element, options); + options.domOperation(); + runner.complete(!reject); + } + } + + function closeChildAnimations(element) { + var node = getDomNode(element); + var children = node.querySelectorAll('[' + NG_ANIMATE_ATTR_NAME + ']'); + forEach(children, function(child) { + var state = parseInt(child.getAttribute(NG_ANIMATE_ATTR_NAME), 10); + var animationDetails = activeAnimationsLookup.get(child); + if (animationDetails) { + switch (state) { + case RUNNING_STATE: + animationDetails.runner.end(); + /* falls through */ + case PRE_DIGEST_STATE: + activeAnimationsLookup.remove(child); + break; + } + } + }); + } + + function clearElementAnimationState(element) { + var node = getDomNode(element); + node.removeAttribute(NG_ANIMATE_ATTR_NAME); + activeAnimationsLookup.remove(node); + } + + function isMatchingElement(nodeOrElmA, nodeOrElmB) { + return getDomNode(nodeOrElmA) === getDomNode(nodeOrElmB); + } + + /** + * This fn returns false if any of the following is true: + * a) animations on any parent element are disabled, and animations on the element aren't explicitly allowed + * b) a parent element has an ongoing structural animation, and animateChildren is false + * c) the element is not a child of the body + * d) the element is not a child of the $rootElement + */ + function areAnimationsAllowed(element, parentElement, event) { + var bodyElement = jqLite($document[0].body); + var bodyElementDetected = isMatchingElement(element, bodyElement) || element[0].nodeName === 'HTML'; + var rootElementDetected = isMatchingElement(element, $rootElement); + var parentAnimationDetected = false; + var animateChildren; + var elementDisabled = disabledElementsLookup.get(getDomNode(element)); + + var parentHost = jqLite.data(element[0], NG_ANIMATE_PIN_DATA); + if (parentHost) { + parentElement = parentHost; + } + + parentElement = getDomNode(parentElement); + + while (parentElement) { + if (!rootElementDetected) { + // angular doesn't want to attempt to animate elements outside of the application + // therefore we need to ensure that the rootElement is an ancestor of the current element + rootElementDetected = isMatchingElement(parentElement, $rootElement); + } + + if (parentElement.nodeType !== ELEMENT_NODE) { + // no point in inspecting the #document element + break; + } + + var details = activeAnimationsLookup.get(parentElement) || {}; + // either an enter, leave or move animation will commence + // therefore we can't allow any animations to take place + // but if a parent animation is class-based then that's ok + if (!parentAnimationDetected) { + var parentElementDisabled = disabledElementsLookup.get(parentElement); + + if (parentElementDisabled === true && elementDisabled !== false) { + // disable animations if the user hasn't explicitly enabled animations on the + // current element + elementDisabled = true; + // element is disabled via parent element, no need to check anything else + break; + } else if (parentElementDisabled === false) { + elementDisabled = false; + } + parentAnimationDetected = details.structural; + } + + if (isUndefined(animateChildren) || animateChildren === true) { + var value = jqLite.data(parentElement, NG_ANIMATE_CHILDREN_DATA); + if (isDefined(value)) { + animateChildren = value; + } + } + + // there is no need to continue traversing at this point + if (parentAnimationDetected && animateChildren === false) break; + + if (!bodyElementDetected) { + // we also need to ensure that the element is or will be a part of the body element + // otherwise it is pointless to even issue an animation to be rendered + bodyElementDetected = isMatchingElement(parentElement, bodyElement); + } + + if (bodyElementDetected && rootElementDetected) { + // If both body and root have been found, any other checks are pointless, + // as no animation data should live outside the application + break; + } + + if (!rootElementDetected) { + // If no rootElement is detected, check if the parentElement is pinned to another element + parentHost = jqLite.data(parentElement, NG_ANIMATE_PIN_DATA); + if (parentHost) { + // The pin target element becomes the next parent element + parentElement = getDomNode(parentHost); + continue; + } + } + + parentElement = parentElement.parentNode; + } + + var allowAnimation = (!parentAnimationDetected || animateChildren) && elementDisabled !== true; + return allowAnimation && rootElementDetected && bodyElementDetected; + } + + function markElementAnimationState(element, state, details) { + details = details || {}; + details.state = state; + + var node = getDomNode(element); + node.setAttribute(NG_ANIMATE_ATTR_NAME, state); + + var oldValue = activeAnimationsLookup.get(node); + var newValue = oldValue + ? extend(oldValue, details) + : details; + activeAnimationsLookup.put(node, newValue); + } + }]; +}]; + +/* exported $$AnimationProvider */ + +var $$AnimationProvider = ['$animateProvider', /** @this */ function($animateProvider) { + var NG_ANIMATE_REF_ATTR = 'ng-animate-ref'; + + var drivers = this.drivers = []; + + var RUNNER_STORAGE_KEY = '$$animationRunner'; + + function setRunner(element, runner) { + element.data(RUNNER_STORAGE_KEY, runner); + } + + function removeRunner(element) { + element.removeData(RUNNER_STORAGE_KEY); + } + + function getRunner(element) { + return element.data(RUNNER_STORAGE_KEY); + } + + this.$get = ['$$jqLite', '$rootScope', '$injector', '$$AnimateRunner', '$$HashMap', '$$rAFScheduler', + function($$jqLite, $rootScope, $injector, $$AnimateRunner, $$HashMap, $$rAFScheduler) { + + var animationQueue = []; + var applyAnimationClasses = applyAnimationClassesFactory($$jqLite); + + function sortAnimations(animations) { + var tree = { children: [] }; + var i, lookup = new $$HashMap(); + + // this is done first beforehand so that the hashmap + // is filled with a list of the elements that will be animated + for (i = 0; i < animations.length; i++) { + var animation = animations[i]; + lookup.put(animation.domNode, animations[i] = { + domNode: animation.domNode, + fn: animation.fn, + children: [] + }); + } + + for (i = 0; i < animations.length; i++) { + processNode(animations[i]); + } + + return flatten(tree); + + function processNode(entry) { + if (entry.processed) return entry; + entry.processed = true; + + var elementNode = entry.domNode; + var parentNode = elementNode.parentNode; + lookup.put(elementNode, entry); + + var parentEntry; + while (parentNode) { + parentEntry = lookup.get(parentNode); + if (parentEntry) { + if (!parentEntry.processed) { + parentEntry = processNode(parentEntry); + } + break; + } + parentNode = parentNode.parentNode; + } + + (parentEntry || tree).children.push(entry); + return entry; + } + + function flatten(tree) { + var result = []; + var queue = []; + var i; + + for (i = 0; i < tree.children.length; i++) { + queue.push(tree.children[i]); + } + + var remainingLevelEntries = queue.length; + var nextLevelEntries = 0; + var row = []; + + for (i = 0; i < queue.length; i++) { + var entry = queue[i]; + if (remainingLevelEntries <= 0) { + remainingLevelEntries = nextLevelEntries; + nextLevelEntries = 0; + result.push(row); + row = []; + } + row.push(entry.fn); + entry.children.forEach(function(childEntry) { + nextLevelEntries++; + queue.push(childEntry); + }); + remainingLevelEntries--; + } + + if (row.length) { + result.push(row); + } + + return result; + } + } + + // TODO(matsko): document the signature in a better way + return function(element, event, options) { + options = prepareAnimationOptions(options); + var isStructural = ['enter', 'move', 'leave'].indexOf(event) >= 0; + + // there is no animation at the current moment, however + // these runner methods will get later updated with the + // methods leading into the driver's end/cancel methods + // for now they just stop the animation from starting + var runner = new $$AnimateRunner({ + end: function() { close(); }, + cancel: function() { close(true); } + }); + + if (!drivers.length) { + close(); + return runner; + } + + setRunner(element, runner); + + var classes = mergeClasses(element.attr('class'), mergeClasses(options.addClass, options.removeClass)); + var tempClasses = options.tempClasses; + if (tempClasses) { + classes += ' ' + tempClasses; + options.tempClasses = null; + } + + var prepareClassName; + if (isStructural) { + prepareClassName = 'ng-' + event + PREPARE_CLASS_SUFFIX; + $$jqLite.addClass(element, prepareClassName); + } + + animationQueue.push({ + // this data is used by the postDigest code and passed into + // the driver step function + element: element, + classes: classes, + event: event, + structural: isStructural, + options: options, + beforeStart: beforeStart, + close: close + }); + + element.on('$destroy', handleDestroyedElement); + + // we only want there to be one function called within the post digest + // block. This way we can group animations for all the animations that + // were apart of the same postDigest flush call. + if (animationQueue.length > 1) return runner; + + $rootScope.$$postDigest(function() { + var animations = []; + forEach(animationQueue, function(entry) { + // the element was destroyed early on which removed the runner + // form its storage. This means we can't animate this element + // at all and it already has been closed due to destruction. + if (getRunner(entry.element)) { + animations.push(entry); + } else { + entry.close(); + } + }); + + // now any future animations will be in another postDigest + animationQueue.length = 0; + + var groupedAnimations = groupAnimations(animations); + var toBeSortedAnimations = []; + + forEach(groupedAnimations, function(animationEntry) { + toBeSortedAnimations.push({ + domNode: getDomNode(animationEntry.from ? animationEntry.from.element : animationEntry.element), + fn: function triggerAnimationStart() { + // it's important that we apply the `ng-animate` CSS class and the + // temporary classes before we do any driver invoking since these + // CSS classes may be required for proper CSS detection. + animationEntry.beforeStart(); + + var startAnimationFn, closeFn = animationEntry.close; + + // in the event that the element was removed before the digest runs or + // during the RAF sequencing then we should not trigger the animation. + var targetElement = animationEntry.anchors + ? (animationEntry.from.element || animationEntry.to.element) + : animationEntry.element; + + if (getRunner(targetElement)) { + var operation = invokeFirstDriver(animationEntry); + if (operation) { + startAnimationFn = operation.start; + } + } + + if (!startAnimationFn) { + closeFn(); + } else { + var animationRunner = startAnimationFn(); + animationRunner.done(function(status) { + closeFn(!status); + }); + updateAnimationRunners(animationEntry, animationRunner); + } + } + }); + }); + + // we need to sort each of the animations in order of parent to child + // relationships. This ensures that the child classes are applied at the + // right time. + $$rAFScheduler(sortAnimations(toBeSortedAnimations)); + }); + + return runner; + + // TODO(matsko): change to reference nodes + function getAnchorNodes(node) { + var SELECTOR = '[' + NG_ANIMATE_REF_ATTR + ']'; + var items = node.hasAttribute(NG_ANIMATE_REF_ATTR) + ? [node] + : node.querySelectorAll(SELECTOR); + var anchors = []; + forEach(items, function(node) { + var attr = node.getAttribute(NG_ANIMATE_REF_ATTR); + if (attr && attr.length) { + anchors.push(node); + } + }); + return anchors; + } + + function groupAnimations(animations) { + var preparedAnimations = []; + var refLookup = {}; + forEach(animations, function(animation, index) { + var element = animation.element; + var node = getDomNode(element); + var event = animation.event; + var enterOrMove = ['enter', 'move'].indexOf(event) >= 0; + var anchorNodes = animation.structural ? getAnchorNodes(node) : []; + + if (anchorNodes.length) { + var direction = enterOrMove ? 'to' : 'from'; + + forEach(anchorNodes, function(anchor) { + var key = anchor.getAttribute(NG_ANIMATE_REF_ATTR); + refLookup[key] = refLookup[key] || {}; + refLookup[key][direction] = { + animationID: index, + element: jqLite(anchor) + }; + }); + } else { + preparedAnimations.push(animation); + } + }); + + var usedIndicesLookup = {}; + var anchorGroups = {}; + forEach(refLookup, function(operations, key) { + var from = operations.from; + var to = operations.to; + + if (!from || !to) { + // only one of these is set therefore we can't have an + // anchor animation since all three pieces are required + var index = from ? from.animationID : to.animationID; + var indexKey = index.toString(); + if (!usedIndicesLookup[indexKey]) { + usedIndicesLookup[indexKey] = true; + preparedAnimations.push(animations[index]); + } + return; + } + + var fromAnimation = animations[from.animationID]; + var toAnimation = animations[to.animationID]; + var lookupKey = from.animationID.toString(); + if (!anchorGroups[lookupKey]) { + var group = anchorGroups[lookupKey] = { + structural: true, + beforeStart: function() { + fromAnimation.beforeStart(); + toAnimation.beforeStart(); + }, + close: function() { + fromAnimation.close(); + toAnimation.close(); + }, + classes: cssClassesIntersection(fromAnimation.classes, toAnimation.classes), + from: fromAnimation, + to: toAnimation, + anchors: [] // TODO(matsko): change to reference nodes + }; + + // the anchor animations require that the from and to elements both have at least + // one shared CSS class which effectively marries the two elements together to use + // the same animation driver and to properly sequence the anchor animation. + if (group.classes.length) { + preparedAnimations.push(group); + } else { + preparedAnimations.push(fromAnimation); + preparedAnimations.push(toAnimation); + } + } + + anchorGroups[lookupKey].anchors.push({ + 'out': from.element, 'in': to.element + }); + }); + + return preparedAnimations; + } + + function cssClassesIntersection(a,b) { + a = a.split(' '); + b = b.split(' '); + var matches = []; + + for (var i = 0; i < a.length; i++) { + var aa = a[i]; + if (aa.substring(0,3) === 'ng-') continue; + + for (var j = 0; j < b.length; j++) { + if (aa === b[j]) { + matches.push(aa); + break; + } + } + } + + return matches.join(' '); + } + + function invokeFirstDriver(animationDetails) { + // we loop in reverse order since the more general drivers (like CSS and JS) + // may attempt more elements, but custom drivers are more particular + for (var i = drivers.length - 1; i >= 0; i--) { + var driverName = drivers[i]; + var factory = $injector.get(driverName); + var driver = factory(animationDetails); + if (driver) { + return driver; + } + } + } + + function beforeStart() { + element.addClass(NG_ANIMATE_CLASSNAME); + if (tempClasses) { + $$jqLite.addClass(element, tempClasses); + } + if (prepareClassName) { + $$jqLite.removeClass(element, prepareClassName); + prepareClassName = null; + } + } + + function updateAnimationRunners(animation, newRunner) { + if (animation.from && animation.to) { + update(animation.from.element); + update(animation.to.element); + } else { + update(animation.element); + } + + function update(element) { + var runner = getRunner(element); + if (runner) runner.setHost(newRunner); + } + } + + function handleDestroyedElement() { + var runner = getRunner(element); + if (runner && (event !== 'leave' || !options.$$domOperationFired)) { + runner.end(); + } + } + + function close(rejected) { + element.off('$destroy', handleDestroyedElement); + removeRunner(element); + + applyAnimationClasses(element, options); + applyAnimationStyles(element, options); + options.domOperation(); + + if (tempClasses) { + $$jqLite.removeClass(element, tempClasses); + } + + element.removeClass(NG_ANIMATE_CLASSNAME); + runner.complete(!rejected); + } + }; + }]; +}]; + +/** + * @ngdoc directive + * @name ngAnimateSwap + * @restrict A + * @scope + * + * @description + * + * ngAnimateSwap is a animation-oriented directive that allows for the container to + * be removed and entered in whenever the associated expression changes. A + * common usecase for this directive is a rotating banner or slider component which + * contains one image being present at a time. When the active image changes + * then the old image will perform a `leave` animation and the new element + * will be inserted via an `enter` animation. + * + * @animations + * | Animation | Occurs | + * |----------------------------------|--------------------------------------| + * | {@link ng.$animate#enter enter} | when the new element is inserted to the DOM | + * | {@link ng.$animate#leave leave} | when the old element is removed from the DOM | + * + * @example + * + * + *
+ *
+ * {{ number }} + *
+ *
+ *
+ * + * angular.module('ngAnimateSwapExample', ['ngAnimate']) + * .controller('AppCtrl', ['$scope', '$interval', function($scope, $interval) { + * $scope.number = 0; + * $interval(function() { + * $scope.number++; + * }, 1000); + * + * var colors = ['red','blue','green','yellow','orange']; + * $scope.colorClass = function(number) { + * return colors[number % colors.length]; + * }; + * }]); + * + * + * .container { + * height:250px; + * width:250px; + * position:relative; + * overflow:hidden; + * border:2px solid black; + * } + * .container .cell { + * font-size:150px; + * text-align:center; + * line-height:250px; + * position:absolute; + * top:0; + * left:0; + * right:0; + * border-bottom:2px solid black; + * } + * .swap-animation.ng-enter, .swap-animation.ng-leave { + * transition:0.5s linear all; + * } + * .swap-animation.ng-enter { + * top:-250px; + * } + * .swap-animation.ng-enter-active { + * top:0px; + * } + * .swap-animation.ng-leave { + * top:0px; + * } + * .swap-animation.ng-leave-active { + * top:250px; + * } + * .red { background:red; } + * .green { background:green; } + * .blue { background:blue; } + * .yellow { background:yellow; } + * .orange { background:orange; } + * + *
+ */ +var ngAnimateSwapDirective = ['$animate', '$rootScope', function($animate, $rootScope) { + return { + restrict: 'A', + transclude: 'element', + terminal: true, + priority: 600, // we use 600 here to ensure that the directive is caught before others + link: function(scope, $element, attrs, ctrl, $transclude) { + var previousElement, previousScope; + scope.$watchCollection(attrs.ngAnimateSwap || attrs['for'], function(value) { + if (previousElement) { + $animate.leave(previousElement); + } + if (previousScope) { + previousScope.$destroy(); + previousScope = null; + } + if (value || value === 0) { + previousScope = scope.$new(); + $transclude(previousScope, function(element) { + previousElement = element; + $animate.enter(element, null, $element); + }); + } + }); + } + }; +}]; + +/** + * @ngdoc module + * @name ngAnimate + * @description + * + * The `ngAnimate` module provides support for CSS-based animations (keyframes and transitions) as well as JavaScript-based animations via + * callback hooks. Animations are not enabled by default, however, by including `ngAnimate` the animation hooks are enabled for an Angular app. + * + *
+ * + * # Usage + * Simply put, there are two ways to make use of animations when ngAnimate is used: by using **CSS** and **JavaScript**. The former works purely based + * using CSS (by using matching CSS selectors/styles) and the latter triggers animations that are registered via `module.animation()`. For + * both CSS and JS animations the sole requirement is to have a matching `CSS class` that exists both in the registered animation and within + * the HTML element that the animation will be triggered on. + * + * ## Directive Support + * The following directives are "animation aware": + * + * | Directive | Supported Animations | + * |----------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------| + * | {@link ng.directive:ngRepeat#animations ngRepeat} | enter, leave and move | + * | {@link ngRoute.directive:ngView#animations ngView} | enter and leave | + * | {@link ng.directive:ngInclude#animations ngInclude} | enter and leave | + * | {@link ng.directive:ngSwitch#animations ngSwitch} | enter and leave | + * | {@link ng.directive:ngIf#animations ngIf} | enter and leave | + * | {@link ng.directive:ngClass#animations ngClass} | add and remove (the CSS class(es) present) | + * | {@link ng.directive:ngShow#animations ngShow} & {@link ng.directive:ngHide#animations ngHide} | add and remove (the ng-hide class value) | + * | {@link ng.directive:form#animation-hooks form} & {@link ng.directive:ngModel#animation-hooks ngModel} | add and remove (dirty, pristine, valid, invalid & all other validations) | + * | {@link module:ngMessages#animations ngMessages} | add and remove (ng-active & ng-inactive) | + * | {@link module:ngMessages#animations ngMessage} | enter and leave | + * + * (More information can be found by visiting each the documentation associated with each directive.) + * + * ## CSS-based Animations + * + * CSS-based animations with ngAnimate are unique since they require no JavaScript code at all. By using a CSS class that we reference between our HTML + * and CSS code we can create an animation that will be picked up by Angular when an the underlying directive performs an operation. + * + * The example below shows how an `enter` animation can be made possible on an element using `ng-if`: + * + * ```html + *
+ * Fade me in out + *
+ * + * + * ``` + * + * Notice the CSS class **fade**? We can now create the CSS transition code that references this class: + * + * ```css + * /* The starting CSS styles for the enter animation */ + * .fade.ng-enter { + * transition:0.5s linear all; + * opacity:0; + * } + * + * /* The finishing CSS styles for the enter animation */ + * .fade.ng-enter.ng-enter-active { + * opacity:1; + * } + * ``` + * + * The key thing to remember here is that, depending on the animation event (which each of the directives above trigger depending on what's going on) two + * generated CSS classes will be applied to the element; in the example above we have `.ng-enter` and `.ng-enter-active`. For CSS transitions, the transition + * code **must** be defined within the starting CSS class (in this case `.ng-enter`). The destination class is what the transition will animate towards. + * + * If for example we wanted to create animations for `leave` and `move` (ngRepeat triggers move) then we can do so using the same CSS naming conventions: + * + * ```css + * /* now the element will fade out before it is removed from the DOM */ + * .fade.ng-leave { + * transition:0.5s linear all; + * opacity:1; + * } + * .fade.ng-leave.ng-leave-active { + * opacity:0; + * } + * ``` + * + * We can also make use of **CSS Keyframes** by referencing the keyframe animation within the starting CSS class: + * + * ```css + * /* there is no need to define anything inside of the destination + * CSS class since the keyframe will take charge of the animation */ + * .fade.ng-leave { + * animation: my_fade_animation 0.5s linear; + * -webkit-animation: my_fade_animation 0.5s linear; + * } + * + * @keyframes my_fade_animation { + * from { opacity:1; } + * to { opacity:0; } + * } + * + * @-webkit-keyframes my_fade_animation { + * from { opacity:1; } + * to { opacity:0; } + * } + * ``` + * + * Feel free also mix transitions and keyframes together as well as any other CSS classes on the same element. + * + * ### CSS Class-based Animations + * + * Class-based animations (animations that are triggered via `ngClass`, `ngShow`, `ngHide` and some other directives) have a slightly different + * naming convention. Class-based animations are basic enough that a standard transition or keyframe can be referenced on the class being added + * and removed. + * + * For example if we wanted to do a CSS animation for `ngHide` then we place an animation on the `.ng-hide` CSS class: + * + * ```html + *
+ * Show and hide me + *
+ * + * + * + * ``` + * + * All that is going on here with ngShow/ngHide behind the scenes is the `.ng-hide` class is added/removed (when the hidden state is valid). Since + * ngShow and ngHide are animation aware then we can match up a transition and ngAnimate handles the rest. + * + * In addition the addition and removal of the CSS class, ngAnimate also provides two helper methods that we can use to further decorate the animation + * with CSS styles. + * + * ```html + *
+ * Highlight this box + *
+ * + * + * + * ``` + * + * We can also make use of CSS keyframes by placing them within the CSS classes. + * + * + * ### CSS Staggering Animations + * A Staggering animation is a collection of animations that are issued with a slight delay in between each successive operation resulting in a + * curtain-like effect. The ngAnimate module (versions >=1.2) supports staggering animations and the stagger effect can be + * performed by creating a **ng-EVENT-stagger** CSS class and attaching that class to the base CSS class used for + * the animation. The style property expected within the stagger class can either be a **transition-delay** or an + * **animation-delay** property (or both if your animation contains both transitions and keyframe animations). + * + * ```css + * .my-animation.ng-enter { + * /* standard transition code */ + * transition: 1s linear all; + * opacity:0; + * } + * .my-animation.ng-enter-stagger { + * /* this will have a 100ms delay between each successive leave animation */ + * transition-delay: 0.1s; + * + * /* As of 1.4.4, this must always be set: it signals ngAnimate + * to not accidentally inherit a delay property from another CSS class */ + * transition-duration: 0s; + * } + * .my-animation.ng-enter.ng-enter-active { + * /* standard transition styles */ + * opacity:1; + * } + * ``` + * + * Staggering animations work by default in ngRepeat (so long as the CSS class is defined). Outside of ngRepeat, to use staggering animations + * on your own, they can be triggered by firing multiple calls to the same event on $animate. However, the restrictions surrounding this + * are that each of the elements must have the same CSS className value as well as the same parent element. A stagger operation + * will also be reset if one or more animation frames have passed since the multiple calls to `$animate` were fired. + * + * The following code will issue the **ng-leave-stagger** event on the element provided: + * + * ```js + * var kids = parent.children(); + * + * $animate.leave(kids[0]); //stagger index=0 + * $animate.leave(kids[1]); //stagger index=1 + * $animate.leave(kids[2]); //stagger index=2 + * $animate.leave(kids[3]); //stagger index=3 + * $animate.leave(kids[4]); //stagger index=4 + * + * window.requestAnimationFrame(function() { + * //stagger has reset itself + * $animate.leave(kids[5]); //stagger index=0 + * $animate.leave(kids[6]); //stagger index=1 + * + * $scope.$digest(); + * }); + * ``` + * + * Stagger animations are currently only supported within CSS-defined animations. + * + * ### The `ng-animate` CSS class + * + * When ngAnimate is animating an element it will apply the `ng-animate` CSS class to the element for the duration of the animation. + * This is a temporary CSS class and it will be removed once the animation is over (for both JavaScript and CSS-based animations). + * + * Therefore, animations can be applied to an element using this temporary class directly via CSS. + * + * ```css + * .zipper.ng-animate { + * transition:0.5s linear all; + * } + * .zipper.ng-enter { + * opacity:0; + * } + * .zipper.ng-enter.ng-enter-active { + * opacity:1; + * } + * .zipper.ng-leave { + * opacity:1; + * } + * .zipper.ng-leave.ng-leave-active { + * opacity:0; + * } + * ``` + * + * (Note that the `ng-animate` CSS class is reserved and it cannot be applied on an element directly since ngAnimate will always remove + * the CSS class once an animation has completed.) + * + * + * ### The `ng-[event]-prepare` class + * + * This is a special class that can be used to prevent unwanted flickering / flash of content before + * the actual animation starts. The class is added as soon as an animation is initialized, but removed + * before the actual animation starts (after waiting for a $digest). + * It is also only added for *structural* animations (`enter`, `move`, and `leave`). + * + * In practice, flickering can appear when nesting elements with structural animations such as `ngIf` + * into elements that have class-based animations such as `ngClass`. + * + * ```html + *
+ *
+ *
+ *
+ *
+ * ``` + * + * It is possible that during the `enter` animation, the `.message` div will be briefly visible before it starts animating. + * In that case, you can add styles to the CSS that make sure the element stays hidden before the animation starts: + * + * ```css + * .message.ng-enter-prepare { + * opacity: 0; + * } + * + * ``` + * + * ## JavaScript-based Animations + * + * ngAnimate also allows for animations to be consumed by JavaScript code. The approach is similar to CSS-based animations (where there is a shared + * CSS class that is referenced in our HTML code) but in addition we need to register the JavaScript animation on the module. By making use of the + * `module.animation()` module function we can register the animation. + * + * Let's see an example of a enter/leave animation using `ngRepeat`: + * + * ```html + *
+ * {{ item }} + *
+ * ``` + * + * See the **slide** CSS class? Let's use that class to define an animation that we'll structure in our module code by using `module.animation`: + * + * ```js + * myModule.animation('.slide', [function() { + * return { + * // make note that other events (like addClass/removeClass) + * // have different function input parameters + * enter: function(element, doneFn) { + * jQuery(element).fadeIn(1000, doneFn); + * + * // remember to call doneFn so that angular + * // knows that the animation has concluded + * }, + * + * move: function(element, doneFn) { + * jQuery(element).fadeIn(1000, doneFn); + * }, + * + * leave: function(element, doneFn) { + * jQuery(element).fadeOut(1000, doneFn); + * } + * } + * }]); + * ``` + * + * The nice thing about JS-based animations is that we can inject other services and make use of advanced animation libraries such as + * greensock.js and velocity.js. + * + * If our animation code class-based (meaning that something like `ngClass`, `ngHide` and `ngShow` triggers it) then we can still define + * our animations inside of the same registered animation, however, the function input arguments are a bit different: + * + * ```html + *
+ * this box is moody + *
+ * + * + * + * ``` + * + * ```js + * myModule.animation('.colorful', [function() { + * return { + * addClass: function(element, className, doneFn) { + * // do some cool animation and call the doneFn + * }, + * removeClass: function(element, className, doneFn) { + * // do some cool animation and call the doneFn + * }, + * setClass: function(element, addedClass, removedClass, doneFn) { + * // do some cool animation and call the doneFn + * } + * } + * }]); + * ``` + * + * ## CSS + JS Animations Together + * + * AngularJS 1.4 and higher has taken steps to make the amalgamation of CSS and JS animations more flexible. However, unlike earlier versions of Angular, + * defining CSS and JS animations to work off of the same CSS class will not work anymore. Therefore the example below will only result in **JS animations taking + * charge of the animation**: + * + * ```html + *
+ * Slide in and out + *
+ * ``` + * + * ```js + * myModule.animation('.slide', [function() { + * return { + * enter: function(element, doneFn) { + * jQuery(element).slideIn(1000, doneFn); + * } + * } + * }]); + * ``` + * + * ```css + * .slide.ng-enter { + * transition:0.5s linear all; + * transform:translateY(-100px); + * } + * .slide.ng-enter.ng-enter-active { + * transform:translateY(0); + * } + * ``` + * + * Does this mean that CSS and JS animations cannot be used together? Do JS-based animations always have higher priority? We can make up for the + * lack of CSS animations by using the `$animateCss` service to trigger our own tweaked-out, CSS-based animations directly from + * our own JS-based animation code: + * + * ```js + * myModule.animation('.slide', ['$animateCss', function($animateCss) { + * return { + * enter: function(element) { +* // this will trigger `.slide.ng-enter` and `.slide.ng-enter-active`. + * return $animateCss(element, { + * event: 'enter', + * structural: true + * }); + * } + * } + * }]); + * ``` + * + * The nice thing here is that we can save bandwidth by sticking to our CSS-based animation code and we don't need to rely on a 3rd-party animation framework. + * + * The `$animateCss` service is very powerful since we can feed in all kinds of extra properties that will be evaluated and fed into a CSS transition or + * keyframe animation. For example if we wanted to animate the height of an element while adding and removing classes then we can do so by providing that + * data into `$animateCss` directly: + * + * ```js + * myModule.animation('.slide', ['$animateCss', function($animateCss) { + * return { + * enter: function(element) { + * return $animateCss(element, { + * event: 'enter', + * structural: true, + * addClass: 'maroon-setting', + * from: { height:0 }, + * to: { height: 200 } + * }); + * } + * } + * }]); + * ``` + * + * Now we can fill in the rest via our transition CSS code: + * + * ```css + * /* the transition tells ngAnimate to make the animation happen */ + * .slide.ng-enter { transition:0.5s linear all; } + * + * /* this extra CSS class will be absorbed into the transition + * since the $animateCss code is adding the class */ + * .maroon-setting { background:red; } + * ``` + * + * And `$animateCss` will figure out the rest. Just make sure to have the `done()` callback fire the `doneFn` function to signal when the animation is over. + * + * To learn more about what's possible be sure to visit the {@link ngAnimate.$animateCss $animateCss service}. + * + * ## Animation Anchoring (via `ng-animate-ref`) + * + * ngAnimate in AngularJS 1.4 comes packed with the ability to cross-animate elements between + * structural areas of an application (like views) by pairing up elements using an attribute + * called `ng-animate-ref`. + * + * Let's say for example we have two views that are managed by `ng-view` and we want to show + * that there is a relationship between two components situated in within these views. By using the + * `ng-animate-ref` attribute we can identify that the two components are paired together and we + * can then attach an animation, which is triggered when the view changes. + * + * Say for example we have the following template code: + * + * ```html + * + *
+ *
+ * + * + * + * + * + * + * + * + * ``` + * + * Now, when the view changes (once the link is clicked), ngAnimate will examine the + * HTML contents to see if there is a match reference between any components in the view + * that is leaving and the view that is entering. It will scan both the view which is being + * removed (leave) and inserted (enter) to see if there are any paired DOM elements that + * contain a matching ref value. + * + * The two images match since they share the same ref value. ngAnimate will now create a + * transport element (which is a clone of the first image element) and it will then attempt + * to animate to the position of the second image element in the next view. For the animation to + * work a special CSS class called `ng-anchor` will be added to the transported element. + * + * We can now attach a transition onto the `.banner.ng-anchor` CSS class and then + * ngAnimate will handle the entire transition for us as well as the addition and removal of + * any changes of CSS classes between the elements: + * + * ```css + * .banner.ng-anchor { + * /* this animation will last for 1 second since there are + * two phases to the animation (an `in` and an `out` phase) */ + * transition:0.5s linear all; + * } + * ``` + * + * We also **must** include animations for the views that are being entered and removed + * (otherwise anchoring wouldn't be possible since the new view would be inserted right away). + * + * ```css + * .view-animation.ng-enter, .view-animation.ng-leave { + * transition:0.5s linear all; + * position:fixed; + * left:0; + * top:0; + * width:100%; + * } + * .view-animation.ng-enter { + * transform:translateX(100%); + * } + * .view-animation.ng-leave, + * .view-animation.ng-enter.ng-enter-active { + * transform:translateX(0%); + * } + * .view-animation.ng-leave.ng-leave-active { + * transform:translateX(-100%); + * } + * ``` + * + * Now we can jump back to the anchor animation. When the animation happens, there are two stages that occur: + * an `out` and an `in` stage. The `out` stage happens first and that is when the element is animated away + * from its origin. Once that animation is over then the `in` stage occurs which animates the + * element to its destination. The reason why there are two animations is to give enough time + * for the enter animation on the new element to be ready. + * + * The example above sets up a transition for both the in and out phases, but we can also target the out or + * in phases directly via `ng-anchor-out` and `ng-anchor-in`. + * + * ```css + * .banner.ng-anchor-out { + * transition: 0.5s linear all; + * + * /* the scale will be applied during the out animation, + * but will be animated away when the in animation runs */ + * transform: scale(1.2); + * } + * + * .banner.ng-anchor-in { + * transition: 1s linear all; + * } + * ``` + * + * + * + * + * ### Anchoring Demo + * + + + Home +
+
+
+
+
+ + angular.module('anchoringExample', ['ngAnimate', 'ngRoute']) + .config(['$routeProvider', function($routeProvider) { + $routeProvider.when('/', { + templateUrl: 'home.html', + controller: 'HomeController as home' + }); + $routeProvider.when('/profile/:id', { + templateUrl: 'profile.html', + controller: 'ProfileController as profile' + }); + }]) + .run(['$rootScope', function($rootScope) { + $rootScope.records = [ + { id: 1, title: 'Miss Beulah Roob' }, + { id: 2, title: 'Trent Morissette' }, + { id: 3, title: 'Miss Ava Pouros' }, + { id: 4, title: 'Rod Pouros' }, + { id: 5, title: 'Abdul Rice' }, + { id: 6, title: 'Laurie Rutherford Sr.' }, + { id: 7, title: 'Nakia McLaughlin' }, + { id: 8, title: 'Jordon Blanda DVM' }, + { id: 9, title: 'Rhoda Hand' }, + { id: 10, title: 'Alexandrea Sauer' } + ]; + }]) + .controller('HomeController', [function() { + //empty + }]) + .controller('ProfileController', ['$rootScope', '$routeParams', + function ProfileController($rootScope, $routeParams) { + var index = parseInt($routeParams.id, 10); + var record = $rootScope.records[index - 1]; + + this.title = record.title; + this.id = record.id; + }]); + + +

Welcome to the home page

+

Please click on an element

+ + {{ record.title }} + +
+ +
+ {{ profile.title }} +
+
+ + .record { + display:block; + font-size:20px; + } + .profile { + background:black; + color:white; + font-size:100px; + } + .view-container { + position:relative; + } + .view-container > .view.ng-animate { + position:absolute; + top:0; + left:0; + width:100%; + min-height:500px; + } + .view.ng-enter, .view.ng-leave, + .record.ng-anchor { + transition:0.5s linear all; + } + .view.ng-enter { + transform:translateX(100%); + } + .view.ng-enter.ng-enter-active, .view.ng-leave { + transform:translateX(0%); + } + .view.ng-leave.ng-leave-active { + transform:translateX(-100%); + } + .record.ng-anchor-out { + background:red; + } + +
+ * + * ### How is the element transported? + * + * When an anchor animation occurs, ngAnimate will clone the starting element and position it exactly where the starting + * element is located on screen via absolute positioning. The cloned element will be placed inside of the root element + * of the application (where ng-app was defined) and all of the CSS classes of the starting element will be applied. The + * element will then animate into the `out` and `in` animations and will eventually reach the coordinates and match + * the dimensions of the destination element. During the entire animation a CSS class of `.ng-animate-shim` will be applied + * to both the starting and destination elements in order to hide them from being visible (the CSS styling for the class + * is: `visibility:hidden`). Once the anchor reaches its destination then it will be removed and the destination element + * will become visible since the shim class will be removed. + * + * ### How is the morphing handled? + * + * CSS Anchoring relies on transitions and keyframes and the internal code is intelligent enough to figure out + * what CSS classes differ between the starting element and the destination element. These different CSS classes + * will be added/removed on the anchor element and a transition will be applied (the transition that is provided + * in the anchor class). Long story short, ngAnimate will figure out what classes to add and remove which will + * make the transition of the element as smooth and automatic as possible. Be sure to use simple CSS classes that + * do not rely on DOM nesting structure so that the anchor element appears the same as the starting element (since + * the cloned element is placed inside of root element which is likely close to the body element). + * + * Note that if the root element is on the `` element then the cloned node will be placed inside of body. + * + * + * ## Using $animate in your directive code + * + * So far we've explored how to feed in animations into an Angular application, but how do we trigger animations within our own directives in our application? + * By injecting the `$animate` service into our directive code, we can trigger structural and class-based hooks which can then be consumed by animations. Let's + * imagine we have a greeting box that shows and hides itself when the data changes + * + * ```html + * Hi there + * ``` + * + * ```js + * ngModule.directive('greetingBox', ['$animate', function($animate) { + * return function(scope, element, attrs) { + * attrs.$observe('active', function(value) { + * value ? $animate.addClass(element, 'on') : $animate.removeClass(element, 'on'); + * }); + * }); + * }]); + * ``` + * + * Now the `on` CSS class is added and removed on the greeting box component. Now if we add a CSS class on top of the greeting box element + * in our HTML code then we can trigger a CSS or JS animation to happen. + * + * ```css + * /* normally we would create a CSS class to reference on the element */ + * greeting-box.on { transition:0.5s linear all; background:green; color:white; } + * ``` + * + * The `$animate` service contains a variety of other methods like `enter`, `leave`, `animate` and `setClass`. To learn more about what's + * possible be sure to visit the {@link ng.$animate $animate service API page}. + * + * + * ## Callbacks and Promises + * + * When `$animate` is called it returns a promise that can be used to capture when the animation has ended. Therefore if we were to trigger + * an animation (within our directive code) then we can continue performing directive and scope related activities after the animation has + * ended by chaining onto the returned promise that animation method returns. + * + * ```js + * // somewhere within the depths of the directive + * $animate.enter(element, parent).then(function() { + * //the animation has completed + * }); + * ``` + * + * (Note that earlier versions of Angular prior to v1.4 required the promise code to be wrapped using `$scope.$apply(...)`. This is not the case + * anymore.) + * + * In addition to the animation promise, we can also make use of animation-related callbacks within our directives and controller code by registering + * an event listener using the `$animate` service. Let's say for example that an animation was triggered on our view + * routing controller to hook into that: + * + * ```js + * ngModule.controller('HomePageController', ['$animate', function($animate) { + * $animate.on('enter', ngViewElement, function(element) { + * // the animation for this route has completed + * }]); + * }]) + * ``` + * + * (Note that you will need to trigger a digest within the callback to get angular to notice any scope-related changes.) + */ + +var copy; +var extend; +var forEach; +var isArray; +var isDefined; +var isElement; +var isFunction; +var isObject; +var isString; +var isUndefined; +var jqLite; +var noop; + +/** + * @ngdoc service + * @name $animate + * @kind object + * + * @description + * The ngAnimate `$animate` service documentation is the same for the core `$animate` service. + * + * Click here {@link ng.$animate to learn more about animations with `$animate`}. + */ +angular.module('ngAnimate', [], function initAngularHelpers() { + // Access helpers from angular core. + // Do it inside a `config` block to ensure `window.angular` is available. + noop = angular.noop; + copy = angular.copy; + extend = angular.extend; + jqLite = angular.element; + forEach = angular.forEach; + isArray = angular.isArray; + isString = angular.isString; + isObject = angular.isObject; + isUndefined = angular.isUndefined; + isDefined = angular.isDefined; + isFunction = angular.isFunction; + isElement = angular.isElement; +}) + .directive('ngAnimateSwap', ngAnimateSwapDirective) + + .directive('ngAnimateChildren', $$AnimateChildrenDirective) + .factory('$$rAFScheduler', $$rAFSchedulerFactory) + + .provider('$$animateQueue', $$AnimateQueueProvider) + .provider('$$animation', $$AnimationProvider) + + .provider('$animateCss', $AnimateCssProvider) + .provider('$$animateCssDriver', $$AnimateCssDriverProvider) + + .provider('$$animateJs', $$AnimateJsProvider) + .provider('$$animateJsDriver', $$AnimateJsDriverProvider); + + +})(window, window.angular); diff --git a/node_modules/angular-animate/angular-animate.min.js b/node_modules/angular-animate/angular-animate.min.js new file mode 100644 index 0000000..cabd0cf --- /dev/null +++ b/node_modules/angular-animate/angular-animate.min.js @@ -0,0 +1,57 @@ +/* + AngularJS v1.5.9 + (c) 2010-2016 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(R,B){'use strict';function Da(a,b,c){if(!a)throw Ma("areq",b||"?",c||"required");return a}function Ea(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;X(a)&&(a=a.join(" "));X(b)&&(b=b.join(" "));return a+" "+b}function Na(a){var b={};a&&(a.to||a.from)&&(b.to=a.to,b.from=a.from);return b}function Y(a,b,c){var d="";a=X(a)?a:a&&G(a)&&a.length?a.split(/\s+/):[];s(a,function(a,l){a&&0=a&&(a=e,e=0,b.push(k),k=[]);k.push(g.fn);g.children.forEach(function(a){e++;c.push(a)});a--}k.length&&b.push(k);return b}(c)}var u=[],C=Z(a);return function(n,Q,t){function H(a){a= +a.hasAttribute("ng-animate-ref")?[a]:a.querySelectorAll("[ng-animate-ref]");var b=[];s(a,function(a){var c=a.getAttribute("ng-animate-ref");c&&c.length&&b.push(a)});return b}function T(a){var b=[],c={};s(a,function(a,d){var h=y(a.element),e=0<=["enter","move"].indexOf(a.event),h=a.structural?H(h):[];if(h.length){var k=e?"to":"from";s(h,function(a){var b=a.getAttribute("ng-animate-ref");c[b]=c[b]||{};c[b][k]={animationID:d,element:F(a)}})}else b.push(a)});var d={},e={};s(c,function(c,k){var r=c.from, +p=c.to;if(r&&p){var z=a[r.animationID],g=a[p.animationID],A=r.animationID.toString();if(!e[A]){var n=e[A]={structural:!0,beforeStart:function(){z.beforeStart();g.beforeStart()},close:function(){z.close();g.close()},classes:O(z.classes,g.classes),from:z,to:g,anchors:[]};n.classes.length?b.push(n):(b.push(z),b.push(g))}e[A].anchors.push({out:r.element,"in":p.element})}else r=r?r.animationID:p.animationID,p=r.toString(),d[p]||(d[p]=!0,b.push(a[r]))});return b}function O(a,b){a=a.split(" ");b=b.split(" "); +for(var c=[],d=0;d=R&&b>=m&&(F=!0,k())}function N(){function b(){if(!w){M(!1);s(x,function(a){h.style[a[0]]=a[1]});T(a,f);e.addClass(a,ea);if(q.recalculateTimingStyles){na= +h.className+" "+ga;ia=B(h,na);D=H(h,na,ia);ca=D.maxDelay;J=Math.max(ca,0);m=D.maxDuration;if(0===m){k();return}q.hasTransitions=0l.expectedEndTime)?n.cancel(l.timer):g.push(k)}N&&(p=n(c,p,!1),g[0]={timer:p,expectedEndTime:d},g.push(k),a.data("$$animateCss",g));if(fa.length)a.on(fa.join(" "),z);f.to&&(f.cleanupStyles&&Ka(A,h,Object.keys(f.to)),Ga(a,f))}}function c(){var b=a.data("$$animateCss");if(b){for(var d=1;d=1.5.7 <2.0.0", + "type": "range" + }, + "C:\\Users\\samue\\documents\\github\\ffftp" + ] + ], + "_from": "angular-animate@>=1.5.7 <2.0.0", + "_id": "angular-animate@1.5.9", + "_inCache": true, + "_location": "/angular-animate", + "_nodeVersion": "4.4.7", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/angular-animate-1.5.9.tgz_1480018642751_0.4278404542710632" + }, + "_npmUser": { + "name": "angularcore", + "email": "angular-core+npm@google.com" + }, + "_npmVersion": "2.15.8", + "_phantomChildren": {}, + "_requested": { + "raw": "angular-animate@^1.5.7", + "scope": null, + "escapedName": "angular-animate", + "name": "angular-animate", + "rawSpec": "^1.5.7", + "spec": ">=1.5.7 <2.0.0", + "type": "range" + }, + "_requiredBy": [ + "#DEV:/" + ], + "_resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.5.9.tgz", + "_shasum": "c958d524f9333a48abdaefb32947c7e611f54453", + "_shrinkwrap": null, + "_spec": "angular-animate@^1.5.7", + "_where": "C:\\Users\\samue\\documents\\github\\ffftp", + "author": { + "name": "Angular Core Team", + "email": "angular-core+npm@google.com" + }, + "bugs": { + "url": "https://github.com/angular/angular.js/issues" + }, + "dependencies": {}, + "description": "AngularJS module for animations", + "devDependencies": {}, + "directories": {}, + "dist": { + "shasum": "c958d524f9333a48abdaefb32947c7e611f54453", + "tarball": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.5.9.tgz" + }, + "gitHead": "0fc827eb7723b82d9db2415ccc68d453673103cb", + "homepage": "http://angularjs.org", + "jspm": { + "shim": { + "angular-animate": { + "deps": [ + "angular" + ] + } + } + }, + "keywords": [ + "angular", + "framework", + "browser", + "animation", + "client-side" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "name": "akalinovski", + "email": "a.kalinovski@gmail.com" + }, + { + "name": "angularcore", + "email": "angular-core+npm@google.com" + }, + { + "name": "petebd", + "email": "pete@bacondarwin.com" + } + ], + "name": "angular-animate", + "optionalDependencies": {}, + "readme": "ERROR: No README data found!", + "repository": { + "type": "git", + "url": "git+https://github.com/angular/angular.js.git" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "version": "1.5.9" +} diff --git a/node_modules/angular-aria/LICENSE.md b/node_modules/angular-aria/LICENSE.md new file mode 100644 index 0000000..2c395ee --- /dev/null +++ b/node_modules/angular-aria/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Angular + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/node_modules/angular-aria/README.md b/node_modules/angular-aria/README.md new file mode 100644 index 0000000..04c5a8f --- /dev/null +++ b/node_modules/angular-aria/README.md @@ -0,0 +1,67 @@ +# packaged angular-aria + +This repo is for distribution on `npm` and `bower`. The source for this module is in the +[main AngularJS repo](https://github.com/angular/angular.js/tree/master/src/ngAria). +Please file issues and pull requests against that repo. + +## Install + +You can install this package either with `npm` or with `bower`. + +### npm + +```shell +npm install angular-aria +``` +Then add `ngAria` as a dependency for your app: + +```javascript +angular.module('myApp', [require('angular-aria')]); +``` + +### bower + +```shell +bower install angular-aria +``` + +Add a ` +``` + +Then add `ngAria` as a dependency for your app: + +```javascript +angular.module('myApp', ['ngAria']); +``` + +## Documentation + +Documentation is available on the +[AngularJS docs site](http://docs.angularjs.org/api/ngAria). + +## License + +The MIT License + +Copyright (c) 2010-2015 Google, Inc. http://angularjs.org + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/node_modules/angular-aria/angular-aria.js b/node_modules/angular-aria/angular-aria.js new file mode 100644 index 0000000..f3f7155 --- /dev/null +++ b/node_modules/angular-aria/angular-aria.js @@ -0,0 +1,407 @@ +/** + * @license AngularJS v1.5.9 + * (c) 2010-2016 Google, Inc. http://angularjs.org + * License: MIT + */ +(function(window, angular) {'use strict'; + +/** + * @ngdoc module + * @name ngAria + * @description + * + * The `ngAria` module provides support for common + * [ARIA](http://www.w3.org/TR/wai-aria/) + * attributes that convey state or semantic information about the application for users + * of assistive technologies, such as screen readers. + * + *
+ * + * ## Usage + * + * For ngAria to do its magic, simply include the module `ngAria` as a dependency. The following + * directives are supported: + * `ngModel`, `ngChecked`, `ngReadonly`, `ngRequired`, `ngValue`, `ngDisabled`, `ngShow`, `ngHide`, `ngClick`, + * `ngDblClick`, and `ngMessages`. + * + * Below is a more detailed breakdown of the attributes handled by ngAria: + * + * | Directive | Supported Attributes | + * |---------------------------------------------|----------------------------------------------------------------------------------------| + * | {@link ng.directive:ngModel ngModel} | aria-checked, aria-valuemin, aria-valuemax, aria-valuenow, aria-invalid, aria-required, input roles | + * | {@link ng.directive:ngDisabled ngDisabled} | aria-disabled | + * | {@link ng.directive:ngRequired ngRequired} | aria-required + * | {@link ng.directive:ngChecked ngChecked} | aria-checked + * | {@link ng.directive:ngReadonly ngReadonly} | aria-readonly | + * | {@link ng.directive:ngValue ngValue} | aria-checked | + * | {@link ng.directive:ngShow ngShow} | aria-hidden | + * | {@link ng.directive:ngHide ngHide} | aria-hidden | + * | {@link ng.directive:ngDblclick ngDblclick} | tabindex | + * | {@link module:ngMessages ngMessages} | aria-live | + * | {@link ng.directive:ngClick ngClick} | tabindex, keypress event, button role | + * + * Find out more information about each directive by reading the + * {@link guide/accessibility ngAria Developer Guide}. + * + * ## Example + * Using ngDisabled with ngAria: + * ```html + * + * ``` + * Becomes: + * ```html + * + * ``` + * + * ## Disabling Attributes + * It's possible to disable individual attributes added by ngAria with the + * {@link ngAria.$ariaProvider#config config} method. For more details, see the + * {@link guide/accessibility Developer Guide}. + */ +var ngAriaModule = angular.module('ngAria', ['ng']). + provider('$aria', $AriaProvider); + +/** +* Internal Utilities +*/ +var nodeBlackList = ['BUTTON', 'A', 'INPUT', 'TEXTAREA', 'SELECT', 'DETAILS', 'SUMMARY']; + +var isNodeOneOf = function(elem, nodeTypeArray) { + if (nodeTypeArray.indexOf(elem[0].nodeName) !== -1) { + return true; + } +}; +/** + * @ngdoc provider + * @name $ariaProvider + * @this + * + * @description + * + * Used for configuring the ARIA attributes injected and managed by ngAria. + * + * ```js + * angular.module('myApp', ['ngAria'], function config($ariaProvider) { + * $ariaProvider.config({ + * ariaValue: true, + * tabindex: false + * }); + * }); + *``` + * + * ## Dependencies + * Requires the {@link ngAria} module to be installed. + * + */ +function $AriaProvider() { + var config = { + ariaHidden: true, + ariaChecked: true, + ariaReadonly: true, + ariaDisabled: true, + ariaRequired: true, + ariaInvalid: true, + ariaValue: true, + tabindex: true, + bindKeypress: true, + bindRoleForClick: true + }; + + /** + * @ngdoc method + * @name $ariaProvider#config + * + * @param {object} config object to enable/disable specific ARIA attributes + * + * - **ariaHidden** – `{boolean}` – Enables/disables aria-hidden tags + * - **ariaChecked** – `{boolean}` – Enables/disables aria-checked tags + * - **ariaReadonly** – `{boolean}` – Enables/disables aria-readonly tags + * - **ariaDisabled** – `{boolean}` – Enables/disables aria-disabled tags + * - **ariaRequired** – `{boolean}` – Enables/disables aria-required tags + * - **ariaInvalid** – `{boolean}` – Enables/disables aria-invalid tags + * - **ariaValue** – `{boolean}` – Enables/disables aria-valuemin, aria-valuemax and aria-valuenow tags + * - **tabindex** – `{boolean}` – Enables/disables tabindex tags + * - **bindKeypress** – `{boolean}` – Enables/disables keypress event binding on `div` and + * `li` elements with ng-click + * - **bindRoleForClick** – `{boolean}` – Adds role=button to non-interactive elements like `div` + * using ng-click, making them more accessible to users of assistive technologies + * + * @description + * Enables/disables various ARIA attributes + */ + this.config = function(newConfig) { + config = angular.extend(config, newConfig); + }; + + function watchExpr(attrName, ariaAttr, nodeBlackList, negate) { + return function(scope, elem, attr) { + var ariaCamelName = attr.$normalize(ariaAttr); + if (config[ariaCamelName] && !isNodeOneOf(elem, nodeBlackList) && !attr[ariaCamelName]) { + scope.$watch(attr[attrName], function(boolVal) { + // ensure boolean value + boolVal = negate ? !boolVal : !!boolVal; + elem.attr(ariaAttr, boolVal); + }); + } + }; + } + /** + * @ngdoc service + * @name $aria + * + * @description + * @priority 200 + * + * The $aria service contains helper methods for applying common + * [ARIA](http://www.w3.org/TR/wai-aria/) attributes to HTML directives. + * + * ngAria injects common accessibility attributes that tell assistive technologies when HTML + * elements are enabled, selected, hidden, and more. To see how this is performed with ngAria, + * let's review a code snippet from ngAria itself: + * + *```js + * ngAriaModule.directive('ngDisabled', ['$aria', function($aria) { + * return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false); + * }]) + *``` + * Shown above, the ngAria module creates a directive with the same signature as the + * traditional `ng-disabled` directive. But this ngAria version is dedicated to + * solely managing accessibility attributes on custom elements. The internal `$aria` service is + * used to watch the boolean attribute `ngDisabled`. If it has not been explicitly set by the + * developer, `aria-disabled` is injected as an attribute with its value synchronized to the + * value in `ngDisabled`. + * + * Because ngAria hooks into the `ng-disabled` directive, developers do not have to do + * anything to enable this feature. The `aria-disabled` attribute is automatically managed + * simply as a silent side-effect of using `ng-disabled` with the ngAria module. + * + * The full list of directives that interface with ngAria: + * * **ngModel** + * * **ngChecked** + * * **ngReadonly** + * * **ngRequired** + * * **ngDisabled** + * * **ngValue** + * * **ngShow** + * * **ngHide** + * * **ngClick** + * * **ngDblclick** + * * **ngMessages** + * + * Read the {@link guide/accessibility ngAria Developer Guide} for a thorough explanation of each + * directive. + * + * + * ## Dependencies + * Requires the {@link ngAria} module to be installed. + */ + this.$get = function() { + return { + config: function(key) { + return config[key]; + }, + $$watchExpr: watchExpr + }; + }; +} + + +ngAriaModule.directive('ngShow', ['$aria', function($aria) { + return $aria.$$watchExpr('ngShow', 'aria-hidden', [], true); +}]) +.directive('ngHide', ['$aria', function($aria) { + return $aria.$$watchExpr('ngHide', 'aria-hidden', [], false); +}]) +.directive('ngValue', ['$aria', function($aria) { + return $aria.$$watchExpr('ngValue', 'aria-checked', nodeBlackList, false); +}]) +.directive('ngChecked', ['$aria', function($aria) { + return $aria.$$watchExpr('ngChecked', 'aria-checked', nodeBlackList, false); +}]) +.directive('ngReadonly', ['$aria', function($aria) { + return $aria.$$watchExpr('ngReadonly', 'aria-readonly', nodeBlackList, false); +}]) +.directive('ngRequired', ['$aria', function($aria) { + return $aria.$$watchExpr('ngRequired', 'aria-required', nodeBlackList, false); +}]) +.directive('ngModel', ['$aria', function($aria) { + + function shouldAttachAttr(attr, normalizedAttr, elem, allowBlacklistEls) { + return $aria.config(normalizedAttr) && !elem.attr(attr) && (allowBlacklistEls || !isNodeOneOf(elem, nodeBlackList)); + } + + function shouldAttachRole(role, elem) { + // if element does not have role attribute + // AND element type is equal to role (if custom element has a type equaling shape) <-- remove? + // AND element is not INPUT + return !elem.attr('role') && (elem.attr('type') === role) && (elem[0].nodeName !== 'INPUT'); + } + + function getShape(attr, elem) { + var type = attr.type, + role = attr.role; + + return ((type || role) === 'checkbox' || role === 'menuitemcheckbox') ? 'checkbox' : + ((type || role) === 'radio' || role === 'menuitemradio') ? 'radio' : + (type === 'range' || role === 'progressbar' || role === 'slider') ? 'range' : ''; + } + + return { + restrict: 'A', + require: 'ngModel', + priority: 200, //Make sure watches are fired after any other directives that affect the ngModel value + compile: function(elem, attr) { + var shape = getShape(attr, elem); + + return { + pre: function(scope, elem, attr, ngModel) { + if (shape === 'checkbox') { + //Use the input[checkbox] $isEmpty implementation for elements with checkbox roles + ngModel.$isEmpty = function(value) { + return value === false; + }; + } + }, + post: function(scope, elem, attr, ngModel) { + var needsTabIndex = shouldAttachAttr('tabindex', 'tabindex', elem, false); + + function ngAriaWatchModelValue() { + return ngModel.$modelValue; + } + + function getRadioReaction(newVal) { + // Strict comparison would cause a BC + // eslint-disable-next-line eqeqeq + var boolVal = (attr.value == ngModel.$viewValue); + elem.attr('aria-checked', boolVal); + } + + function getCheckboxReaction() { + elem.attr('aria-checked', !ngModel.$isEmpty(ngModel.$viewValue)); + } + + switch (shape) { + case 'radio': + case 'checkbox': + if (shouldAttachRole(shape, elem)) { + elem.attr('role', shape); + } + if (shouldAttachAttr('aria-checked', 'ariaChecked', elem, false)) { + scope.$watch(ngAriaWatchModelValue, shape === 'radio' ? + getRadioReaction : getCheckboxReaction); + } + if (needsTabIndex) { + elem.attr('tabindex', 0); + } + break; + case 'range': + if (shouldAttachRole(shape, elem)) { + elem.attr('role', 'slider'); + } + if ($aria.config('ariaValue')) { + var needsAriaValuemin = !elem.attr('aria-valuemin') && + (attr.hasOwnProperty('min') || attr.hasOwnProperty('ngMin')); + var needsAriaValuemax = !elem.attr('aria-valuemax') && + (attr.hasOwnProperty('max') || attr.hasOwnProperty('ngMax')); + var needsAriaValuenow = !elem.attr('aria-valuenow'); + + if (needsAriaValuemin) { + attr.$observe('min', function ngAriaValueMinReaction(newVal) { + elem.attr('aria-valuemin', newVal); + }); + } + if (needsAriaValuemax) { + attr.$observe('max', function ngAriaValueMinReaction(newVal) { + elem.attr('aria-valuemax', newVal); + }); + } + if (needsAriaValuenow) { + scope.$watch(ngAriaWatchModelValue, function ngAriaValueNowReaction(newVal) { + elem.attr('aria-valuenow', newVal); + }); + } + } + if (needsTabIndex) { + elem.attr('tabindex', 0); + } + break; + } + + if (!attr.hasOwnProperty('ngRequired') && ngModel.$validators.required + && shouldAttachAttr('aria-required', 'ariaRequired', elem, false)) { + // ngModel.$error.required is undefined on custom controls + attr.$observe('required', function() { + elem.attr('aria-required', !!attr['required']); + }); + } + + if (shouldAttachAttr('aria-invalid', 'ariaInvalid', elem, true)) { + scope.$watch(function ngAriaInvalidWatch() { + return ngModel.$invalid; + }, function ngAriaInvalidReaction(newVal) { + elem.attr('aria-invalid', !!newVal); + }); + } + } + }; + } + }; +}]) +.directive('ngDisabled', ['$aria', function($aria) { + return $aria.$$watchExpr('ngDisabled', 'aria-disabled', nodeBlackList, false); +}]) +.directive('ngMessages', function() { + return { + restrict: 'A', + require: '?ngMessages', + link: function(scope, elem, attr, ngMessages) { + if (!elem.attr('aria-live')) { + elem.attr('aria-live', 'assertive'); + } + } + }; +}) +.directive('ngClick',['$aria', '$parse', function($aria, $parse) { + return { + restrict: 'A', + compile: function(elem, attr) { + var fn = $parse(attr.ngClick, /* interceptorFn */ null, /* expensiveChecks */ true); + return function(scope, elem, attr) { + + if (!isNodeOneOf(elem, nodeBlackList)) { + + if ($aria.config('bindRoleForClick') && !elem.attr('role')) { + elem.attr('role', 'button'); + } + + if ($aria.config('tabindex') && !elem.attr('tabindex')) { + elem.attr('tabindex', 0); + } + + if ($aria.config('bindKeypress') && !attr.ngKeypress) { + elem.on('keypress', function(event) { + var keyCode = event.which || event.keyCode; + if (keyCode === 32 || keyCode === 13) { + scope.$apply(callback); + } + + function callback() { + fn(scope, { $event: event }); + } + }); + } + } + }; + } + }; +}]) +.directive('ngDblclick', ['$aria', function($aria) { + return function(scope, elem, attr) { + if ($aria.config('tabindex') && !elem.attr('tabindex') && !isNodeOneOf(elem, nodeBlackList)) { + elem.attr('tabindex', 0); + } + }; +}]); + + +})(window, window.angular); diff --git a/node_modules/angular-aria/angular-aria.min.js b/node_modules/angular-aria/angular-aria.min.js new file mode 100644 index 0000000..f668059 --- /dev/null +++ b/node_modules/angular-aria/angular-aria.min.js @@ -0,0 +1,14 @@ +/* + AngularJS v1.5.9 + (c) 2010-2016 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(t,p){'use strict';var b="BUTTON A INPUT TEXTAREA SELECT DETAILS SUMMARY".split(" "),l=function(a,c){if(-1!==c.indexOf(a[0].nodeName))return!0};p.module("ngAria",["ng"]).provider("$aria",function(){function a(a,b,m,h){return function(d,f,e){var q=e.$normalize(b);!c[q]||l(f,m)||e[q]||d.$watch(e[a],function(a){a=h?!a:!!a;f.attr(b,a)})}}var c={ariaHidden:!0,ariaChecked:!0,ariaReadonly:!0,ariaDisabled:!0,ariaRequired:!0,ariaInvalid:!0,ariaValue:!0,tabindex:!0,bindKeypress:!0,bindRoleForClick:!0}; +this.config=function(a){c=p.extend(c,a)};this.$get=function(){return{config:function(a){return c[a]},$$watchExpr:a}}}).directive("ngShow",["$aria",function(a){return a.$$watchExpr("ngShow","aria-hidden",[],!0)}]).directive("ngHide",["$aria",function(a){return a.$$watchExpr("ngHide","aria-hidden",[],!1)}]).directive("ngValue",["$aria",function(a){return a.$$watchExpr("ngValue","aria-checked",b,!1)}]).directive("ngChecked",["$aria",function(a){return a.$$watchExpr("ngChecked","aria-checked",b,!1)}]).directive("ngReadonly", +["$aria",function(a){return a.$$watchExpr("ngReadonly","aria-readonly",b,!1)}]).directive("ngRequired",["$aria",function(a){return a.$$watchExpr("ngRequired","aria-required",b,!1)}]).directive("ngModel",["$aria",function(a){function c(c,h,d,f){return a.config(h)&&!d.attr(c)&&(f||!l(d,b))}function g(a,c){return!c.attr("role")&&c.attr("type")===a&&"INPUT"!==c[0].nodeName}function k(a,c){var d=a.type,f=a.role;return"checkbox"===(d||f)||"menuitemcheckbox"===f?"checkbox":"radio"===(d||f)||"menuitemradio"=== +f?"radio":"range"===d||"progressbar"===f||"slider"===f?"range":""}return{restrict:"A",require:"ngModel",priority:200,compile:function(b,h){var d=k(h,b);return{pre:function(a,e,c,b){"checkbox"===d&&(b.$isEmpty=function(a){return!1===a})},post:function(f,e,b,n){function h(){return n.$modelValue}function k(a){e.attr("aria-checked",b.value==n.$viewValue)}function l(){e.attr("aria-checked",!n.$isEmpty(n.$viewValue))}var m=c("tabindex","tabindex",e,!1);switch(d){case "radio":case "checkbox":g(d,e)&&e.attr("role", +d);c("aria-checked","ariaChecked",e,!1)&&f.$watch(h,"radio"===d?k:l);m&&e.attr("tabindex",0);break;case "range":g(d,e)&&e.attr("role","slider");if(a.config("ariaValue")){var p=!e.attr("aria-valuemin")&&(b.hasOwnProperty("min")||b.hasOwnProperty("ngMin")),r=!e.attr("aria-valuemax")&&(b.hasOwnProperty("max")||b.hasOwnProperty("ngMax")),s=!e.attr("aria-valuenow");p&&b.$observe("min",function(a){e.attr("aria-valuemin",a)});r&&b.$observe("max",function(a){e.attr("aria-valuemax",a)});s&&f.$watch(h,function(a){e.attr("aria-valuenow", +a)})}m&&e.attr("tabindex",0)}!b.hasOwnProperty("ngRequired")&&n.$validators.required&&c("aria-required","ariaRequired",e,!1)&&b.$observe("required",function(){e.attr("aria-required",!!b.required)});c("aria-invalid","ariaInvalid",e,!0)&&f.$watch(function(){return n.$invalid},function(a){e.attr("aria-invalid",!!a)})}}}}}]).directive("ngDisabled",["$aria",function(a){return a.$$watchExpr("ngDisabled","aria-disabled",b,!1)}]).directive("ngMessages",function(){return{restrict:"A",require:"?ngMessages", +link:function(a,b,g,k){b.attr("aria-live")||b.attr("aria-live","assertive")}}}).directive("ngClick",["$aria","$parse",function(a,c){return{restrict:"A",compile:function(g,k){var m=c(k.ngClick,null,!0);return function(c,d,f){if(!l(d,b)&&(a.config("bindRoleForClick")&&!d.attr("role")&&d.attr("role","button"),a.config("tabindex")&&!d.attr("tabindex")&&d.attr("tabindex",0),a.config("bindKeypress")&&!f.ngKeypress))d.on("keypress",function(a){function b(){m(c,{$event:a})}var d=a.which||a.keyCode;32!==d&& +13!==d||c.$apply(b)})}}}}]).directive("ngDblclick",["$aria",function(a){return function(c,g,k){!a.config("tabindex")||g.attr("tabindex")||l(g,b)||g.attr("tabindex",0)}}])})(window,window.angular); +//# sourceMappingURL=angular-aria.min.js.map diff --git a/node_modules/angular-aria/angular-aria.min.js.map b/node_modules/angular-aria/angular-aria.min.js.map new file mode 100644 index 0000000..61f27a1 --- /dev/null +++ b/node_modules/angular-aria/angular-aria.min.js.map @@ -0,0 +1,8 @@ +{ +"version":3, +"file":"angular-aria.min.js", +"lineCount":13, +"mappings":"A;;;;;aAKC,SAAQ,CAACA,CAAD,CAASC,CAAT,CAAkB,CA6D3B,IAAIC,EAAgB,gDAAA,MAAA,CAAA,GAAA,CAApB,CAEIC,EAAcA,QAAQ,CAACC,CAAD,CAAOC,CAAP,CAAsB,CAC9C,GAAiD,EAAjD,GAAIA,CAAAC,QAAA,CAAsBF,CAAA,CAAK,CAAL,CAAAG,SAAtB,CAAJ,CACE,MAAO,CAAA,CAFqC,CAR7BN,EAAAO,OAAA,CAAe,QAAf,CAAyB,CAAC,IAAD,CAAzB,CAAAC,SAAAC,CACc,OADdA,CAmCnBC,QAAsB,EAAG,CAwCvBC,QAASA,EAAS,CAACC,CAAD,CAAWC,CAAX,CAAqBZ,CAArB,CAAoCa,CAApC,CAA4C,CAC5D,MAAO,SAAQ,CAACC,CAAD,CAAQZ,CAAR,CAAca,CAAd,CAAoB,CACjC,IAAIC,EAAgBD,CAAAE,WAAA,CAAgBL,CAAhB,CAChB,EAAAM,CAAA,CAAOF,CAAP,CAAJ,EAA8Bf,CAAA,CAAYC,CAAZ,CAAkBF,CAAlB,CAA9B,EAAmEe,CAAA,CAAKC,CAAL,CAAnE,EACEF,CAAAK,OAAA,CAAaJ,CAAA,CAAKJ,CAAL,CAAb,CAA6B,QAAQ,CAACS,CAAD,CAAU,CAE7CA,CAAA,CAAUP,CAAA,CAAS,CAACO,CAAV,CAAoB,CAAEA,CAAAA,CAChClB,EAAAa,KAAA,CAAUH,CAAV,CAAoBQ,CAApB,CAH6C,CAA/C,CAH+B,CADyB,CAvC9D,IAAIF,EAAS,CACXG,WAAY,CAAA,CADD,CAEXC,YAAa,CAAA,CAFF,CAGXC,aAAc,CAAA,CAHH,CAIXC,aAAc,CAAA,CAJH,CAKXC,aAAc,CAAA,CALH,CAMXC,YAAa,CAAA,CANF,CAOXC,UAAW,CAAA,CAPA,CAQXC,SAAU,CAAA,CARC,CASXC,aAAc,CAAA,CATH,CAUXC,iBAAkB,CAAA,CAVP,CAmCb;IAAAZ,OAAA,CAAca,QAAQ,CAACC,CAAD,CAAY,CAChCd,CAAA,CAASnB,CAAAkC,OAAA,CAAef,CAAf,CAAuBc,CAAvB,CADuB,CAkElC,KAAAE,KAAA,CAAYC,QAAQ,EAAG,CACrB,MAAO,CACLjB,OAAQA,QAAQ,CAACkB,CAAD,CAAM,CACpB,MAAOlB,EAAA,CAAOkB,CAAP,CADa,CADjB,CAILC,YAAa3B,CAJR,CADc,CAtGA,CAnCNF,CAoJnB8B,UAAA,CAAuB,QAAvB,CAAiC,CAAC,OAAD,CAAU,QAAQ,CAACC,CAAD,CAAQ,CACzD,MAAOA,EAAAF,YAAA,CAAkB,QAAlB,CAA4B,aAA5B,CAA2C,EAA3C,CAA+C,CAAA,CAA/C,CADkD,CAA1B,CAAjC,CAAAC,UAAA,CAGW,QAHX,CAGqB,CAAC,OAAD,CAAU,QAAQ,CAACC,CAAD,CAAQ,CAC7C,MAAOA,EAAAF,YAAA,CAAkB,QAAlB,CAA4B,aAA5B,CAA2C,EAA3C,CAA+C,CAAA,CAA/C,CADsC,CAA1B,CAHrB,CAAAC,UAAA,CAMW,SANX,CAMsB,CAAC,OAAD,CAAU,QAAQ,CAACC,CAAD,CAAQ,CAC9C,MAAOA,EAAAF,YAAA,CAAkB,SAAlB,CAA6B,cAA7B,CAA6CrC,CAA7C,CAA4D,CAAA,CAA5D,CADuC,CAA1B,CANtB,CAAAsC,UAAA,CASW,WATX,CASwB,CAAC,OAAD,CAAU,QAAQ,CAACC,CAAD,CAAQ,CAChD,MAAOA,EAAAF,YAAA,CAAkB,WAAlB,CAA+B,cAA/B,CAA+CrC,CAA/C,CAA8D,CAAA,CAA9D,CADyC,CAA1B,CATxB,CAAAsC,UAAA,CAYW,YAZX;AAYyB,CAAC,OAAD,CAAU,QAAQ,CAACC,CAAD,CAAQ,CACjD,MAAOA,EAAAF,YAAA,CAAkB,YAAlB,CAAgC,eAAhC,CAAiDrC,CAAjD,CAAgE,CAAA,CAAhE,CAD0C,CAA1B,CAZzB,CAAAsC,UAAA,CAeW,YAfX,CAeyB,CAAC,OAAD,CAAU,QAAQ,CAACC,CAAD,CAAQ,CACjD,MAAOA,EAAAF,YAAA,CAAkB,YAAlB,CAAgC,eAAhC,CAAiDrC,CAAjD,CAAgE,CAAA,CAAhE,CAD0C,CAA1B,CAfzB,CAAAsC,UAAA,CAkBW,SAlBX,CAkBsB,CAAC,OAAD,CAAU,QAAQ,CAACC,CAAD,CAAQ,CAE9CC,QAASA,EAAgB,CAACzB,CAAD,CAAO0B,CAAP,CAAuBvC,CAAvB,CAA6BwC,CAA7B,CAAgD,CACvE,MAAOH,EAAArB,OAAA,CAAauB,CAAb,CAAP,EAAuC,CAACvC,CAAAa,KAAA,CAAUA,CAAV,CAAxC,GAA4D2B,CAA5D,EAAiF,CAACzC,CAAA,CAAYC,CAAZ,CAAkBF,CAAlB,CAAlF,CADuE,CAIzE2C,QAASA,EAAgB,CAACC,CAAD,CAAO1C,CAAP,CAAa,CAIpC,MAAO,CAACA,CAAAa,KAAA,CAAU,MAAV,CAAR,EAA8Bb,CAAAa,KAAA,CAAU,MAAV,CAA9B,GAAoD6B,CAApD,EAAmF,OAAnF,GAA8D1C,CAAA,CAAK,CAAL,CAAAG,SAJ1B,CAOtCwC,QAASA,EAAQ,CAAC9B,CAAD,CAAOb,CAAP,CAAa,CAAA,IACxB4C,EAAO/B,CAAA+B,KADiB,CAExBF,EAAO7B,CAAA6B,KAEX,OAA2B,UAApB,IAAEE,CAAF,EAAUF,CAAV,GAA2C,kBAA3C,GAAkCA,CAAlC,CAAiE,UAAjE,CACoB,OAApB,IAAEE,CAAF,EAAUF,CAAV,GAA2C,eAA3C;AAAkCA,CAAlC,CAA8D,OAA9D,CACU,OAAV,GAACE,CAAD,EAA2C,aAA3C,GAAkCF,CAAlC,EAAqE,QAArE,GAA4DA,CAA5D,CAAiF,OAAjF,CAA2F,EANtE,CAS9B,MAAO,CACLG,SAAU,GADL,CAELC,QAAS,SAFJ,CAGLC,SAAU,GAHL,CAILC,QAASA,QAAQ,CAAChD,CAAD,CAAOa,CAAP,CAAa,CAC5B,IAAIoC,EAAQN,CAAA,CAAS9B,CAAT,CAAeb,CAAf,CAEZ,OAAO,CACLkD,IAAKA,QAAQ,CAACtC,CAAD,CAAQZ,CAAR,CAAca,CAAd,CAAoBsC,CAApB,CAA6B,CAC1B,UAAd,GAAIF,CAAJ,GAEEE,CAAAC,SAFF,CAEqBC,QAAQ,CAACC,CAAD,CAAQ,CACjC,MAAiB,CAAA,CAAjB,GAAOA,CAD0B,CAFrC,CADwC,CADrC,CASLC,KAAMA,QAAQ,CAAC3C,CAAD,CAAQZ,CAAR,CAAca,CAAd,CAAoBsC,CAApB,CAA6B,CAGzCK,QAASA,EAAqB,EAAG,CAC/B,MAAOL,EAAAM,YADwB,CAIjCC,QAASA,EAAgB,CAACC,CAAD,CAAS,CAIhC3D,CAAAa,KAAA,CAAU,cAAV,CADeA,CAAAyC,MACf,EAD6BH,CAAAS,WAC7B,CAJgC,CAOlCC,QAASA,EAAmB,EAAG,CAC7B7D,CAAAa,KAAA,CAAU,cAAV,CAA0B,CAACsC,CAAAC,SAAA,CAAiBD,CAAAS,WAAjB,CAA3B,CAD6B,CAb/B,IAAIE,EAAgBxB,CAAA,CAAiB,UAAjB,CAA6B,UAA7B,CAAyCtC,CAAzC,CAA+C,CAAA,CAA/C,CAiBpB,QAAQiD,CAAR,EACE,KAAK,OAAL,CACA,KAAK,UAAL,CACMR,CAAA,CAAiBQ,CAAjB,CAAwBjD,CAAxB,CAAJ,EACEA,CAAAa,KAAA,CAAU,MAAV;AAAkBoC,CAAlB,CAEEX,EAAA,CAAiB,cAAjB,CAAiC,aAAjC,CAAgDtC,CAAhD,CAAsD,CAAA,CAAtD,CAAJ,EACEY,CAAAK,OAAA,CAAauC,CAAb,CAA8C,OAAV,GAAAP,CAAA,CAChCS,CADgC,CACbG,CADvB,CAGEC,EAAJ,EACE9D,CAAAa,KAAA,CAAU,UAAV,CAAsB,CAAtB,CAEF,MACF,MAAK,OAAL,CACM4B,CAAA,CAAiBQ,CAAjB,CAAwBjD,CAAxB,CAAJ,EACEA,CAAAa,KAAA,CAAU,MAAV,CAAkB,QAAlB,CAEF,IAAIwB,CAAArB,OAAA,CAAa,WAAb,CAAJ,CAA+B,CAC7B,IAAI+C,EAAoB,CAAC/D,CAAAa,KAAA,CAAU,eAAV,CAArBkD,GACClD,CAAAmD,eAAA,CAAoB,KAApB,CADDD,EAC+BlD,CAAAmD,eAAA,CAAoB,OAApB,CAD/BD,CAAJ,CAEIE,EAAoB,CAACjE,CAAAa,KAAA,CAAU,eAAV,CAArBoD,GACCpD,CAAAmD,eAAA,CAAoB,KAApB,CADDC,EAC+BpD,CAAAmD,eAAA,CAAoB,OAApB,CAD/BC,CAFJ,CAIIC,EAAoB,CAAClE,CAAAa,KAAA,CAAU,eAAV,CAErBkD,EAAJ,EACElD,CAAAsD,SAAA,CAAc,KAAd,CAAqBC,QAA+B,CAACT,CAAD,CAAS,CAC3D3D,CAAAa,KAAA,CAAU,eAAV,CAA2B8C,CAA3B,CAD2D,CAA7D,CAIEM,EAAJ,EACEpD,CAAAsD,SAAA,CAAc,KAAd,CAAqBC,QAA+B,CAACT,CAAD,CAAS,CAC3D3D,CAAAa,KAAA,CAAU,eAAV,CAA2B8C,CAA3B,CAD2D,CAA7D,CAIEO,EAAJ,EACEtD,CAAAK,OAAA,CAAauC,CAAb,CAAoCa,QAA+B,CAACV,CAAD,CAAS,CAC1E3D,CAAAa,KAAA,CAAU,eAAV;AAA2B8C,CAA3B,CAD0E,CAA5E,CAlB2B,CAuB3BG,CAAJ,EACE9D,CAAAa,KAAA,CAAU,UAAV,CAAsB,CAAtB,CA1CN,CA+CK,CAAAA,CAAAmD,eAAA,CAAoB,YAApB,CAAL,EAA0Cb,CAAAmB,YAAAC,SAA1C,EACKjC,CAAA,CAAiB,eAAjB,CAAkC,cAAlC,CAAkDtC,CAAlD,CAAwD,CAAA,CAAxD,CADL,EAGEa,CAAAsD,SAAA,CAAc,UAAd,CAA0B,QAAQ,EAAG,CACnCnE,CAAAa,KAAA,CAAU,eAAV,CAA2B,CAAE,CAAAA,CAAA,SAA7B,CADmC,CAArC,CAKEyB,EAAA,CAAiB,cAAjB,CAAiC,aAAjC,CAAgDtC,CAAhD,CAAsD,CAAA,CAAtD,CAAJ,EACEY,CAAAK,OAAA,CAAauD,QAA2B,EAAG,CACzC,MAAOrB,EAAAsB,SADkC,CAA3C,CAEGC,QAA8B,CAACf,CAAD,CAAS,CACxC3D,CAAAa,KAAA,CAAU,cAAV,CAA0B,CAAE8C,CAAAA,CAA5B,CADwC,CAF1C,CA1EuC,CATtC,CAHqB,CAJzB,CAtBuC,CAA1B,CAlBtB,CAAAvB,UAAA,CA6IW,YA7IX,CA6IyB,CAAC,OAAD,CAAU,QAAQ,CAACC,CAAD,CAAQ,CACjD,MAAOA,EAAAF,YAAA,CAAkB,YAAlB,CAAgC,eAAhC,CAAiDrC,CAAjD,CAAgE,CAAA,CAAhE,CAD0C,CAA1B,CA7IzB,CAAAsC,UAAA,CAgJW,YAhJX,CAgJyB,QAAQ,EAAG,CAClC,MAAO,CACLS,SAAU,GADL,CAELC,QAAS,aAFJ;AAGL6B,KAAMA,QAAQ,CAAC/D,CAAD,CAAQZ,CAAR,CAAca,CAAd,CAAoB+D,CAApB,CAAgC,CACvC5E,CAAAa,KAAA,CAAU,WAAV,CAAL,EACEb,CAAAa,KAAA,CAAU,WAAV,CAAuB,WAAvB,CAF0C,CAHzC,CAD2B,CAhJpC,CAAAuB,UAAA,CA2JW,SA3JX,CA2JqB,CAAC,OAAD,CAAU,QAAV,CAAoB,QAAQ,CAACC,CAAD,CAAQwC,CAAR,CAAgB,CAC/D,MAAO,CACLhC,SAAU,GADL,CAELG,QAASA,QAAQ,CAAChD,CAAD,CAAOa,CAAP,CAAa,CAC5B,IAAIiE,EAAKD,CAAA,CAAOhE,CAAAkE,QAAP,CAAyC,IAAzC,CAAqE,CAAA,CAArE,CACT,OAAO,SAAQ,CAACnE,CAAD,CAAQZ,CAAR,CAAca,CAAd,CAAoB,CAEjC,GAAK,CAAAd,CAAA,CAAYC,CAAZ,CAAkBF,CAAlB,CAAL,GAEMuC,CAAArB,OAAA,CAAa,kBAAb,CAQA,EARqC,CAAAhB,CAAAa,KAAA,CAAU,MAAV,CAQrC,EAPFb,CAAAa,KAAA,CAAU,MAAV,CAAkB,QAAlB,CAOE,CAJAwB,CAAArB,OAAA,CAAa,UAAb,CAIA,EAJ6B,CAAAhB,CAAAa,KAAA,CAAU,UAAV,CAI7B,EAHFb,CAAAa,KAAA,CAAU,UAAV,CAAsB,CAAtB,CAGE,CAAAwB,CAAArB,OAAA,CAAa,cAAb,CAAA,EAAiCgE,CAAAnE,CAAAmE,WAVvC,EAWIhF,CAAAiF,GAAA,CAAQ,UAAR,CAAoB,QAAQ,CAACC,CAAD,CAAQ,CAMlCC,QAASA,EAAQ,EAAG,CAClBL,CAAA,CAAGlE,CAAH,CAAU,CAAEwE,OAAQF,CAAV,CAAV,CADkB,CALpB,IAAIG,EAAUH,CAAAI,MAAVD,EAAyBH,CAAAG,QACb,GAAhB,GAAIA,CAAJ;AAAkC,EAAlC,GAAsBA,CAAtB,EACEzE,CAAA2E,OAAA,CAAaJ,CAAb,CAHgC,CAApC,CAb6B,CAFP,CAFzB,CADwD,CAA5C,CA3JrB,CAAA/C,UAAA,CA6LW,YA7LX,CA6LyB,CAAC,OAAD,CAAU,QAAQ,CAACC,CAAD,CAAQ,CACjD,MAAO,SAAQ,CAACzB,CAAD,CAAQZ,CAAR,CAAca,CAAd,CAAoB,CAC7B,CAAAwB,CAAArB,OAAA,CAAa,UAAb,CAAJ,EAAiChB,CAAAa,KAAA,CAAU,UAAV,CAAjC,EAA2Dd,CAAA,CAAYC,CAAZ,CAAkBF,CAAlB,CAA3D,EACEE,CAAAa,KAAA,CAAU,UAAV,CAAsB,CAAtB,CAF+B,CADc,CAA1B,CA7LzB,CA3M2B,CAA1B,CAAD,CAiZGjB,MAjZH,CAiZWA,MAAAC,QAjZX;", +"sources":["angular-aria.js"], +"names":["window","angular","nodeBlackList","isNodeOneOf","elem","nodeTypeArray","indexOf","nodeName","module","provider","ngAriaModule","$AriaProvider","watchExpr","attrName","ariaAttr","negate","scope","attr","ariaCamelName","$normalize","config","$watch","boolVal","ariaHidden","ariaChecked","ariaReadonly","ariaDisabled","ariaRequired","ariaInvalid","ariaValue","tabindex","bindKeypress","bindRoleForClick","this.config","newConfig","extend","$get","this.$get","key","$$watchExpr","directive","$aria","shouldAttachAttr","normalizedAttr","allowBlacklistEls","shouldAttachRole","role","getShape","type","restrict","require","priority","compile","shape","pre","ngModel","$isEmpty","ngModel.$isEmpty","value","post","ngAriaWatchModelValue","$modelValue","getRadioReaction","newVal","$viewValue","getCheckboxReaction","needsTabIndex","needsAriaValuemin","hasOwnProperty","needsAriaValuemax","needsAriaValuenow","$observe","ngAriaValueMinReaction","ngAriaValueNowReaction","$validators","required","ngAriaInvalidWatch","$invalid","ngAriaInvalidReaction","link","ngMessages","$parse","fn","ngClick","ngKeypress","on","event","callback","$event","keyCode","which","$apply"] +} diff --git a/node_modules/angular-aria/bower.json b/node_modules/angular-aria/bower.json new file mode 100644 index 0000000..2ea8f8b --- /dev/null +++ b/node_modules/angular-aria/bower.json @@ -0,0 +1,10 @@ +{ + "name": "angular-aria", + "version": "1.5.9", + "license": "MIT", + "main": "./angular-aria.js", + "ignore": [], + "dependencies": { + "angular": "1.5.9" + } +} diff --git a/node_modules/angular-aria/index.js b/node_modules/angular-aria/index.js new file mode 100644 index 0000000..0a8f0d9 --- /dev/null +++ b/node_modules/angular-aria/index.js @@ -0,0 +1,2 @@ +require('./angular-aria'); +module.exports = 'ngAria'; diff --git a/node_modules/angular-aria/package.json b/node_modules/angular-aria/package.json new file mode 100644 index 0000000..f758f3c --- /dev/null +++ b/node_modules/angular-aria/package.json @@ -0,0 +1,105 @@ +{ + "_args": [ + [ + { + "raw": "angular-aria@^1.5.7", + "scope": null, + "escapedName": "angular-aria", + "name": "angular-aria", + "rawSpec": "^1.5.7", + "spec": ">=1.5.7 <2.0.0", + "type": "range" + }, + "C:\\Users\\samue\\documents\\github\\ffftp" + ] + ], + "_from": "angular-aria@>=1.5.7 <2.0.0", + "_id": "angular-aria@1.5.9", + "_inCache": true, + "_location": "/angular-aria", + "_nodeVersion": "4.4.7", + "_npmOperationalInternal": { + "host": "packages-18-east.internal.npmjs.com", + "tmp": "tmp/angular-aria-1.5.9.tgz_1480018650213_0.7419410247821361" + }, + "_npmUser": { + "name": "angularcore", + "email": "angular-core+npm@google.com" + }, + "_npmVersion": "2.15.8", + "_phantomChildren": {}, + "_requested": { + "raw": "angular-aria@^1.5.7", + "scope": null, + "escapedName": "angular-aria", + "name": "angular-aria", + "rawSpec": "^1.5.7", + "spec": ">=1.5.7 <2.0.0", + "type": "range" + }, + "_requiredBy": [ + "#DEV:/" + ], + "_resolved": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.5.9.tgz", + "_shasum": "cef4936fbcf8dbf7ecdabc5b475da59d98b3694b", + "_shrinkwrap": null, + "_spec": "angular-aria@^1.5.7", + "_where": "C:\\Users\\samue\\documents\\github\\ffftp", + "author": { + "name": "Angular Core Team", + "email": "angular-core+npm@google.com" + }, + "bugs": { + "url": "https://github.com/angular/angular.js/issues" + }, + "dependencies": {}, + "description": "AngularJS module for making accessibility easy", + "devDependencies": {}, + "directories": {}, + "dist": { + "shasum": "cef4936fbcf8dbf7ecdabc5b475da59d98b3694b", + "tarball": "https://registry.npmjs.org/angular-aria/-/angular-aria-1.5.9.tgz" + }, + "gitHead": "ae7ad8b3766264d91a5de60d7dca73365388a556", + "homepage": "http://angularjs.org", + "jspm": { + "shim": { + "angular-aria": { + "deps": [ + "angular" + ] + } + } + }, + "keywords": [ + "angular", + "framework", + "browser", + "accessibility", + "a11y", + "client-side" + ], + "license": "MIT", + "main": "index.js", + "maintainers": [ + { + "name": "angularcore", + "email": "angular-core+npm@google.com" + }, + { + "name": "petebd", + "email": "pete@bacondarwin.com" + } + ], + "name": "angular-aria", + "optionalDependencies": {}, + "readme": "ERROR: No README data found!", + "repository": { + "type": "git", + "url": "git+https://github.com/angular/angular.js.git" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "version": "1.5.9" +} diff --git a/node_modules/angular-material/.npmignore b/node_modules/angular-material/.npmignore new file mode 100644 index 0000000..f45564b --- /dev/null +++ b/node_modules/angular-material/.npmignore @@ -0,0 +1,5 @@ +*.log +*.sw* +.DS_STORE +/.idea/ +default-theme.css diff --git a/node_modules/angular-material/CHANGELOG.md b/node_modules/angular-material/CHANGELOG.md new file mode 100644 index 0000000..8cb73da --- /dev/null +++ b/node_modules/angular-material/CHANGELOG.md @@ -0,0 +1,3911 @@ + +## [1.1.1](https://github.com/angular/material/compare/v1.1.0...v1.1.1) (2016-09-01) + +We continue to maintain our momentum with Angular Material. Today we published a patch release for Angular Material; a patch that contains more than 60 improvements and fixes. + +-- + +* Add improvements to Themes registrations +* Add improvements to Docs to discuss differences between **TabBar** vs **NavBar** +* Add improve **SideNav** to specify disableScroll target when open +* Add feature **BrowserColor** to enable browser header coloring with Material Design Colors +* Add blur or focus features to **Chips** and **Autocomplete** + +-- + +* Revert a Layout change for `layout="column"` +* Fix animations for **Input** messages, **Autocomplete**, **Dialog** +* Fix **Card** images inside `md-card-title-media` to use flexbox CSS +* Fix **AutoComplete**, **Input**, **Menubar**, **Select**, and theming +* Fix **Datepicker**, **Tooltip** colors, **Navbar** theming, **Virtual repeat** with scrolling + + +-- + +### Features + +* **autocomplete:** forward ngBlur and ngFocus attributes ([#9233](https://github.com/angular/material/issues/9233)) ([a3755d0](https://github.com/angular/material/commit/a3755d0)) +* **browser-color:** enable browser header coloring ([#9192](https://github.com/angular/material/issues/9192)) ([57f2afd](https://github.com/angular/material/commit/57f2afd)), closes [#8062](https://github.com/angular/material/issues/8062) +* **chips:** md-add-on-blur functionality ([#9095](https://github.com/angular/material/issues/9095)) ([bbc6c07](https://github.com/angular/material/commit/bbc6c07)), closes [#3364](https://github.com/angular/material/issues/3364) +* **datepicker:** add timezone support ([#9410](https://github.com/angular/material/issues/9410)) ([14fa477](https://github.com/angular/material/commit/14fa477)), closes [#8448](https://github.com/angular/material/issues/8448) [#8936](https://github.com/angular/material/issues/8936) +* **datepicker:** configurable start/end dates, consistency improvements ([#9309](https://github.com/angular/material/issues/9309)) ([522d428](https://github.com/angular/material/commit/522d428)), closes [#9269](https://github.com/angular/material/issues/9269) +* **mdPanel:** Wrapper and Panel elements referenced in the MdPanelRef ([#9231](https://github.com/angular/material/issues/9231)) ([87c4b01](https://github.com/angular/material/commit/87c4b01)), closes [#9109](https://github.com/angular/material/issues/9109) +* **panel:** Configuration ID for tracking ([#9379](https://github.com/angular/material/issues/9379)) ([d230aec](https://github.com/angular/material/commit/d230aec)), closes [#9356](https://github.com/angular/material/issues/9356) [#9357](https://github.com/angular/material/issues/9357) +* **sidenav:** configurable scroll prevent target ([#9338](https://github.com/angular/material/issues/9338)) ([218c3ec](https://github.com/angular/material/commit/218c3ec)), closes [#8634](https://github.com/angular/material/issues/8634) +* **themes:** register theme on the fly ([#9413](https://github.com/angular/material/issues/9413)) ([0d2386c](https://github.com/angular/material/commit/0d2386c)), closes [#2965](https://github.com/angular/material/issues/2965) + + +### Bug Fixes + +* **autocomplete:** don't use $mdUtils.nextTick in handleHiddenChange ([#9319](https://github.com/angular/material/issues/9319)) ([8f8ad78](https://github.com/angular/material/commit/8f8ad78)), closes [#9318](https://github.com/angular/material/issues/9318) +* **autocomplete:** properly run animation for dialog in demo. ([#9437](https://github.com/angular/material/issues/9437)) ([69607e0](https://github.com/angular/material/commit/69607e0)) +* **autocomplete:** properly show dropdown on focus when minlength is met. ([#9291](https://github.com/angular/material/issues/9291)) ([e65ffc8](https://github.com/angular/material/commit/e65ffc8)), closes [#9283](https://github.com/angular/material/issues/9283) [#9288](https://github.com/angular/material/issues/9288) [#9289](https://github.com/angular/material/issues/9289) +* **autocomplete:** remove autofocus ambiguity. ([#9438](https://github.com/angular/material/issues/9438)) ([00a4c05](https://github.com/angular/material/commit/00a4c05)) +* **build:** properly filter core module files with updated gulp-filter ([#9399](https://github.com/angular/material/issues/9399)) ([0cd2a59](https://github.com/angular/material/commit/0cd2a59)) +* **card:** limit img size when using md-card-title-media ([#9446](https://github.com/angular/material/issues/9446)) ([d086e2b](https://github.com/angular/material/commit/d086e2b)), closes [#9355](https://github.com/angular/material/issues/9355) +* **checkbox:** not being marked as checked with ng-checked on load ([#9424](https://github.com/angular/material/issues/9424)) ([904b455](https://github.com/angular/material/commit/904b455)), closes [#9349](https://github.com/angular/material/issues/9349) +* **compiler:** remove manual controllerAs logic ([#9462](https://github.com/angular/material/issues/9462)) ([18afebe](https://github.com/angular/material/commit/18afebe)) +* **datepicker:** arrow direction in rtl ([#9384](https://github.com/angular/material/issues/9384)) ([f6da4d3](https://github.com/angular/material/commit/f6da4d3)) +* **datepicker:** forward aria-label to generated input ([#9364](https://github.com/angular/material/issues/9364)) ([165d4e7](https://github.com/angular/material/commit/165d4e7)), closes [#9340](https://github.com/angular/material/issues/9340) +* **datepicker:** forward tabindex to generated input ([#9325](https://github.com/angular/material/issues/9325)) ([6cfb542](https://github.com/angular/material/commit/6cfb542)), closes [#8147](https://github.com/angular/material/issues/8147) +* **datepicker:** improved overlay positioning ([#9432](https://github.com/angular/material/issues/9432)) ([d0a7765](https://github.com/angular/material/commit/d0a7765)) +* **datepicker:** jumping forward if min date is in the same month as model ([#9305](https://github.com/angular/material/issues/9305)) ([412bc2c](https://github.com/angular/material/commit/412bc2c)), closes [#9284](https://github.com/angular/material/issues/9284) +* **datepicker:** keyboard navigation not working if the user scrolls too much ([#9302](https://github.com/angular/material/issues/9302)) ([30f6a74](https://github.com/angular/material/commit/30f6a74)), closes [#9294](https://github.com/angular/material/issues/9294) +* **datepicker, menu, slider:** remove duplicate properties ([#9335](https://github.com/angular/material/issues/9335)) ([1c098a6](https://github.com/angular/material/commit/1c098a6)) +* **demos:** update core-icons svg in assets cache to latest changes. ([#9418](https://github.com/angular/material/issues/9418)) ([7e21118](https://github.com/angular/material/commit/7e21118)) +* **dialog:** add extra classes to identify buttons ([#9463](https://github.com/angular/material/issues/9463)) ([b11441c](https://github.com/angular/material/commit/b11441c)) +* **dialog:** do not compile an empty element when using a content element ([#9303](https://github.com/angular/material/issues/9303)) ([7c4b434](https://github.com/angular/material/commit/7c4b434)) +* **dialog:** focus dialog element when no actions are set ([#9272](https://github.com/angular/material/issues/9272)) ([bcfe00a](https://github.com/angular/material/commit/bcfe00a)), closes [#9271](https://github.com/angular/material/issues/9271) +* **dialog:** remove transition classes after hide ([#9299](https://github.com/angular/material/issues/9299)) ([f170133](https://github.com/angular/material/commit/f170133)), closes [#9276](https://github.com/angular/material/issues/9276) +* **input:** Ensure animated messages disappear. ([#9466](https://github.com/angular/material/issues/9466)) ([4e302c2](https://github.com/angular/material/commit/4e302c2)), closes [#9454](https://github.com/angular/material/issues/9454) +* **layout:** Revert overzealous IE11 flexbox fix. ([#9412](https://github.com/angular/material/issues/9412)) ([660826b](https://github.com/angular/material/commit/660826b)), closes [#9354](https://github.com/angular/material/issues/9354) +* **menu-bar:** unable to close menu when clicking on toolbar ([#9428](https://github.com/angular/material/issues/9428)) ([6dcecd5](https://github.com/angular/material/commit/6dcecd5)), closes [#8965](https://github.com/angular/material/issues/8965) +* **menu-bar:** use checked icon from $$mdSvgRegistry ([#9417](https://github.com/angular/material/issues/9417)) ([04124d8](https://github.com/angular/material/commit/04124d8)), closes [#9407](https://github.com/angular/material/issues/9407) +* **navbar:** add theming support ([#9210](https://github.com/angular/material/issues/9210)) ([4cfd4a1](https://github.com/angular/material/commit/4cfd4a1)), closes [#9137](https://github.com/angular/material/issues/9137) +* **panel:** Element reference error ([#9375](https://github.com/angular/material/issues/9375)) ([6383b52](https://github.com/angular/material/commit/6383b52)), closes [#9374](https://github.com/angular/material/issues/9374) +* **prefixer:** do not throw an exception if element is undefined ([#9345](https://github.com/angular/material/issues/9345)) ([d07240b](https://github.com/angular/material/commit/d07240b)) +* undo change to unknown symbol for prod build ([#9393](https://github.com/angular/material/issues/9393)) ([bd4034d](https://github.com/angular/material/commit/bd4034d)) +* **progressCircular:** better support for older ios versions ([#9254](https://github.com/angular/material/issues/9254)) ([215fae4](https://github.com/angular/material/commit/215fae4)), closes [#9253](https://github.com/angular/material/issues/9253) +* **select:** Ensure `md-no-asterisk` attribute works. ([#9347](https://github.com/angular/material/issues/9347)) ([f265a0e](https://github.com/angular/material/commit/f265a0e)), closes [#9339](https://github.com/angular/material/issues/9339) +* **tabs:** ie10 MutationObserver issue ([#9397](https://github.com/angular/material/issues/9397)) ([bd70022](https://github.com/angular/material/commit/bd70022)) +* **tabs:** scroll blocks in pagination (related to [#5439](https://github.com/angular/material/issues/5439)) ([#9457](https://github.com/angular/material/issues/9457)) ([b26c01c](https://github.com/angular/material/commit/b26c01c)) +* **textarea:** resize handle position occasionally wrong ([#9155](https://github.com/angular/material/issues/9155)) ([3fc1004](https://github.com/angular/material/commit/3fc1004)), closes [#9151](https://github.com/angular/material/issues/9151) +* **theming:** fix read-only .configuration() ([#9389](https://github.com/angular/material/issues/9389)) ([b328882](https://github.com/angular/material/commit/b328882)) +* **virtual-repeat:** not re-rendering when switching to a smaller list ([#9363](https://github.com/angular/material/issues/9363)) ([fce551d](https://github.com/angular/material/commit/fce551d)), closes [#9315](https://github.com/angular/material/issues/9315) + + +#### Contributors + +Thanks to the great contributors who helped with this v1.1.1 patch release: + +[akaij](https://github.com/akaij) |[bradrich](https://github.com/bradrich) |[clshortfuse](https://github.com/clshortfuse) |[crisbeto](https://github.com/crisbeto) |[DevVersion](https://github.com/DevVersion) |[EladBezalel](https://github.com/EladBezalel) | +:---: |:---: |:---: |:---: |:---: |:---: | +[akaij](https://github.com/akaij) |[bradrich](https://github.com/bradrich) |[clshortfuse](https://github.com/clshortfuse) |[crisbeto](https://github.com/crisbeto) |[DevVersion](https://github.com/DevVersion) |[EladBezalel](https://github.com/EladBezalel) | + +[enne30](https://github.com/enne30) |[hansl](https://github.com/hansl) |[j3ski](https://github.com/j3ski) |[jelbourn](https://github.com/jelbourn) |[leibale](https://github.com/leibale) |[norkunas](https://github.com/norkunas) | +:---: |:---: |:---: |:---: |:---: |:---: | +[enne30](https://github.com/enne30) |[hansl](https://github.com/hansl) |[j3ski](https://github.com/j3ski) |[jelbourn](https://github.com/jelbourn) |[leibale](https://github.com/leibale) |[norkunas](https://github.com/norkunas) | + +[ThomasBurleson](https://github.com/ThomasBurleson) |[topherfangio](https://github.com/topherfangio) | +:---: |:---: | +[ThomasBurleson](https://github.com/ThomasBurleson) |[topherfangio](https://github.com/topherfangio) | + + + + + +## 1.1.0 (2016-08-14) + +BREAKING CHANGE + +The ``'s, `` component now acts more like +the default `