├── 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 |
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 | 
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 | Name |
38 | Created |
39 | |
40 | |
41 |
42 |
43 |
44 |  |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |

55 |
Identity infrastructure, for developers.
Learn More
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 | }})();
--------------------------------------------------------------------------------