├── img ├── logo.png ├── loading.gif ├── identification.png └── loading_black.gif ├── metadata.json ├── drop-anywhere ├── build.css └── build.js ├── .gitignore ├── js ├── config.js └── files.js ├── LICENSE.md ├── index.html ├── README.md ├── files.html └── css ├── app.styl └── app.css /img/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sengkim/auth0-s3-sample/HEAD/img/logo.png -------------------------------------------------------------------------------- /img/loading.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sengkim/auth0-s3-sample/HEAD/img/loading.gif -------------------------------------------------------------------------------- /img/identification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sengkim/auth0-s3-sample/HEAD/img/identification.png -------------------------------------------------------------------------------- /img/loading_black.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sengkim/auth0-s3-sample/HEAD/img/loading_black.gif -------------------------------------------------------------------------------- /metadata.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Backend-less dropbox clone", 3 | "type": "sample", 4 | "tags": [ 5 | "amazon-s3", 6 | "browser", 7 | "javascript", 8 | "single-page-app" 9 | ] 10 | } 11 | -------------------------------------------------------------------------------- /drop-anywhere/build.css: -------------------------------------------------------------------------------- 1 | #drop-anywhere { 2 | position: fixed; 3 | top: 0; 4 | left: 0; 5 | width: 100%; 6 | height: 100%; 7 | background: rgba(0,0,0,.8); 8 | display: none; 9 | } 10 | 11 | #drop-anywhere.show { 12 | display: block; 13 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Default nodeJS gitignore 2 | # Source: https://github.com/github/gitignore/blob/master/Node.gitignore 3 | lib-cov 4 | *.seed 5 | *.log 6 | *.csv 7 | *.dat 8 | *.out 9 | *.pid 10 | *.gz 11 | 12 | pids 13 | logs 14 | results 15 | 16 | npm-debug.log 17 | node_modules 18 | 19 | .DS_Store -------------------------------------------------------------------------------- /js/config.js: -------------------------------------------------------------------------------- 1 | window.config = { 2 | role: 'arn:aws:iam::010616021751:role/access-to-s3-per-user', // AWS role arn 3 | principal: 'arn:aws:iam::010616021751:saml-provider/auth0-provider', // AWS saml provider arn 4 | domain: 'matugit.auth0.com', // Auth0 domain 5 | clientID: 'bm70oLevwEM6PjICBnczyNySHjFkDcNR', // Auth0 app client id 6 | targetClientId: 'jAbmUzhI7KZ5cWG8gsyT3IaTg0dS9KZV' // Auth0 AWS API client id 7 | }; 8 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Auth0, Inc. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Backendless Dropbox Clone 5 | 6 | 7 | 10 | 11 | 12 |
13 |
14 |
15 |

Backendless Dropbox Clone

16 |

Auth0 + AWS S3

Sign In 17 |
18 |
19 |

Get started today with Auth0 for FreeCreate Free Account

20 |
21 |
22 | 23 | 24 | 25 | 26 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Backend-less dropbox clone 2 | 3 | This sample uses Auth0 and its integration with AWS APIs (S3, SES, DynamoDB, EC2, etc.) in combination with the powerful IAM policies. 4 | 5 | **Demo: ** 6 | 7 | ![](https://cloudup.com/cSwYBXbHdfc+) 8 | 9 | ## How this works? 10 | 11 | 1. User logs in with Auth0 (any identity provider) 12 | 2. Auth0 returns a JSON Web Token to the browser 13 | 3. The browser calls Auth0 `/delegation` endpoint to validate the token and exchange it for AWS temporal credentials 14 | 15 | ```js 16 | var aws_arns = { role: 'arn:aws:iam::account_number:role/role_name', principal: 'arn:aws:iam::account_number:saml-provider/provider_name' }; 17 | auth0.getDelegationToken(aws_api_auth0_clientid, jwt, aws_arns, function(err, result) { 18 | var aws_credentials = result.Credentials; // AWS temp credentials 19 | // call AWS API e.g. bucket.getObject(...) 20 | }); 21 | ``` 22 | 23 | 4. With the AWS credentials you can now call the AWS APIs and have policies using ${saml:sub} to create policies to authorize users to access a bucket, rows or columns in DynamoDB, sending an email, etc. 24 | 25 | ## FAQ 26 | 27 | ### How is this secure if it's all client side? 28 | 29 | The key is: when the user log in, we obtain an AWS session token. With this token and the IAM policy using `{saml:sub}` we can constraint the resources the client can access. 30 | 31 | ### What else can I do with AWS IAM policies? 32 | 33 | Almost every API in AWS support IAM Policies. For instance, you can create a policy in DynamoDB to do fine grained access control as [shown here](http://docs.aws.amazon.com/amazondynamodb/latest/developerguide/FGAC_DDB.html) 34 | 35 | * [AWS Products that support IAM policies](http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_SpecificProducts.html) 36 | 37 | ### What IAM policies this sample uses? 38 | 39 | A role needs to be created in the IAM console containing these two statements. These statements allow everything on a specific bucket path and allow listing the bucket objects with a prefix condition based on user id. This effectively creates a secure compartiment in S3 for each user. 40 | 41 | ``` 42 | { 43 | "Version": "2012-10-17", 44 | "Statement": [{ 45 | "Sid": "AllowEverythingOnSpecificUserPath", 46 | "Effect": "Allow", 47 | "Action": [ 48 | "*" 49 | ], 50 | "Resource": [ 51 | "arn:aws:s3:::YOUR_BUCKET/dropboxclone/${saml:sub}", 52 | "arn:aws:s3:::YOUR_BUCKET/dropboxclone/${saml:sub}/*"] 53 | }, 54 | { 55 | "Sid": "AllowListBucketIfSpecificPrefixIsIncludedInRequest", 56 | "Action": ["s3:ListBucket"], 57 | "Effect": "Allow", 58 | "Resource": ["arn:aws:s3:::YOUR_BUCKET"], 59 | "Condition":{ 60 | "StringEquals": { "s3:prefix":["dropboxclone/${saml:sub}"] } 61 | } 62 | } 63 | ] 64 | } 65 | ``` 66 | 67 | The `${saml:sub}` variable in the policy is replaced in runtime by AWS with the user id. For example, if a user logged in with any of the identity providers supported by Auth0, and its `user_id` is `ad|3456783129`, then this policy will allow any action to the S3 resource with this path `YOUR_BUCKET/dropboxclone/ad|3456783129`. It will also allow executing `s3:ListBucket` over the bucket with the prefix `dropboxclone/ad|3456783129`. 68 | 69 | ## Running it locally 70 | 71 | Install `serve` 72 | 73 | npm install -g serve 74 | 75 | Run it on port 1338 76 | 77 | serve -p 1338 78 | 79 | And point your browser to 80 | 81 | -------------------------------------------------------------------------------- /js/files.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Gets the AWS temporal credentials using Auth0 delegation endpoint 3 | * 4 | * @param {Object} role: AWS role arn 5 | * principal: AWS saml provider arn 6 | * domain: Auth0 domain (foo.auth0.com) 7 | * clientID: Auth0 application Client ID 8 | * targetClientId: Auth0 AWS API Client ID 9 | */ 10 | function get_aws_token(options, callback) { 11 | var auth0 = new Auth0({ 12 | domain: options.domain, 13 | clientID: options.clientID, 14 | callbackURL: 'dummy' 15 | }); 16 | 17 | auth0.getDelegationToken(options.targetClientId, user.get().id_token, { role: options.role, principal: options.principal }, callback); 18 | } 19 | 20 | /** 21 | * Refresh the file list and bind the actions 22 | * 23 | * @param {Bucket} bucket 24 | */ 25 | function refresh_list(bucket) { 26 | list_files(bucket, function(err) { 27 | if (err) console.log(err); 28 | 29 | bind_actions(bucket); 30 | }); 31 | } 32 | 33 | /** 34 | * List files from a bucket 35 | * 36 | * @param {Bucket} bucket 37 | * @param {function} callback 38 | */ 39 | function list_files(bucket, callback) { 40 | bucket.listObjects({Prefix: folder_prefix + user.get().profile.user_id}, function(err, data) { 41 | if (err) return callback(err); 42 | var files = []; 43 | 44 | for (var i in data.Contents) { 45 | bucket.getSignedUrl('getObject', { Expires: 24 * 60, Key: data.Contents[i].Key }, function(err, url_bucket) { 46 | files.push({ 47 | url: url_bucket, 48 | name: data.Contents[i].Key.replace(folder_prefix + user.get().profile.user_id + '/', ''), 49 | date: moment(new Date(data.Contents[i].LastModified)).fromNow(), 50 | key : data.Contents[i].Key 51 | }); 52 | }); 53 | } 54 | 55 | var source = $("#files-template").html(); 56 | var template = Handlebars.compile(source); 57 | 58 | $('.files').html(template({ files: files })); 59 | 60 | 61 | callback(); 62 | }); 63 | } 64 | 65 | /** 66 | * Remove file from bucket 67 | * 68 | * @param {Bucket} bucket 69 | * @param {function} callback 70 | */ 71 | function remove_file(bucket) { 72 | return function() { 73 | bucket.deleteObject({Key: $(this).data('key')}, function(err) { 74 | if (err) console.log(err); 75 | refresh_list(bucket); 76 | }); 77 | } 78 | } 79 | 80 | /** 81 | * Creates an AWS signed URL 82 | * 83 | * @param {Bucket} bucket 84 | * @param {ZeroClipboard} clipboard object 85 | * @param {Object} options: { clipboard: true/false } 86 | */ 87 | function share_file(bucket, client, options) { 88 | return function() { 89 | bucket.getSignedUrl('getObject', { Expires: 24 * 60, Key: $(this).data('key') }, function(err, url) { 90 | if (!options.clipboard) return prompt('Copy this link', url); 91 | client.setText(url); 92 | }); 93 | } 94 | } 95 | 96 | /** 97 | * Uploads a file to the bucket specified using private ACL 98 | * 99 | * @param {Bucket} bucket 100 | * @param {HTMLInputFile} the file to upload 101 | */ 102 | function upload_file(bucket, file, callback) { 103 | var objKey = folder_prefix + user.get().profile.user_id + '/' + file.name; 104 | var params = {Key: objKey, ContentType: file.type, Body: file, ACL: 'private'}; 105 | bucket.putObject(params, function (err, data) { 106 | if (err) callback(err); 107 | callback(); 108 | }); 109 | } 110 | 111 | function bind_actions(bucket) { 112 | $('.share-link').unbind('click'); 113 | $('.remove').unbind('click'); 114 | 115 | 116 | $(".share-link").click(function(){ 117 | $("#global-zeroclipboard-flash-bridge").click(); 118 | }); 119 | 120 | 121 | ZeroClipboard.config( { moviePath: "http://cdnjs.cloudflare.com/ajax/libs/zeroclipboard/1.3.2/ZeroClipboard.swf" } ); 122 | var client = new ZeroClipboard($('.share-link')); 123 | client.on('complete', function(client, args) { 124 | console.log('Link copied to your clipboard!') 125 | }); 126 | 127 | client.on('dataRequested', share_file(bucket, client, {clipboard: true})); 128 | $('.share-link').on('click', share_file(bucket, client, {clipboard: false})); 129 | $('.remove').on('click', remove_file(bucket)); 130 | 131 | $('#global-zeroclipboard-html-bridge').attr('title', "Copy Link").tooltip(); 132 | 133 | $('#global-zeroclipboard-html-bridge').click(function(){ 134 | 135 | $(this).attr('title', 'Copied!').tooltip('fixTitle').tooltip('show'); 136 | 137 | $(this).attr('data-original-title', "Copy Link").tooltip('fixTitle'); 138 | 139 | }); 140 | 141 | } 142 | 143 | function bind_upload(bucket) { 144 | $('.upload-button').on('click', function() { 145 | $('.upload-file').click(); 146 | }); 147 | 148 | $('.upload-file').on('change', function() { 149 | $('.glyphicon').hide(); 150 | $('.upload-button span').hide(); 151 | $(".upload-button").append('').attr("disabled", "disabled"); 152 | 153 | var file = this.files[0]; 154 | if (file) upload_file(bucket, file, function(err) { 155 | if (err) console.log('error uploading file'); 156 | refresh_list(bucket); 157 | $('.upload-button .glyphicon').show(); 158 | $('.upload-button span').show(); 159 | $('.upload-button .loading').remove(); 160 | $('.upload-button').removeAttr("disabled"); 161 | }); 162 | }); 163 | 164 | var container = document.getElementById('drop-here'); 165 | var drop = DropAnywhere(function(e){ 166 | $('body').append(''); 167 | e.items.forEach(function(item) { 168 | 169 | if (item) upload_file(bucket, item, function(err) { 170 | if (err) console.log('error uploading file'); 171 | 172 | $('.loading-global').remove(); 173 | refresh_list(bucket); 174 | }); 175 | }); 176 | 177 | }); 178 | } -------------------------------------------------------------------------------- /files.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | S3-box 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 |
18 |
19 | 20 | 28 | 29 | 30 | 31 |
32 |
Upload Files 33 |

Your files

34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 |
NameCreated  
48 |
49 |
50 | 51 | 52 |
53 |
54 | 55 |

Identity infrastructure, for developers.

56 |
57 |
58 | 59 | 60 |
61 |
62 | 63 | 66 | 67 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /css/app.styl: -------------------------------------------------------------------------------- 1 | color_blue = #14204d; 2 | color_blue_light = #3cc8f4; 3 | color_red = #eb5422; 4 | color_grey = #d1d2d4; 5 | 6 | body 7 | -webkit-font-smoothing antialiased 8 | 9 | h1, h2, h3 10 | font-family "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif 11 | font-weight lighter 12 | 13 | 14 | body.home 15 | background color_grey 16 | 17 | a 18 | &:hover 19 | text-decoration none 20 | 21 | .btn 22 | border 0 23 | font-weight bold 24 | text-transform uppercase 25 | font-size 11px 26 | letter-spacing 1px 27 | line-height 22px 28 | &.btn-success 29 | background-color #94c231 30 | 31 | .login-page 32 | 33 | .login-box 34 | margin 5% auto 35 | background: white; 36 | width 320px 37 | text-align center 38 | padding 40px 10px 39 | border-radius 3px 40 | .welcome-badge 41 | background #eea20a url("../img/logo.png") center no-repeat 42 | background-size 70% 43 | width 140px 44 | height 140px 45 | margin auto auto 30px auto 46 | border-radius 5px 47 | display block 48 | .btn 49 | &.btn-primary 50 | font-weight bold 51 | text-transform uppercase 52 | font-size 12px 53 | letter-spacing 1px 54 | margin-top 20px 55 | i 56 | margin-right 10px 57 | 58 | .get-auth0 59 | color #fff 60 | background color_blue 61 | background: linear-gradient(45deg, rgba(20,32,77,1) 0%,rgba(60,200,244,1) 100%); /* W3C */ 62 | position fixed 63 | bottom 0 64 | left 0 65 | right 0 66 | text-align center 67 | padding 30px 68 | min-width 700px 69 | h3 70 | font-weight 200 71 | position relative 72 | display inline-block 73 | span 74 | -webkit-animation get_auth0_text 2s 75 | display inline-block 76 | .btn 77 | border-radius 3px 78 | text-transform uppercase 79 | padding 14px 20px 80 | font-size: 12px; 81 | letter-spacing 1px 82 | background-color color_red 83 | margin-left 20px 84 | -webkit-animation get_auth0_btn 2s 85 | &:hover 86 | background-color darken(color_red, 40%) 87 | 88 | .container 89 | max-width 800px 90 | 91 | .loading-global 92 | position: fixed; 93 | top: 20px; 94 | right: 20px; 95 | opacity: .8; 96 | 97 | .files-page 98 | header 99 | &.site-header 100 | margin-bottom 50px 101 | h1 102 | font-size 20px 103 | a 104 | color #000 105 | .login-info 106 | text-align right 107 | margin-top 20px 108 | font-size 12px 109 | span 110 | opacity 0.5 111 | img 112 | width 30px 113 | margin-right 10px 114 | border-radius 40px 115 | .get-auth-left 116 | background-color #4e7ac7 117 | background: linear-gradient(45deg, rgba(20,32,77,1) 0%,rgba(60,200,244,1) 100%); /* W3C */ 118 | color #fff 119 | padding 20px 10px 120 | text-align center 121 | border-radius 3px 122 | .icon-badge 123 | background #fff; 124 | display: block 125 | width 60px 126 | height 60px 127 | border-radius 3px 128 | margin auto 129 | margin-bottom 10px 130 | img 131 | max-width: 40% 132 | margin: auto; 133 | margin-bottom: 10px; 134 | // width 40px 135 | // height auto 136 | // margin-top 14px 137 | h3 138 | margin-top 0 139 | font-weight 200 140 | a 141 | color: white; 142 | font-weight: bold 143 | p 144 | opacity 0.6 145 | .btn 146 | margin-top 10px 147 | background-color: color_red 148 | &:hover 149 | background-color: darken(color_red, 30%) 150 | 151 | 152 | .files-list 153 | h3 154 | margin-top 0 155 | margin-bottom 30px 156 | float left 157 | .upload-button 158 | float right 159 | i 160 | margin-right 6px 161 | 162 | img 163 | position: relative 164 | top: -3px; 165 | a 166 | font-weight bold 167 | tbody 168 | tr 169 | &:hover 170 | background #f9f9f9 171 | 172 | a.glyphicon-link 173 | color: black; 174 | font-weight: normal 175 | &:hover 176 | color: blue; 177 | .glyphicon-trash:hover 178 | color: red; 179 | cursor: pointer; 180 | 181 | footer 182 | text-align center 183 | a 184 | color #333 185 | 186 | 187 | @-moz-keyframes get_auth0_text { 188 | 0% { 189 | padding-right: 100px; 190 | } 191 | 192 | 100% { 193 | padding-right: 0; 194 | } 195 | } 196 | @-webkit-keyframes get_auth0_text { 197 | 0% { 198 | padding-right: 100px; 199 | } 200 | 201 | 100% { 202 | padding-right: 0; 203 | } 204 | } 205 | @-o-keyframes get_auth0_text { 206 | 0% { 207 | padding-right: 100px; 208 | } 209 | 210 | 100% { 211 | padding-right: 0; 212 | } 213 | } 214 | @-ms-keyframes get_auth0_text { 215 | 0% { 216 | padding-right: 100px; 217 | } 218 | 219 | 100% { 220 | padding-right: 0; 221 | } 222 | } 223 | @keyframes get_auth0_text { 224 | 0% { 225 | padding-right: 100px; 226 | } 227 | 228 | 100% { 229 | padding-right: 0; 230 | } 231 | } 232 | @-moz-keyframes get_auth0_btn { 233 | 0% { 234 | opacity: 0; 235 | } 236 | 237 | 100% { 238 | opacity: 1; 239 | } 240 | } 241 | @-webkit-keyframes get_auth0_btn { 242 | 0% { 243 | opacity: 0; 244 | } 245 | 246 | 100% { 247 | opacity: 1; 248 | } 249 | } 250 | @-o-keyframes get_auth0_btn { 251 | 0% { 252 | opacity: 0; 253 | } 254 | 255 | 100% { 256 | opacity: 1; 257 | } 258 | } 259 | @-ms-keyframes get_auth0_btn { 260 | 0% { 261 | opacity: 0; 262 | } 263 | 264 | 100% { 265 | opacity: 1; 266 | } 267 | } 268 | @keyframes get_auth0_btn { 269 | 0% { 270 | opacity: 0; 271 | } 272 | 273 | 100% { 274 | opacity: 1; 275 | } 276 | } 277 | 278 | 279 | @-webkit-keyframes show { 280 | from { 281 | opacity: 0; 282 | } 283 | to { 284 | opacity: 1; 285 | } 286 | } 287 | 288 | #drop-anywhere { 289 | line-height: 500px; 290 | text-align: center; 291 | color: white; 292 | display: none; 293 | } 294 | 295 | #drop-anywhere::before { 296 | content: 'Drop to upload!'; 297 | font-size: 30px; 298 | font-weight: bold 299 | text-align: center; 300 | } 301 | 302 | #drop-anywhere.show { 303 | -webkit-animation: show 300ms; 304 | } 305 | 306 | 307 | .left 308 | float: left 309 | 310 | .right 311 | float: right 312 | 313 | @media screen and (max-width: 700px) 314 | body.home 315 | background: #fff; 316 | 317 | 318 | .login-page 319 | .login-box 320 | .welcome-badge 321 | width: 100px; 322 | height: 100px; 323 | .btn.btn-primary 324 | display: block 325 | margin-top: 30px; 326 | 327 | .get-auth0 328 | min-width: 0; 329 | padding: 10px; 330 | position: static; 331 | 332 | 333 | .get-auth0 h3 span 334 | display: block 335 | -webkit-animation: none; 336 | font-size: 18px; 337 | margin-bottom: 20px; 338 | 339 | .get-auth0 h3 .btn 340 | margin: 0; 341 | -webkit-animation: none; 342 | 343 | .get-auth-left 344 | margin-bottom: 30px; 345 | 346 | .left, .right 347 | float: none; 348 | 349 | 350 | @media screen and (max-width: 400px) 351 | .login-info 352 | span 353 | display: none 354 | 355 | .login-page 356 | h1 357 | font-size: 20px; 358 | h2 359 | font-size: 14px; 360 | .login-box 361 | padding-top: 0; 362 | table 363 | thead, tbody 364 | tr th:first-child, tr td:first-child 365 | display: none 366 | 367 | td 368 | font-size: 12px; 369 | 370 | 371 | .get-auth-left 372 | float: none; -------------------------------------------------------------------------------- /css/app.css: -------------------------------------------------------------------------------- 1 | body { 2 | -webkit-font-smoothing: antialiased; 3 | } 4 | h1, 5 | h2, 6 | h3 { 7 | font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif; 8 | font-weight: lighter; 9 | } 10 | body.home { 11 | background: #d1d2d4; 12 | } 13 | a:hover { 14 | text-decoration: none; 15 | } 16 | .btn { 17 | border: 0; 18 | font-weight: bold; 19 | text-transform: uppercase; 20 | font-size: 11px; 21 | letter-spacing: 1px; 22 | line-height: 22px; 23 | } 24 | .btn.btn-success { 25 | background-color: #94c231; 26 | } 27 | .login-page .login-box { 28 | margin: 5% auto; 29 | background: #fff; 30 | width: 320px; 31 | text-align: center; 32 | padding: 40px 10px; 33 | border-radius: 3px; 34 | } 35 | .login-page .login-box .welcome-badge { 36 | background: #eea20a url("../img/logo.png") center no-repeat; 37 | background-size: 70%; 38 | width: 140px; 39 | height: 140px; 40 | margin: auto auto 30px auto; 41 | border-radius: 5px; 42 | display: block; 43 | } 44 | .login-page .login-box .btn.btn-primary { 45 | font-weight: bold; 46 | text-transform: uppercase; 47 | font-size: 12px; 48 | letter-spacing: 1px; 49 | margin-top: 20px; 50 | } 51 | .login-page .login-box .btn.btn-primary i { 52 | margin-right: 10px; 53 | } 54 | .login-page .get-auth0 { 55 | color: #fff; 56 | background: #14204d; 57 | background: linear-gradient(45deg, #14204d 0%, #3cc8f4 100%); 58 | /* W3C */ 59 | position: fixed; 60 | bottom: 0; 61 | left: 0; 62 | right: 0; 63 | text-align: center; 64 | padding: 30px; 65 | min-width: 700px; 66 | } 67 | .login-page .get-auth0 h3 { 68 | font-weight: 200; 69 | position: relative; 70 | display: inline-block; 71 | } 72 | .login-page .get-auth0 h3 span { 73 | -webkit-animation: get_auth0_text 2s; 74 | display: inline-block; 75 | } 76 | .login-page .get-auth0 h3 .btn { 77 | border-radius: 3px; 78 | text-transform: uppercase; 79 | padding: 14px 20px; 80 | font-size: 12px; 81 | letter-spacing: 1px; 82 | background-color: #eb5422; 83 | margin-left: 20px; 84 | -webkit-animation: get_auth0_btn 2s; 85 | } 86 | .login-page .get-auth0 h3 .btn:hover { 87 | background-color: #942f0d; 88 | } 89 | .container { 90 | max-width: 800px; 91 | } 92 | .loading-global { 93 | position: fixed; 94 | top: 20px; 95 | right: 20px; 96 | opacity: 0.8; 97 | } 98 | .files-page header.site-header { 99 | margin-bottom: 50px; 100 | } 101 | .files-page header.site-header h1 { 102 | font-size: 20px; 103 | } 104 | .files-page header.site-header h1 a { 105 | color: #000; 106 | } 107 | .files-page header.site-header .login-info { 108 | text-align: right; 109 | margin-top: 20px; 110 | font-size: 12px; 111 | } 112 | .files-page header.site-header .login-info span { 113 | opacity: 0.5; 114 | } 115 | .files-page header.site-header .login-info img { 116 | width: 30px; 117 | margin-right: 10px; 118 | border-radius: 40px; 119 | } 120 | .files-page .get-auth-left { 121 | background-color: #4e7ac7; 122 | background: linear-gradient(45deg, #14204d 0%, #3cc8f4 100%); 123 | /* W3C */ 124 | color: #fff; 125 | padding: 20px 10px; 126 | text-align: center; 127 | border-radius: 3px; 128 | } 129 | .files-page .get-auth-left .icon-badge { 130 | background: #fff; 131 | display: block; 132 | width: 60px; 133 | height: 60px; 134 | border-radius: 3px; 135 | margin: auto; 136 | margin-bottom: 10px; 137 | } 138 | .files-page .get-auth-left img { 139 | max-width: 40%; 140 | margin: auto; 141 | margin-bottom: 10px; 142 | } 143 | .files-page .get-auth-left h3 { 144 | margin-top: 0; 145 | font-weight: 200; 146 | } 147 | .files-page .get-auth-left h3 a { 148 | color: #fff; 149 | font-weight: bold; 150 | } 151 | .files-page .get-auth-left p { 152 | opacity: 0.6; 153 | } 154 | .files-page .get-auth-left .btn { 155 | margin-top: 10px; 156 | background-color: #eb5422; 157 | } 158 | .files-page .get-auth-left .btn:hover { 159 | background-color: #ad3710; 160 | } 161 | .files-page .files-list h3 { 162 | margin-top: 0; 163 | margin-bottom: 30px; 164 | float: left; 165 | } 166 | .files-page .files-list .upload-button { 167 | float: right; 168 | } 169 | .files-page .files-list .upload-button i { 170 | margin-right: 6px; 171 | } 172 | .files-page .files-list .upload-button img { 173 | position: relative; 174 | top: -3px; 175 | } 176 | .files-page .files-list a { 177 | font-weight: bold; 178 | } 179 | .files-page .files-list tbody tr:hover { 180 | background: #f9f9f9; 181 | } 182 | .files-page .files-list tbody tr a.glyphicon-link { 183 | color: #000; 184 | font-weight: normal; 185 | } 186 | .files-page .files-list tbody tr a.glyphicon-link:hover { 187 | color: #00f; 188 | } 189 | .files-page .files-list tbody tr .glyphicon-trash:hover { 190 | color: #f00; 191 | cursor: pointer; 192 | } 193 | footer { 194 | text-align: center; 195 | } 196 | footer a { 197 | color: #333; 198 | } 199 | @-moz-keyframes get_auth0_text { 200 | 0% { 201 | padding-right: 100px; 202 | } 203 | 204 | 100% { 205 | padding-right: 0; 206 | } 207 | } 208 | @-webkit-keyframes get_auth0_text { 209 | 0% { 210 | padding-right: 100px; 211 | } 212 | 213 | 100% { 214 | padding-right: 0; 215 | } 216 | } 217 | @-o-keyframes get_auth0_text { 218 | 0% { 219 | padding-right: 100px; 220 | } 221 | 222 | 100% { 223 | padding-right: 0; 224 | } 225 | } 226 | @-ms-keyframes get_auth0_text { 227 | 0% { 228 | padding-right: 100px; 229 | } 230 | 231 | 100% { 232 | padding-right: 0; 233 | } 234 | } 235 | @-moz-keyframes get_auth0_btn { 236 | 0% { 237 | opacity: 0; 238 | } 239 | 240 | 100% { 241 | opacity: 1; 242 | } 243 | } 244 | @-webkit-keyframes get_auth0_btn { 245 | 0% { 246 | opacity: 0; 247 | } 248 | 249 | 100% { 250 | opacity: 1; 251 | } 252 | } 253 | @-o-keyframes get_auth0_btn { 254 | 0% { 255 | opacity: 0; 256 | } 257 | 258 | 100% { 259 | opacity: 1; 260 | } 261 | } 262 | @-ms-keyframes get_auth0_btn { 263 | 0% { 264 | opacity: 0; 265 | } 266 | 267 | 100% { 268 | opacity: 1; 269 | } 270 | } 271 | @-webkit-keyframes show { 272 | 0% { 273 | opacity: 0; 274 | } 275 | 276 | 100% { 277 | opacity: 1; 278 | } 279 | } 280 | #drop-anywhere { 281 | line-height: 500px; 282 | text-align: center; 283 | color: #fff; 284 | display: none; 285 | } 286 | #drop-anywhere::before { 287 | content: 'Drop to upload!'; 288 | font-size: 30px; 289 | font-weight: bold; 290 | text-align: center; 291 | } 292 | #drop-anywhere.show { 293 | -webkit-animation: show 300ms; 294 | } 295 | .left { 296 | float: left; 297 | } 298 | .right { 299 | float: right; 300 | } 301 | @media screen and (max-width: 700px) { 302 | body.home { 303 | background: #fff; 304 | } 305 | .login-page .login-box .welcome-badge { 306 | width: 100px; 307 | height: 100px; 308 | } 309 | .login-page .login-box .btn.btn-primary { 310 | display: block; 311 | margin-top: 30px; 312 | } 313 | .login-page .get-auth0 { 314 | min-width: 0; 315 | padding: 10px; 316 | position: static; 317 | } 318 | .login-page .get-auth0 h3 span { 319 | display: block; 320 | -webkit-animation: none; 321 | font-size: 18px; 322 | margin-bottom: 20px; 323 | } 324 | .login-page .get-auth0 h3 .btn { 325 | margin: 0; 326 | -webkit-animation: none; 327 | } 328 | .get-auth-left { 329 | margin-bottom: 30px; 330 | } 331 | .left, 332 | .right { 333 | float: none; 334 | } 335 | } 336 | @media screen and (max-width: 400px) { 337 | .login-info span { 338 | display: none; 339 | } 340 | .login-page h1 { 341 | font-size: 20px; 342 | } 343 | .login-page h2 { 344 | font-size: 14px; 345 | } 346 | .login-page .login-box { 347 | padding-top: 0; 348 | } 349 | table thead tr th:first-child, 350 | table tbody tr th:first-child, 351 | table thead tr td:first-child, 352 | table tbody tr td:first-child { 353 | display: none; 354 | } 355 | table thead td, 356 | table tbody td { 357 | font-size: 12px; 358 | } 359 | .get-auth-left { 360 | float: none; 361 | } 362 | } 363 | @-moz-keyframes get_auth0_text { 364 | 0% { 365 | padding-right: 100px; 366 | } 367 | 368 | 100% { 369 | padding-right: 0; 370 | } 371 | } 372 | @-webkit-keyframes get_auth0_text { 373 | 0% { 374 | padding-right: 100px; 375 | } 376 | 377 | 100% { 378 | padding-right: 0; 379 | } 380 | } 381 | @-o-keyframes get_auth0_text { 382 | 0% { 383 | padding-right: 100px; 384 | } 385 | 386 | 100% { 387 | padding-right: 0; 388 | } 389 | } 390 | @-ms-keyframes get_auth0_text { 391 | 0% { 392 | padding-right: 100px; 393 | } 394 | 395 | 100% { 396 | padding-right: 0; 397 | } 398 | } 399 | @keyframes get_auth0_text { 400 | 0% { 401 | padding-right: 100px; 402 | } 403 | 404 | 100% { 405 | padding-right: 0; 406 | } 407 | } 408 | @-moz-keyframes get_auth0_btn { 409 | 0% { 410 | opacity: 0; 411 | } 412 | 413 | 100% { 414 | opacity: 1; 415 | } 416 | } 417 | @-webkit-keyframes get_auth0_btn { 418 | 0% { 419 | opacity: 0; 420 | } 421 | 422 | 100% { 423 | opacity: 1; 424 | } 425 | } 426 | @-o-keyframes get_auth0_btn { 427 | 0% { 428 | opacity: 0; 429 | } 430 | 431 | 100% { 432 | opacity: 1; 433 | } 434 | } 435 | @-ms-keyframes get_auth0_btn { 436 | 0% { 437 | opacity: 0; 438 | } 439 | 440 | 100% { 441 | opacity: 1; 442 | } 443 | } 444 | @keyframes get_auth0_btn { 445 | 0% { 446 | opacity: 0; 447 | } 448 | 449 | 100% { 450 | opacity: 1; 451 | } 452 | } 453 | -------------------------------------------------------------------------------- /drop-anywhere/build.js: -------------------------------------------------------------------------------- 1 | ;(function(){ 2 | 3 | /** 4 | * Require the given path. 5 | * 6 | * @param {String} path 7 | * @return {Object} exports 8 | * @api public 9 | */ 10 | 11 | function require(path, parent, orig) { 12 | var resolved = require.resolve(path); 13 | 14 | // lookup failed 15 | if (null == resolved) { 16 | orig = orig || path; 17 | parent = parent || 'root'; 18 | var err = new Error('Failed to require "' + orig + '" from "' + parent + '"'); 19 | err.path = orig; 20 | err.parent = parent; 21 | err.require = true; 22 | throw err; 23 | } 24 | 25 | var module = require.modules[resolved]; 26 | 27 | // perform real require() 28 | // by invoking the module's 29 | // registered function 30 | if (!module._resolving && !module.exports) { 31 | var mod = {}; 32 | mod.exports = {}; 33 | mod.client = mod.component = true; 34 | module._resolving = true; 35 | module.call(this, mod.exports, require.relative(resolved), mod); 36 | delete module._resolving; 37 | module.exports = mod.exports; 38 | } 39 | 40 | return module.exports; 41 | } 42 | 43 | /** 44 | * Registered modules. 45 | */ 46 | 47 | require.modules = {}; 48 | 49 | /** 50 | * Registered aliases. 51 | */ 52 | 53 | require.aliases = {}; 54 | 55 | /** 56 | * Resolve `path`. 57 | * 58 | * Lookup: 59 | * 60 | * - PATH/index.js 61 | * - PATH.js 62 | * - PATH 63 | * 64 | * @param {String} path 65 | * @return {String} path or null 66 | * @api private 67 | */ 68 | 69 | require.resolve = function(path) { 70 | if (path.charAt(0) === '/') path = path.slice(1); 71 | 72 | var paths = [ 73 | path, 74 | path + '.js', 75 | path + '.json', 76 | path + '/index.js', 77 | path + '/index.json' 78 | ]; 79 | 80 | for (var i = 0; i < paths.length; i++) { 81 | var path = paths[i]; 82 | if (require.modules.hasOwnProperty(path)) return path; 83 | if (require.aliases.hasOwnProperty(path)) return require.aliases[path]; 84 | } 85 | }; 86 | 87 | /** 88 | * Normalize `path` relative to the current path. 89 | * 90 | * @param {String} curr 91 | * @param {String} path 92 | * @return {String} 93 | * @api private 94 | */ 95 | 96 | require.normalize = function(curr, path) { 97 | var segs = []; 98 | 99 | if ('.' != path.charAt(0)) return path; 100 | 101 | curr = curr.split('/'); 102 | path = path.split('/'); 103 | 104 | for (var i = 0; i < path.length; ++i) { 105 | if ('..' == path[i]) { 106 | curr.pop(); 107 | } else if ('.' != path[i] && '' != path[i]) { 108 | segs.push(path[i]); 109 | } 110 | } 111 | 112 | return curr.concat(segs).join('/'); 113 | }; 114 | 115 | /** 116 | * Register module at `path` with callback `definition`. 117 | * 118 | * @param {String} path 119 | * @param {Function} definition 120 | * @api private 121 | */ 122 | 123 | require.register = function(path, definition) { 124 | require.modules[path] = definition; 125 | }; 126 | 127 | /** 128 | * Alias a module definition. 129 | * 130 | * @param {String} from 131 | * @param {String} to 132 | * @api private 133 | */ 134 | 135 | require.alias = function(from, to) { 136 | if (!require.modules.hasOwnProperty(from)) { 137 | throw new Error('Failed to alias "' + from + '", it does not exist'); 138 | } 139 | require.aliases[to] = from; 140 | }; 141 | 142 | /** 143 | * Return a require function relative to the `parent` path. 144 | * 145 | * @param {String} parent 146 | * @return {Function} 147 | * @api private 148 | */ 149 | 150 | require.relative = function(parent) { 151 | var p = require.normalize(parent, '..'); 152 | 153 | /** 154 | * lastIndexOf helper. 155 | */ 156 | 157 | function lastIndexOf(arr, obj) { 158 | var i = arr.length; 159 | while (i--) { 160 | if (arr[i] === obj) return i; 161 | } 162 | return -1; 163 | } 164 | 165 | /** 166 | * The relative require() itself. 167 | */ 168 | 169 | function localRequire(path) { 170 | var resolved = localRequire.resolve(path); 171 | return require(resolved, parent, path); 172 | } 173 | 174 | /** 175 | * Resolve relative to the parent. 176 | */ 177 | 178 | localRequire.resolve = function(path) { 179 | var c = path.charAt(0); 180 | if ('/' == c) return path.slice(1); 181 | if ('.' == c) return require.normalize(p, path); 182 | 183 | // resolve deps by returning 184 | // the dep in the nearest "deps" 185 | // directory 186 | var segs = parent.split('/'); 187 | var i = lastIndexOf(segs, 'deps') + 1; 188 | if (!i) i = 0; 189 | path = segs.slice(0, i + 1).join('/') + '/deps/' + path; 190 | return path; 191 | }; 192 | 193 | /** 194 | * Check if module is defined at `path`. 195 | */ 196 | 197 | localRequire.exists = function(path) { 198 | return require.modules.hasOwnProperty(localRequire.resolve(path)); 199 | }; 200 | 201 | return localRequire; 202 | }; 203 | require.register("component-indexof/index.js", function(exports, require, module){ 204 | module.exports = function(arr, obj){ 205 | if (arr.indexOf) return arr.indexOf(obj); 206 | for (var i = 0; i < arr.length; ++i) { 207 | if (arr[i] === obj) return i; 208 | } 209 | return -1; 210 | }; 211 | }); 212 | require.register("component-classes/index.js", function(exports, require, module){ 213 | /** 214 | * Module dependencies. 215 | */ 216 | 217 | var index = require('indexof'); 218 | 219 | /** 220 | * Whitespace regexp. 221 | */ 222 | 223 | var re = /\s+/; 224 | 225 | /** 226 | * toString reference. 227 | */ 228 | 229 | var toString = Object.prototype.toString; 230 | 231 | /** 232 | * Wrap `el` in a `ClassList`. 233 | * 234 | * @param {Element} el 235 | * @return {ClassList} 236 | * @api public 237 | */ 238 | 239 | module.exports = function(el){ 240 | return new ClassList(el); 241 | }; 242 | 243 | /** 244 | * Initialize a new ClassList for `el`. 245 | * 246 | * @param {Element} el 247 | * @api private 248 | */ 249 | 250 | function ClassList(el) { 251 | if (!el) throw new Error('A DOM element reference is required'); 252 | this.el = el; 253 | this.list = el.classList; 254 | } 255 | 256 | /** 257 | * Add class `name` if not already present. 258 | * 259 | * @param {String} name 260 | * @return {ClassList} 261 | * @api public 262 | */ 263 | 264 | ClassList.prototype.add = function(name){ 265 | // classList 266 | if (this.list) { 267 | this.list.add(name); 268 | return this; 269 | } 270 | 271 | // fallback 272 | var arr = this.array(); 273 | var i = index(arr, name); 274 | if (!~i) arr.push(name); 275 | this.el.className = arr.join(' '); 276 | return this; 277 | }; 278 | 279 | /** 280 | * Remove class `name` when present, or 281 | * pass a regular expression to remove 282 | * any which match. 283 | * 284 | * @param {String|RegExp} name 285 | * @return {ClassList} 286 | * @api public 287 | */ 288 | 289 | ClassList.prototype.remove = function(name){ 290 | if ('[object RegExp]' == toString.call(name)) { 291 | return this.removeMatching(name); 292 | } 293 | 294 | // classList 295 | if (this.list) { 296 | this.list.remove(name); 297 | return this; 298 | } 299 | 300 | // fallback 301 | var arr = this.array(); 302 | var i = index(arr, name); 303 | if (~i) arr.splice(i, 1); 304 | this.el.className = arr.join(' '); 305 | return this; 306 | }; 307 | 308 | /** 309 | * Remove all classes matching `re`. 310 | * 311 | * @param {RegExp} re 312 | * @return {ClassList} 313 | * @api private 314 | */ 315 | 316 | ClassList.prototype.removeMatching = function(re){ 317 | var arr = this.array(); 318 | for (var i = 0; i < arr.length; i++) { 319 | if (re.test(arr[i])) { 320 | this.remove(arr[i]); 321 | } 322 | } 323 | return this; 324 | }; 325 | 326 | /** 327 | * Toggle class `name`, can force state via `force`. 328 | * 329 | * For browsers that support classList, but do not support `force` yet, 330 | * the mistake will be detected and corrected. 331 | * 332 | * @param {String} name 333 | * @param {Boolean} force 334 | * @return {ClassList} 335 | * @api public 336 | */ 337 | 338 | ClassList.prototype.toggle = function(name, force){ 339 | // classList 340 | if (this.list) { 341 | if ("undefined" !== typeof force) { 342 | if (force !== this.list.toggle(name, force)) { 343 | this.list.toggle(name); // toggle again to correct 344 | } 345 | } else { 346 | this.list.toggle(name); 347 | } 348 | return this; 349 | } 350 | 351 | // fallback 352 | if ("undefined" !== typeof force) { 353 | if (!force) { 354 | this.remove(name); 355 | } else { 356 | this.add(name); 357 | } 358 | } else { 359 | if (this.has(name)) { 360 | this.remove(name); 361 | } else { 362 | this.add(name); 363 | } 364 | } 365 | 366 | return this; 367 | }; 368 | 369 | /** 370 | * Return an array of classes. 371 | * 372 | * @return {Array} 373 | * @api public 374 | */ 375 | 376 | ClassList.prototype.array = function(){ 377 | var str = this.el.className.replace(/^\s+|\s+$/g, ''); 378 | var arr = str.split(re); 379 | if ('' === arr[0]) arr.shift(); 380 | return arr; 381 | }; 382 | 383 | /** 384 | * Check if class `name` is present. 385 | * 386 | * @param {String} name 387 | * @return {ClassList} 388 | * @api public 389 | */ 390 | 391 | ClassList.prototype.has = 392 | ClassList.prototype.contains = function(name){ 393 | return this.list 394 | ? this.list.contains(name) 395 | : !! ~index(this.array(), name); 396 | }; 397 | 398 | }); 399 | require.register("component-event/index.js", function(exports, require, module){ 400 | var bind = window.addEventListener ? 'addEventListener' : 'attachEvent', 401 | unbind = window.removeEventListener ? 'removeEventListener' : 'detachEvent', 402 | prefix = bind !== 'addEventListener' ? 'on' : ''; 403 | 404 | /** 405 | * Bind `el` event `type` to `fn`. 406 | * 407 | * @param {Element} el 408 | * @param {String} type 409 | * @param {Function} fn 410 | * @param {Boolean} capture 411 | * @return {Function} 412 | * @api public 413 | */ 414 | 415 | exports.bind = function(el, type, fn, capture){ 416 | el[bind](prefix + type, fn, capture || false); 417 | return fn; 418 | }; 419 | 420 | /** 421 | * Unbind `el` event `type`'s callback `fn`. 422 | * 423 | * @param {Element} el 424 | * @param {String} type 425 | * @param {Function} fn 426 | * @param {Boolean} capture 427 | * @return {Function} 428 | * @api public 429 | */ 430 | 431 | exports.unbind = function(el, type, fn, capture){ 432 | el[unbind](prefix + type, fn, capture || false); 433 | return fn; 434 | }; 435 | }); 436 | require.register("component-query/index.js", function(exports, require, module){ 437 | function one(selector, el) { 438 | return el.querySelector(selector); 439 | } 440 | 441 | exports = module.exports = function(selector, el){ 442 | el = el || document; 443 | return one(selector, el); 444 | }; 445 | 446 | exports.all = function(selector, el){ 447 | el = el || document; 448 | return el.querySelectorAll(selector); 449 | }; 450 | 451 | exports.engine = function(obj){ 452 | if (!obj.one) throw new Error('.one callback required'); 453 | if (!obj.all) throw new Error('.all callback required'); 454 | one = obj.one; 455 | exports.all = obj.all; 456 | return exports; 457 | }; 458 | 459 | }); 460 | require.register("component-matches-selector/index.js", function(exports, require, module){ 461 | /** 462 | * Module dependencies. 463 | */ 464 | 465 | var query = require('query'); 466 | 467 | /** 468 | * Element prototype. 469 | */ 470 | 471 | var proto = Element.prototype; 472 | 473 | /** 474 | * Vendor function. 475 | */ 476 | 477 | var vendor = proto.matches 478 | || proto.webkitMatchesSelector 479 | || proto.mozMatchesSelector 480 | || proto.msMatchesSelector 481 | || proto.oMatchesSelector; 482 | 483 | /** 484 | * Expose `match()`. 485 | */ 486 | 487 | module.exports = match; 488 | 489 | /** 490 | * Match `el` to `selector`. 491 | * 492 | * @param {Element} el 493 | * @param {String} selector 494 | * @return {Boolean} 495 | * @api public 496 | */ 497 | 498 | function match(el, selector) { 499 | if (vendor) return vendor.call(el, selector); 500 | var nodes = query.all(selector, el.parentNode); 501 | for (var i = 0; i < nodes.length; ++i) { 502 | if (nodes[i] == el) return true; 503 | } 504 | return false; 505 | } 506 | 507 | }); 508 | require.register("discore-closest/index.js", function(exports, require, module){ 509 | var matches = require('matches-selector') 510 | 511 | module.exports = function (element, selector, checkYoSelf, root) { 512 | element = checkYoSelf ? {parentNode: element} : element 513 | 514 | root = root || document 515 | 516 | // Make sure `element !== document` and `element != null` 517 | // otherwise we get an illegal invocation 518 | while ((element = element.parentNode) && element !== document) { 519 | if (matches(element, selector)) 520 | return element 521 | // After `matches` on the edge case that 522 | // the selector matches the root 523 | // (when the root is not the document) 524 | if (element === root) 525 | return 526 | } 527 | } 528 | }); 529 | require.register("component-delegate/index.js", function(exports, require, module){ 530 | /** 531 | * Module dependencies. 532 | */ 533 | 534 | var closest = require('closest') 535 | , event = require('event'); 536 | 537 | /** 538 | * Delegate event `type` to `selector` 539 | * and invoke `fn(e)`. A callback function 540 | * is returned which may be passed to `.unbind()`. 541 | * 542 | * @param {Element} el 543 | * @param {String} selector 544 | * @param {String} type 545 | * @param {Function} fn 546 | * @param {Boolean} capture 547 | * @return {Function} 548 | * @api public 549 | */ 550 | 551 | exports.bind = function(el, selector, type, fn, capture){ 552 | return event.bind(el, type, function(e){ 553 | var target = e.target || e.srcElement; 554 | e.delegateTarget = closest(target, selector, true, el); 555 | if (e.delegateTarget) fn.call(el, e); 556 | }, capture); 557 | }; 558 | 559 | /** 560 | * Unbind event `type`'s callback `fn`. 561 | * 562 | * @param {Element} el 563 | * @param {String} type 564 | * @param {Function} fn 565 | * @param {Boolean} capture 566 | * @api public 567 | */ 568 | 569 | exports.unbind = function(el, type, fn, capture){ 570 | event.unbind(el, type, fn, capture); 571 | }; 572 | 573 | }); 574 | require.register("component-events/index.js", function(exports, require, module){ 575 | 576 | /** 577 | * Module dependencies. 578 | */ 579 | 580 | var events = require('event'); 581 | var delegate = require('delegate'); 582 | 583 | /** 584 | * Expose `Events`. 585 | */ 586 | 587 | module.exports = Events; 588 | 589 | /** 590 | * Initialize an `Events` with the given 591 | * `el` object which events will be bound to, 592 | * and the `obj` which will receive method calls. 593 | * 594 | * @param {Object} el 595 | * @param {Object} obj 596 | * @api public 597 | */ 598 | 599 | function Events(el, obj) { 600 | if (!(this instanceof Events)) return new Events(el, obj); 601 | if (!el) throw new Error('element required'); 602 | if (!obj) throw new Error('object required'); 603 | this.el = el; 604 | this.obj = obj; 605 | this._events = {}; 606 | } 607 | 608 | /** 609 | * Subscription helper. 610 | */ 611 | 612 | Events.prototype.sub = function(event, method, cb){ 613 | this._events[event] = this._events[event] || {}; 614 | this._events[event][method] = cb; 615 | }; 616 | 617 | /** 618 | * Bind to `event` with optional `method` name. 619 | * When `method` is undefined it becomes `event` 620 | * with the "on" prefix. 621 | * 622 | * Examples: 623 | * 624 | * Direct event handling: 625 | * 626 | * events.bind('click') // implies "onclick" 627 | * events.bind('click', 'remove') 628 | * events.bind('click', 'sort', 'asc') 629 | * 630 | * Delegated event handling: 631 | * 632 | * events.bind('click li > a') 633 | * events.bind('click li > a', 'remove') 634 | * events.bind('click a.sort-ascending', 'sort', 'asc') 635 | * events.bind('click a.sort-descending', 'sort', 'desc') 636 | * 637 | * @param {String} event 638 | * @param {String|function} [method] 639 | * @return {Function} callback 640 | * @api public 641 | */ 642 | 643 | Events.prototype.bind = function(event, method){ 644 | var e = parse(event); 645 | var el = this.el; 646 | var obj = this.obj; 647 | var name = e.name; 648 | var method = method || 'on' + name; 649 | var args = [].slice.call(arguments, 2); 650 | 651 | // callback 652 | function cb(){ 653 | var a = [].slice.call(arguments).concat(args); 654 | obj[method].apply(obj, a); 655 | } 656 | 657 | // bind 658 | if (e.selector) { 659 | cb = delegate.bind(el, e.selector, name, cb); 660 | } else { 661 | events.bind(el, name, cb); 662 | } 663 | 664 | // subscription for unbinding 665 | this.sub(name, method, cb); 666 | 667 | return cb; 668 | }; 669 | 670 | /** 671 | * Unbind a single binding, all bindings for `event`, 672 | * or all bindings within the manager. 673 | * 674 | * Examples: 675 | * 676 | * Unbind direct handlers: 677 | * 678 | * events.unbind('click', 'remove') 679 | * events.unbind('click') 680 | * events.unbind() 681 | * 682 | * Unbind delegate handlers: 683 | * 684 | * events.unbind('click', 'remove') 685 | * events.unbind('click') 686 | * events.unbind() 687 | * 688 | * @param {String|Function} [event] 689 | * @param {String|Function} [method] 690 | * @api public 691 | */ 692 | 693 | Events.prototype.unbind = function(event, method){ 694 | if (0 == arguments.length) return this.unbindAll(); 695 | if (1 == arguments.length) return this.unbindAllOf(event); 696 | 697 | // no bindings for this event 698 | var bindings = this._events[event]; 699 | if (!bindings) return; 700 | 701 | // no bindings for this method 702 | var cb = bindings[method]; 703 | if (!cb) return; 704 | 705 | events.unbind(this.el, event, cb); 706 | }; 707 | 708 | /** 709 | * Unbind all events. 710 | * 711 | * @api private 712 | */ 713 | 714 | Events.prototype.unbindAll = function(){ 715 | for (var event in this._events) { 716 | this.unbindAllOf(event); 717 | } 718 | }; 719 | 720 | /** 721 | * Unbind all events for `event`. 722 | * 723 | * @param {String} event 724 | * @api private 725 | */ 726 | 727 | Events.prototype.unbindAllOf = function(event){ 728 | var bindings = this._events[event]; 729 | if (!bindings) return; 730 | 731 | for (var method in bindings) { 732 | this.unbind(event, method); 733 | } 734 | }; 735 | 736 | /** 737 | * Parse `event`. 738 | * 739 | * @param {String} event 740 | * @return {Object} 741 | * @api private 742 | */ 743 | 744 | function parse(event) { 745 | var parts = event.split(/ +/); 746 | return { 747 | name: parts.shift(), 748 | selector: parts.join(' ') 749 | } 750 | } 751 | 752 | }); 753 | require.register("component-normalized-upload/index.js", function(exports, require, module){ 754 | 755 | /** 756 | * Expose `normalize()`. 757 | */ 758 | 759 | module.exports = normalize; 760 | 761 | /** 762 | * Normalize `e` adding the `e.items` array and invoke `fn()`. 763 | * 764 | * @param {Event} e 765 | * @param {Function} fn 766 | * @api public 767 | */ 768 | 769 | function normalize(e, fn) { 770 | e.items = []; 771 | 772 | var ignore = []; 773 | 774 | var files = e.clipboardData 775 | ? e.clipboardData.files 776 | : e.dataTransfer.files; 777 | 778 | var items = e.clipboardData 779 | ? e.clipboardData.items 780 | : e.dataTransfer.items; 781 | 782 | items = items || []; 783 | files = files || []; 784 | 785 | normalizeItems(e, items, ignore, function(){ 786 | normalizeFiles(e, files, ignore, function(){ 787 | fn(e) 788 | }); 789 | }); 790 | } 791 | 792 | /** 793 | * Process `files`. 794 | * 795 | * Some browsers (chrome) populate both .items and .files 796 | * with the same things, so we need to check that the `File` 797 | * is not already present. 798 | * 799 | * @param {Event} e 800 | * @param {FileList} files 801 | * @param {Function} fn 802 | * @api private 803 | */ 804 | 805 | function normalizeFiles(e, files, ignore, fn) { 806 | var pending = files.length; 807 | 808 | if (!pending) return fn(); 809 | 810 | for (var i = 0; i < files.length; i++) { 811 | var file = files[i]; 812 | if (~ignore.indexOf(file)) continue; 813 | if (~e.items.indexOf(file)) continue; 814 | file.kind = 'file'; 815 | e.items.push(file); 816 | } 817 | 818 | fn(); 819 | } 820 | 821 | /** 822 | * Process `items`. 823 | * 824 | * @param {Event} e 825 | * @param {ItemList} items 826 | * @param {Function} fn 827 | * @return {Type} 828 | * @api private 829 | */ 830 | 831 | function normalizeItems(e, items, ignore, fn){ 832 | var pending = items.length; 833 | 834 | if (!pending) return fn(); 835 | 836 | for (var i = 0; i < items.length; i++) { 837 | var item = items[i]; 838 | 839 | // directories 840 | if ('file' == item.kind && item.webkitGetAsEntry) { 841 | var entry = item.webkitGetAsEntry(); 842 | if (entry && entry.isDirectory) { 843 | ignore.push(item.getAsFile()); 844 | walk(e, entry, function(){ 845 | --pending || fn(e); 846 | }); 847 | continue; 848 | } 849 | } 850 | 851 | // files 852 | if ('file' == item.kind) { 853 | var file = item.getAsFile(); 854 | file.kind = 'file'; 855 | e.items.push(file); 856 | --pending || fn(e); 857 | continue; 858 | } 859 | 860 | // others 861 | (function(){ 862 | var type = item.type; 863 | var kind = item.kind; 864 | item.getAsString(function(str){ 865 | e.items.push({ 866 | kind: kind, 867 | type: type, 868 | string: str 869 | }); 870 | 871 | --pending || fn(e); 872 | }) 873 | })() 874 | } 875 | }; 876 | 877 | /** 878 | * Walk `entry`. 879 | * 880 | * @param {Event} e 881 | * @param {FileEntry} entry 882 | * @param {Function} fn 883 | * @api private 884 | */ 885 | 886 | function walk(e, entry, fn){ 887 | if (entry.isFile) { 888 | return entry.file(function(file){ 889 | file.entry = entry; 890 | file.kind = 'file'; 891 | e.items.push(file); 892 | fn(); 893 | }) 894 | } 895 | 896 | if (entry.isDirectory) { 897 | var dir = entry.createReader(); 898 | dir.readEntries(function(entries){ 899 | entries = filterHidden(entries); 900 | var pending = entries.length; 901 | 902 | for (var i = 0; i < entries.length; i++) { 903 | walk(e, entries[i], function(){ 904 | --pending || fn(); 905 | }); 906 | } 907 | }) 908 | } 909 | } 910 | 911 | /** 912 | * Filter hidden entries. 913 | * 914 | * @param {Array} entries 915 | * @return {Array} 916 | * @api private 917 | */ 918 | 919 | function filterHidden(entries) { 920 | var arr = []; 921 | 922 | for (var i = 0; i < entries.length; i++) { 923 | if ('.' == entries[i].name[0]) continue; 924 | arr.push(entries[i]); 925 | } 926 | 927 | return arr; 928 | } 929 | 930 | }); 931 | require.register("component-drop/index.js", function(exports, require, module){ 932 | 933 | /** 934 | * Module dependencies. 935 | */ 936 | 937 | var normalize = require('normalized-upload'); 938 | var classes = require('classes'); 939 | var events = require('events'); 940 | 941 | /** 942 | * Expose `Drop`. 943 | */ 944 | 945 | module.exports = Drop; 946 | 947 | /** 948 | * Initialize a drop point 949 | * on the given `el` and callback `fn(e)`. 950 | * 951 | * @param {Element} el 952 | * @param {Function} fn 953 | * @api public 954 | */ 955 | 956 | function Drop(el, fn) { 957 | if (!(this instanceof Drop)) return new Drop(el, fn); 958 | this.el = el; 959 | this.callback = fn; 960 | this.classes = classes(el); 961 | this.events = events(el, this); 962 | this.events.bind('drop'); 963 | this.events.bind('dragenter'); 964 | this.events.bind('dragleave'); 965 | this.events.bind('dragover'); 966 | } 967 | 968 | /** 969 | * Unbind event handlers. 970 | * 971 | * @api public 972 | */ 973 | 974 | Drop.prototype.unbind = function(){ 975 | this.events.unbind(); 976 | }; 977 | 978 | /** 979 | * Dragenter handler. 980 | */ 981 | 982 | Drop.prototype.ondragenter = function(e){ 983 | this.classes.add('over'); 984 | }; 985 | 986 | /** 987 | * Dragover handler. 988 | */ 989 | 990 | Drop.prototype.ondragover = function(e){ 991 | e.preventDefault(); 992 | }; 993 | 994 | /** 995 | * Dragleave handler. 996 | */ 997 | 998 | Drop.prototype.ondragleave = function(e){ 999 | this.classes.remove('over'); 1000 | }; 1001 | 1002 | /** 1003 | * Drop handler. 1004 | */ 1005 | 1006 | Drop.prototype.ondrop = function(e){ 1007 | e.stopPropagation(); 1008 | e.preventDefault(); 1009 | this.classes.remove('over'); 1010 | normalize(e, this.callback); 1011 | }; 1012 | 1013 | 1014 | }); 1015 | require.register("drop-anywhere/index.js", function(exports, require, module){ 1016 | 1017 | /** 1018 | * Module dependencies. 1019 | */ 1020 | 1021 | var drop = require('drop'); 1022 | var events = require('events'); 1023 | var classes = require('classes'); 1024 | 1025 | /** 1026 | * Expose `DropAnywhere`. 1027 | */ 1028 | 1029 | module.exports = DropAnywhere; 1030 | 1031 | /** 1032 | * Make the document droppable and invoke `fn(err, upload)`. 1033 | * 1034 | * @param {Function} fn 1035 | * @api public 1036 | */ 1037 | 1038 | function DropAnywhere(fn) { 1039 | if (!(this instanceof DropAnywhere)) return new DropAnywhere(fn); 1040 | this.callback = fn; 1041 | this.el = document.createElement('div'); 1042 | this.el.id = 'drop-anywhere'; 1043 | this.events = events(this.el, this); 1044 | this.classes = classes(this.el); 1045 | this.winEvents = events(window, this); 1046 | this.events.bind('click', 'hide'); 1047 | this.events.bind('drop', 'hide'); 1048 | this.events.bind('dragleave', 'hide'); 1049 | this.winEvents.bind('dragenter', 'show'); 1050 | this.drop = drop(this.el, this.callback); 1051 | this.add(); 1052 | } 1053 | 1054 | /** 1055 | * Add the element. 1056 | */ 1057 | 1058 | DropAnywhere.prototype.add = function(){ 1059 | document.body.appendChild(this.el); 1060 | }; 1061 | 1062 | /** 1063 | * Remove the element. 1064 | */ 1065 | 1066 | DropAnywhere.prototype.remove = function(){ 1067 | document.body.removeChild(this.el); 1068 | }; 1069 | 1070 | /** 1071 | * Show the dropzone. 1072 | */ 1073 | 1074 | DropAnywhere.prototype.show = function(){ 1075 | this.classes.add('show'); 1076 | }; 1077 | 1078 | /** 1079 | * Hide the dropzone. 1080 | */ 1081 | 1082 | DropAnywhere.prototype.hide = function(){ 1083 | this.classes.remove('show'); 1084 | }; 1085 | 1086 | /** 1087 | * Unbind. 1088 | * 1089 | * @api public 1090 | */ 1091 | 1092 | DropAnywhere.prototype.unbind = function(){ 1093 | this.remove(); 1094 | this.winEvents.unbind(); 1095 | this.events.unbind(); 1096 | this.drop.unbind(); 1097 | }; 1098 | 1099 | }); 1100 | 1101 | 1102 | 1103 | 1104 | 1105 | 1106 | 1107 | 1108 | require.alias("component-classes/index.js", "drop-anywhere/deps/classes/index.js"); 1109 | require.alias("component-classes/index.js", "classes/index.js"); 1110 | require.alias("component-indexof/index.js", "component-classes/deps/indexof/index.js"); 1111 | 1112 | require.alias("component-events/index.js", "drop-anywhere/deps/events/index.js"); 1113 | require.alias("component-events/index.js", "events/index.js"); 1114 | require.alias("component-event/index.js", "component-events/deps/event/index.js"); 1115 | 1116 | require.alias("component-delegate/index.js", "component-events/deps/delegate/index.js"); 1117 | require.alias("discore-closest/index.js", "component-delegate/deps/closest/index.js"); 1118 | require.alias("discore-closest/index.js", "component-delegate/deps/closest/index.js"); 1119 | require.alias("component-matches-selector/index.js", "discore-closest/deps/matches-selector/index.js"); 1120 | require.alias("component-query/index.js", "component-matches-selector/deps/query/index.js"); 1121 | 1122 | require.alias("discore-closest/index.js", "discore-closest/index.js"); 1123 | require.alias("component-event/index.js", "component-delegate/deps/event/index.js"); 1124 | 1125 | require.alias("component-drop/index.js", "drop-anywhere/deps/drop/index.js"); 1126 | require.alias("component-drop/index.js", "drop/index.js"); 1127 | require.alias("component-classes/index.js", "component-drop/deps/classes/index.js"); 1128 | require.alias("component-indexof/index.js", "component-classes/deps/indexof/index.js"); 1129 | 1130 | require.alias("component-events/index.js", "component-drop/deps/events/index.js"); 1131 | require.alias("component-event/index.js", "component-events/deps/event/index.js"); 1132 | 1133 | require.alias("component-delegate/index.js", "component-events/deps/delegate/index.js"); 1134 | require.alias("discore-closest/index.js", "component-delegate/deps/closest/index.js"); 1135 | require.alias("discore-closest/index.js", "component-delegate/deps/closest/index.js"); 1136 | require.alias("component-matches-selector/index.js", "discore-closest/deps/matches-selector/index.js"); 1137 | require.alias("component-query/index.js", "component-matches-selector/deps/query/index.js"); 1138 | 1139 | require.alias("discore-closest/index.js", "discore-closest/index.js"); 1140 | require.alias("component-event/index.js", "component-delegate/deps/event/index.js"); 1141 | 1142 | require.alias("component-normalized-upload/index.js", "component-drop/deps/normalized-upload/index.js"); 1143 | require.alias("component-normalized-upload/index.js", "component-drop/deps/normalized-upload/index.js"); 1144 | require.alias("component-normalized-upload/index.js", "component-normalized-upload/index.js");if (typeof exports == "object") { 1145 | module.exports = require("drop-anywhere"); 1146 | } else if (typeof define == "function" && define.amd) { 1147 | define(function(){ return require("drop-anywhere"); }); 1148 | } else { 1149 | this["DropAnywhere"] = require("drop-anywhere"); 1150 | }})(); --------------------------------------------------------------------------------