Merge pull request #4 from coderaiser/dev

CloudCmd v0.2.0
This commit is contained in:
coderaiser 2013-04-21 07:57:16 -07:00
commit a49401623a
151 changed files with 8528 additions and 29364 deletions

12
.gitignore vendored
View file

@ -1,24 +1,18 @@
#files, the will be ignored by git
#file with changes
json/changes.json
#c9 files
.c9revisions
*tmp
#minified scripts
min
#node modules folder
node_modules
#temporary files folder
tmp
#file used by c9
apache-status
#file used by nodester
.nodester.appconfig
#log file
log.txt

View file

@ -2,7 +2,7 @@ language: node_js
node_js:
- 0.6
- 0.8
- 0.9
- 0.10
notifications:
#webhooks:
#http://requestb.in/12h5bl71

16
CONTRIBUTING.md Normal file
View file

@ -0,0 +1,16 @@
If you would like to contribute - send pull request to dev branch.
Getting dev version of **Cloud Commander**:
git clone git://github.com/coderaiser/cloudcmd.git
git checkout dev
or by [link](https://github.com/coderaiser/cloudcmd/tree/dev "Dev version").
It is possible thet dev version Cloud Commander will needed dev version of Minify,
so to get it you should type a couple more commands:
cd node_modules
rm -rf minify
git clone git://github.com/coderaiser/minify
cd minify
git checkout dev

116
ChangeLog
View file

@ -1,4 +1,100 @@
2012.03.01, Version 0.1.9
2012.04.22, v0.2.0
* Added alerting about errors.
* Removed Listeners array for DOM.
* Added ability create folders.
* Added filepicker.
* Added ability to load files to cloud
and get them out to file system.
* Added SSL suport.
* jquery updated to v1.9.1.
* Fixed bug with multiple click events on f3 and f4 buttons.
* Added ability to move files.
* Added ability to degradate to http from https.
* Fixed bug of multiple call of sockets.
* If create directory command executed - loading spinner
would be on top.
* Refactored getShortSize.
* Added update of size on file changing in editor.
* Removed cache control of fs resour.
* Added ability to copy files.
* Fixed bug with saving json to localStorage, it's always
writed root directory.
* Moved out set current file from cloudfunc to client.js.
* Changed the way file table building. From now templating used.
* Changed sync reading of certs to async.
* Updated dropbox to v0.9.2 and moved to packege.json
from storage folder.
* Updated socket.io to v0.9.14.
* Fixed Util.time and Util.timeEnd
* Added ability to select files with Insert key.
* Added ability to delete selected files from DOM.
* Added ability to recursivly get current folder size.
* Added ability to get directory size, when space button
is pressed and current file is directory.
* Added ability to select all files on Ctrl + A
* Fixed the href generation of renamed file.
* Renamed promptNewFolder -> promptNewDir'.
* Added function getSelectedNames.
* Added ability to delete file when f8 key pressed.
* Fixed bug with selectecting root directory with space.
* Fixed bug with processing selected root directory.
* Added ability to delete files.
* Removed modules cache.js.
* Fixed bug with writing new file.
* Updated jquery to v2.0.0.
* Improved CloudFunc module.
* Fixed selection style in opera.
* Fixed header of api fs GET.
* Fixed bug with sending response on
query other then json on /fs url.
* Fixed bug with getting back to
directory where file was removed.
2012.03.01, v0.1.9
* Changed the way of getting github application id
(now it's just from config, rest api removed).
@ -192,7 +288,7 @@ CloudCmd REST API.
* Added help screen (on F1 after viewer loads).
2012.12.12, Version 0.1.8
2012.12.12, v0.1.8
* Added ability to shutdown Cloud Commander
thru terminal command: "cloudcmd exit"
@ -385,7 +481,7 @@ Just propose install git and clone from github repo.
* Fixed bug with appcache.
2012.10.01, Version 0.1.7
2012.10.01, v0.1.7
* Changed name of menu files, fixed npm and jitsu
problem with menu showing.
@ -471,7 +567,7 @@ disabled in browsers.
* Added fix of 866 charset.
2012.08.24, Version 0.1.6
2012.08.24, v0.1.6
* From now jsload, cssload and anyload suport arrays.
@ -524,7 +620,7 @@ current, file that was previously current must be unset automatically.
* Added ability to download files.
2012.08.06, Version 0.1.5
2012.08.06, v0.1.5
* Added tab support.
@ -564,7 +660,7 @@ pressed couple times and CodeMirror not loaded fully.
and passive panels and show/hide any of panels.
2012.07.27, Version 0.1.4
2012.07.27, v0.1.4
* Added local version of Droids font for offline mode
@ -600,7 +696,7 @@ be more faster, DOM, short, simple and logical.
* Added ability remove not loaded scripts from DOM.
2012.07.19, Version 0.1.3
2012.07.19, v0.1.3
* Fixed bug with nodester (jitsu env.HOME make him go done).
@ -633,7 +729,7 @@ setted b char: "0b".
* Added supporting of Russian language in directory names.
2012.07.14, Version 0.1.2
2012.07.14, v0.1.2
* Added suport of jitsu.
@ -644,7 +740,7 @@ setted b char: "0b".
* Changed the minimize function calls accroding to Minify 0.1.2 changes.
2012.07.11, Version 0.1.1
2012.07.11, v0.1.1
* Added onerror parametr to anyload in Clinet Side, so now we can process situation when
js-script(or something other) has not loaded.
@ -663,4 +759,4 @@ post-processing functions passed like this {'client.js': function(){}}
* Added ability to read file data from Minify Cache, without writing to disk
* Changed the passing MoreProcessing agrument to jsScripts function Minify module,
no it passes with a file name, and js file name writing only once.
no it passes with a file name, and js file name writing only once.

View file

@ -1,5 +1,64 @@
**2012.03.01**
2012.04.22, v0.2.0
===============
Весна в разгаре, на деревьях появляются почки, наконец-то начинает теплеть.
А это значит, что пришло время выпускать на свободу новую версию Командира.
Много интересных вещей произошло и изменилось со времени прошлого релиза.
Сейчас мы рассмотрим поближе, что же именно изменилось. Итак начнём.
**Добавленна внешняя возможность:**
- Всплывающее сообщение об ошибках
- Создание папки.
- FilePicker.
- загружать файлы в облако и скачивать их в файловую систему.
- SSL.
- Перемещения файлов.
- Перехода на http, если https-сервер запустить не удалось.
- Если создаётся директория индикатор загрузки будет сверху.
- Обновления размера файла после изминения оного в редакторе.
- Копирования файлов.
- Выделения файлов клавишей Insert.
- Получения размера текущей папки при нажатии клавиши Space.
- Выделение всех файлов по Ctrl + A.
- Удаления файла по F8.
- Удаления нескольких файлов.
**Исправленные ошибки:**
- Несколько обработчиков клика по нажатию клавиш F3 и F4.
- Множественный вызов Sockets.
- Запись json в localStorage: всегда обновлялось содержимое корневой директории.
- В функциях Util.time и Util.timeEnd.
- Генереация href файла, который переименовывается.
- Выделение коревой директории клавишей Space
- Обработка выделенной корневой директории.
- Создание нового файла.
- Стиль выделения в Опере.
- Заголовок выдачи REST-фукнции fs метода GET.
- Отправка данных в ответ на запрос отличный от ?json в пути /url.
- Возвращение в папку, из которой был удален файл.
**Обновлены:**
- jquery до версии v2.0.0
- dropbox до версии v0.9.2
- socket.io до версии v0.9.14.
**Внутренние изминения:**
- Удален массив Listeners из модуля DOM.
- Переделан и оптимизирована функция getShortSize в CloudFunc.
- Контроль генерации заголовка кеша перенесен в модуль Main.
- Установка текущего файла перенесена в Client из CloudFunc.
- Изменён способ построения таблицы файлов. Теперь используются шаблоны.
- Загрузка SSL-сертификатов теперь асинхронная.
- Модуль DropBox перенесён в packege.json из папки Storage.
- Удаления выделеного файла из DOM.
- Рекурсивного получения размера текущей папки.
- Переименована функция promptNewFolder -> promptNewDir.
- Добавлена функция getSelectedNames.
- Удален модуль Сache.
- Оптимизирован модуль CloudFunc.
2012.03.01, v0.1.9
===============
Сегодня, в первый день весны, вышла новая версия облачного менеджера файлов: 0.1.9.
В связи с этим, есть двойной повод порадоватся: наконец, пришла весна,
с полной готовностью согревать серца жителей, после зимних холодов,

View file

@ -1,11 +1,22 @@
Cloud Commander [![Build Status](https://secure.travis-ci.org/coderaiser/cloudcmd.png?branch=master)](http://travis-ci.org/coderaiser/cloudcmd)
Cloud Commander v0.2.0 [![NPM version][NPMIMGURL]][NPMURL] [![Dependency Status][DependencyStatusIMGURL]][DependencyStatusURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL]
===============
[![Flattr][FlattrIMGURL]][FlattrURL]
[NPMIMGURL]: https://badge.fury.io/js/cloudcmd.png
[BuildStatusIMGURL]: https://secure.travis-ci.org/coderaiser/cloudcmd.png?branch=master
[DependencyStatusIMGURL]: https://gemnasium.com/coderaiser/cloudcmd.png
[FlattrIMGURL]: http://api.flattr.com/button/flattr-badge-large.png
[NPMURL]: http://badge.fury.io/js/cloudcmd
[BuildStatusURL]: http://travis-ci.org/coderaiser/cloudcmd "Build Status"
[DependencyStatusURL]: https://gemnasium.com/coderaiser/cloudcmd "Dependency Status"
[FlattrURL]: https://flattr.com/submit/auto?user_id=coderaiser&url=github.com/coderaiser/cloudcmd&title=cloudcmd&language=&tags=github&category=software
**Cloud Commander** - user friendly cloud file manager.
DEMO:
[cloudfoundry] (http://cloudcmd.cloudfoundry.com "cloudfoundry"),
[appfog] (http://cloudcmd.aws.af.cm "appfog").
[cloudfoundry] (https://cloudcmd.cloudfoundry.com "cloudfoundry"),
[appfog] (https://cloudcmd.aws.af.cm "appfog"),
[jitsu] (https://cloudcmd.jit.su "jitsu").
Google PageSpeed Score : [100](http://developers.google.com/speed/pagespeed/insights#url=http_3A_2F_2Fcloudcmd.cloudfoundry.com_2F&mobile=false "score") (out of 100)
Google PageSpeed Score : [100](//developers.google.com/speed/pagespeed/insights#url=http_3A_2F_2Fcloudcmd.aws.af.cm_2F&mobile=false "score") (out of 100)
(or 96 if js or css minification disabled in config.json).
![Cloud Commander](img/logo/cloudcmd.png "Cloud Commander")
@ -40,15 +51,18 @@ There is a short list:
- **Ctrl + d** - clear local cache (wich contains dir contents)
- **Alt + q** - disable key bindings
- **Alt + s** - get all key bindings back
- **Ctrl + A** - select all files in a panel
- **up, down, enter** - filesystem navigation
- **Tab** - move thru panels
- **Page Up** - up on one page
- **Page Down** - down on one page
- **Home** - to begin of list
- **End** - to end of list
- **Shift + F10** - show context menu
- **F8, Delete** - remove current file
- **Shift + Delete** - remove without prompt
- **Insert** - select current file
- **F2** - rename current file
- **Alt + g** - authorization in GitHub
- **Shift + F10** - show context menu
Viewer's hot keys
---------------
@ -70,12 +84,8 @@ Right mouse click button show context menu with items:
- Rename
- Delete
- Upload to (Dropbox, Github, GDrive)
- Downloud
Documentation
---------------
JS Doc documentation could be found in [http://jsdoc.info/coderaiser/cloudcmd/](http://jsdoc.info/coderaiser/cloudcmd/)
- Download
- New (File, Dir, from cloud)
Installing
---------------
@ -107,11 +117,46 @@ All main configuration could be done thrue config.json.
"server" : true, /* server mode or testing mode */
"logs" : false, /* logs or console ouput */
"socket" : true /* enable web sockets */
"port" : 80, /* Cloud Commander port */
"port" : 80, /* http port */
"sslPort" : 443, /* https port */
"ip" : "127.0.0.1", /* Cloud Commander IP */
"ssl" : true /* should use https? */
"rest" : true /* enable rest interface */
}
```
Server
---------------
Standard practices say no non-root process gets to talk to
the Internet on a port less than 1024. Anyway I suggest you
to start Cloud Commander as non-root. How it could be solved?
There is a couple easy and fast ways. One of them is port forwarding by iptables.
```sh
@:/tmp/cloudcmd (dev) $ su iptables -t nat -L # look rules before
@:/tmp/cloudcmd (dev) $ su iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-ports 8000
@:/tmp/cloudcmd (dev) $ su iptables -t nat -A PREROUTING -p tcp --dport 443 -j REDIRECT --to-ports 4430
@:/tmp/cloudcmd (dev) $ su iptables -t nat -L # look reles after
```
You should see somethins like this ( **8000** and **4430** should be in config as **port** and **sslPort** )
target prot opt source destination
REDIRECT tcp -- anywhere anywhere tcp dpt:http redir ports 8000
REDIRECT tcp -- anywhere anywhere tcp dpt:https redir ports 4430
If you would want to get things back just clear rules ( **1** and **2** it's rules numbers,
in your list they could differ).
```sh
@:/tmp/cloudcmd (dev) $ su iptables -t nat -D PREROUTING 1
@:/tmp/cloudcmd (dev) $ su iptables -t nat -D PREROUTING 2
```
To run Cloud Commander as daemon in linux you could set **log** to true in config and
do something like this:
nohup node cloudcmd
Authorization
---------------
Thru openID Cloud Commander could authorize clients on GitHub.
@ -210,6 +255,20 @@ so to get it you should type a couple more commands:
git clone git://github.com/coderaiser/minify
git checkout dev
Version history
---------------
- *2012.04.22*, **[v0.2.0](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.2.0.zip)**
- *2012.03.01*, **[v0.1.9](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.9.zip)**
- *2012.12.12*, **[v0.1.8](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.8.zip)**
- *2012.10.01*, **[v0.1.7](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.7.zip)**
- *2012.08.24*, **[v0.1.6](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.6.zip)**
- *2012.08.06*, **[v0.1.5](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.5.zip)**
- *2012.07.27*, **[v0.1.4](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.4.zip)**
- *2012.07.19*, **[v0.1.3](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.3.zip)**
- *2012.07.14*, **[v0.1.2](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.2.zip)**
- *2012.07.11*, **[v0.1.1](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.1.zip)**
- *2012.00.00*, **[v0.1.0](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.0.zip)**
Special Thanks
---------------
[Elena Zalitok](http://vk.com/politilena "Elena Zalitok") for logo.

View file

@ -23,12 +23,21 @@
INDEX = HTMLDIR + 'index.html',
CONFIG_PATH = JSONDIR + 'config.json',
CA = DIR + 'ssl/sub.class1.server.ca.pem',
KEY = DIR + 'ssl/ssl.key',
CERT = DIR + 'ssl/ssl.crt',
FILE_TMPL = HTMLDIR + 'file.html',
PATH_TMPL = HTMLDIR + 'path.html',
FileTemplate, PathTemplate,
FS = CloudFunc.FS;
/* reinit main dir os if we on
* Win32 should be backslashes */
/* reinit main dir os if we on Win32 should be backslashes */
DIR = main.DIR;
readConfig(init);
@ -36,8 +45,7 @@
* additional processing of index file
*/
function indexProcessing(pData){
var lReplace_s,
lData = pData.data,
var lData = pData.data,
lAdditional = pData.additional;
/*
@ -46,9 +54,9 @@
* минифицированый
*/
if(Minify.allowed.css){
var lPath = '/' + Util.removeStr(Minify.MinFolder, DIR);
lReplace_s = '<link rel=stylesheet href="/css/reset.css">';
lData = Util.removeStr(lData, lReplace_s)
var lPath = '/' + Util.removeStr(Minify.MinFolder, DIR),
lReplace = '<link rel=stylesheet href="/css/reset.css">';
lData = Util.removeStr(lData, lReplace)
.replace('/css/style.css', lPath + 'all.min.css');
}
@ -73,9 +81,14 @@
* init and process of appcache if it allowed in config
*/
function appCacheProcessing(){
var lFiles = [
{'//themes.googleusercontent.com/static/fonts/droidsansmono/v4/ns-m2xQYezAtqh7ai59hJUYuTAAIFFn5GTWtryCmBQ4.woff' : './font/DroidSansMono.woff'},
{'//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js' : './lib/client/jquery.js'}];
var lFONT_REMOTE = '//themes.googleusercontent.com/static/fonts/droidsansmono/v4/ns-m2xQYezAtqh7ai59hJUYuTAAIFFn5GTWtryCmBQ4.woff',
lFONT_LOCAL = './font/DroidSansMono.woff',
lJQUERY_REMOTE = 'http://code.jquery.com/jquery-2.0.0.min.js',
lJQUERY_LOCAL = './lib/client/jquery.js',
lFiles = [{}, {}];
lFiles[0][lFONT_REMOTE] = lFONT_LOCAL;
lFiles[1][lJQUERY_REMOTE] = lJQUERY_LOCAL;
if(Config.minification.css)
lFiles.push('node_modules/minify/min/all.min.css');
@ -121,7 +134,7 @@
/**
* rest interface
* @pConnectionData {request, responce}
* @pParams pConnectionData {request, responce}
*/
function rest(pConnectionData){
return Util.exec(main.rest, pConnectionData);
@ -151,8 +164,7 @@
/* if command line parameter testing resolved
* setting config to testing, so server
* not created, just init and
* all logs writed to screen
*/
* all logs writed to screen */
var lArg = process.argv;
lArg = lArg[lArg.length - 1];
if ( lArg === 'test' || lArg === 'test\r') {
@ -165,22 +177,46 @@
'from now all logs will be writed to log.txt');
writeLogsToFile();
}
}
if(Config.server)
fs.watch(CONFIG_PATH, function(){
/* every catch up - calling twice */
setTimeout(function() {
readConfig();
}, 1000);
if(Config.server)
fs.watch(CONFIG_PATH, function(){
/* every catch up - calling twice */
setTimeout(function() {
readConfig();
}, 1000);
});
var lParams = {
appcache : appCacheProcessing,
minimize : minimize,
rest : rest,
route : route
},
lFiles = [FILE_TMPL, PATH_TMPL];
if(Config.ssl)
lFiles.push(CA, KEY, CERT);
main.readFiles(lFiles, function(pErrors, pFiles){
if(pErrors)
Util.log(pErrors);
else{
FileTemplate = pFiles[FILE_TMPL].toString();
PathTemplate = pFiles[PATH_TMPL].toString();
if(Config.ssl)
lParams.ssl = {
ca : pFiles[CA],
key : pFiles[KEY],
cert : pFiles[CERT]
};
server.start(lParams);
}
});
server.start({
appcache : appCacheProcessing,
minimize : minimize,
rest : rest,
route : route
});
}
else
Util.log('read error: config.json');
}
function readConfig(pCallBack){
@ -220,12 +256,16 @@
if( Util.strCmp(p.name, ['/auth', '/auth/github']) ){
Util.log('* Routing' +
'-> ' + p.name);
pParams.name = main.HTMLDIR + p.name + '.html';
lRet = main.sendFile( pParams );
pParams.name = main.HTMLDIR + p.name + '.html';
lRet = main.sendFile( pParams );
}
else if( Util.isContainStr(p.name, FS) || Util.strCmp( p.name, '/') ){
if( Util.isContainStr(p.name, 'no-js/') )
return noJSTMPRedirection(pParams);
else if( Util.isContainStrAtBegin(p.name, FS) || Util.strCmp( p.name, '/') ){
if( Util.isContainStrAtBegin(p.name, FS + 'no-js/') ){
var lURL = Util.removeStr(pParams.name, 'no-js/');
return main.redirect(pParams, lURL);
}
lRet = sendCommanderContent(p);
}
@ -241,7 +281,7 @@
var lRet = main.checkParams(pParams);
if(lRet){
var p = pParams;
p.name = Util.removeStr(p.name, CloudFunc.FS) || main.SLASH;
p.name = Util.removeStrOneTime(p.name, CloudFunc.FS) || main.SLASH;
fs.stat(p.name, function(pError, pStat){
if(!pError)
@ -260,6 +300,7 @@
function processCommanderContent(pParams){
var lRet = main.checkParams(pParams);
if(lRet){
var p = pParams;
main.commander.getDirContent(p.name, function(pError, pJSON){
@ -270,11 +311,11 @@
p.name +='.json';
main.sendResponse(p);
}
else if(!lQuery){ /* get back html*/
else{ /* get back html*/
p.name = Minify.allowed.html ? Minify.getName(INDEX) : INDEX;
fs.readFile(p.name, function(pError, pData){
if(!pError){
var lPanel = CloudFunc.buildFromJSON(pJSON),
var lPanel = CloudFunc.buildFromJSON(pJSON, FileTemplate, PathTemplate),
lList = '<ul id=left class=panel>' + lPanel + '</ul>' +
'<ul id=right class=panel>' + lPanel + '</ul>';
@ -294,16 +335,6 @@
}
}
function noJSTMPRedirection(pParams){
var MOVED_PERMANENTLY = 301,
lPath = Util.removeStr(pParams.name, 'no-js/');
pParams.response.writeHead(MOVED_PERMANENTLY, {'Location': lPath});
pParams.response.end();
return true;
}
/* function sets stdout to file log.txt */
function writeLogsToFile(){

View file

@ -26,7 +26,12 @@ body { margin: 0; font-size: 1em; line-height: 1.4; }
::selection { background: #fe57a1; color: #fff; text-shadow: none; }
*/
::-moz-selection{ text-shadow: none; opacity: 0;}
::selection { text-shadow: none; opacity: 0;}
::selection {
text-shadow: none;
opacity: 0;
background-color:white; /* opera */
}
/* =============================================================================
Links

View file

@ -1,352 +1,353 @@
/*
@import url(//fonts.googleapis.com/css?family=Droid+Sans+Mono);
*/
/* символьный шрифт от гитхаба*/
@font-face {
font-family: "GeneralFoundicons";
src: url('/font/general_foundicons.woff') format('woff');
font-weight: normal;
font-style: normal;
}
/* http://fontello.com/ */
@font-face {
font-family: 'Fontello';
src: url("/font/fontello.eot");
src: url("/font/fontello.eot?#iefix") format('embedded-opentype'), url("/font/fontello.woff") format('woff'), url("/font/fontello.ttf") format('truetype'), url("/font/fontello.svg#cloudcmd") format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Droid Sans Mono';
font-style: normal;
font-weight: normal;
src: local('Droid Sans Mono'), local('DroidSansMono'), url('http://themes.googleusercontent.com/static/fonts/droidsansmono/v4/ns-m2xQYezAtqh7ai59hJUYuTAAIFFn5GTWtryCmBQ4.woff') format('woff');
}
body{
font:16px "Droid Sans Mono";
background-color:white;
}
.path-icon{
position: relative;
top: 3px;
left: -4px;
display: inline-block;
/* размер иконки и позиция на png-файле*/
width: 15px;
height: 15px;
font-family:'FoundationIconsGeneralEnclosed';
font-size:30px;
color: #46A4C3;/*#55BF3F; green*/
text-shadow:black 0 2px 1px;
}
.path-icon:hover{
cursor:pointer;
}
.path-icon:active{
position: relative;
top: 4px;
text-shadow:black 0 0 1px;
}
.icon{
display:inline-block;
width:16px;
height:16px;
margin-left:0.5%;
/* font-family: 'GeneralFoundicons'; */
font-family: 'Fontello';
}
.error::before{
position: relative;
font-size: 14px;
color: rgb(222, 41, 41);
cursor :default;
content: '\2757';
}
.loading{
position:relative;
top:1px;
background:url(/img/spinner.gif);
}
.error:hover{
color:rgb(222, 41, 41);
color:rgba(222, 41, 41, 0.81);
}
.refresh-icon{
background:url(/img/panel_refresh.png) no-repeat;
}
.refresh-icon:active{
/*background-position-y: -15px;*/
background:url(/img/panel_refresh.png) 0 -15px no-repeat;
}
.cmd-button{
width: 10%;
margin: 20px 2px 0 2px;
overflow: hidden;
color: rgb(49,123,249);
text-overflow: ellipsis;
white-space: nowrap;
background-color: white;
border: 1.5px solid rgba(49,123,249,.40);
}
.cmd-button:hover{
border: 1.5px solid rgb(0,0,0);
}
.cmd-button:active{
color: white;
background-color: rgb(49,123,249);
}
.clear-cache{
margin-right: 6px;
margin-left: 7px;
background:url(/img/console_clear.png) -4px -4px no-repeat;
}
.clear-cache:active{
top:5px;
}
.links{
color:red;
}
.mini-icon {
position: relative;
top: 2px;
left: -5px;
float: left;
width: 16px;
height: 16px;
margin-left: 6px;
/* отступ перед картинкой
* для нормального отображения
* рамки
*/
}
/* уменьшаем отступ
* между между иконкой и
* именем файла во время
* установления курсора
*/
.current-file > .mini-icon{
left: -6px;
}
/* freeupex */
.directory{
/*list-style-image*/
background-image:url('/img/dir.png');
background-position: 0 0;
background-repeat: no-repeat;
}
.text-file{
/*list-style-image*/
background-image:url('/img/txt.png');
background-position: 0 0;
background-repeat: no-repeat;
}
#fm{
height: 90%;
margin: 26px 26px 0 26px;
}
.fm-header{
font-weight: bold;
}
#path{
margin-left:1.5%;
}
.left, #left{
float:left;
}
/* фон файла, на котором курсор*/
.current-file{
border: 1.5px solid rgba(49, 123, 249, .40);
}
.selected-file{
color:white;
background-color: rgb(49, 123, 249);
background-color: rgba(49, 123, 249, .40);
}
.right, #right{
float:right;
}
.panel{
width: 46%;
overflow-y: auto;
border: 1.5px solid rgba(49, 123, 249, .40);
}
#keyspanel{
text-align: center;
}
/* информация о файлах и папках*/
.name{
float: left;
width: 37%;
/* если длина имени файла больше 16 символов
* отрезаем лишнее, оставляя лишь 16,
* и добавляем две точки и тайтл
*/
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.size{
float:left;
width:16%;
/* Ставим отступ, что бы
* size не налазил на uid
* (owner)
*/
margin-right: 27px;
/* Ставим выравнивание,
* что бы размер был в
* одной ровной колонке
*/
text-align: right;
}
.mode{
float: right;
width: 25%;
}
/* changin ul to panel for high speed parsing*/
.panel, li{
list-style-type:none;
/* making cursor just arrow,
* not text editing cursor
*/
cursor:default;
}
button{
width:10%;
}
a{
text-decoration:none;
}
a:hover, a:active {
color: #06e;
cursor:pointer;
}
/* Если размер окна очень маленький
* располагаем имя и атрибуты файла
* друг-под-другом
*/
/* responsive design */
@media only screen and (max-width: 600px){
.panel{
width:94% !important;
}
/* если правая панель не помещаеться - прячем её */
#right{
display:none;
}
/* текущий файл под курсором */
.current-file{
background-color: rgb(49, 123, 249);
background-color: rgba(49, 123, 249, .40);
color:white;
}
/* делаем иконки под курсом белыми*/
.current-file > .mini-icon{
color:white;
}
.current-file > .text-file::before{
color:white;
}
.fm-header{
display:none;
}
/* меняем иконки на шрифтовые*/
.mini-icon {
color: rgb(246, 224, 124);
color: rgba(246, 224, 124, 0.56);
font: 60px 'Fontello';
width: 40%;
height: 0;
margin-left: 0;
float: right;
position: relative;
top: 10px
}
.directory::before{
content: '\1f4c1';
}
.text-file::before{
color: rgb(26, 224, 124);
color: rgba(26, 224, 124, 0.56);
content: '\1f4c4';
}
.text-file{
background-image:none;
}
/* убираем заголовок*/
.fm_header{
display:none;
}
.mode,.size,.owner{
/* располагаем элементы
* один под другим
*/
display: table;
float: none;
width: 0;
text-align: left;
}
/* выводим заголовки рядом с полями */
.name::before{
content: 'name:';
font-weight: bold;
font-size: 13px;
}
.mode::before{
content: 'mode:';
font-weight: bold;
font-size: 13px;
}
.size::before{
content: 'size:';
font-weight: bold;
font-size: 13px;
}
.owner::before{
content: 'owner:';
font-weight: bold;
font-size: 13px;
}
.name{
float: none;
width:100%;
font-size: 18px;
}
}
@media only screen and (min-width: 601px) and (max-width: 785px){
.panel{
width:94% !important;
}
#right{
display:none;
}
}
@media only screen and (min-width:786px) and (max-width: 1155px){
.panel{
width:94% !important;
}
/* если правая панель не помещаеться - прячем её */
#right{
display:none;
}
/*
@import url(//fonts.googleapis.com/css?family=Droid+Sans+Mono);
*/
/* символьный шрифт от гитхаба*/
@font-face {
font-family: "GeneralFoundicons";
src: url('/font/general_foundicons.woff') format('woff');
font-weight: normal;
font-style: normal;
}
/* http://fontello.com/ */
@font-face {
font-family: 'Fontello';
src: url("/font/fontello.eot");
src: url("/font/fontello.eot?#iefix") format('embedded-opentype'), url("/font/fontello.woff") format('woff'), url("/font/fontello.ttf") format('truetype'), url("/font/fontello.svg#cloudcmd") format('svg');
font-weight: normal;
font-style: normal;
}
@font-face {
font-family: 'Droid Sans Mono';
font-style: normal;
font-weight: normal;
src: local('Droid Sans Mono'), local('DroidSansMono'), url('//themes.googleusercontent.com/static/fonts/droidsansmono/v4/ns-m2xQYezAtqh7ai59hJUYuTAAIFFn5GTWtryCmBQ4.woff') format('woff');
}
body{
font:16px "Droid Sans Mono";
background-color:white;
}
.path-icon{
position: relative;
top: 3px;
left: -4px;
display: inline-block;
/* размер иконки и позиция на png-файле*/
width: 15px;
height: 15px;
font-family:'FoundationIconsGeneralEnclosed';
font-size:30px;
color: #46A4C3;/*#55BF3F; green*/
text-shadow:black 0 2px 1px;
}
.path-icon:hover{
cursor:pointer;
}
.path-icon:active{
position: relative;
top: 4px;
text-shadow:black 0 0 1px;
}
.icon{
display:inline-block;
width:16px;
height:16px;
margin-left:0.5%;
/* font-family: 'GeneralFoundicons'; */
font-family: 'Fontello';
}
.error::before{
position: relative;
font-size: 14px;
color: rgb(222, 41, 41);
cursor :default;
content: '\2757';
}
.loading{
position:relative;
top:1px;
background:url(/img/spinner.gif);
}
.error:hover{
color:rgb(222, 41, 41);
color:rgba(222, 41, 41, 0.81);
}
.refresh-icon{
background:url(/img/panel_refresh.png) no-repeat;
}
.refresh-icon:active{
/*background-position-y: -15px;*/
background:url(/img/panel_refresh.png) 0 -15px no-repeat;
}
.cmd-button{
width: 10%;
margin: 20px 2px 0 2px;
overflow: hidden;
color: rgb(49,123,249);
text-overflow: ellipsis;
white-space: nowrap;
background-color: white;
border: 1.5px solid rgba(49,123,249,.40);
}
.cmd-button:hover{
border: 1.5px solid rgb(0,0,0);
}
.cmd-button:active{
color: white;
background-color: rgb(49,123,249);
}
.clear-cache{
margin-right: 6px;
margin-left: 7px;
background:url(/img/console_clear.png) -4px -4px no-repeat;
}
.clear-cache:active{
top:5px;
}
.links{
color:red;
}
.mini-icon {
position: relative;
top: 2px;
left: -5px;
float: left;
width: 16px;
height: 16px;
margin-left: 6px;
/* отступ перед картинкой
* для нормального отображения
* рамки
*/
}
/* уменьшаем отступ
* между между иконкой и
* именем файла во время
* установления курсора
*/
.current-file > .mini-icon{
left: -6px;
}
/* freeupex */
.directory{
/*list-style-image*/
background-image:url('/img/dir.png');
background-position: 0 0;
background-repeat: no-repeat;
}
.text-file{
/*list-style-image*/
background-image:url('/img/txt.png');
background-position: 0 0;
background-repeat: no-repeat;
}
#fm{
height: 90%;
margin: 26px 26px 0 26px;
}
.fm-header{
font-weight: bold;
}
#path{
margin-left:1.5%;
}
.left, #left{
float:left;
}
/* фон файла, на котором курсор*/
.current-file{
border: 1.5px solid rgba(49, 123, 249, .40);
}
.selected-file, .selected-file .name > a{
color: rgb(254,159,224);
}
.right, #right{
float:right;
}
.panel{
width: 46%;
overflow-y: auto;
border: 1.5px solid rgba(49, 123, 249, .40);
}
#keyspanel{
text-align: center;
}
/* информация о файлах и папках*/
.name{
float: left;
width: 37%;
/* если длина имени файла больше 16 символов
* отрезаем лишнее, оставляя лишь 16,
* и добавляем две точки и тайтл
*/
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.size{
float:left;
width:16%;
/* Ставим отступ, что бы
* size не налазил на uid
* (owner)
*/
margin-right: 27px;
/* Ставим выравнивание,
* что бы размер был в
* одной ровной колонке
*/
text-align: right;
}
.mode{
float: right;
width: 25%;
}
/* changin ul to panel for high speed parsing*/
.panel, li{
list-style-type:none;
/* making cursor just arrow,
* not text editing cursor
*/
cursor:default;
}
button{
width:10%;
}
a{
text-decoration:none;
}
a:hover, a:active {
color: #06e;
cursor:pointer;
}
/* Если размер окна очень маленький
* располагаем имя и атрибуты файла
* друг-под-другом
*/
/* responsive design */
@media only screen and (max-width: 600px){
.panel{
width:94% !important;
}
/* если правая панель не помещаеться - прячем её */
#right{
display:none;
}
/* текущий файл под курсором */
.current-file{
background-color: rgb(49, 123, 249);
background-color: rgba(49, 123, 249, .40);
color:white;
}
/* делаем иконки под курсом белыми*/
.current-file > .mini-icon{
color:white;
}
.current-file > .text-file::before{
color:white;
}
.fm-header{
display:none;
}
/* меняем иконки на шрифтовые*/
.mini-icon {
color: rgb(246, 224, 124);
color: rgba(246, 224, 124, 0.56);
font: 60px 'Fontello';
width: 40%;
height: 0;
margin-left: 0;
float: right;
position: relative;
top: 10px
}
.directory::before{
content: '\1f4c1';
}
.text-file::before{
color: rgb(26, 224, 124);
color: rgba(26, 224, 124, 0.56);
content: '\1f4c4';
}
.text-file{
background-image:none;
}
/* убираем заголовок*/
.fm_header{
display:none;
}
.mode,.size,.owner{
/* располагаем элементы
* один под другим
*/
display: table;
float: none;
width: 0;
text-align: left;
}
/* выводим заголовки рядом с полями */
.name::before{
content: 'name:';
font-weight: bold;
font-size: 13px;
}
.mode::before{
content: 'mode:';
font-weight: bold;
font-size: 13px;
}
.size::before{
content: 'size:';
font-weight: bold;
font-size: 13px;
}
.owner::before{
content: 'owner:';
font-weight: bold;
font-size: 13px;
}
.name{
float: none;
width:100%;
font-size: 18px;
}
}
@media only screen and (min-width: 601px) and (max-width: 785px){
.panel{
width:94% !important;
}
#right{
display:none;
}
}
@media only screen and (min-width:786px) and (max-width: 1155px){
.panel{
width:94% !important;
}
/* если правая панель не помещаеться - прячем её */
#right{
display:none;
}
}

View file

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<head>
<script src=http://cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.8.1/dropbox.min.js></script>
<script src=//cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.9.2/dropbox.min.js></script>
</head>
<body>
<script>

8
html/file.html Normal file
View file

@ -0,0 +1,8 @@
<li draggable class>
<span class="mini-icon {type}"></span>
<span class=name>
<a href="{link}" title="{name}" target="{target}" draggable=true>{name}</a>
</span>
<span class=size>{size}</span><span class=owner>{owner}</span>
<span class=mode>{mode}</span>
</li>

View file

@ -4,6 +4,8 @@
<meta charset="utf-8">
<!-- mobile first design -->
<meta content="width=device-width,initial-scale=1" name="viewport" />
<!-- chrome frame -->
<meta http-equiv="X-UA-Compatible" content=" chrome=1" />
<title>{title}</title>
<link rel=stylesheet href="/css/reset.css">

1
html/path.html Normal file
View file

@ -0,0 +1 @@
<li class=path><span class="path-icon clear-cache" id=clear-cache title="clear cache (Ctrl+D)"></span><span class="path-icon refresh-icon" title="refresh (Ctrl+R)"><a href="{link}"></a></span><span>{path}</span></li>

View file

@ -13,6 +13,8 @@
"server" : true,
"socket" : true,
"port" : 80,
"sslPort" : 443,
"ip" : null,
"ssl" : false,
"rest" : true
}

View file

@ -34,6 +34,12 @@
"data": {
"id" : "00000000440E696F"
}
},
{
"name": "FilePicker",
"data": {
"key" : "AACq5fTfzRY2E_Rw_4kyaz"
}
}
]
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,12 @@
var CloudCommander, Util, DOM, CloudFunc, CodeMirror;
var CloudCommander, Util, DOM, CodeMirror;
/* object contains editors CodeMirror */
(function(CloudCmd, Util, DOM, CloudFunc){
(function(CloudCmd, Util, DOM){
'use strict';
var KeyBinding = CloudCommander.KeyBinding,
CodeMirrorEditor = {},
CodeMirrorEditor = {},
FM,
CodeMirrorElement,
CodeMirrorElement,
CodeMirrorLoaded = false,
/* indicator says CodeMirror still loads */
Loading = false,
@ -52,9 +52,11 @@ var CloudCommander, Util, DOM, CloudFunc, CodeMirror;
*/
function initCodeMirror(pParams){
if(!FM)
FM = DOM.getFM();
FM = DOM.getFM();
var lCSS = setCSS();
var lCSS = setCSS(),
lCurrent = DOM.getCurrentFile(),
lPath = DOM.getCurrentPath( lCurrent );
CodeMirrorElement = DOM.anyload({
name : 'div',
@ -72,13 +74,18 @@ var CloudCommander, Util, DOM, CloudFunc, CodeMirror;
lineWrapping: false,
autofocus : true,
extraKeys: {
//Сохранение
/* Exit */
'Esc': function(){
Util.exec(pParams);
DOM.remove(lCSS, document.head);
},
'Ctrl-S': function(){
DOM.RESTfull.save(DOM.getCurrentPath(), lEditor.getValue());
/* Save */
'Ctrl-S': function(){
var lValue = lEditor.getValue();
DOM.setCurrentSize( lValue.length, lCurrent );
DOM.RESTfull.save( lPath, lValue );
}
},
readOnly : ReadOnly
@ -89,7 +96,7 @@ var CloudCommander, Util, DOM, CloudFunc, CodeMirror;
* function loads CodeMirror js and css files
*/
function load(pCallBack){
console.time('codemirror load');
Util.time('codemirror load');
var lDir = CloudCmd.LIBDIRCLIENT + 'editor/codemirror/',
lFiles =
[
@ -103,7 +110,7 @@ var CloudCommander, Util, DOM, CloudFunc, CodeMirror;
];
DOM.anyLoadOnLoad(lFiles, function(){
console.timeEnd('codemirror load');
Util.timeEnd('codemirror load');
CodeMirrorLoaded = true;
Util.exec(pCallBack);
});
@ -118,33 +125,30 @@ var CloudCommander, Util, DOM, CloudFunc, CodeMirror;
* called do not call it again
* if f4 key pressed couple times
*/
if(Loading)
return;
/* checking is this link is to directory
* when folder view is no need to edit data
*/
if ( DOM.getCurrentSize() === '<dir>' )
ReadOnly = true;
Loading = true;
var lFalseLoading = function(){ Loading = false; };
setTimeout(lFalseLoading, 400);
/* reading data from current file */
DOM.getCurrentData({
error : lFalseLoading,
success : function(data){
if( DOM.hidePanel() ){
Util.exec(pCallBack, data);
KeyBinding.unSet();
if(!Loading){
/* checking is this link is to directory
* when folder view is no need to edit data */
ReadOnly = DOM.isCurrentIsDir();
Loading = true;
var lFalseLoading = function(){ Loading = false; };
setTimeout(lFalseLoading, 400);
DOM.getCurrentData({
error : lFalseLoading,
success : function(data){
if( DOM.hidePanel() ){
Util.exec(pCallBack, data);
KeyBinding.unSet();
}
DOM.Images.hideLoad();
lFalseLoading();
}
DOM.Images.hideLoad();
lFalseLoading();
}
});
});
}
}
/**
@ -205,10 +209,10 @@ var CloudCommander, Util, DOM, CloudFunc, CodeMirror;
};
/* добавляем обработчик клавишь */
DOM.addKeyListener( lKeyListener );
DOM.setButtonKey('f4', CodeMirrorEditor.show);
DOM .addKeyListener( lKeyListener )
.setButtonKey('f4', CodeMirrorEditor.show);
};
CloudCmd.Editor.CodeMirror = CodeMirrorEditor;
})(CloudCommander, Util, DOM, CloudFunc);
})(CloudCommander, Util, DOM);

View file

@ -1,5 +1,5 @@
/* script, fixes ie */
//var Util, DOM, jQuery;
var Util, DOM, jQuery;
(function(Util, DOM, $){
'use strict';
@ -126,7 +126,7 @@
(parent.scrollLeft + parent.clientWidth),
alignWithTop = overTop && !overBottom;
if ((overTop || overBottom) && centerIfNeeded)
parent.scrollTop =
pElement.offsetTop -
@ -134,7 +134,7 @@
parent.clientHeight / 2 -
parentBorderTopWidth +
pElement.clientHeight / 2;
if ((overLeft || overRight) && centerIfNeeded)
parent.scrollLeft =
pElement.offsetLeft -
@ -142,24 +142,34 @@
parent.clientWidth / 2 -
parentBorderLeftWidth +
pElement.clientWidth / 2;
if ( (overTop || overBottom || overLeft || overRight) &&
!centerIfNeeded)
pElement.scrollIntoView(alignWithTop);
};
if(!document.body.classList){
DOM.isContainClass = function(pElement, pClass){
var lRet,
lClassName = pElement && pElement.className;
if(lClassName)
lRet = lClassName.indexOf(pClass) > 0;
return lRet;
};
DOM.addClass = function(pElement, pClass){
var lSpaceChar = '',
lClassName = pElement.className,
lRet_b = true;
var lRet,
lClassName = pElement && pElement.className,
lSpaceChar = lClassName ? ' ' : '';
if(lClassName) lSpaceChar = ' ';
if( lClassName.indexOf(pClass) < 0 )
lRet = !DOM.isContainClass(pElement, pClass);
if( lRet )
pElement.className += lSpaceChar + pClass;
else
lRet_b = false;
return lRet;
};
DOM.removeClass = function(pElement, pClass){
@ -215,12 +225,9 @@
return CacheAllowed;
};
this.setAllowed = function(){
CacheAllowed = true;
};
this.UnSetAllowed = function(){
CacheAllowed = false;
this.setAllowed = function(pAllowed){
CacheAllowed = pAllowed;
return pAllowed;
};
/** remove element */
@ -255,6 +262,16 @@
return lRet;
},
/* get all cache from local storage */
this.getAll = function(){
var lRet = null;
if(CacheAllowed)
lRet = Data;
return lRet;
};
/** функция чистит весь кэш для всех каталогов*/
this.clear = function(){
var lRet = this;
@ -269,5 +286,4 @@
DOM.Cache = new Cache();
}
})(Util, DOM, jQuery);

File diff suppressed because it is too large Load diff

10949
lib/client/jquery.js vendored

File diff suppressed because it is too large Load diff

View file

@ -11,6 +11,7 @@ var CloudCommander, Util, DOM;
ENTER : 13,
ESC : 27,
SPACE : 32,
PAGE_UP : 33,
PAGE_DOWN : 34,
END : 35,
@ -18,7 +19,10 @@ var CloudCommander, Util, DOM;
UP : 38,
DOWN : 40,
Delete : 46,
INSERT : 45,
DELETE : 46,
A : 65,
D : 68,
@ -34,6 +38,10 @@ var CloudCommander, Util, DOM;
F2 : 113,
F3 : 114,
F4 : 115,
F5 : 116,
F6 : 117,
F7 : 118,
F8 : 119,
F10 : 121,
TRA : 192 /* Typewritten Reverse Apostrophe (`) */
@ -61,43 +69,20 @@ var CloudCommander, Util, DOM;
var key_event = function(pEvent){
/* получаем выдленный файл*/
var lCurrentFile = DOM.getCurrentFile(), i;
var i, n, lCurrent = DOM.getCurrentFile(),
lKeyCode = pEvent.keyCode,
lShift = pEvent.shiftKey,
lAlt = pEvent.altKey,
lCtrl = pEvent.ctrlKey;
/* если клавиши можно обрабатывать*/
if(keyBinded && pEvent){
var lKeyCode = pEvent.keyCode;
/* open configuration window */
if(lKeyCode === KEY.O && pEvent.altKey){
console.log('openning config window...');
DOM.Images.showLoad({top: true});
Util.exec(CloudCmd.Config);
}
else if(lKeyCode === KEY.G && pEvent.altKey)
Util.exec(CloudCmd.GitHub);
else if(lKeyCode === KEY.D && pEvent.altKey){
Util.exec(CloudCmd.DropBox);
DOM.preventDefault(pEvent);
}
/* если нажали таб:
* переносим курсор на
* правую панель, если
* мы были на левой и
* наоборот
*/
else if(lKeyCode === KEY.TAB){
console.log('Tab pressed');
Util.tryCatchLog(function(){
if(keyBinded){
switch(lKeyCode){
case KEY.TAB:
/* changing parent panel of curent-file */
var lPanel = DOM.getPanel(),
lId = lPanel.id;
lTabPanel[lId] = lCurrentFile;
lTabPanel[lId] = lCurrent;
lPanel = DOM.getPanel({active:false});
lId = lPanel.id;
@ -109,194 +94,253 @@ var CloudCommander, Util, DOM;
DOM.setCurrentFile(lFirstFileOnList);
}
});
DOM.preventDefault(pEvent);//запрет на дальнейшее действие
break;
DOM.preventDefault(pEvent);//запрет на дальнейшее действие
}
else if(lKeyCode === KEY.Delete){
if(pEvent.shiftKey)
DOM.deleteCurrent(lCurrentFile);
else
DOM.promptDeleteCurrent(lCurrentFile);
}
else if(lKeyCode === KEY.F1){
DOM.preventDefault(pEvent);
}
else if(lKeyCode === KEY.F2)
DOM.renameCurrentFile(lCurrentFile);
/* if f3 or shift+f3 or alt+f3 pressed */
else if(lKeyCode === KEY.F3){
var lEditor = CloudCmd[pEvent.shiftKey ?
'Viewer' : 'Editor'];
case KEY.INSERT:
DOM.setSelectedFile( lCurrent );
DOM.setCurrentFile( lCurrent.nextSibling );
break;
case KEY.DELETE:
if(lShift){
var lUrl = DOM.getCurrentPath(lCurrent);
if( DOM.isCurrentIsDir(lCurrent) )
lUrl += '?dir';
DOM.RESTfull.delete(lUrl, function(){
DOM.deleteCurrent(lCurrent);
});
}
else
DOM.promptDeleteSelected(lCurrent);
break;
case KEY.F1:
DOM.preventDefault(pEvent);
break;
case KEY.F2:
DOM.renameCurrent(lCurrent);
break;
case KEY.F3:
var lEditor = CloudCmd[lShift ? 'Viewer' : 'Editor'];
Util.exec(lEditor, true);
DOM.preventDefault(pEvent);
break;
case KEY.F4:
DOM.Images.showLoad();
Util.exec(CloudCmd.Editor);
DOM.preventDefault(pEvent);
break;
case KEY.F5:
DOM.copyCurrent(lCurrent);
DOM.preventDefault(pEvent);
break;
case KEY.F6:
DOM.moveCurrent(lCurrent);
DOM.preventDefault(pEvent);
break;
case KEY.F7:
DOM.promptNewDir();
break;
case KEY.F8:
DOM.promptDeleteSelected(lCurrent);
break;
case KEY.F:
DOM.promptDeleteCurrent(lCurrent);
break;
case KEY.F10:
if(lShift){
Util.exec(CloudCmd.Menu);
DOM.preventDefault(pEvent);
}
break;
case KEY.TRA:
DOM.Images.showLoad({top: true});
Util.exec(CloudCmd.Terminal);
break;
DOM.preventDefault(pEvent);
}
/* if f4 pressed */
else if(lKeyCode === KEY.F4) {
DOM.Images.showLoad();
Util.exec(CloudCmd.Editor);
case KEY.SPACE:
var lSelected = DOM.isSelected(lCurrent),
lDir = DOM.isCurrentIsDir(lCurrent),
lName = DOM.getCurrentName(lCurrent);
DOM.preventDefault(pEvent);
}
else if(lKeyCode === KEY.F10 && pEvent.shiftKey){
Util.exec(CloudCmd.Menu);
DOM.preventDefault(pEvent);
}
else if (lKeyCode === KEY.TRA){
DOM.Images.showLoad({top: true});
Util.exec(CloudCmd.Terminal);
}
/* навигация по таблице файлов *
* если нажали клавишу вверх *
* выделяем предыдущую строку */
else if(lKeyCode === KEY.UP){
DOM.setCurrentFile( lCurrentFile.previousSibling );
DOM.preventDefault( pEvent );
}
/* если нажали клавишу в низ *
* выделяем следующую строку */
else if(lKeyCode === KEY.DOWN){
DOM.setCurrentFile( lCurrentFile.nextSibling );
DOM.preventDefault( pEvent );
}
/* если нажали клавишу Home *
* переходим к самому верхнему *
* элементу */
else if(lKeyCode === KEY.HOME){
DOM.setCurrentFile( lCurrentFile.parentElement.firstChild );
DOM.preventDefault(pEvent);
}
/* если нажали клавишу End
* выделяем последний элемент
*/
else if(lKeyCode === KEY.END){
DOM.setCurrentFile( lCurrentFile.parentElement.lastElementChild );
DOM.preventDefault( pEvent );
}
/* если нажали клавишу page down
* проматываем экран
*/
else if(lKeyCode === KEY.PAGE_DOWN){
DOM.scrollByPages( DOM.getPanel(), 1 );
for(i=0; i<30; i++){
if(!lCurrentFile.nextSibling) break;
lCurrentFile = lCurrentFile.nextSibling;
}
DOM.setCurrentFile(lCurrentFile);
DOM.preventDefault(pEvent);
}
/* если нажали клавишу page up
* проматываем экран
*/
else if(lKeyCode === KEY.PAGE_UP){
DOM.scrollByPages( DOM.getPanel(), -1 );
var lC = lCurrentFile,
tryCatch = function(pCurrentFile){
Util.tryCatch(function(){
return pCurrentFile
.previousSibling
.previousSibling
.previousSibling
.previousSibling;
});
};
for(i = 0; i < 30; i++){
if(!lC.previousSibling || tryCatch(lC) ) break;
lC = lC.previousSibling;
}
DOM.setCurrentFile(lC);
DOM.preventDefault(pEvent);
}
/* если нажали Enter - открываем папку*/
else if(lKeyCode === KEY.ENTER && DOM.isCurrentIsDir())
Util.exec(CloudCmd._loadDir());
/* если нажали <ctr>+r
* обновляем страницу,
* загружаем содержимое каталога
* при этом данные берём всегда с
* сервера, а не из кэша
* (обновляем кэш)
*/
else if(lKeyCode === KEY.R && pEvent.ctrlKey){
console.log('<ctrl>+r pressed\n' +
'reloading page...\n' +
'press <alt>+q to remove all key-handlers');
/* Программно нажимаем на кнопку перезагрузки
* содержимого каталога
*/
var lRefreshIcon = DOM.getRefreshButton();
if(lRefreshIcon){
/* если нашли элемент нажимаем него
* а если не можем - нажимаем на
* ссылку, на которую повешен eventHandler
* onclick
*/
Util.exec( DOM.getListener(lRefreshIcon) );
if(!lDir || lName === '..')
lSelected = true;
Util.ifExec(lSelected, function(){
DOM.setSelectedFile(lCurrent);
}, function(pCallBack){
DOM.loadCurrentSize(pCallBack, lCurrent);
});
DOM.preventDefault(pEvent);
}
}
/* если нажали <ctrl>+d чистим кэш */
else if(lKeyCode === KEY.D && pEvent.ctrlKey){
Util.log('<ctrl>+d pressed\n' +
'clearing cache...\n' +
'press <alt>+q to remove all key-handlers');
break;
/* навигация по таблице файлов *
* если нажали клавишу вверх *
* выделяем предыдущую строку */
case KEY.UP:
if(lShift)
DOM.setSelectedFile(lCurrent);
DOM.Cache.clear();
DOM.preventDefault();
}
DOM.setCurrentFile( lCurrent.previousSibling );
DOM.preventDefault( pEvent );
break;
/* если нажали клавишу в низ *
* выделяем следующую строку */
case KEY.DOWN:
if(lShift)
DOM.setSelectedFile(lCurrent);
DOM.setCurrentFile( lCurrent.nextSibling );
DOM.preventDefault( pEvent );
break;
/* если нажали клавишу Home *
* переходим к самому верхнему *
* элементу */
case KEY.HOME:
DOM.setCurrentFile( lCurrent.parentElement.firstChild );
DOM.preventDefault(pEvent);
break;
/* если нажали клавишу End
* выделяем последний элемент */
case KEY.END:
DOM.setCurrentFile( lCurrent.parentElement.lastElementChild );
DOM.preventDefault( pEvent );
break;
/* если нажали клавишу page down
* проматываем экран */
case KEY.PAGE_DOWN:
DOM.scrollByPages( DOM.getPanel(), 1 );
for(i=0; i<30; i++){
if(!lCurrent.nextSibling) break;
lCurrent = lCurrent.nextSibling;
}
DOM.setCurrentFile(lCurrent);
DOM.preventDefault(pEvent);
break;
/* если нажали клавишу page up проматываем экран */
case KEY.PAGE_UP:
DOM.scrollByPages( DOM.getPanel(), -1 );
var lC = lCurrent,
tryCatch = function(pCurrentFile){
Util.tryCatch(function(){
return pCurrentFile
.previousSibling
.previousSibling
.previousSibling
.previousSibling;
});
};
for(i = 0; i < 30; i++){
if(!lC.previousSibling || tryCatch(lC) ) break;
lC = lC.previousSibling;
}
DOM.setCurrentFile(lC);
DOM.preventDefault(pEvent);
break;
/* открываем папку*/
case KEY.ENTER:
if( DOM.isCurrentIsDir() )
Util.exec( CloudCmd.loadDir() );
break;
case KEY.A:
if(pEvent.ctrlKey){
var lParent = lCurrent.parentElement,
lNodes = lParent.childNodes;
/* not path and fm_header */
for(i = 2, n = lNodes.length; i < n; i++)
DOM.setSelectedFile( lNodes[i] );
DOM.preventDefault(pEvent);
}
break;
/* обновляем страницу,
* загружаем содержимое каталога
* при этом данные берём всегда с
* сервера, а не из кэша
* (обновляем кэш)*/
case KEY.R:
if(lCtrl){
Util.log('<ctrl>+r pressed\n' +
'reloading page...\n' +
'press <alt>+q to remove all key-handlers');
CloudCmd.refresh();
DOM.preventDefault(pEvent);
}
break;
/* чистим кэш */
case KEY.D:
if(lCtrl){
Util.log('<ctrl>+d pressed\n' +
'clearing cache...\n' +
'press <alt>+q to remove all key-handlers');
DOM.Cache.clear();
DOM.preventDefault();
}
break;
/* если нажали <alt>+q
* убираем все обработчики
* нажатий клавиш
*/
else if(lKeyCode === KEY.Q && pEvent.altKey){
console.log('<alt>+q pressed\n' +
'<ctrl>+r reload key-handerl - removed' +
'<ctrl>+s clear cache key-handler - removed'+
'press <alt>+s to to set them');
/* обработчик нажатий клавиш снят*/
keyBinded = false;
pEvent.preventDefault();//запрет на дальнейшее действие
}
/* убираем все обработчики
* нажатий клавиш */
case KEY.Q:
if(lAlt){
Util.log('<alt>+q pressed\n' +
'<ctrl>+r reload key-handerl - removed' +
'<ctrl>+s clear cache key-handler - removed'+
'press <alt>+s to to set them');
/* обработчик нажатий клавиш снят*/
keyBinded = false;
DOM.preventDefault(pEvent);
}
break;
}
}
/* если нажали <alt>+s
* устанавливаем все обработчики
/* устанавливаем все обработчики
* нажатий клавиш
*/
else if(pEvent.keyCode === KEY.S && pEvent.altKey){
else if(lKeyCode === KEY.S && lAlt){
/* обрабатываем нажатия на клавиши*/
keyBinded = true;
console.log('<alt>+s pressed\n' +
Util.log('<alt>+s pressed\n' +
'<ctrl>+r reload key-handerl - set\n' +
'<ctrl>+s clear cache key-handler - set\n' +
'press <alt>+q to remove them');
pEvent.preventDefault();//запрет на дальнейшее действие
DOM.preventDefault(pEvent);
}
};

View file

@ -46,6 +46,7 @@ var CloudCommander, Util, DOM, CloudFunc, $;
});
}
/**
* function get menu item object for Upload To
*/
@ -60,20 +61,9 @@ var CloudCommander, Util, DOM, CloudFunc, $;
}
}
else if( Util.isString(pObjectName) ){
lObj.name = pObjectName;
lObj.callback = function(key, opt){
lObj = function(key, opt){
DOM.getCurrentData(function(pParams){
var lObject = CloudCmd[pObjectName];
Util.ifExec('init' in lObject,
function(){
CloudCmd[pObjectName].uploadFile(pParams);
},
function(pCallBack){
Util.exec(lObject, pCallBack);
});
CloudCmd.execFromModule(pObjectName, 'uploadFile', pParams);
});
Util.log('Uploading to ' + pObjectName+ '...');
@ -91,11 +81,15 @@ var CloudCommander, Util, DOM, CloudFunc, $;
name : pName
};
if(Util.isFunction(pCallBack) )
lRet.callback = pCallBack;
if( Util.isFunction(pCallBack) )
lRet.callback = pCallBack;
else if (Util.isObject(pCallBack))
lRet.items = pCallBack;
else if ( Util.isObject(pCallBack) ){
if(pCallBack.name)
lRet.items = pCallBack;
else
lRet.items = getAllItems(pCallBack);
}
return lRet;
}
@ -162,9 +156,9 @@ var CloudCommander, Util, DOM, CloudFunc, $;
'View' : Util.retExec(showEditor, true),
'Edit' : Util.retExec(showEditor, false),
'Rename' : function(){
setTimeout( Util.retExec(DOM.renameCurrentFile), 100);
setTimeout( Util.retExec(DOM.renameCurrent), 100);
},
'Delete' : Util.retExec(DOM.promptDeleteCurrent),
'Delete' : Util.retExec(DOM.promptDeleteCurrent)
};
if(UploadToItemNames.length)
@ -172,6 +166,19 @@ var CloudCommander, Util, DOM, CloudFunc, $;
lMenuItems.Download = Util.retExec(downloadFromMenu);
lMenuItems.New = {
'File' : DOM.promptNewFile,
'Dir' : DOM.promptNewDir,
'From cloud...' : function(){
CloudCmd.execFromModule('FilePicker', 'saveFile', function(pName, pData){
var lPath = DOM.getCurrentDirPath() + pName;
DOM.RESTfull.save(lPath, pData, CloudCmd.refresh);
});
}
};
lRet = {
// define which elements trigger this menu
selector: 'li',

View file

@ -1,49 +0,0 @@
{
"name": "jQuery contextMenu",
"version": "git-master",
"title": "jQuery.contextMenu()",
"author": {
"name": "Rodney Rehm",
"url": "http://rodneyrehm.de"
},
"licenses": [
{
"type": "MIT",
"url": "http://www.opensource.org/licenses/mit-license"
},
{
"type": "GPL",
"url": "http://opensource.org/licenses/GPL-3.0"
}
],
"dependencies": {
"jquery": ">=1.7.0"
},
"description": "Management facility for context menus. Developed for a large number of triggering objects. HTML5 Polyfill",
"keywords": [
"contextmenu",
"context menu",
"context-menu",
"menu",
"html5 menu",
"html5 contextmenu",
"html5 context menu",
"html5 context-menu"
],
"homepage": "http://medialize.github.com/jQuery-contextMenu/",
"contributors": [
{
"name": "Addy Osmani",
"url": "https://github.com/addyosmani"
},
{
"name": "Christiaan Baartse",
"url": "https://github.com/christiaan"
}
],
"files": [
"src/jquery.contextMenu.js",
"src/jquery.contextMenu.css",
"src/jquery.ui.position.js"
]
}

View file

@ -1,67 +1,67 @@
/* module make possible connectoin thrue socket.io on a client */
var CloudCommander, DOM, Util, io;
(function(CloudCmd, DOM, Util){
var CloudCommander, Util, DOM, io;
(function(CloudCmd, Util, DOM){
'use strict';
var Messages = [],
socket,
JqueryTerminal;
Terminal,
function getJqueryTerminal(){
ERROR_MSG = 'could not connect to socket.io\n'+
'npm i socket.io';
function getTerminal(){
return CloudCmd.Terminal.JqueryTerminal;
}
DOM.jsload('/socket.io/lib/socket.io.js', {
onload : function(){
socket = io.connect(document.location.hostname);
onerror : Util.retExec(Util.log, ERROR_MSG),
onload : function(){
socket = io.connect(CloudCmd.HOST);
CloudCmd.Socket = socket;
socket.on('connect', function () {
JqueryTerminal = getJqueryTerminal();
Terminal = getTerminal();
if(JqueryTerminal){
if(Terminal){
outToTerminal({stdout: 'socket connected'});
JqueryTerminal.Term.resume();
Terminal.Term.resume();
}
});
socket.on('message', function (msg) {
var lMsg = JSON.parse(msg);
var lMsg = Util.parseJSON(msg);
outToTerminal(lMsg);
});
socket.on('disconnect', function () {
JqueryTerminal = getJqueryTerminal();
Terminal = getTerminal();
if(JqueryTerminal){
if(Terminal){
outToTerminal({stderr: 'socket disconected'});
JqueryTerminal.Term.pause();
Terminal.Term.pause();
}
});
},
onerror : function(){
console.log('could not connect to socket.io\n'+
'npm i socket.io');
}
});
function outToTerminal(pMsg){
var lTerm,
lResult = true;
var lResult, lTerm;
JqueryTerminal = getJqueryTerminal();
if(JqueryTerminal)
lTerm = JqueryTerminal.Term;
Terminal = getTerminal();
if(Terminal)
lTerm = Terminal.Term;
if(lTerm){
var lStdout,
lStderr;
if(Messages.length){
/* show oll msg from buffer */
for(var i = 0, n = Messages.length; i < n; i++){
@ -74,7 +74,7 @@ var CloudCommander, DOM, Util, io;
if(lStderr){
/* if it's object - convert is to string' */
if( Util.isObject(lStderr) )
lStderr = JSON.Stringify(lStderr);
lStderr = Util.stringifyJSON(lStderr);
lTerm.error(lStderr);
}
@ -91,16 +91,13 @@ var CloudCommander, DOM, Util, io;
if(lStderr && lStderr.code !== 1)
lResult = lTerm.error(lStderr.toString());
}
else{
/* if term not accesable
* save msg to buffer
*/
else
/* if term not accesable save msg to buffer */
Messages.push(pMsg);
lResult = false;
}
console.log(pMsg);
Util.log(pMsg);
return lResult;
}
})(CloudCommander, DOM, Util);
})(CloudCommander, Util, DOM);

View file

@ -1,111 +1,111 @@
var CloudCommander, Util, DOM, Dropbox, cb, Client;
/* module for work with github */
(function(CloudCmd, Util, DOM){
'use strict';
var DropBoxStore = {};
/* temporary callback function for work with github */
cb = function (err, data){ Util.log(err || data);};
/* PRIVATE FUNCTIONS */
/**
* function loads dropbox.js
*/
function load(pCallBack){
console.time('dropbox load');
var lSrc = 'http://cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.8.1/dropbox.min.js',
lLocal = CloudCmd.LIBDIRCLIENT + 'storage/dropbox/lib/dropbox.min.js',
lOnload = function(){
console.timeEnd('dropbox load');
DOM.Images.hideLoad();
Util.exec(pCallBack);
};
DOM.jsload(lSrc, {
onload : lOnload,
error : DOM.retJSLoad(lLocal, lOnload)
});
}
function getUserData(pCallBack){
Client.getUserInfo(function(pError, pData){
var lHello = 'Hello ' + pData.name + ' :)!',
lMsg = pError ? pError : lHello;
Util.log(lMsg);
});
Util.exec(pCallBack);
}
/**
* function logins on dropbox
*
* @param pData = {key, secret}
*/
DropBoxStore.login = function(pCallBack){
CloudCmd.getModules(function(pModules){
var lStorage = Util.findObjByNameInArr(pModules, 'storage'),
lDropBox = Util.findObjByNameInArr(lStorage, 'DropBox'),
lDropBoxKey = lDropBox && lDropBox.encodedKey;
Client = new Dropbox.Client({
key: lDropBoxKey
});
//Client.authDriver(new Dropbox.Drivers.Redirect({rememberUser: true}));
var lURL = CloudCmd.HOST + '/html/auth/dropbox.html';
Client.authDriver(new Dropbox.Drivers.Popup({
receiverUrl: lURL, noFragment: true
}));
Client.authenticate(function(pError, pClient) {
Util.log(pError);
Client = pClient;
Util.exec(pCallBack);
});
});
};
/**
* upload file to DropBox
*/
DropBoxStore.uploadFile = function(pParams, pCallBack){
var lContent = pParams.data,
lName = pParams.name;
if(lContent){
DOM.Images.showLoad();
if(!lName)
lName = Util.getDate();
Client.writeFile(lName, lContent, function(pError, pData){
DOM.Images.hideLoad();
Util.log(pError || pData);
Util.exec(pCallBack);
});
}
return lContent;
};
DropBoxStore.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
getUserData,
DropBoxStore.login,
load
]);
CloudCmd.DropBox.init = null;
};
CloudCmd.DropBox = DropBoxStore;
})(CloudCommander, Util, DOM);
var CloudCommander, Util, DOM, Dropbox, cb, Client;
/* module for work with github */
(function(CloudCmd, Util, DOM){
'use strict';
var DropBoxStore = {};
/* temporary callback function for work with github */
cb = function (err, data){ Util.log(err || data);};
/* PRIVATE FUNCTIONS */
/**
* function loads dropbox.js
*/
function load(pCallBack){
console.time('dropbox load');
var lSrc = '//cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.9.2/dropbox.min.js',
lLocal = '/node_modules/dropbox/lib/dropbox.js',
lOnload = function(){
console.timeEnd('dropbox load');
DOM.Images.hideLoad();
Util.exec(pCallBack);
};
DOM.jsload(lSrc, {
onload : lOnload,
error : DOM.retJSLoad(lLocal, lOnload)
});
}
function getUserData(pCallBack){
Client.getUserInfo(function(pError, pData){
var lHello = 'Hello ' + pData.name + ' :)!',
lMsg = pError ? pError : lHello;
Util.log(lMsg);
});
Util.exec(pCallBack);
}
/**
* function logins on dropbox
*
* @param pData = {key, secret}
*/
DropBoxStore.login = function(pCallBack){
CloudCmd.getModules(function(pModules){
var lStorage = Util.findObjByNameInArr(pModules, 'storage'),
lDropBox = Util.findObjByNameInArr(lStorage, 'DropBox'),
lDropBoxKey = lDropBox && lDropBox.encodedKey;
Client = new Dropbox.Client({
key: lDropBoxKey
});
//Client.authDriver(new Dropbox.Drivers.Redirect({rememberUser: true}));
var lURL = CloudCmd.HOST + '/html/auth/dropbox.html';
Client.authDriver(new Dropbox.Drivers.Popup({
receiverUrl: lURL, noFragment: true
}));
Client.authenticate(function(pError, pClient) {
Util.log(pError);
Client = pClient;
Util.exec(pCallBack);
});
});
};
/**
* upload file to DropBox
*/
DropBoxStore.uploadFile = function(pParams, pCallBack){
var lContent = pParams.data,
lName = pParams.name;
if(lContent){
DOM.Images.showLoad();
if(!lName)
lName = Util.getDate();
Client.writeFile(lName, lContent, function(pError, pData){
DOM.Images.hideLoad();
Util.log(pError || pData);
Util.exec(pCallBack);
});
}
return lContent;
};
DropBoxStore.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
getUserData,
DropBoxStore.login,
load
]);
CloudCmd.DropBox.init = null;
};
CloudCmd.DropBox = DropBoxStore;
})(CloudCommander, Util, DOM);

View file

@ -0,0 +1,58 @@
var CloudCommander, Util, DOM, $, filepicker;
/* module for work with filepicker */
(function(CloudCmd, Util, DOM){
'use strict';
var FilePicker = function(){
this.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
load
]);
};
this.uploadFile = function(pParams, pCallBack){
var lContent = pParams.data,
lName = pParams.name;
filepicker.store(lContent, {filename: lName}, function(new_fpfile){
console.log(new_fpfile);
});
filepicker.pick(function(FPFile){
console.log(FPFile.url);
Util.exec(pCallBack);
});
};
this.saveFile = function(pCallBack){
filepicker.pick(function(FPFile){
console.log(FPFile);
DOM.ajax({
url : FPFile.url,
success : function(pData){
Util.exec(pCallBack, FPFile.filename, pData);
}
});
});
};
function load(pCallBack){
console.time('filepicker load');
var lHTTP = "https:"===document.location.protocol? "https:" : "http:";
DOM.jsload(lHTTP + '//api.filepicker.io/v1/filepicker.js', function(){
filepicker.setKey('AACq5fTfzRY2E_Rw_4kyaz');
DOM.Images.hideLoad();
console.timeEnd('filepicker loaded');
Util.exec(pCallBack);
});
}
};
CloudCmd.FilePicker = new FilePicker();
})(CloudCommander, Util, DOM);

View file

@ -1,159 +1,159 @@
var CloudCommander, Util, DOM, $, Github, cb;
/* module for work with github */
(function(CloudCmd, Util, DOM){
"use strict";
var Cache = DOM.Cache,
GithubLocal,
User,
GitHubStore = {};
/* temporary callback function for work with github */
cb = function (err, data){ Util.log(err || data);};
/* PRIVATE FUNCTIONS */
/**
* function loads github.js
*/
function load(pCallBack){
console.time('github load');
var lDir = '/lib/client/storage/github/',
lFiles = [
lDir + 'github.js',
lDir + 'lib/base64.js',
lDir + 'lib/underscore.js'
];
DOM.anyLoadInParallel(lFiles, function(){
console.timeEnd('github load');
DOM.Images.hideLoad();
Util.exec(pCallBack);
});
}
GitHubStore.autorize = function(pCallBack, pCode){
var lToken = Cache.get('token');
if(lToken){
GitHubStore.Login(lToken);
Util.exec(pCallBack);
}
else{
var lCode = pCode || window.location.search;
if (lCode || Util.isContainStr(lCode, '?code=') )
CloudCmd.getConfig(function(pConfig){
DOM.ajax({
type : 'put',
url : pConfig && pConfig.api_url + '/auth',
data : Util.removeStr(lCode, '?code='),
success : function(pData){
if(pData && pData.token){
lToken = pData.token;
GitHubStore.Login(lToken);
Cache.set('token', lToken);
Util.exec(pCallBack);
}
else
Util.log('Worning: token not getted...');
}
});
});
else{
var lUrl = '//' + window.location.host + '/auth/github';
DOM.openWindow(lUrl);
}
}
};
GitHubStore.getUserData = function(pCallBack){
User.show(null, function(pError, pData){
if(!pError){
var lName = pData.name;
Util.log('Hello ' + lName + ' :)!');
}
else
DOM.Cache.remove('token');
});
Util.exec(pCallBack);
};
/* PUBLIC FUNCTIONS */
GitHubStore.basicLogin = function(pUser, pPasswd){
GithubLocal = new Github({
username: pUser,
password: pPasswd,
auth : 'basic'
});
};
GitHubStore.Login = function(pToken){
Github = GithubLocal = new Github({
token : pToken,
auth : 'oauth'
});
User = GithubLocal.getUser();
};
/**
* function creates gist
*/
GitHubStore.uploadFile = function(pParams, pCallBack){
var lContent = pParams.data,
lName = pParams.name;
if(lContent){
DOM.Images.showLoad();
if(!lName)
lName = Util.getDate();
var lGist = GithubLocal.getGist(),
lFiles = {},
lHost = CloudCommander.HOST,
lOptions = {
description: 'Uplouded by Cloud Commander from ' + lHost,
public: true
};
lFiles[lName] ={
content: lContent
};
lOptions.files = lFiles;
lGist.create(lOptions, function(pError, pData){
DOM.Images.hideLoad();
Util.log(pError || pData);
Util.log(pData && pData.html_url);
Util.exec(pCallBack);
});
}
return lContent;
};
GitHubStore.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
GitHubStore.getUserData,
GitHubStore.autorize,
load
]);
GitHubStore.callback = function(){
Util.loadOnLoad([
Util.retExec(pCallBack),
GitHubStore.getUserData,
]);
};
};
CloudCmd.GitHub = GitHubStore;
})(CloudCommander, Util, DOM);
var CloudCommander, Util, DOM, $, Github, cb;
/* module for work with github */
(function(CloudCmd, Util, DOM){
'use strict';
var Cache = DOM.Cache,
GithubLocal,
User,
GitHubStore = {};
/* temporary callback function for work with github */
cb = function (err, data){ Util.log(err || data);};
/* PRIVATE FUNCTIONS */
/**
* function loads github.js
*/
function load(pCallBack){
console.time('github load');
var lDir = '/lib/client/storage/github/',
lFiles = [
lDir + 'github.js',
lDir + 'lib/base64.js',
lDir + 'lib/underscore.js'
];
DOM.anyLoadInParallel(lFiles, function(){
console.timeEnd('github load');
DOM.Images.hideLoad();
Util.exec(pCallBack);
});
}
GitHubStore.autorize = function(pCallBack, pCode){
var lToken = Cache.get('token');
if(lToken){
GitHubStore.Login(lToken);
Util.exec(pCallBack);
}
else{
var lCode = pCode || window.location.search;
if (lCode || Util.isContainStr(lCode, '?code=') )
CloudCmd.getConfig(function(pConfig){
DOM.ajax({
type : 'put',
url : pConfig && pConfig.api_url + '/auth',
data : Util.removeStr(lCode, '?code='),
success : function(pData){
if(pData && pData.token){
lToken = pData.token;
GitHubStore.Login(lToken);
Cache.set('token', lToken);
Util.exec(pCallBack);
}
else
Util.log('Worning: token not getted...');
}
});
});
else{
var lUrl = '//' + window.location.host + '/auth/github';
DOM.openWindow(lUrl);
}
}
};
GitHubStore.getUserData = function(pCallBack){
User.show(null, function(pError, pData){
if(!pError){
var lName = pData.name;
Util.log('Hello ' + lName + ' :)!');
}
else
DOM.Cache.remove('token');
});
Util.exec(pCallBack);
};
/* PUBLIC FUNCTIONS */
GitHubStore.basicLogin = function(pUser, pPasswd){
GithubLocal = new Github({
username: pUser,
password: pPasswd,
auth : 'basic'
});
};
GitHubStore.Login = function(pToken){
Github = GithubLocal = new Github({
token : pToken,
auth : 'oauth'
});
User = GithubLocal.getUser();
};
/**
* function creates gist
*/
GitHubStore.uploadFile = function(pParams, pCallBack){
var lContent = pParams.data,
lName = pParams.name;
if(lContent){
DOM.Images.showLoad();
if(!lName)
lName = Util.getDate();
var lGist = GithubLocal.getGist(),
lFiles = {},
lHost = CloudCommander.HOST,
lOptions = {
description: 'Uplouded by Cloud Commander from ' + lHost,
public: true
};
lFiles[lName] ={
content: lContent
};
lOptions.files = lFiles;
lGist.create(lOptions, function(pError, pData){
DOM.Images.hideLoad();
Util.log(pError || pData);
Util.log(pData && pData.html_url);
Util.exec(pCallBack);
});
}
return lContent;
};
GitHubStore.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
GitHubStore.getUserData,
GitHubStore.autorize,
load
]);
GitHubStore.callback = function(){
Util.loadOnLoad([
Util.retExec(pCallBack),
GitHubStore.getUserData,
]);
};
};
CloudCmd.GitHub = GitHubStore;
})(CloudCommander, Util, DOM);

View file

@ -0,0 +1,78 @@
//http://isdk.dev.live.com/ISDK.aspx?category=scenarioGroup_skyDrive&index=0
var CloudCommander, Util, DOM, WL;
(function(CloudCmd, Util, DOM){
'use strict';
var SkyDrive = {};
/* PRIVATE FUNCTIONS */
/**
* load google api library
*/
function load(pCallBack){
console.time('SkyDrive');
var lUrl = '//js.live.net/v5.0/wl.js';
DOM.jsload(lUrl, function(){
console.timeEnd('SkyDrive load');
DOM.Images.hideLoad();
Util.exec(pCallBack);
});
}
function auth(pCallBack){
CloudCmd.getModules(function(pModules){
var lStorage = Util.findObjByNameInArr(pModules, 'storage'),
lSkyDrive = Util.findObjByNameInArr(lStorage, 'SkyDrive'),
lSkyDriveKey = lSkyDrive && lSkyDrive.id;
WL.init({
client_id: lSkyDriveKey,
redirect_uri: CloudCmd.HOST,
});
WL.login({
scope: ["wl.skydrive wl.signin"]
}).then(
function(response) {
Util.log(response);
},
function(response) {
Util.log("Failed to authenticate.");
});
WL.Event.subscribe("auth.login", onLogin);
});
}
function onLogin(session) {
var strGreeting = "";
WL.api({
path: "me",
method: "GET"
},
function (response) {
if (!response.error) {
strGreeting = "Hi, " + response.first_name + "!";
Util.log(strGreeting);
}
});
}
SkyDrive.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
auth,
load
]);
};
CloudCmd.SkyDrive = SkyDrive;
})(CloudCommander, Util, DOM);

View file

@ -1,37 +0,0 @@
# Vim.
*.swp
# OSX
.DS_Store
# Npm modules.
node_modules
# Vendored javascript modules.
test/vendor
# Build output.
lib/dropbox.js
lib/dropbox.min.js
test/chrome_app/lib
test/chrome_app/manifest.json
test/chrome_app/node_modules
test/chrome_app/test
test/chrome_extension/*.js
test/chrome_profile
test/js
tmp/*.js
# Documentation output.
doc/*.html
doc/assets
doc/classes
doc/files
# Node packaging output.
dropbox-*.tgz
# Potentially sensitive credentials and keys used during testing.
test/.token
test/ssl

View file

@ -1,45 +0,0 @@
# Vim.
*.swp
.vimrc
# OSX
.DS_Store
# Git.
.git
# Npm modules.
node_modules
# Vendored javascript modules.
test/vendor
# Minified library.
lib/dropbox.min.js
# Sample apps.
samples
# Test build output.
test/chrome_profile
test/js
tmp/*.js
# Test code that is not related to node.js.
test/chrome_app
# Documentation output.
doc/*.html
doc/assets
doc/classes
doc/files
# Test app logos.
test/app_icon
# Test automating Chrome extension.
test/chrome_extension
# Potentially sensitive credentials and keys used during testing.
test/.token
test/ssl

View file

@ -1,6 +0,0 @@
" Indentation settings for the project: 2-space indentation, no tabs.
set tabstop=2
set softtabstop=2
set shiftwidth=2
set expandtab

View file

@ -1,215 +0,0 @@
async = require 'async'
{spawn, exec} = require 'child_process'
fs = require 'fs'
glob = require 'glob'
log = console.log
path = require 'path'
remove = require 'remove'
# Node 0.6 compatibility hack.
unless fs.existsSync
fs.existsSync = (filePath) -> path.existsSync filePath
task 'build', ->
build()
task 'test', ->
vendor ->
build ->
ssl_cert ->
tokens ->
run 'node_modules/mocha/bin/mocha --colors --slow 200 ' +
'--timeout 10000 --require test/js/helper.js test/js/*test.js'
task 'webtest', ->
vendor ->
build ->
ssl_cert ->
tokens ->
webtest()
task 'cert', ->
remove.removeSync 'test/ssl', ignoreMissing: true
ssl_cert()
task 'vendor', ->
remove.removeSync './test/vendor', ignoreMissing: true
vendor()
task 'tokens', ->
remove.removeSync './test/.token', ignoreMissing: true
build ->
tokens ->
process.exit 0
task 'doc', ->
run 'node_modules/codo/bin/codo src'
task 'extension', ->
run 'node_modules/coffee-script/bin/coffee ' +
'--compile test/chrome_extension/*.coffee'
task 'chrome', ->
vendor ->
build ->
# The v2 Chrome App API isn't supported yet.
buildChromeApp 'app_v1'
task 'chrometest', ->
vendor ->
build ->
# The v2 Chrome App API isn't supported yet.
buildChromeApp 'app_v1', ->
testChromeApp()
build = (callback) ->
commands = []
# Ignoring ".coffee" when sorting.
# We want "driver.coffee" to sort before "driver-browser.coffee"
source_files = glob.sync 'src/*.coffee'
source_files.sort (a, b) ->
a.replace(/\.coffee$/, '').localeCompare b.replace(/\.coffee$/, '')
# Compile without --join for decent error messages.
commands.push 'node_modules/coffee-script/bin/coffee --output tmp ' +
"--compile #{source_files.join(' ')}"
commands.push 'node_modules/coffee-script/bin/coffee --output lib ' +
"--compile --join dropbox.js #{source_files.join(' ')}"
# Minify the javascript, for browser distribution.
commands.push 'node_modules/uglify-js/bin/uglifyjs --compress --mangle ' +
'--output lib/dropbox.min.js lib/dropbox.js'
commands.push 'node_modules/coffee-script/bin/coffee --output test/js ' +
'--compile test/src/*.coffee'
async.forEachSeries commands, run, ->
callback() if callback
webtest = (callback) ->
webFileServer = require './test/js/web_file_server.js'
if 'BROWSER' of process.env
if process.env['BROWSER'] is 'false'
url = webFileServer.testUrl()
console.log "Please open the URL below in your browser:\n #{url}"
else
webFileServer.openBrowser process.env['BROWSER']
else
webFileServer.openBrowser()
callback() if callback?
ssl_cert = (callback) ->
fs.mkdirSync 'test/ssl' unless fs.existsSync 'test/ssl'
if fs.existsSync 'test/ssl/cert.pem'
callback() if callback?
return
run 'openssl req -new -x509 -days 365 -nodes -batch ' +
'-out test/ssl/cert.pem -keyout test/ssl/cert.pem ' +
'-subj /O=dropbox.js/OU=Testing/CN=localhost ', callback
vendor = (callback) ->
# All the files will be dumped here.
fs.mkdirSync 'test/vendor' unless fs.existsSync 'test/vendor'
# Embed the binary test image into a 7-bit ASCII JavaScript.
bytes = fs.readFileSync 'test/binary/dropbox.png'
fragments = []
for i in [0...bytes.length]
fragment = bytes.readUInt8(i).toString 16
while fragment.length < 4
fragment = '0' + fragment
fragments.push "\\u#{fragment}"
js = "window.testImageBytes = \"#{fragments.join('')}\";"
fs.writeFileSync 'test/vendor/favicon.js', js
downloads = [
# chai.js ships different builds for browsers vs node.js
['http://chaijs.com/chai.js', 'test/vendor/chai.js'],
# sinon.js also ships special builds for browsers
['http://sinonjs.org/releases/sinon.js', 'test/vendor/sinon.js'],
# ... and sinon.js ships an IE-only module
['http://sinonjs.org/releases/sinon-ie.js', 'test/vendor/sinon-ie.js']
]
async.forEachSeries downloads, download, ->
callback() if callback
testChromeApp = (callback) ->
# Clean up the profile.
fs.mkdirSync 'test/chrome_profile' unless fs.existsSync 'test/chrome_profile'
command = "\"#{chromeCommand()}\" --load-extension=test/chrome_app " +
'--user-data-dir=test/chrome_profile --no-default-browser-check ' +
'--no-first-run --no-service-autorun --disable-default-apps ' +
'--homepage=about:blank --v=-1'
run command, ->
callback() if callback
buildChromeApp = (manifestFile, callback) ->
unless fs.existsSync 'test/chrome_app/test'
fs.mkdirSync 'test/chrome_app/test'
unless fs.existsSync 'test/chrome_app/node_modules'
fs.mkdirSync 'test/chrome_app/node_modules'
links = [
['lib', 'test/chrome_app/lib'],
['node_modules/mocha', 'test/chrome_app/node_modules/mocha'],
['node_modules/sinon-chai', 'test/chrome_app/node_modules/sinon-chai'],
['test/.token', 'test/chrome_app/test/.token'],
['test/binary', 'test/chrome_app/test/binary'],
['test/html', 'test/chrome_app/test/html'],
['test/js', 'test/chrome_app/test/js'],
['test/vendor', 'test/chrome_app/test/vendor'],
]
commands = [
"cp test/chrome_app/manifests/#{manifestFile}.json " +
'test/chrome_app/manifest.json'
]
for link in links
# fs.symlinkSync(path.resolve(link[0]), link[1]) unless fs.existsSync link[1]
commands.push "cp -r #{link[0]} #{path.dirname(link[1])}"
async.forEachSeries commands, run, ->
callback() if callback
chromeCommand = ->
paths = [
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
'/Applications/Chromium.app/MacOS/Contents/Chromium',
]
for path in paths
return path if fs.existsSync path
if 'process.platform' is 'win32'
'chrome'
else
'google-chrome'
tokens = (callback) ->
TokenStash = require './test/js/token_stash.js'
tokenStash = new TokenStash
(new TokenStash()).get ->
callback() if callback?
run = (args...) ->
for a in args
switch typeof a
when 'string' then command = a
when 'object'
if a instanceof Array then params = a
else options = a
when 'function' then callback = a
command += ' ' + params.join ' ' if params?
cmd = spawn '/bin/sh', ['-c', command], options
cmd.stdout.on 'data', (data) -> process.stdout.write data
cmd.stderr.on 'data', (data) -> process.stderr.write data
process.on 'SIGHUP', -> cmd.kill()
cmd.on 'exit', (code) -> callback() if callback? and code is 0
download = ([url, file], callback) ->
if fs.existsSync file
callback() if callback?
return
run "curl -o #{file} #{url}", callback

View file

@ -1,19 +0,0 @@
Copyright (c) 2012 Dropbox, Inc., http://www.dropbox.com
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -1,73 +0,0 @@
# Client Library for the Dropbox API
This is a JavaScript client library for the Dropbox API,
[written in CoffeeScript](https://github.com/dropbox/dropbox-js/blob/master/doc/coffee_faq.md),
suitable for use in both modern browsers and in server-side code running under
[node.js](http://nodejs.org/).
## Supported Platforms
This library is tested against the following JavaScript platforms
* [node.js](http://nodejs.org/) 0.6 and 0.8
* [Chrome](https://www.google.com/chrome) 23
* [Firefox](www.mozilla.org/firefox) 17
* Internet Explorer 9
Keep in mind that the versions above are not hard requirements.
## Installation and Usage
The
[getting started guide](https://github.com/dropbox/dropbox-js/blob/master/doc/getting_started.md)
will help you get your first dropbox.js application up and running.
Peruse the source code of the
[sample apps](https://github.com/dropbox/dropbox-js/tree/master/samples),
and borrow as much as you need.
The
[dropbox.js API reference](http://coffeedoc.info/github/dropbox/dropbox-js/master/class_index.html)
can be a good place to bookmark while building your application.
If you run into a problem, take a look at
[the dropbox.js GitHub issue list](https://github.com/dropbox/dropbox-js/issues).
Please open a new issue if your problem wasn't already reported.
## Development
This library is written in CoffeeScript.
[This document](https://github.com/dropbox/dropbox-js/blob/master/doc/coffee_faq.md)
can help you understand if that matters to you.
The
[development guide](https://github.com/dropbox/dropbox-js/blob/master/doc/development.md)
will make your life easier if you need to change the source code.
## Platform-Specific Issues
This lists the most serious problems that you might run into while using
`dropbox.js`. See
[the GitHub issue list](https://github.com/dropbox/dropbox-js/issues) for a
full list of outstanding problems.
### node.js
Reading and writing binary files is currently broken.
### Internet Explorer 9
The library only works when used from `https://` pages, due to
[these issues](http://blogs.msdn.com/b/ieinternals/archive/2010/05/13/xdomainrequest-restrictions-limitations-and-workarounds.aspx).
Reading and writing binary files is unsupported.
## Copyright and License
The library is Copyright (c) 2012 Dropbox Inc., and distributed under the MIT
License.

View file

@ -1,203 +0,0 @@
# Authentication Drivers
This document explains the structure and functionality of a `dropbox.js` OAuth
driver, and describes the drivers that ship with the library.
## The OAuth Driver Interface
An OAuth driver is a JavaScript object that implements the methods documented
in the
[Dropbox.AuthDriver class](http://coffeedoc.info/github/dropbox/dropbox-js/master/classes/Dropbox/AuthDriver.html).
This class exists solely for the purpose of documenting these methods.
A simple driver can get away with implementing `url` and `doAuthorize`. The
following example shows an awfully unusable node.js driver that asks the user
to visit the authorization URL in a browser.
```javascript
var util = require("util");
var simpleDriver = {
url: function() { return ""; },
doAuthorize: function(authUrl, token, tokenSecret, callback) {
util.print("Visit the following in a browser, then press Enter\n" +
authUrl + "\n");
var onEnterKey = function() {
process.stdin.removeListener("data", onEnterKey);
callback(token);
}
process.stdin.addListener("data", onEnterKey);
process.stdin.resume();
}
};
```
Complex drivers can take control of the OAuth process by implementing
`onAuthStateChange`. Implementations of this method should read the `authState`
field of the `Dropbox.Client` instance they are given to make decisions.
Implementations should call the `credentials` and `setCredentials` methods on
the client to control the OAuth process.
See the
[Dropbox.Drivers.Redirect source](https://github.com/dropbox/dropbox-js/blob/master/src/drivers.coffee)
for a sample implementation of `onAuthStateChange`.
### The OAuth Process Steps
The `authenticate` method in `Dropbox.Client` implements the OAuth process as a
finite state machine (FSM). The current state is available in the `authState`
field.
The authentication FSM has the following states.
* `Dropbox.Client.RESET` is the initial state, where the client has no OAuth
tokens; after `onAuthStateChange` is triggered, the client will attempt to
obtain an OAuth request token
* `Dropbox.Client.REQUEST` indicates that the client has obtained an OAuth
request token; after `onAuthStateChange` is triggered, the client will call
`doAuthorize` on the OAuth driver, to get the OAuth request token authorized by
the user
* `Dropbox.Client.AUTHORIZED` is reached after the `doAuthorize` calls its
callback, indicating that the user has authorized the OAuth request token;
after `onAuthStateChange` is triggered, the client will attempt to exchange the
request token for an OAuth access token
* `Dropbox.Client.DONE` indicates that the OAuth process has completed, and the
client has an OAuth access token that can be used in API calls; after
`onAuthStateChange` is triggered, `authorize` will call its callback function,
and report success
* `Dropbox.Client.SIGNED_OFF` is reached when the client's `signOut` method is
called, after the API call succeeds; after `onAuthStateChange` is triggered,
`signOut` will call its callback function, and report success
* `Dropbox.Client.ERROR` is reached if any of the Dropbox API calls used by
`authorize` or `signOut` results in an error; after `onAuthStateChange` is
triggered, `authorize` or `signOut` will call its callback function and report
the error
## Built-in OAuth Drivers
`dropbox.js` ships with the OAuth drivers below.
### Dropbox.Drivers.Redirect
The recommended built-in driver for browser applications completes the OAuth
token authorization step by redirecting the browser to the Dropbox page that
performs the authorization and having that page redirect back to the
application page.
This driver's constructor takes the following options.
* `useQuery` should be set to true for applications that use the URL fragment
(the part after `#`) to store state information
* `rememberUser` can be set to true to have the driver store the user's OAuth
token in `localStorage`, so the user doesn't have to authorize the application
on every request
Although it seems that `rememberUser` should be true by default, it brings a
couple of drawbacks. The user's token will still be valid after signing off the
Dropbox web site, so your application will still recognize the user and access
their Dropbox. This behavior is unintuitive to users. A reasonable compromise
for apps that use `rememberUser` is to provide a `Sign out` button that calls
the `signOut` method on the app's `Dropbox.Client` instance.
The driver's constructor takes a `receiverPath` option t
[checkbox.js](https://github.com/dropbox/dropbox-js/tree/master/samples/checkbox.js)
sample application uses `rememberUser`, and implements signing off as described
above.
### Dropbox.Drivers.Popup
This driver may be useful for browser applications that can't handle the
redirections peformed by `Dropbox.Drivers.Redirect`. This driver avoids
changing the location of the application's browser window by popping up a
separate window, and loading the Dropbox authorization page in that window.
The popup method has a couple of serious drawbacks. Most browsers will not
display the popup window by default, and instead will show a hard-to-notice
warning that the user must interact with to display the popup. The driver's
code for communicating between the popup and the main application window does
not work on IE9 and below, so applications that use it will only work on
Chrome, Firefox and IE10+.
If the drawbacks above are more acceptable than restructuring your application
to handle redirects, create a page on your site that contains the
[receiver code](https://github.com/dropbox/dropbox-js/blob/master/test/html/oauth_receiver.html),
change the code to reflect the location of `dropbox.js` on your site, and point
the `Dropbox.Drivers.Popup` constructor to it.
```javascript
client.authDriver(new Dropbox.Drivers.Popup({
receiverUrl: "https://url.to/oauth_receiver.html"}));
```
The popup driver adds a `#` (fragment hash) to the receiver URL if necessary,
to ensure that the user's Dropbox uid and OAuth token are passed to the
receiver in a URL fragment. This measure may improve your users' privacy, as it
reduces the chance that their uid or token ends up in a server log.
If you have a good reason to disable the behavior above, set the `noFragment`
option to true.
```javascript
client.authDriver(new Dropbox.Drivers.Popup({
receiverUrl: "https://url.to/receiver.html", noFragment: true}));
```
The popup driver implements the `rememberUser` option with the same semantics
and caveats as the redirecting driver.
### Dropbox.Drivers.Chrome
Google Chrome [extensions](http://developer.chrome.com/extensions/) and
[applications](developer.chrome.com/apps/) are supported by a driver that opens
a new browser tab (in the case of extensions and legacy applications) or
an application window (for new applications) to complete the OAuth
authorization.
To use this driver, first add the following files to your extension.
* the [receiver script](https://github.com/dropbox/dropbox-js/blob/master/test/src/chrome_oauth_receiver.coffee);
the file is both valid JavaScript and valid CoffeeScript
* the [receiver page](https://github.com/dropbox/dropbox-js/blob/master/test/html/chrome_oauth_receiver.html);
change the page to reflect the paths to `dropbox.js` and to the receiver script
file
Point the driver constructor to the receiver page:
```javascript
client.authDriver(new Dropbox.Drivers.Chrome({
receiverPath: "path/to/chrome_oauth_receiver.html"}));
```
This driver caches the user's credentials so that users don't have to authorize
applications / extensions on every browser launch. Applications and extensions'
UI should include a method for the user to sign out of Dropbox, which can be
implemented by calling the `signOut` instance method of `Dropbox.Client`.
### Dropbox.Drivers.NodeServer
This driver is designed for use in the automated test suites of node.js
applications. It completes the OAuth token authorization step by opening the
Dropbox authorization page in a new browser window, and "catches" the OAuth
redirection by setting up a small server using the `https` built-in node.js
library.
The driver's constructor takes the following options.
* `port` is the HTTP port number; the default is 8192, and works well with the
Chrome extension described below
* `favicon` is a path to a file that will be served in response to requests to
`/favicon.ico`; setting this to a proper image will avoid some warnings in the
browsers' consoles
To fully automate your test suite, you need to load up the Chrome extension
bundled in the `dropbox.js` source tree. The extension automatically clicks on
the "Authorize" button in the Dropbox token authorization page, and closes the
page after the token authorization process completes. Follow the steps in the
[development guide](https://github.com/dropbox/dropbox-js/blob/master/doc/development.md)
to build and install the extension.

View file

@ -1,84 +0,0 @@
# dropbox.js and CoffeeScript FAQ
dropbox.js is written in [CoffeeScript](http://coffeescript.org/), which
compiles into very readable JavaScript. This document addresses the concerns
that are commonly raised by JavaScript developers that do not use or wish to
learn CoffeeScript.
## Do I need to learn CoffeeScript to use the library?
**No.**
The examples in the
[getting started guide](https://github.com/dropbox/dropbox-js/blob/master/doc/getting_started.md)
are all written in JavaScript. The
[dropbox.js API reference](http://coffeedoc.info/github/dropbox/dropbox-js/master/class_index.html)
covers the entire library, so you should not need to read the library source
code to understand how to use it.
_Please open an issue if the documentation is unclear!_
The
[sample apps](https://github.com/dropbox/dropbox-js/tree/master/samples),
are written in CoffeeScript. Please use the `Try CoffeeScript` button on the
[CoffeeScript](http://coffeescript.org/) home page to quickly compile the
sample CoffeeScript into very readable JavaScript.
## Do I need to learn CoffeeScript to know how dropbox.js works?
**No.**
You can follow the
[development guide](https://github.com/dropbox/dropbox-js/blob/master/doc/development.md)
to build the un-minified JavaScript library in `lib/dropbox.js` and then use
your editor's find feature to get to the source code for the methods that you
are interested in.
The building instructions in the development guide do not require familiarity
with CoffeeScript.
## Do I need to learn CoffeeScript to modify dropbox.js?
**Yes, but you might not need to modify the library.**
You do need to learn CoffeeScript to change the `dropbox.js` source code. At
the same time, you can take advantage of the library hooks and the dynamic
nature of the JavaScript language to change the behavior of `dropbox.js`
without touching the source code.
* You can implement your OAuth strategy.
* You can add methods to the prototype classes such as `Dropbox.Client` to
implement custom operations. _Please open an issue if you think your addition
is generally useful!_
* You can replace internal classes such as `Dropbox.Xhr` (or selectively
replace methods) with wrappers that tweak the original behavior
## Can I contribute to dropbox.js without learning CoffeeScript?
**Yes.**
Most of the development time is spent on API design, developing tests,
documentation and sample code. Contributing a good testing strategy with a bug
report can save us 90% of the development time. A feature request that also
includes a well thought-out API change proposal and testing strategy can also
save us 90-95% of the implementation time.
At the same time, _please open issues for bugs and feature requests even if you
don't have time to include any of the above_. Knowing of a problem is the first
step towards fixing it.
Last, please share your constructive suggestions on how to make `dropbox.js`
easier to use for JavaScript developers that don't speak CoffeeScript.
## Can I complain to get dropbox.js to switch away from CoffeeScript?
**No.**
At the moment, 100% of the library's development comes from unpaid, voluntary
efforts. Switching to JavaScript would reduce the efficiency of these efforts,
and it would kill developer motivation.

View file

@ -1,195 +0,0 @@
# dropbox.js Development
Read this document if you want to build `dropbox.js` or modify its source code.
If you want to write applications using dropbox.js, check out the
[Getting Started](getting_started.md).
The library is written using [CoffeeScript](http://coffeescript.org/), built
using [cake](http://coffeescript.org/documentation/docs/cake.html), minified
using [uglify.js](https://github.com/mishoo/UglifyJS/), tested using
[mocha](http://visionmedia.github.com/mocha/) and
[chai.js](http://chaijs.com/), and packaged using [npm](https://npmjs.org/).
If you don't "speak" CoffeeScript,
[this document](https://github.com/dropbox/dropbox-js/blob/master/doc/coffee_faq.md)
might address some of your concerns.
## Dev Environment Setup
Install [node.js](http://nodejs.org/#download) to get `npm` (the node
package manager), then use it to install the libraries required by the test
suite.
```bash
git clone https://github.com/dropbox/dropbox-js.git
cd dropbox-js
npm install
```
## Build
Run `npm pack` and ignore any deprecation warnings that might come up.
```bash
npm pack
```
The build output is in the `lib/` directory. `dropbox.js` is the compiled
library that ships in the npm package, and `dropbox.min.js` is a minified
version, optimized for browser apps.
## Test
Install the CoffeeScript npm package globally, so you can type `cake` instead
of `node_modules/coffee-script/bin/cake`.
```bash
npm install -g coffee-script
```
First, you will need to obtain a couple of Dropbox tokens that will be used by
the automated tests.
```bash
cake tokens
```
Re-run the command above if the tests fail due to authentication errors.
Once you have Dropbox tokens, you can run the test suite in node.js, in your
default browser, or as a Chrome application.
```bash
cake test
cake webtest
cake chrometest
```
The library is automatically re-built when running tests, so you don't need to
run `npm pack`. Please run the tests in both node.js and a browser before
submitting pull requests.
The tests store all their data in folders named along the lines of
`js tests.0.ac1n6lgs0e3lerk9`. If tests fail, you might have to clean up these
folders yourself.
### Solving Browser Issues
An easy method to test a browser in a virtual machine is to skip the automated
browser opening.
```bash
BROWSER=false cake webtest
```
A similar method can be used to launch a specific browser.
```bash
BROWSER=firefox cake webtest
```
When fighting a bug, it can be useful to keep the server process running after
the test suite completes, so tests can be re-started with a browser refresh.
```bash
BROWSER=false NO_EXIT=1 cake webtest
```
[Mocha's exclusive tests](http://visionmedia.github.com/mocha/#exclusive-tests)
(`it.only` and `describe.only`) are very useful for quickly iterating while
figuring out a bug.
### Chrome Application / Extension Testing
The tests for Chrome apps / extensions require manual intervention right now.
The `cake chrometest` command will open a Google Chrome instance. The
`dropbox.js Test Suite` application must be clicked.
### Fully Automated Tests
The test suite opens up the Dropbox authorization page a few times, and also
pops up a page that cannot close itself. dropbox.js ships with a Google Chrome
extension that can fully automate the testing process on Chrome / Chromium.
The extension is written in CoffeeScript, so you will have to compile it.
```bash
cake extension
```
After compilation, have Chrome load the unpacked extension at
`test/chrome_extension` and click on the scary-looking toolbar icon to activate
the extension. The icon's color should turn red, to indicate that it is active.
The extension performs some checks to prevent against attacks. However, you
should still disable the automation (by clicking on the extension icon) when
you're not testing dropbox.js, just in case the extension code has bugs.
## Release Process
1. At the very least, test in node.js and in a browser before releasing.
```bash
cake test
cake webtest
```
1. Bump the version in `package.json`.
1. Publish a new npm package.
```bash
npm publish
```
1. Commit and tag the version bump on GitHub.
```bash
git add package.json
git commit -m "Release X.Y.Z."
git tag -a -m "Release X.Y.Z" vX.Y.Z
git push
git push --tags
```
1. If you haven't already, go to the
[cdnjs GitHub page](https://github.com/cdnjs/cdnjs) and fork it.
1. If you haven't already, set up cdnjs on your machine.
```bash
cd ..
git clone git@github.com:you/cdnjs.git
cd cdnjs
git remote add up git://github.com/cdnjs/cdnjs.git
cd ../dropbox-js
```
1. Add the new release to your cdnjs fork.
```bash
cd ../cdnjs
git checkout master
git pull up master
npm install
git checkout -b dbXYZ
mkdir ajax/libs/dropbox.js/X.Y.Z
cp ../dropbox-js/lib/dropbox.min.js ajax/libs/dropbox.js/X.Y.Z/
vim ajax/libs/dropbox.js/package.json # Replace "version"'s value with "X.Y.Z"
npm test
git add -A
git commit -m "Added dropbox.js X.Y.Z"
git push origin dbXYZ
```
1. Go to your cdnjs for on GitHub and open a pull request. Use these examples
of accepted
[major release pull request](https://github.com/cdnjs/cdnjs/pull/735) and
[minor release pull request](https://github.com/cdnjs/cdnjs/pull/753).

View file

@ -1,280 +0,0 @@
# Getting Started
This is a guide to writing your first dropbox.js application.
## Library Setup
This section describes how to get the library hooked up into your application.
### Browser Applications
To get started right away, place this snippet in your page's `<head>`.
```html
<script src="//cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.8.1/dropbox.min.js">
</script>
```
The snippet is not a typo. [cdnjs](https://cdnjs.com) recommends using
[protocol-relative URLs](http://paulirish.com/2010/the-protocol-relative-url/).
To get the latest development build of dropbox.js, follow the steps in the
[development guide](https://github.com/dropbox/dropbox-js/blob/master/doc/development.md).
#### "Powered by Dropbox" Static Web Apps
Before writing any source code, use the
[console app](https://dl-web.dropbox.com/spa/pjlfdak1tmznswp/powered_by.js/public/index.html)
to set up your Dropbox. After adding an application, place the source code at
`/Apps/Static Web Apps/my_awesome_app/public`. You should find a pre-generated
`index.html` file in there.
### node.js Applications
First, install the `dropbox` [npm](https://npmjs.org/) package.
```bash
npm install dropbox
```
Once the npm package is installed, the following `require` statement lets you
access the same API as browser applications
```javascript
var Dropbox = require("dropbox");
```
## Initialization
[Register your application](https://www.dropbox.com/developers/apps) to obtain
an API key. Read the brief
[API core concepts intro](https://www.dropbox.com/developers/start/core).
Once you have an API key, use it to create a `Dropbox.Client`.
```javascript
var client = new Dropbox.Client({
key: "your-key-here", secret: "your-secret-here", sandbox: true
});
```
If your application requires full Dropbox access, leave out the `sandbox: true`
parameter.
### Browser and Open-Source Applications
The Dropbox API guidelines ask that the API key and secret is never exposed in
cleartext. This is an issue for browser-side and open-source applications.
To meet this requirement,
[encode your API key](https://dl-web.dropbox.com/spa/pjlfdak1tmznswp/api_keys.js/public/index.html)
and pass the encoded key string to the `Dropbox.Client` constructor.
```javascript
var client = new Dropbox.Client({
key: "encoded-key-string|it-is-really-really-long", sandbox: true
});
```
## Authentication
Before you can make any API calls, you need to authenticate your application's
user with Dropbox, and have them authorize your app's to access their Dropbox.
This process follows the [OAuth 1.0](http://tools.ietf.org/html/rfc5849)
protocol, which entails sending the user to a Web page on `www.dropbox.com`,
and then having them redirected back to your application. Each Web application
has its requirements, so `dropbox.js` lets you customize the authentication
process by implementing an
[OAuth driver](https://github.com/dropbox/dropbox-js/blob/master/src/drivers.coffee).
At the same time, dropbox.js ships with a couple of OAuth drivers, and you
should take advantage of them as you prototype your application.
Read the
[authentication doc](https://github.com/dropbox/dropbox-js/blob/master/doc/auth_drivers.md)
for further information about writing an OAuth driver, and to learn about all
the drivers that ship with `dropbox.js`.
### Browser Setup
The following snippet will set up the recommended driver.
```javascript
client.authDriver(new Dropbox.Drivers.Redirect());
```
The
[authentication doc](https://github.com/dropbox/dropbox-js/blob/master/doc/auth_drivers.md)
describes some useful options that you can pass to the
`Dropbox.Drivers.Redirect` constructor.
### node.js Setup
Single-process node.js applications should create one driver to authenticate
all the clients.
```javascript
client.authDriver(new Dropbox.Drivers.NodeServer(8191));
```
The
[authentication doc](https://github.com/dropbox/dropbox-js/blob/master/doc/auth_drivers.md)
has useful tips on using the `NodeServer` driver.
### Shared Code
After setting up an OAuth driver, authenticating the user is one method call
away.
```javascript
client.authenticate(function(error, client) {
if (error) {
// Replace with a call to your own error-handling code.
//
// Don't forget to return from the callback, so you don't execute the code
// that assumes everything went well.
return showError(error);
}
// Replace with a call to your own application code.
//
// The user authorized your app, and everything went well.
// client is a Dropbox.Client instance that you can use to make API calls.
doSomethingCool(client);
});
```
## Error Handlng
When Dropbox API calls fail, dropbox.js methods pass a `Dropbox.ApiError`
instance as the first parameter in their callbacks. This parameter is named
`error` in all the code snippets on this page.
If `error` is a truthy value, you should either recover from the error, or
notify the user that an error occurred. The `status` field in the
`Dropbox.ApiError` instance contains the HTTP error code, which should be one
of the
[error codes in the REST API](https://www.dropbox.com/developers/reference/api#error-handling).
The snippet below is a template for an extensive error handler.
```javascript
var showError = function(error) {
switch (error.status) {
case 401:
// If you're using dropbox.js, the only cause behind this error is that
// the user token expired.
// Get the user through the authentication flow again.
break;
case 404:
// The file or folder you tried to access is not in the user's Dropbox.
// Handling this error is specific to your application.
break;
case 507:
// The user is over their Dropbox quota.
// Tell them their Dropbox is full. Refreshing the page won't help.
break;
case 503:
// Too many API requests. Tell the user to try again later.
// Long-term, optimize your code to use fewer API calls.
break;
case 400: // Bad input parameter
case 403: // Bad OAuth request.
case 405: // Request method not expected
default:
// Caused by a bug in dropbox.js, in your application, or in Dropbox.
// Tell the user an error occurred, ask them to refresh the page.
}
};
```
`Dropbox.Client` also supports a DOM event-like API for receiving all errors.
This can be used to log API errors, or to upload them to your server for
further analysis.
```javascript
client.onError.addListener(function(error) {
if (window.console) { // Skip the "if" in node.js code.
console.error(error);
}
});
```
## The Fun Part
Authentication was the hard part of the API integration, and error handling was
the most boring part. Now that these are both behind us, you can interact
with the user's Dropbox and focus on coding up your application!
The following sections have some commonly used code snippets. The
[Dropbox.Client API reference](http://coffeedoc.info/github/dropbox/dropbox-js/master/classes/Dropbox/Client.html)
will help you navigate less common scenarios, and the
[Dropbox REST API reference](https://www.dropbox.com/developers/reference/api)
describes the underlying HTTP protocol, and can come in handy when debugging
your application, or if you want to extend dropbox.js.
### User Info
```javascript
client.getUserInfo(function(error, userInfo) {
if (error) {
return showError(error); // Something went wrong.
}
alert("Hello, " + userInfo.name + "!");
});
```
### Write a File
```javascript
client.writeFile("hello_world.txt", "Hello, world!\n", function(error, stat) {
if (error) {
return showError(error); // Something went wrong.
}
alert("File saved as revision " + stat.revisionTag);
});
```
### Read a File
```javascript
client.readFile("hello_world.txt", function(error, data) {
if (error) {
return showError(error); // Something went wrong.
}
alert(data); // data has the file's contents
});
```
### List a Directory's Contents
```javascript
client.readdir("/", function(error, entries) {
if (error) {
return showError(error); // Something went wrong.
}
alert("Your Dropbox contains " + entries.join(", "));
});
```
### Sample Applications
Check out the
[sample apps](https://github.com/dropbox/dropbox-js/tree/master/samples)
to see how all these concepts play out together.

View file

@ -1,10 +0,0 @@
# dropbox.js Build Directory
dropbox.js is written in [CoffeeScript](http://coffeescript.org/), and its
source code is in the `src/` directory. The `lib/` directory contains the
compiled JavaScript produced by the build process.
The
[development guide](https://github.com/dropbox/dropbox-js/blob/master/doc/development.md)
contains a step-by-step guide to the library's build process, and will help you
populate this directory.

File diff suppressed because it is too large Load diff

File diff suppressed because one or more lines are too long

View file

@ -1,48 +0,0 @@
{
"name": "dropbox",
"version": "0.8.1",
"description": "Client library for the Dropbox API",
"keywords": ["dropbox", "filesystem", "storage"],
"homepage": "http://github.com/dropbox/dropbox-js",
"author": "Victor Costan <victor@costan.us> (http://www.costan.us)",
"license": "MIT",
"contributors": [
"Aakanksha Sarda <aaki@mit.edu>"
],
"repository": {
"type": "git",
"url": "https://github.com/dropbox/dropbox-js.git"
},
"engines": {
"node": ">= 0.6"
},
"dependencies": {
"open": ">= 0.0.2",
"xmlhttprequest": ">= 1.5.0"
},
"devDependencies": {
"async": ">= 0.1.22",
"chai": ">= 1.4.0",
"codo": ">= 1.5.4",
"coffee-script": ">= 1.4.0",
"express": ">= 3.0.6",
"glob": ">= 3.1.14",
"mocha": ">= 1.7.4",
"open": "https://github.com/pwnall/node-open/tarball/master",
"remove": ">= 0.1.5",
"sinon": ">= 1.5.2",
"sinon-chai": ">= 2.3.0",
"uglify-js": ">= 2.2.3"
},
"main": "lib/dropbox.js",
"directories": {
"doc": "doc",
"lib": "lib",
"src": "src",
"test": "test"
},
"scripts": {
"prepublish": "node_modules/coffee-script/bin/cake build",
"test": "node_modules/coffee-script/bin/cake test"
}
}

View file

@ -1,67 +0,0 @@
# Checkbox, a dropbox.js Sample Application
This application demonstrates the use of the JavaScript client library for the
Dropbox API to implement a Dropbox-backed To Do list application.
In 70 lines of HTML, and 250 lines of commented CoffeeScript, Checkbox lets you
store your To Do list in your Dropbox! Just don't expect award winning design
or usability from a sample application.
See this sample in action
[here](https://dl-web.dropbox.com/spa/pjlfdak1tmznswp/checkbox.js/public/index.html).
## Dropbox Integration
This proof-of-concept application uses the "App folder" Dropbox access level,
so Dropbox automatically creates a directory for its app data in the users'
Dropboxes. The data model optimizes for ease of development and debugging.
Each task is stored as a file whose name is the tasks description. Tasks are
grouped under two folders, active and done.
The main advantage of this data model is that operations on tasks cleanly map
to file operations in Dropbox. At initialization time, the application creates
its two folders, active and done. A task is created by writing an empty string
to a file in the active folder, marked as completed by moving the file to the
done folder, and removed by deleting the associated file.
The lists of tasks are obtained by listing the contents of the active and done
folders. The data model can be easily extended, by storing JSON-encoded
information, such as deadlines, in the task files.
This sample uses the following `Dropbox.Client` methods:
* authenticate
* signOff
* getUserInfo
* mkdir
* readdir
* writeFile
* move
* remove
## Building
This sample does not require building. Follow the steps below to get your own
copy of the sample that you can hack on.
1. [Create a powered_by.js app in your Dropbox](https://dl-web.dropbox.com/spa/pjlfdak1tmznswp/powered_by.js/public/index.html).
1. [Get your own API key](https://www.dropbox.com/developers/apps).
1. [Encode your API key](https://dl-web.dropbox.com/spa/pjlfdak1tmznswp/api_keys.js/public/index.html).
1. Copy the source code to `/Apps/Static Web Apps/powered_by.js` in your Dropbox
## Dependencies
The application uses the following JavaScript libraries.
* [dropbox.js](https://github.com/dropbox/dropbox-js) for Dropbox integration
* [less](http://lesscss.org/) for CSS conciseness
* [CoffeeScript](http://coffeescript.org/) for JavaScript conciseness
* [jQuery](http://jquery.com/) for cross-browser compatibitility
The icons used in the application are all from
[the noun project](http://thenounproject.com/).
The application follows a good practice of packaging its dependencies, and not
hot-linking them.

View file

@ -1,249 +0,0 @@
# vim: set tabstop=2 shiftwidth=2 softtabstop=2 expandtab :
# Controller/View for the application.
class Checkbox
# @param {Dropbox.Client} dbClient a non-authenticated Dropbox client
# @param {DOMElement} root the app's main UI element
constructor: (@dbClient, root) ->
@$root = $ root
@taskTemplate = $('#task-template').text()
@$activeList = $('#active-task-list')
@$doneList = $('#done-task-list')
$('#signout-button').click (event) => @onSignOut event
@dbClient.authenticate (error, data) =>
return @showError(error) if error
@dbClient.getUserInfo (error, userInfo) =>
return @showError(error) if error
$('#user-name').text userInfo.name
@tasks = new Tasks @, @dbClient
@tasks.load =>
@wire()
@render()
@$root.removeClass 'hidden'
# Re-renders all the data.
render: ->
@$activeList.empty()
@$doneList.empty()
@renderTask(task) for task in @tasks.active
@renderTask(task) for task in @tasks.done
# Renders a task into the
renderTask: (task) ->
$list = if task.done then @$doneList else @$activeList
$list.append @$taskDom(task)
# Renders the list element representing a task.
#
# @param {Task} task the task to be rendered
# @return {jQuery<li>} jQuery wrapper for the DOM representing the task
$taskDom: (task) ->
$task = $ @taskTemplate
$('.task-name', $task).text task.name
$('.task-remove-button', $task).click (event) => @onRemoveTask event, task
if task.done
$('.task-done-button', $task).addClass 'hidden'
$('.task-active-button', $task).click (event) =>
@onActiveTask event, task
else
$('.task-active-button', $task).addClass 'hidden'
$('.task-done-button', $task).click (event) => @onDoneTask event, task
$task
# Called when the user wants to create a new task.
onNewTask: (event) ->
event.preventDefault()
name = $('#new-task-name').val()
if @tasks.findByName name
alert "You already have this task on your list!"
else
$('#new-task-button').attr 'disabled', 'disabled'
$('#new-task-name').attr 'disabled', 'disabled'
task = new Task()
task.name = name
@tasks.addTask task, =>
$('#new-task-name').removeAttr('disabled').val ''
$('#new-task-button').removeAttr 'disabled'
@renderTask task
# Called when the user wants to mark a task as done.
onDoneTask: (event, task) ->
$task = @$taskElement event.target
$('button', $task).attr 'disabled', 'disabled'
@tasks.setTaskDone task, true, =>
$task.remove()
@renderTask task
# Called when the user wants to mark a task as active.
onActiveTask: (event, task) ->
$task = @$taskElement event.target
$('button', $task).attr 'disabled', 'disabled'
@tasks.setTaskDone task, false, =>
$task.remove()
@renderTask task
# Called when the user wants to permanently remove a task.
onRemoveTask: (event, task) ->
$task = @$taskElement event.target
$('button', $task).attr 'disabled', 'disabled'
@tasks.removeTask task, ->
$task.remove()
# Called when the user wants to sign out of the application.
onSignOut: (event, task) ->
@dbClient.signOut (error) =>
return @showError(error) if error
window.location.reload()
# Finds the DOM element representing a task.
#
# @param {DOMElement} element any element inside the task element
# @return {jQuery<DOMElement>} a jQuery wrapper around the DOM element
# representing a task
$taskElement: (element) ->
$(element).closest 'li.task'
# Sets up listeners for the relevant DOM events.
wire: ->
$('#new-task-form').submit (event) => @onNewTask event
# Updates the UI to show that an error has occurred.
showError: (error) ->
$('#error-notice').removeClass 'hidden'
console.log error if window.console
# Model that wraps all a user's tasks.
class Tasks
# @param {Checkbox} controller the application controller
constructor: (@controller) ->
@dbClient = @controller.dbClient
[@active, @done] = [[], []]
# Reads all the from a user's Dropbox.
#
# @param {function()} done called when all the tasks are read from the user's
# Dropbox, and the active and done properties are set
load: (done) ->
# We read the done tasks and the active tasks in parallel. The variables
# below tell us when we're done with both.
readActive = readDone = false
@dbClient.mkdir '/active', (error, stat) =>
# Issued mkdir so we always have a directory to read from.
# In most cases, this will fail, so don't bother checking for errors.
@dbClient.readdir '/active', (error, entries, dir_stat, entry_stats) =>
return @showError(error) if error
@active = ((new Task()).fromStat(stat) for stat in entry_stats)
readActive = true
done() if readActive and readDone
@dbClient.mkdir '/done', (error, stat) =>
@dbClient.readdir '/done', (error, entries, dir_stat, entry_stats) =>
return @showError(error) if error
@done = ((new Task()).fromStat(stat) for stat in entry_stats)
readDone = true
done() if readActive and readDone
@
# Adds a new task to the user's set of tasks.
#
# @param {Task} task the task to be added
# @param {function()} done called when the task is saved to the user's
# Dropbox
addTask: (task, done) ->
task.cleanupName()
@dbClient.writeFile task.path(), '', (error, stat) =>
return @showError(error) if error
@addTaskToModel task
done()
# Returns a task with the given name, if it exists.
#
# @param {String} name the name to search for
# @return {?Task} task the task with the given name, or null if no such task
# exists
findByName: (name) ->
for tasks in [@active, @done]
for task in tasks
return task if task.name is name
null
# Removes a task from the list of tasks.
#
# @param {Task} task the task to be removed
# @param {function()} done called when the task is removed from the user's
# Dropbox
removeTask: (task, done) ->
@dbClient.remove task.path(), (error, stat) =>
return @showError(error) if error
@removeTaskFromModel task
done()
# Marks a active task as done, or a done task as active.
#
# @param {Task} the task to be changed
setTaskDone: (task, newDoneValue, done) ->
[oldDoneValue, task.done] = [task.done, newDoneValue]
newPath = task.path()
task.done = oldDoneValue
@dbClient.move task.path(), newPath, (error, stat) =>
return @showError(error) if error
@removeTaskFromModel task
task.done = newDoneValue
@addTaskToModel task
done()
# Adds a task to the in-memory model. Should not be called directly.
addTaskToModel: (task) ->
@taskArray(task).push task
# Remove a task from the in-memory model. Should not be called directly.
removeTaskFromModel: (task) ->
taskArray = @taskArray task
for _task, index in taskArray
if _task is task
taskArray.splice index, 1
break
# @param {Task} the task whose containing array should be returned
# @return {Array<Task>} the array that should contain the given task
taskArray: (task) ->
if task.done then @done else @active
# Updates the UI to show that an error has occurred.
showError: (error) ->
@controller.showError error
# Model for a single user task.
class Task
# Creates a task with default values.
constructor: ->
@name = null
@done = false
# Reads data about a task from the stat of is file in a user's Dropbox.
#
# @param {Dropbox.Stat} entry the directory entry representing the task
fromStat: (entry) ->
@name = entry.name
@done = entry.path.split('/', 3)[1] is 'done'
@
# Cleans up the task name so that it's valid Dropbox file name.
cleanupName: (name) ->
# English-only hack that removes slashes from the task name.
@name = @name.replace(/\ \/\ /g, ' or ').replace(/\//g, ' or ')
@
# Path to the file representing the task in the user's Dropbox.
# @return {String} fully-qualified path
path: ->
(if @done then '/done/' else '/active/') + @name
# Start up the code when the DOM is fully loaded.
$ ->
client = new Dropbox.Client(
key: '/Fahm0FLioA|ZxKxLxy5irfHqsCRs+Ceo8bwJjVPu8xZlfjgGzeCjQ', sandbox: true)
client.authDriver new Dropbox.Drivers.Redirect(rememberUser: true)
new Checkbox client, '#app-ui'

View file

@ -1,298 +0,0 @@
// vim: set tabstop=2 shiftwidth=2 softtabstop=2 expandtab :
.linear-gradient (@top: #ffffff, @bottom: #000000) {
background: @top;
filter: ~"progid:DXImageTransform.Microsoft.gradient(startColorstr='@{top}', endColorstr='@{bottom}')";
background: -webkit-linear-gradient(top, @top 0%, @bottom 100%);
background: -moz-linear-gradient(top, @top 0%, @bottom 100%);
background: -ms-linear-gradient(top, @top 0%, @bottom 100%);
background: linear-gradient(to bottom, @top 0%, @bottom 100%);
}
.border-radius (@radius) {
-webkit-border-radius: @radius;
-moz-border-radius: @radius;
border-radius: @radius;
}
.box-sizing (@sizing) {
-webkit-box-sizing: @sizing;
-moz-box-sizing: @sizing;
box-sizing: @sizing;
}
.box-shadow (@offset-x, @offset-y, @blur, @spread, @color) {
-webkit-box-shadow: @offset-x @offset-y @blur @spread @color;
-moz-box-shadow: @offset-x @offset-y @blur @spread @color;
box-shadow: @offset-x @offset-y @blur @spread @color;
}
.font-verdana () {
font-family: Verdana, Geneva, sans-serif;
}
.font-helvetica () {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
html, body {
.linear-gradient (#ffffff, #f2f7fc);
background-attachment: fixed;
height: 100%;
margin: 0;
padding: 0;
}
#error-notice {
display: block;
position: absolute;
width: 100%;
.box-sizing(border-box);
color: hsl(0, 75%, 33%);
font-size: 12pt;
.font-verdana();
line-height: 1.5;
text-align: center;
margin: 0;
padding: 0.2em 0 0.1em 0;
#error-refresh-button {
margin: 0 1em;
padding: 0 1em;
}
&.hidden {
display: none;
}
}
#app-ui {
width: 700px;
margin: 0 auto;
h1 {
.font-verdana();
font-size: 24px;
font-weight: 300;
line-height: 2;
text-transform: lowercase;
color: #bfbfbf;
margin: 0;
padding: 2em 0 0.25em 0;
small {
.font-verdana();
font-size: 10px;
font-weight: 400;
text-transform: uppercase;
color: #888888;
a {
font-style: normal;
color: #649cd1;
text-decoration: none;
}
}
}
#notebook-page {
border: 1px solid #aaaaaa;
-webkit-border-top-left-radius: 8px;
-webkit-border-top-right-radius: 8px;
-moz-border-radius-topleft: 8px;
-moz-border-radius-topright: 8px;
border-top-left-radius: 8px;
border-top-right-radius: 8px;
.box-shadow(0, 0, 10px, 5px, rgba(0, 0, 0, 0.05));
background-color: #fffddb;
margin: 0;
padding: 24px 0 60px 0;
}
#notebook-page, .task {
background-image: -webkit-linear-gradient(top, #f3aaaa 0%, #f3aaaa 100%),
-webkit-linear-gradient(top, #f3aaaa 0%, #f3aaaa 100%);
background-image: -moz-linear-gradient(top, #f3aaaa 0%, #f3aaaa 100%),
-moz-linear-gradient(top, #f3aaaa 0%, #f3aaaa 100%);
background-image: -ms-linear-gradient(top, #f3aaaa 0%, #f3aaaa 100%),
-ms-linear-gradient(top, #f3aaaa 0%, #f3aaaa 100%);
background-image: linear-gradient(to bottom, #f3aaaa 0%, #f3aaaa 100%),
linear-gradient(to bottom, #f3aaaa 0%, #f3aaaa 100%);
background-position: 70px 0px, 76px 0px;
background-size: 1px 100%, 1px 100%;
background-repeat: no-repeat, no-repeat;
}
#user-info {
margin: 0;
padding: 0 16px 8px 88px;
color: #555555;
.font-helvetica();
font-size: 16px;
line-height: 32px;
font-weight: 200;
text-align: right;
#user-name {
display: inline-block;
padding: 0 0 0 8px;
}
#signout-button {
visibility: hidden;
}
&:hover {
color: #000000;
#signout-button {
visibility: visible;
}
}
}
#new-task-form, h2, .task, .empty-task {
border-bottom: 1px solid #c9e4f2;
.font-helvetica();
font-size: 18px;
font-weight: 300;
line-height: 36px;
color: #555555;
margin: 0;
padding: 6px 10px 6px 88px;
}
#new-task-form {
margin: 0;
#new-task-name {
font-family: inherit;
font-size: inherit;
background: rgb(255, 254, 236);
}
.task-actions {
visibility: visible;
}
}
h2 {
font-size: 20px;
font-weight: 400;
color: #000000;
}
.task-list {
list-style: none;
margin: 0;
padding: 0;
}
.task {
span.task-name {
display: inline-block;
}
&:hover {
background-color: #fffcaf;
.task-actions {
visibility: visible;
}
}
}
.task-name {
width: 373px;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
vertical-align: middle;
}
.task-actions {
width: 220px;
display: inline-block;
vertical-align: middle;
line-height: 1;
visibility: hidden;
}
}
input[type=text] {
display: inline-block;
margin: 0;
padding: 0 0.25em 0 0.25em;
line-height: 30px;
.box-sizing(border-box);
position: relative;
left: -4px;
height: 32px;
border: 1px solid #d2cd70;
.border-radius(4px);
-webkit-box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.1);
-moz-box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.1);
box-shadow: inset 0 2px 4px 0 rgba(0, 0, 0, 0.1);
}
button {
display: inline-block;
vertical-align: middle;
height: 32px;
margin: 0;
padding: 0;
min-width: 104px;
cursor: pointer;
border: 1px solid;
.border-radius(4px);
.box-shadow(0, 2px, -1px, 0, rgba(0, 0, 0, 0.1));
.font-verdana();
font-size: 13px;
font-weight: 400;
text-align: center;
text-transform: uppercase;
color: #ffffff;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
filter: dropshadow(color=rgba(0, 0, 0, 0.5), offx=1, offy=1);
&:hover {
-webkit-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
inset 0 0 4px rgba(255, 255, 255, 0.6);
-moz-box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
inset 0 0 4px rgba(255, 255, 255, 0.6);
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.3),
inset 0 0 4px rgba(255, 255, 255, 0.6);
}
&:focus {
.box-shadow(0, 0, 3px, 1px, #33a0e8);
}
&:active {
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
}
img {
display: inline-block;
vertical-align: -4px;
}
&.task-done-button, &#new-task-button, &.task-active-button {
border-color: #448c42;
.linear-gradient(#8ed66b, #58ba6d);
&:active {
.linear-gradient(#58ba6d, #8ed66b);
}
}
&.task-remove-button, &#error-refresh-button, &#signout-button {
border-color: #a73030;
.linear-gradient(#f67f73, #bb5757);
&:active {
.linear-gradient(#bb5757, #f67f73);
}
}
}
.hidden {
display: none;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -1,10 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" width="100px" height="87.5px" viewBox="0 0 100 87.5" style="enable-background:new 0 0 100 87.5;" xml:space="preserve">
<path style="fill:#010101;" d="M12.5,6.25c0,3.455-2.795,6.25-6.25,6.25C2.795,12.5,0,9.705,0,6.25C0,2.795,2.795,0,6.25,0 C9.705,0,12.5,2.795,12.5,6.25z"/>
<rect x="25" style="fill:#010101;" width="75" height="12.5"/>
<path style="fill:#010101;" d="M12.5,56.25c0,3.455-2.795,6.25-6.25,6.25C2.795,62.5,0,59.705,0,56.25S2.795,50,6.25,50 C9.705,50,12.5,52.795,12.5,56.25z"/>
<rect x="25" y="50" style="fill:#010101;" width="75" height="12.5"/>
<path style="fill:#010101;" d="M12.5,31.25c0,3.455-2.795,6.25-6.25,6.25C2.795,37.5,0,34.705,0,31.25S2.795,25,6.25,25 C9.705,25,12.5,27.795,12.5,31.25z"/>
<path style="fill:#010101;" d="M12.5,81.25c0,3.455-2.795,6.25-6.25,6.25C2.795,87.5,0,84.705,0,81.25S2.795,75,6.25,75 C9.705,75,12.5,77.795,12.5,81.25z"/>
<rect x="25" y="25" style="fill:#010101;" width="75" height="12.5"/>
<rect x="25" y="75" style="fill:#010101;" width="75" height="12.5"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -1,68 +0,0 @@
<!DOCTYPE html>
<!-- vim: set tabstop=2 shiftwidth=2 softtabstop=2 expandtab : -->
<html lang="en">
<head>
<title>Checkbox - dropbox.js Sample Application</title>
<link rel="icon" type="image/png" href="images/icon16.png" />
<link rel="stylesheet/less" type="text/css" href="./checkbox.less" />
<script type="text/javascript" src="lib/coffee-script.js"></script>
<script type="text/javascript" src="lib/dropbox.js"></script>
<script type="text/javascript" src="lib/jquery.js"></script>
<script type="text/javascript" src="lib/less.js"></script>
<script type="text/coffeescript" src="./checkbox.coffee"></script>
</head>
<body>
<aside id="error-notice" class="hidden">
<form action="#" method="GET">
Something went wrong :(
<button type="submit" id="error-refresh-button">
<img src="images/not_done.png" alt="" /> reload the app
</button>
</form>
</aside>
<article id="app-ui" class="hidden">
<h1>
checkbox
<small>powered by
<a href="https://www.dropbox.com/developers">dropbox</a>
</small>
</h1>
<div id="notebook-page">
<aside id="user-info">
<button type="button" id="signout-button">
<img src="images/remove.png" alt="" /> Sign out
</button>
<span id="user-name" />
</aside>
<h2 id="active-task-heading">Active</h2>
<ol class="task-list" id="active-task-list"></ol>
<form action="" method="GET" id="new-task-form">
<input type="text" id="new-task-name" class="task-name"
required="required" placeholder="e.g., buy milk" />
<button type="submit" id="new-task-button">
<img src="images/add.png" alt="" /> Add
</button>
</form>
<div class="empty-task">&nbsp;</div>
<h2 id="done-task-heading">Done</h2>
<ol class="task-list" id="done-task-list"></ol>
</div>
</article>
<script type="text/html" id="task-template">
<li class="task">
<span class="task-name" />
<span class="task-actions">
<button type="button" class="task-done-button">
<img src="images/done.png" alt="" /> Done
</button>
<button type="button" class="task-active-button">
<img src="images/not_done.png" alt="" /> Undo
</button>
<button type="button" class="task-remove-button">
<img src="images/remove.png" alt="" /> Delete
</button>
</span>
</li>
</script>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,6 +0,0 @@
# Main entry point to the Dropbox API.
class Dropbox
constructor: (options) ->
@client = new DropboxClient options
# NOTE: this is not yet implemented.

View file

@ -1,59 +0,0 @@
# Information about a failed call to the Dropbox API.
class Dropbox.ApiError
# @property {Number} the HTTP error code (e.g., 403)
status: undefined
# @property {String} the HTTP method of the failed request (e.g., 'GET')
method: undefined
# @property {String} the URL of the failed request
url: undefined
# @property {?String} the body of the HTTP error response; can be null if
# the error was caused by a network failure or by a security issue
responseText: undefined
# @property {?Object} the result of parsing the JSON in the HTTP error
# response; can be null if the API server didn't return JSON, or if the
# HTTP response body is unavailable
response: undefined
# Wraps a failed XHR call to the Dropbox API.
#
# @param {String} method the HTTP verb of the API request (e.g., 'GET')
# @param {String} url the URL of the API request
# @param {XMLHttpRequest} xhr the XMLHttpRequest instance of the failed
# request
constructor: (xhr, @method, @url) ->
@status = xhr.status
if xhr.responseType
try
text = xhr.response or xhr.responseText
catch e
try
text = xhr.responseText
catch e
text = null
else
try
text = xhr.responseText
catch e
text = null
if text
try
@responseText = text.toString()
@response = JSON.parse text
catch e
@response = null
else
@responseText = '(no response)'
@response = null
# Used when the error is printed out by developers.
toString: ->
"Dropbox API error #{@status} from #{@method} #{@url} :: #{@responseText}"
# Used by some testing frameworks.
inspect: ->
@toString()

View file

@ -1,72 +0,0 @@
# node.js implementation of atob and btoa
if window?
if window.atob and window.btoa
atob = (string) -> window.atob string
btoa = (base64) -> window.btoa base64
else
# IE < 10 doesn't implement the standard atob / btoa functions.
base64Digits =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
btoaNibble = (accumulator, bytes, result) ->
limit = 3 - bytes
accumulator <<= limit * 8
i = 3
while i >= limit
result.push base64Digits.charAt((accumulator >> (i * 6)) & 0x3F)
i -= 1
i = bytes
while i < 3
result.push '='
i += 1
null
atobNibble = (accumulator, digits, result) ->
limit = 4 - digits
accumulator <<= limit * 6
i = 2
while i >= limit
result.push String.fromCharCode((accumulator >> (8 * i)) & 0xFF)
i -= 1
null
btoa = (string) ->
result = []
accumulator = 0
bytes = 0
for i in [0...string.length]
accumulator = (accumulator << 8) | string.charCodeAt(i)
bytes += 1
if bytes is 3
btoaNibble accumulator, bytes, result
accumulator = bytes = 0
if bytes > 0
btoaNibble accumulator, bytes, result
result.join ''
atob = (base64) ->
result = []
accumulator = 0
digits = 0
for i in [0...base64.length]
digit = base64.charAt i
break if digit is '='
accumulator = (accumulator << 6) | base64Digits.indexOf(digit)
digits += 1
if digits is 4
atobNibble accumulator, digits, result
accumulator = digits = 0
if digits > 0
atobNibble accumulator, digits, result
result.join ''
else
# NOTE: the npm packages atob and btoa don't do base64-encoding correctly.
atob = (arg) ->
buffer = new Buffer arg, 'base64'
(String.fromCharCode(buffer[i]) for i in [0...buffer.length]).join ''
btoa = (arg) ->
buffer = new Buffer(arg.charCodeAt(i) for i in [0...arg.length])
buffer.toString 'base64'

File diff suppressed because it is too large Load diff

View file

@ -1,342 +0,0 @@
# Base class for drivers that run in the browser.
#
# Inheriting from this class makes a driver use HTML5 localStorage to preserve
# OAuth tokens across page reloads.
class Dropbox.Drivers.BrowserBase
# Sets up the OAuth driver.
#
# Subclasses should pass the options object they receive to the superclass
# constructor.
#
# @param {?Object} options the advanced settings below
# @option options {Boolean} rememberUser if true, the user's OAuth tokens are
# saved in localStorage; if you use this, you MUST provide a UI item that
# calls signOut() on Dropbox.Client, to let the user "log out" of the
# application
# @option options {String} scope embedded in the localStorage key that holds
# the authentication data; useful for having multiple OAuth tokens in a
# single application
constructor: (options) ->
@rememberUser = options?.rememberUser or false
@scope = options?.scope or 'default'
@storageKey = null
# The magic happens here.
onAuthStateChange: (client, callback) ->
@setStorageKey client
switch client.authState
when DropboxClient.RESET
@loadCredentials (credentials) =>
return callback() unless credentials
if credentials.authState # Incomplete authentication.
client.setCredentials credentials
return callback()
# There is an old access token. Only use it if the app supports
# logout.
unless @rememberUser
@forgetCredentials()
return callback()
# Verify that the old access token still works.
client.setCredentials credentials
client.getUserInfo (error) =>
if error
client.reset()
@forgetCredentials callback
else
callback()
when DropboxClient.REQUEST
@storeCredentials client.credentials(), callback
when DropboxClient.DONE
if @rememberUser
return @storeCredentials(client.credentials(), callback)
@forgetCredentials callback
when DropboxClient.SIGNED_OFF
@forgetCredentials callback
when DropboxClient.ERROR
@forgetCredentials callback
else
callback()
@
# Computes the @storageKey used by loadCredentials and forgetCredentials.
#
# @private
# This is called by onAuthStateChange.
#
# @param {Dropbox.Client} client the client instance that is running the
# authorization process
# @return {Dropbox.Driver} this, for easy call chaining
setStorageKey: (client) ->
# NOTE: the storage key is dependent on the app hash so that multiple apps
# hosted off the same server don't step on eachother's toes
@storageKey = "dropbox-auth:#{@scope}:#{client.appHash()}"
@
# Stores a Dropbox.Client's credentials to localStorage.
#
# @private
# onAuthStateChange calls this method during the authentication flow.
#
# @param {Object} credentials the result of a Drobpox.Client#credentials call
# @param {function()} callback called when the storing operation is complete
# @return {Dropbox.Drivers.BrowserBase} this, for easy call chaining
storeCredentials: (credentials, callback) ->
localStorage.setItem @storageKey, JSON.stringify(credentials)
callback()
@
# Retrieves a token and secret from localStorage.
#
# @private
# onAuthStateChange calls this method during the authentication flow.
#
# @param {function(?Object)} callback supplied with the credentials object
# stored by a previous call to
# Dropbox.Drivers.BrowserBase#storeCredentials; null if no credentials were
# stored, or if the previously stored credentials were deleted
# @return {Dropbox.Drivers.BrowserBase} this, for easy call chaining
loadCredentials: (callback) ->
jsonString = localStorage.getItem @storageKey
unless jsonString
callback null
return @
try
callback JSON.parse(jsonString)
catch e
# Parse errors.
callback null
@
# Deletes information previously stored by a call to storeCredentials.
#
# @private
# onAuthStateChange calls this method during the authentication flow.
#
# @param {function()} callback called after the credentials are deleted
# @return {Dropbox.Drivers.BrowserBase} this, for easy call chaining
forgetCredentials: (callback) ->
localStorage.removeItem @storageKey
callback()
@
# Wrapper for window.location, for testing purposes.
#
# @return {String} the current page's URL
@currentLocation: ->
window.location.href
# OAuth driver that uses a redirect and localStorage to complete the flow.
class Dropbox.Drivers.Redirect extends Dropbox.Drivers.BrowserBase
# Sets up the redirect-based OAuth driver.
#
# @param {?Object} options the advanced settings below
# @option options {Boolean} useQuery if true, the page will receive OAuth
# data as query parameters; by default, the page receives OAuth data in
# the fragment part of the URL (the string following the #,
# available as document.location.hash), to avoid confusing the server
# generating the page
# @option options {Boolean} rememberUser if true, the user's OAuth tokens are
# saved in localStorage; if you use this, you MUST provide a UI item that
# calls signOut() on Dropbox.Client, to let the user "log out" of the
# application
# @option options {String} scope embedded in the localStorage key that holds
# the authentication data; useful for having multiple OAuth tokens in a
# single application
constructor: (options) ->
super options
@useQuery = options?.useQuery or false
@receiverUrl = @computeUrl options
@tokenRe = new RegExp "(#|\\?|&)oauth_token=([^&#]+)(&|#|$)"
# Forwards the authentication process from REQUEST to AUTHORIZED on redirect.
onAuthStateChange: (client, callback) ->
superCall = do => => super client, callback
@setStorageKey client
if client.authState is DropboxClient.RESET
@loadCredentials (credentials) =>
if credentials and credentials.authState # Incomplete authentication.
if credentials.token is @locationToken() and
credentials.authState is DropboxClient.REQUEST
# locationToken matched, so the redirect happened
credentials.authState = DropboxClient.AUTHORIZED
return @storeCredentials credentials, superCall
else
# The authentication process broke down, start over.
return @forgetCredentials superCall
superCall()
else
superCall()
# URL of the current page, since the user will be sent right back.
url: ->
@receiverUrl
# Redirects to the authorize page.
doAuthorize: (authUrl) ->
window.location.assign authUrl
# Pre-computes the return value of url.
computeUrl: ->
querySuffix = "_dropboxjs_scope=#{encodeURIComponent @scope}"
location = Dropbox.Drivers.BrowserBase.currentLocation()
if location.indexOf('#') is -1
fragment = null
else
locationPair = location.split '#', 2
location = locationPair[0]
fragment = locationPair[1]
if @useQuery
if location.indexOf('?') is -1
location += "?#{querySuffix}" # No query string in the URL.
else
location += "&#{querySuffix}" # The URL already has a query string.
else
fragment = "?#{querySuffix}"
if fragment
location + '#' + fragment
else
location
# Figures out if the user completed the OAuth flow based on the current URL.
#
# @return {?String} the OAuth token that the user just authorized, or null if
# the user accessed this directly, without having authorized a token
locationToken: ->
location = Dropbox.Drivers.BrowserBase.currentLocation()
# Check for the scope.
scopePattern = "_dropboxjs_scope=#{encodeURIComponent @scope}&"
return null if location.indexOf?(scopePattern) is -1
# Extract the token.
match = @tokenRe.exec location
if match then decodeURIComponent(match[2]) else null
# OAuth driver that uses a popup window and postMessage to complete the flow.
class Dropbox.Drivers.Popup extends Dropbox.Drivers.BrowserBase
# Sets up a popup-based OAuth driver.
#
# @param {?Object} options one of the settings below; leave out the argument
# to use the current location for redirecting
# @option options {Boolean} rememberUser if true, the user's OAuth tokens are
# saved in localStorage; if you use this, you MUST provide a UI item that
# calls signOut() on Dropbox.Client, to let the user "log out" of the
# application
# @option options {String} scope embedded in the localStorage key that holds
# the authentication data; useful for having multiple OAuth tokens in a
# single application
# @option options {String} receiverUrl URL to the page that receives the
# /authorize redirect and performs the postMessage
# @option options {Boolean} noFragment if true, the receiverUrl will be used
# as given; by default, a hash "#" is appended to URLs that don't have
# one, so the OAuth token is received as a URL fragment and does not hit
# the file server
# @option options {String} receiverFile the URL to the receiver page will be
# computed by replacing the file name (everything after the last /) of
# the current location with this parameter's value
constructor: (options) ->
super options
@receiverUrl = @computeUrl options
@tokenRe = new RegExp "(#|\\?|&)oauth_token=([^&#]+)(&|#|$)"
# Removes credentials stuck in the REQUEST stage.
onAuthStateChange: (client, callback) ->
superCall = do => => super client, callback
@setStorageKey client
if client.authState is DropboxClient.RESET
@loadCredentials (credentials) =>
if credentials and credentials.authState # Incomplete authentication.
# The authentication process broke down, start over.
return @forgetCredentials superCall
superCall()
else
superCall()
# Shows the authorization URL in a pop-up, waits for it to send a message.
doAuthorize: (authUrl, token, tokenSecret, callback) ->
@listenForMessage token, callback
@openWindow authUrl
# URL of the redirect receiver page, which posts a message back to this page.
url: ->
@receiverUrl
# Pre-computes the return value of url.
computeUrl: (options) ->
if options
if options.receiverUrl
if options.noFragment or options.receiverUrl.indexOf('#') isnt -1
return options.receiverUrl
else
return options.receiverUrl + '#'
else if options.receiverFile
fragments = Dropbox.Drivers.BrowserBase.currentLocation().split '/'
fragments[fragments.length - 1] = options.receiverFile
if options.noFragment
return fragments.join('/')
else
return fragments.join('/') + '#'
Dropbox.Drivers.BrowserBase.currentLocation()
# Creates a popup window.
#
# @param {String} url the URL that will be loaded in the popup window
# @return {?DOMRef} reference to the opened window, or null if the call
# failed
openWindow: (url) ->
window.open url, '_dropboxOauthSigninWindow', @popupWindowSpec(980, 700)
# Spec string for window.open to create a nice popup.
#
# @param {Number} popupWidth the desired width of the popup window
# @param {Number} popupHeight the desired height of the popup window
# @return {String} spec string for the popup window
popupWindowSpec: (popupWidth, popupHeight) ->
# Metrics for the current browser window.
x0 = window.screenX ? window.screenLeft
y0 = window.screenY ? window.screenTop
width = window.outerWidth ? document.documentElement.clientWidth
height = window.outerHeight ? document.documentElement.clientHeight
# Computed popup window metrics.
popupLeft = Math.round x0 + (width - popupWidth) / 2
popupTop = Math.round y0 + (height - popupHeight) / 2.5
popupLeft = x0 if popupLeft < x0
popupTop = y0 if popupTop < y0
# The specification string.
"width=#{popupWidth},height=#{popupHeight}," +
"left=#{popupLeft},top=#{popupTop}" +
'dialog=yes,dependent=yes,scrollbars=yes,location=yes'
# Listens for a postMessage from a previously opened popup window.
#
# @param {String} token the token string that must be received from the popup
# window
# @param {function()} called when the received message matches the token
listenForMessage: (token, callback) ->
listener = (event) =>
match = @tokenRe.exec event.data.toString()
if match and decodeURIComponent(match[2]) is token
window.removeEventListener 'message', listener
callback()
window.addEventListener 'message', listener, false
# Communicates with the driver from the OAuth receiver page.
@oauthReceiver: ->
window.addEventListener 'load', ->
opener = window.opener
if window.parent isnt window.top
opener or= window.parent
if opener
try
opener.postMessage window.location.href, '*'
catch e
# IE 9 doesn't support opener.postMessage for popup windows.
window.close()

View file

@ -1,202 +0,0 @@
DropboxChromeOnMessage = null
DropboxChromeSendMessage = null
if chrome?
# v2 manifest APIs.
if chrome.runtime
if chrome.runtime.onMessage
DropboxChromeOnMessage = chrome.runtime.onMessage
if chrome.runtime.sendMessage
DropboxChromeSendMessage = (m) -> chrome.runtime.sendMessage m
# v1 manifest APIs.
if chrome.extension
if chrome.extension.onMessage
DropboxChromeOnMessage or= chrome.extension.onMessage
if chrome.extension.sendMessage
DropboxChromeSendMessage or= (m) -> chrome.extension.sendMessage m
# Apps that use the v2 manifest don't get messenging in Chrome 25.
unless DropboxChromeOnMessage
do ->
pageHack = (page) ->
if page.Dropbox
Dropbox.Drivers.Chrome::onMessage =
page.Dropbox.Drivers.Chrome.onMessage
Dropbox.Drivers.Chrome::sendMessage =
page.Dropbox.Drivers.Chrome.sendMessage
else
page.Dropbox = Dropbox
Dropbox.Drivers.Chrome::onMessage = new Dropbox.EventSource
Dropbox.Drivers.Chrome::sendMessage =
(m) -> Dropbox.Drivers.Chrome::onMessage.dispatch m
if chrome.extension and chrome.extension.getBackgroundPage
if page = chrome.extension.getBackgroundPage()
return pageHack(page)
if chrome.runtime and chrome.runtime.getBackgroundPage
return chrome.runtime.getBackgroundPage (page) -> pageHack page
# OAuth driver specialized for Chrome apps and extensions.
class Dropbox.Drivers.Chrome
# @property {Chrome.Event<>, Dropbox.EventSource<>} fires non-cancelable
# events when Dropbox.Drivers.Chrome#sendMessage is called
onMessage: DropboxChromeOnMessage
# Sends a message across the Chrome extension / application.
#
# When a message is sent, the listeners registered to
#
# @param {Object} message the message to be sent
sendMessage: DropboxChromeSendMessage
# Expans an URL relative to the Chrome extension / application root.
#
# @param {String} url a resource URL relative to the extension root
# @return {String} the absolute resource URL
expandUrl: (url) ->
if chrome.runtime and chrome.runtime.getURL
return chrome.runtime.getURL(url)
if chrome.extension and chrome.extension.getURL
return chrome.extension.getURL(url)
url
# @param {?Object} options the settings below
# @option {String} receiverPath the path of page that receives the /authorize
# redirect and performs the postMessage; the path should be relative to the
# extension folder; by default, is 'chrome_oauth_receiver.html'
constructor: (options) ->
receiverPath = (options and options.receiverPath) or
'chrome_oauth_receiver.html'
@receiverUrl = @expandUrl receiverPath
@tokenRe = new RegExp "(#|\\?|&)oauth_token=([^&#]+)(&|#|$)"
scope = (options and options.scope) or 'default'
@storageKey = "dropbox_js_#{scope}_credentials"
# Saves token information when appropriate.
onAuthStateChange: (client, callback) ->
switch client.authState
when Dropbox.Client.RESET
@loadCredentials (credentials) =>
if credentials
if credentials.authState
# Stuck authentication process, reset.
return @forgetCredentials(callback)
client.setCredentials credentials
callback()
when Dropbox.Client.DONE
@storeCredentials client.credentials(), callback
when Dropbox.Client.SIGNED_OFF
@forgetCredentials callback
when Dropbox.Client.ERROR
@forgetCredentials callback
else
callback()
# Shows the authorization URL in a pop-up, waits for it to send a message.
doAuthorize: (authUrl, token, tokenSecret, callback) ->
window = handle: null
@listenForMessage token, window, callback
@openWindow authUrl, (handle) -> window.handle = handle
# Creates a popup window.
#
# @param {String} url the URL that will be loaded in the popup window
# @param {function(Object)} callback called with a handle that can be passed
# to Dropbox.Driver.Chrome#closeWindow
# @return {Dropbox.Driver.Chrome} this
openWindow: (url, callback) ->
if chrome.tabs and chrome.tabs.create
chrome.tabs.create url: url, active: true, pinned: false, (tab) ->
callback tab
return @
if chrome.app and chrome.app.window and chrome.app.window.create
chrome.app.window.create url, frame: 'none', id: 'dropbox-auth',
(window) -> callback window
return @
@
# Closes a window that was previously opened with openWindow.
#
# @param {Object} handle the object passed to an openWindow callback
closeWindow: (handle) ->
if chrome.tabs and chrome.tabs.remove and handle.id
chrome.tabs.remove handle.id
return @
if chrome.app and chrome.app.window and handle.close
handle.close()
return @
@
# URL of the redirect receiver page that messages the app / extension.
url: ->
@receiverUrl
# Listens for a postMessage from a previously opened tab.
#
# @param {String} token the token string that must be received from the tab
# @param {Object} window a JavaScript object whose "handle" property is a
# window handle passed to the callback of a
# Dropbox.Driver.Chrome#openWindow call
# @param {function()} called when the received message matches the token
listenForMessage: (token, window, callback) ->
listener = (message, sender) =>
# Reject messages not coming from the OAuth receiver window.
if sender and sender.tab
unless sender.tab.url.substring(0, @receiverUrl.length) is @receiverUrl
return
match = @tokenRe.exec message.dropbox_oauth_receiver_href or ''
if match and decodeURIComponent(match[2]) is token
@closeWindow window.handle if window.handle
@onMessage.removeListener listener
callback()
@onMessage.addListener listener
# Stores a Dropbox.Client's credentials to local storage.
#
# @private
# onAuthStateChange calls this method during the authentication flow.
#
# @param {Object} credentials the result of a Drobpox.Client#credentials call
# @param {function()} callback called when the storing operation is complete
# @return {Dropbox.Drivers.BrowserBase} this, for easy call chaining
storeCredentials: (credentials, callback) ->
items= {}
items[@storageKey] = credentials
chrome.storage.local.set items, callback
@
# Retrieves a token and secret from localStorage.
#
# @private
# onAuthStateChange calls this method during the authentication flow.
#
# @param {function(?Object)} callback supplied with the credentials object
# stored by a previous call to
# Dropbox.Drivers.BrowserBase#storeCredentials; null if no credentials were
# stored, or if the previously stored credentials were deleted
# @return {Dropbox.Drivers.BrowserBase} this, for easy call chaining
loadCredentials: (callback) ->
chrome.storage.local.get @storageKey, (items) =>
callback items[@storageKey] or null
@
# Deletes information previously stored by a call to storeCredentials.
#
# @private
# onAuthStateChange calls this method during the authentication flow.
#
# @param {function()} callback called after the credentials are deleted
# @return {Dropbox.Drivers.BrowserBase} this, for easy call chaining
forgetCredentials: (callback) ->
chrome.storage.local.remove @storageKey, callback
@
# Communicates with the driver from the OAuth receiver page.
@oauthReceiver: ->
window.addEventListener 'load', ->
driver = new Dropbox.Drivers.Chrome()
driver.sendMessage dropbox_oauth_receiver_href: window.location.href
window.close() if window.close

View file

@ -1,87 +0,0 @@
# OAuth driver that redirects the browser to a node app to complete the flow.
#
# This is useful for testing node.js libraries and applications.
class Dropbox.Drivers.NodeServer
# Starts up the node app that intercepts the browser redirect.
#
# @param {?Object} options one or more of the options below
# @option options {Number} port the number of the TCP port that will receive
# HTTP requests
# @param {String} faviconFile the path to a file that will be served at
# /favicon.ico
constructor: (options) ->
@port = options?.port or 8912
@faviconFile = options?.favicon or null
# Calling require in the constructor because this doesn't work in browsers.
@fs = require 'fs'
@http = require 'http'
@open = require 'open'
@callbacks = {}
@urlRe = new RegExp "^/oauth_callback\\?"
@tokenRe = new RegExp "(\\?|&)oauth_token=([^&]+)(&|$)"
@createApp()
# URL to the node.js OAuth callback handler.
url: ->
"http://localhost:#{@port}/oauth_callback"
# Opens the token
doAuthorize: (authUrl, token, tokenSecret, callback) ->
@callbacks[token] = callback
@openBrowser authUrl
# Opens the given URL in a browser.
openBrowser: (url) ->
unless url.match /^https?:\/\//
throw new Error("Not a http/https URL: #{url}")
@open url
# Creates and starts up an HTTP server that will intercept the redirect.
createApp: ->
@app = @http.createServer (request, response) =>
@doRequest request, response
@app.listen @port
# Shuts down the HTTP server.
#
# The driver will become unusable after this call.
closeServer: ->
@app.close()
# Reads out an /authorize callback.
doRequest: (request, response) ->
if @urlRe.exec request.url
match = @tokenRe.exec request.url
if match
token = decodeURIComponent match[2]
if @callbacks[token]
@callbacks[token]()
delete @callbacks[token]
data = ''
request.on 'data', (dataFragment) -> data += dataFragment
request.on 'end', =>
if @faviconFile and (request.url is '/favicon.ico')
@sendFavicon response
else
@closeBrowser response
# Renders a response that will close the browser window used for OAuth.
closeBrowser: (response) ->
closeHtml = """
<!doctype html>
<script type="text/javascript">window.close();</script>
<p>Please close this window.</p>
"""
response.writeHead(200,
{'Content-Length': closeHtml.length, 'Content-Type': 'text/html' })
response.write closeHtml
response.end
# Renders the favicon file.
sendFavicon: (response) ->
@fs.readFile @faviconFile, (error, data) ->
response.writeHead(200,
{ 'Content-Length': data.length, 'Content-Type': 'image/x-icon' })
response.write data
response.end

View file

@ -1,59 +0,0 @@
# Documentation for the interface to a Dropbox OAuth driver.
class Dropbox.AuthDriver
# The callback URL that should be supplied to the OAuth /authorize call.
#
# The driver must be able to intercept redirects to the returned URL, in
# order to know when a user has completed the authorization flow.
#
# @return {String} an absolute URL
url: ->
'https://some.url'
# Redirects users to /authorize and waits for them to complete the flow.
#
# This method is called when the OAuth process reaches the REQUEST state,
# meaning the client has a request token that must be authorized by the user.
#
# @param {String} authUrl the URL that users should be sent to in order to
# authorize the application's token; this points to a Web page on
# Dropbox' servers
# @param {String} token the OAuth token that the user is authorizing; this
# will be provided by the Dropbox servers as a query parameter when the
# user is redirected to the URL returned by the driver's url() method
# @param {String} tokenSecret the secret associated with the given OAuth
# token; the driver may store this together with the token
# @param {function()} callback called when users have completed the
# authorization flow; the driver should call this when Dropbox redirects
# users to the URL returned by the url() method, and the 'token' query
# parameter matches the value of the token parameter
doAuthorize: (authUrl, token, tokenSecret, callback) ->
callback 'oauth-token'
# Called when there is some progress in the OAuth process.
#
# The OAuth process goes through the following states:
#
# * Dropbox.Client.RESET - the client has no OAuth token, and is about to
# ask for a request token
# * Dropbox.Client.REQUEST - the client has a request OAuth token, and the
# user must go to an URL on the Dropbox servers to authorize the token
# * Dropbox.Client.AUTHORIZED - the client has a request OAuth token that
# was authorized by the user, and is about to exchange it for an access
# token
# * Dropbox.Client.DONE - the client has an access OAuth token that can be
# used for all API calls; the OAuth process is complete, and the callback
# passed to authorize is about to be called
# * Dropbox.Client.SIGNED_OFF - the client's Dropbox.Client#signOut() was
# called, and the client's OAuth token was invalidated
# * Dropbox.Client.ERROR - the client encounered an error during the OAuth
# process; the callback passed to authorize is about to be called with the
# error information
#
# @param {Dropbox.Client} client the client performing the OAuth process
# @param {function()} callback called when onAuthStateChange acknowledges the
# state change
onAuthStateChange: (client, callback) ->
callback()
# Namespace for authentication drivers.
Dropbox.Drivers = {}

View file

@ -1,71 +0,0 @@
# Event dispatch following a publisher-subscriber (PubSub) model.
class Dropbox.EventSource
# Sets up an event source (publisher).
#
# @param {?Object} options one or more of the options below
# @option options {Boolean} cancelable if true,
constructor: (options) ->
@_cancelable = options and options.cancelable
@_listeners = []
# Registers a listener (subscriber) to events coming from this source.
#
# This is a simplified version of the addEventListener DOM API. Listeners
# must be functions, and they can be removed by calling removeListener.
#
# This method is idempotent, so a function will not be added to the list of
# listeners if was previously added.
#
# @param {function(Object)} listener called every time an event is fired; if
# the event is cancelable, the function can return false to cancel the
# event, or any other value to allow it to propagate; the return value is
# ignored for non-cancelable events
# @return {Dropbox.EventSource} this, for easy call chaining
addListener: (listener) ->
unless typeof listener is 'function'
throw new TypeError 'Invalid listener type; expected function'
unless listener in @_listeners
@_listeners.push listener
@
# Un-registers a listener (subscriber) previously added by addListener.
#
# This is a simplified version of the removeEventListener DOM API. The
# listener must be exactly the same object supplied to addListener.
#
# This method is idempotent, so it will fail silently if the given listener
# is not registered as a subscriber.
#
# @param {function(Object)} listener function that was previously passed in
# an addListener call
# @return {Dropbox.EventSource} this, for easy call chaining
removeListener: (listener) ->
if @_listeners.indexOf
# IE9+
index = @_listeners.indexOf listener
@_listeners.splice index, 1 if index isnt -1
else
# IE8 doesn't implement Array#indexOf in ES5.
for subscriber, i in @_listeners
if subscriber is listener
@_listeners.splice i, 1
break
@
# Informs the listeners (subscribers) that an event occurred.
#
# Event sources configured for non-cancelable events call all listeners in an
# unspecified order. Sources configured for cancelable events stop calling
# listeners as soon as one listener returns false value.
#
# @param {Object} event passed to all the registered listeners
# @return {Boolean} sources of cancelable events return false if the event
# was canceled and true otherwise; sources of non-cancelable events always
# return true
dispatch: (event) ->
for listener in @_listeners
returnValue = listener event
if @_cancelable and returnValue is false
return false
true

View file

@ -1,180 +0,0 @@
# HMAC-SHA1 implementation heavily inspired from
# http://pajhome.org.uk/crypt/md5/sha1.js
# Base64-encoded HMAC-SHA1.
#
# @param {String} string the ASCII string to be signed
# @param {String} key the HMAC key
# @return {String} a base64-encoded HMAC of the given string and key
base64HmacSha1 = (string, key) ->
arrayToBase64 hmacSha1(stringToArray(string), stringToArray(key),
string.length, key.length)
# Base64-encoded SHA1.
#
# @param {String} string the ASCII string to be hashed
# @return {String} a base64-encoded SHA1 hash of the given string
base64Sha1 = (string) ->
arrayToBase64 sha1(stringToArray(string), string.length)
# SHA1 and HMAC-SHA1 versions that use the node.js builtin crypto.
unless window?
crypto = require 'crypto'
base64HmacSha1 = (string, key) ->
hmac = crypto.createHmac 'sha1', key
hmac.update string
hmac.digest 'base64'
base64Sha1 = (string) ->
hash = crypto.createHash 'sha1'
hash.update string
hash.digest 'base64'
# HMAC-SHA1 implementation.
#
# @param {Array} string the HMAC input, as an array of 32-bit numbers
# @param {Array} key the HMAC input, as an array of 32-bit numbers
# @param {Number} length the length of the HMAC input, in bytes
# @return {Array} the HMAC output, as an array of 32-bit numbers
hmacSha1 = (string, key, length, keyLength) ->
if key.length > 16
key = sha1 key, keyLength
ipad = (key[i] ^ 0x36363636 for i in [0...16])
opad = (key[i] ^ 0x5C5C5C5C for i in [0...16])
hash1 = sha1 ipad.concat(string), 64 + length
sha1 opad.concat(hash1), 64 + 20
# SHA1 implementation.
#
# @param {Array} string the SHA1 input, as an array of 32-bit numbers; the
# computation trashes the array
# @param {Number} length the number of bytes in the SHA1 input; used in the
# SHA1 padding algorithm
# @return {Array<Number>} the SHA1 output, as an array of 32-bit numbers
sha1 = (string, length) ->
string[length >> 2] |= 1 << (31 - ((length & 0x03) << 3))
string[(((length + 8) >> 6) << 4) + 15] = length << 3
state = Array 80
a = 1732584193 # 0x67452301
b = -271733879 # 0xefcdab89
c = -1732584194 # 0x98badcfe
d = 271733878 # 0x10325476
e = -1009589776 # 0xc3d2e1f0
i = 0
limit = string.length
# Uncomment the line below to debug packing.
# console.log string.map(xxx)
while i < limit
a0 = a
b0 = b
c0 = c
d0 = d
e0 = e
for j in [0...80]
if j < 16
state[j] = string[i + j]
else
state[j] = rotateLeft32 state[j - 3] ^ state[j - 8] ^ state[j - 14] ^
state[j - 16], 1
if j < 20
ft = (b & c) | ((~b) & d)
kt = 1518500249 # 0x5a827999
else if j < 40
ft = b ^ c ^ d
kt = 1859775393 # 0x6ed9eba1
else if j < 60
ft = (b & c) | (b & d) | (c & d)
kt = -1894007588 # 0x8f1bbcdc
else
ft = b ^ c ^ d
kt = -899497514 # 0xca62c1d6
t = add32 add32(rotateLeft32(a, 5), ft), add32(add32(e, state[j]), kt)
e = d
d = c
c = rotateLeft32 b, 30
b = a
a = t
# Uncomment the line below to debug block computation.
# console.log [xxx(a), xxx(b), xxx(c), xxx(d), xxx(e)]
a = add32 a, a0
b = add32 b, b0
c = add32 c, c0
d = add32 d, d0
e = add32 e, e0
i += 16
# Uncomment the line below to see the input to the base64 encoder.
# console.log [xxx(a), xxx(b), xxx(c), xxx(d), xxx(e)]
[a, b, c, d, e]
###
# Uncomment the definition below for debugging.
#
# Returns the hexadecimal representation of a 32-bit number.
xxx = (n) ->
if n < 0
n = (1 << 30) * 4 + n
n.toString 16
###
# Rotates a 32-bit word.
#
# @param {Number} value the 32-bit number to be rotated
# @param {Number} count the number of bits (0..31) to rotate by
# @return {Number} the rotated value
rotateLeft32 = (value, count) ->
(value << count) | (value >>> (32 - count))
# 32-bit unsigned addition.
#
# @param {Number} a, b the 32-bit numbers to be added modulo 2^32
# @return {Number} the 32-bit representation of a + b
add32 = (a, b) ->
low = (a & 0xFFFF) + (b & 0xFFFF)
high = (a >> 16) + (b >> 16) + (low >> 16)
(high << 16) | (low & 0xFFFF)
# Converts a 32-bit number array into a base64-encoded string.
#
# @param {Array} an array of big-endian 32-bit numbers
# @return {String} base64 encoding of the given array of numbers
arrayToBase64 = (array) ->
string = ""
i = 0
limit = array.length * 4
while i < limit
i2 = i
trit = ((array[i2 >> 2] >> ((3 - (i2 & 3)) << 3)) & 0xFF) << 16
i2 += 1
trit |= ((array[i2 >> 2] >> ((3 - (i2 & 3)) << 3)) & 0xFF) << 8
i2 += 1
trit |= (array[i2 >> 2] >> ((3 - (i2 & 3)) << 3)) & 0xFF
string += _base64Digits[(trit >> 18) & 0x3F]
string += _base64Digits[(trit >> 12) & 0x3F]
i += 1
if i >= limit
string += '='
else
string += _base64Digits[(trit >> 6) & 0x3F]
i += 1
if i >= limit
string += '='
else
string += _base64Digits[trit & 0x3F]
i += 1
string
_base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
# Converts an ASCII string into array of 32-bit numbers.
stringToArray = (string) ->
array = []
mask = 0xFF
for i in [0...string.length]
array[i >> 2] |= (string.charCodeAt(i) & mask) << ((3 - (i & 3)) << 3)
array

View file

@ -1,162 +0,0 @@
# Stripped-down OAuth implementation that works with the Dropbox API server.
class Dropbox.Oauth
# Creates an Oauth instance that manages an application's keys and token.
#
# @param {Object} options the following properties
# @option options {String} key the Dropbox application's key (consumer key,
# in OAuth vocabulary); browser-side applications should use
# Dropbox.encodeKey to obtain an encoded key string, and pass it as the
# key option
# @option options {String} secret the Dropbox application's secret (consumer
# secret, in OAuth vocabulary); browser-side applications should not use
# the secret option; instead, they should pass the result of
# Dropbox.encodeKey as the key option
constructor: (options) ->
@key = @k = null
@secret = @s = null
@token = null
@tokenSecret = null
@_appHash = null
@reset options
# Creates an Oauth instance that manages an application's keys and token.
#
# @see Dropbox.Oauth#constructor for options
reset: (options) ->
if options.secret
@k = @key = options.key
@s = @secret = options.secret
@_appHash = null
else if options.key
@key = options.key
@secret = null
secret = atob dropboxEncodeKey(@key).split('|', 2)[1]
[k, s] = secret.split '?', 2
@k = decodeURIComponent k
@s = decodeURIComponent s
@_appHash = null
else
unless @k
throw new Error('No API key supplied')
if options.token
@setToken options.token, options.tokenSecret
else
@setToken null, ''
# Sets the OAuth token to be used for future requests.
setToken: (token, tokenSecret) ->
if token and (not tokenSecret)
throw new Error('No secret supplied with the user token')
@token = token
@tokenSecret = tokenSecret || ''
# This is part of signing, but it's set here so it can be cached.
@hmacKey = Dropbox.Xhr.urlEncodeValue(@s) + '&' +
Dropbox.Xhr.urlEncodeValue(tokenSecret)
null
# Computes the value of the Authorization HTTP header.
#
# This method mutates the params object, and removes all the OAuth-related
# parameters from it.
#
# @param {String} method the HTTP method used to make the request ('GET',
# 'POST', etc)
# @param {String} url the HTTP URL (e.g. "http://www.example.com/photos")
# that receives the request
# @param {Object} params an associative array (hash) containing the HTTP
# request parameters; the parameters should include the oauth_
# parameters generated by calling {Dropbox.Oauth#boilerplateParams}
# @return {String} the value to be used for the Authorization HTTP header
authHeader: (method, url, params) ->
@addAuthParams method, url, params
# Collect all the OAuth parameters.
oauth_params = []
for param, value of params
if param.substring(0, 6) == 'oauth_'
oauth_params.push param
oauth_params.sort()
# Remove the parameters from the params hash and add them to the header.
header = []
for param in oauth_params
header.push Dropbox.Xhr.urlEncodeValue(param) + '="' +
Dropbox.Xhr.urlEncodeValue(params[param]) + '"'
delete params[param]
# NOTE: the space after the comma is optional in the OAuth spec, so we'll
# skip it to save some bandwidth
'OAuth ' + header.join(',')
# Generates OAuth-required HTTP parameters.
#
# This method mutates the params object, and adds the OAuth-related
# parameters to it.
#
# @param {String} method the HTTP method used to make the request ('GET',
# 'POST', etc)
# @param {String} url the HTTP URL (e.g. "http://www.example.com/photos")
# that receives the request
# @param {Object} params an associative array (hash) containing the HTTP
# request parameters; the parameters should include the oauth_
# parameters generated by calling {Dropbox.Oauth#boilerplateParams}
# @return {String} the value to be used for the Authorization HTTP header
addAuthParams: (method, url, params) ->
# Augment params with OAuth parameters.
@boilerplateParams params
params.oauth_signature = @signature method, url, params
params
# Adds boilerplate OAuth parameters to a request's parameter list.
#
# This should be called right before signing a request, to maximize the
# chances that the OAuth timestamp will be fresh.
#
# @param {Object} params an associative array (hash) containing the
# parameters for an OAuth request; the boilerplate parameters will be
# added to this hash
# @return {Object} params
boilerplateParams: (params) ->
params.oauth_consumer_key = @k
params.oauth_nonce = @nonce()
params.oauth_signature_method = 'HMAC-SHA1'
params.oauth_token = @token if @token
params.oauth_timestamp = Math.floor(Date.now() / 1000)
params.oauth_version = '1.0'
params
# Generates a nonce for an OAuth request.
#
# @return {String} the nonce to be used as the oauth_nonce parameter
nonce: ->
Date.now().toString(36) + Math.random().toString(36)
# Computes the signature for an OAuth request.
#
# @param {String} method the HTTP method used to make the request ('GET',
# 'POST', etc)
# @param {String} url the HTTP URL (e.g. "http://www.example.com/photos")
# that receives the request
# @param {Object} params an associative array (hash) containing the HTTP
# request parameters; the parameters should include the oauth_
# parameters generated by calling {Dropbox.Oauth#boilerplateParams}
# @return {String} the signature, ready to be used as the oauth_signature
# OAuth parameter
signature: (method, url, params) ->
string = method.toUpperCase() + '&' + Dropbox.Xhr.urlEncodeValue(url) +
'&' + Dropbox.Xhr.urlEncodeValue(Dropbox.Xhr.urlEncode(params))
base64HmacSha1 string, @hmacKey
# @return {String} a string that uniquely identifies the OAuth application
appHash: ->
return @_appHash if @_appHash
@_appHash = base64Sha1(@k).replace(/\=/g, '')
# Polyfill for Internet Explorer 8.
unless Date.now?
Date.now = () ->
(new Date()).getTime()

View file

@ -1,36 +0,0 @@
# Necessary bits to get a browser-side app in production.
# Packs up a key and secret into a string, to bring script kiddies some pain.
#
# @param {String} key the application's API key
# @param {String} secret the application's API secret
# @return {String} encoded key string that can be passed as the key option to
# the Dropbox.Client constructor
dropboxEncodeKey = (key, secret) ->
if secret
secret = [encodeURIComponent(key), encodeURIComponent(secret)].join('?')
key = for i in [0...(key.length / 2)]
((key.charCodeAt(i * 2) & 15) * 16) + (key.charCodeAt(i * 2 + 1) & 15)
else
[key, secret] = key.split '|', 2
key = atob key
key = (key.charCodeAt(i) for i in [0...key.length])
secret = atob secret
s = [0...256]
y = 0
for x in [0...256]
y = (y + s[i] + key[x % key.length]) % 256
[s[x], s[y]] = [s[y], s[x]]
x = y = 0
result = for z in [0...secret.length]
x = (x + 1) % 256
y = (y + s[x]) % 256
[s[x], s[y]] = [s[y], s[x]]
k = s[(s[x] + s[y]) % 256]
String.fromCharCode((k ^ secret.charCodeAt(z)) % 256)
key = (String.fromCharCode(key[i]) for i in [0...key.length])
[btoa(key.join('')), btoa(result.join(''))].join '|'

View file

@ -1,106 +0,0 @@
# Wraps the result of pullChanges, describing the changes in a user's Dropbox.
class Dropbox.PulledChanges
# Creates a new Dropbox.PulledChanges instance from a /delta API call result.
#
# @param {?Object} deltaInfo the parsed JSON of a /delta API call result
# @return {?Dropbox.PulledChanges} a Dropbox.PulledChanges instance wrapping
# the given information; if the parameter does not look like parsed JSON,
# it is returned as is
@parse: (deltaInfo) ->
if deltaInfo and typeof deltaInfo is 'object'
new Dropbox.PulledChanges deltaInfo
else
deltaInfo
# @property {Boolean} if true, the application should reset its copy of the
# user's Dropbox before applying the changes described by this instance
blankSlate: undefined
# @property {String} encodes a cursor in the list of changes to a user's
# Dropbox; a pullChanges call returns some changes at the cursor, and then
# advance the cursor to account for the returned changes; the new cursor is
# returned by pullChanges, and meant to be used by a subsequent pullChanges
# call
cursorTag: undefined
# @property {Array<Dropbox.PullChange> an array with one entry for each
# change to the user's Dropbox returned by a pullChanges call.
changes: undefined
# @property {Boolean} if true, the pullChanges call returned a subset of the
# available changes, and the application should repeat the call
# immediately to get more changes
shouldPullAgain: undefined
# @property {Boolean} if true, the API call will not have any more changes
# available in the nearby future, so the application should wait for at
# least 5 miuntes before issuing another pullChanges request
shouldBackOff: undefined
# Serializable representation of the pull cursor inside this object.
#
# @return {String} an ASCII string that can be passed to pullChanges instead
# of this PulledChanges instance
cursor: -> @cursorTag
# Creates a new Dropbox.PulledChanges instance from a /delta API call result.
#
# @private
# This constructor is used by Dropbox.PulledChanges, and should not be called
# directly.
#
# @param {Object} deltaInfo the parsed JSON of a /delta API call result
constructor: (deltaInfo) ->
@blankSlate = deltaInfo.reset or false
@cursorTag = deltaInfo.cursor
@shouldPullAgain = deltaInfo.has_more
@shouldBackOff = not @shouldPullAgain
if deltaInfo.cursor and deltaInfo.cursor.length
@changes = (Dropbox.PullChange.parse entry for entry in deltaInfo.entries)
else
@changes = []
# Wraps a single change in a pullChanges result.
class Dropbox.PullChange
# Creates a Dropbox.PullChange instance wrapping an entry in a /delta result.
#
# @param {?Object} entry the parsed JSON of a single entry in a /delta API
# call result
# @return {?Dropbox.PullChange} a Dropbox.PullChange instance wrapping the
# given entry of a /delta API call; if the parameter does not look like
# parsed JSON, it is returned as is
@parse: (entry) ->
if entry and typeof entry is 'object'
new Dropbox.PullChange entry
else
entry
# @property {String} the path of the changed file or folder
path: undefined
# @property {Boolean} if true, this change is a deletion of the file or folder
# at the change's path; if a folder is deleted, all its contents (files
# and sub-folders) were also be deleted; pullChanges might not return
# separate changes expressing for the files or sub-folders
wasRemoved: undefined
# @property {?Dropbox.Stat} a Stat instance containing updated information for
# the file or folder; this is null if the change is a deletion
stat: undefined
# Creates a Dropbox.PullChange instance wrapping an entry in a /delta result.
#
# @private
# This constructor is used by Dropbox.PullChange.parse, and should not be
# called directly.
#
# @param {Object} entry the parsed JSON of a single entry in a /delta API
# call result
constructor: (entry) ->
@path = entry[0]
@stat = Dropbox.Stat.parse entry[1]
if @stat
@wasRemoved = false
else
@stat = null
@wasRemoved = true

View file

@ -1,119 +0,0 @@
# Wraps an URL to a Dropbox file or folder that can be publicly shared.
class Dropbox.PublicUrl
# Creates a PublicUrl instance from a raw API response.
#
# @param {?Object, ?String} urlData the parsed JSON describing a public URL
# @param {?Boolean} isDirect true if this is a direct download link, false if
# is a file / folder preview link
# @return {?Dropbox.PublicUrl} a PublicUrl instance wrapping the given public
# link info; parameters that don't look like parsed JSON are returned as
# they are
@parse: (urlData, isDirect) ->
if urlData and typeof urlData is 'object'
new Dropbox.PublicUrl urlData, isDirect
else
urlData
# @property {String} the public URL
url: null
# @property {Date} after this time, the URL is not usable
expiresAt: null
# @property {Boolean} true if this is a direct download URL, false for URLs to
# preview pages in the Dropbox web app; folders do not have direct link
#
isDirect: null
# @property {Boolean} true if this is URL points to a file's preview page in
# Dropbox, false for direct links
isPreview: null
# JSON representation of this file / folder's metadata
#
# @return {Object} conforms to the JSON restrictions; can be passed to
# Dropbox.PublicUrl#parse to obtain an identical PublicUrl instance
json: ->
# HACK: this can break if the Dropbox API ever decides to use 'direct' in
# its link info
@_json ||= url: @url, expires: @expiresAt.toString(), direct: @isDirect
# Creates a PublicUrl instance from a raw API response.
#
# @private
# This constructor is used by Dropbox.PublicUrl.parse, and should not be
# called directly.
#
# @param {?Object} urlData the parsed JSON describing a public URL
# @param {Boolean} isDirect true if this is a direct download link, false if
# is a file / folder preview link
constructor: (urlData, isDirect) ->
@url = urlData.url
@expiresAt = new Date Date.parse(urlData.expires)
if isDirect is true
@isDirect = true
else if isDirect is false
@isDirect = false
else
# HACK: this can break if the Dropbox API ever decides to use 'direct' in
# its link info; unfortunately, there's no elegant way to guess
# between direct download URLs and preview URLs
if 'direct' of urlData
@isDirect = urlData.direct
else
@isDirect = Date.now() - @expiresAt <= 86400000 # 1 day
@isPreview = !@isDirect
# The JSON representation is created on-demand, to avoid unnecessary object
# creation.
# We can't use the original JSON object because we add a 'direct' field.
@_json = null
# Reference to a file that can be used to make a copy across users' Dropboxes.
class Dropbox.CopyReference
# Creates a CopyReference instance from a raw reference or API response.
#
# @param {?Object, ?String} refData the parsed JSON describing a copy
# reference, or the reference string
@parse: (refData) ->
if refData and (typeof refData is 'object' or typeof refData is 'string')
new Dropbox.CopyReference refData
else
refData
# @property {String} the raw reference, for use with Dropbox APIs
tag: null
# @property {Date} deadline for using the reference in a copy operation
expiresAt: null
# JSON representation of this file / folder's metadata
#
# @return {Object} conforms to the JSON restrictions; can be passed to
# Dropbox.CopyReference#parse to obtain an identical CopyReference instance
json: ->
# NOTE: the assignment only occurs if the CopyReference was built around a
# string; CopyReferences parsed from API responses hold onto the
# original JSON
@_json ||= copy_ref: @tag, expires: @expiresAt.toString()
# Creates a CopyReference instance from a raw reference or API response.
#
# @private
# This constructor is used by Dropbox.CopyReference.parse, and should not be
# called directly.
#
# @param {Object, String} refData the parsed JSON describing a copy
# reference, or the reference string
constructor: (refData) ->
if typeof refData is 'object'
@tag = refData.copy_ref
@expiresAt = new Date Date.parse(refData.expires)
@_json = refData
else
@tag = refData
@expiresAt = new Date Math.ceil(Date.now() / 1000) * 1000
# The JSON representation is created on-demand, to avoid unnecessary
# object creation.
@_json = null

View file

@ -1,135 +0,0 @@
# The result of stat-ing a file or directory in a user's Dropbox.
class Dropbox.Stat
# Creates a Stat instance from a raw "metadata" response.
#
# @param {?Object} metadata the result of parsing JSON API responses that are
# called "metadata" in the API documentation
# @return {?Dropbox.Stat} a Stat instance wrapping the given API response;
# parameters that aren't parsed JSON objects are returned as they are
@parse: (metadata) ->
if metadata and typeof metadata is 'object'
new Dropbox.Stat metadata
else
metadata
# @property {String} the path of this file or folder, relative to the user's
# Dropbox or to the application's folder
path: null
# @property {String} the name of this file or folder
name: null
# @property {Boolean} if true, the file or folder's path is relative to the
# application's folder; otherwise, the path is relative to the user's
# Dropbox
inAppFolder: null
# @property {Boolean} if true, this Stat instance describes a folder
isFolder: null
# @property {Boolean} if true, this Stat instance describes a file
isFile: null
# @property {Boolean} if true, the file or folder described by this Stat
# instance was from the user's Dropbox, and was obtained by an API call
# that returns deleted items
isRemoved: null
# @property {String} name of an icon in Dropbox's icon library that most
# accurately represents this file or folder
#
# See the Dropbox API documentation to obtain the Dropbox icon library.
# https://www.dropbox.com/developers/reference/api#metadata
typeIcon: null
# @property {String} an identifier for the contents of the described file or
# directories; this can used to be restored a file's contents to a
# previous version, or to save bandwidth by not retrieving the same
# folder contents twice
versionTag: null
# @property {String} a guess of the MIME type representing the file or
# folder's contents
mimeType: null
# @property {Number} the size of the file, in bytes; null for folders
size: null
# @property {String} the size of the file, in a human-readable format, such
# as "225.4KB"; the format of this string is influenced by the API client's
# locale
humanSize: null
# @property {Boolean} if false, the URL generated by thumbnailUrl does not
# point to a valid image, and should not be used
hasThumbnail: null
# @property {Date} the file or folder's last modification time
modifiedAt: null
# @property {?Date} the file or folder's last modification time, as reported
# by the Dropbox client that uploaded the file; this time should not be
# trusted, but can be used for UI (display, sorting); null if the server
# does not report any time
clientModifiedAt: null
# JSON representation of this file / folder's metadata
#
# @return {Object} conforms to the JSON restrictions; can be passed to
# Dropbox.Stat#parse to obtain an identical Stat instance
json: ->
@_json
# Creates a Stat instance from a raw "metadata" response.
#
# @private
# This constructor is used by Dropbox.Stat.parse, and should not be called
# directly.
#
# @param {Object} metadata the result of parsing JSON API responses that are
# called "metadata" in the API documentation
constructor: (metadata) ->
@_json = metadata
@path = metadata.path
# Ensure there is a trailing /, to make path processing reliable.
@path = '/' + @path if @path.substring(0, 1) isnt '/'
# Strip any trailing /, to make path joining predictable.
lastIndex = @path.length - 1
if lastIndex >= 0 and @path.substring(lastIndex) is '/'
@path = @path.substring 0, lastIndex
nameSlash = @path.lastIndexOf '/'
@name = @path.substring nameSlash + 1
@isFolder = metadata.is_dir || false
@isFile = !@isFolder
@isRemoved = metadata.is_deleted || false
@typeIcon = metadata.icon
if metadata.modified?.length
@modifiedAt = new Date Date.parse(metadata.modified)
else
@modifiedAt = null
if metadata.client_mtime?.length
@clientModifiedAt = new Date Date.parse(metadata.client_mtime)
else
@clientModifiedAt = null
switch metadata.root
when 'dropbox'
@inAppFolder = false
when 'app_folder'
@inAppFolder = true
else
# New "root" value that we're not aware of.
@inAppFolder = null
@size = metadata.bytes or 0
@humanSize = metadata.size or ''
@hasThumbnail = metadata.thumb_exists or false
if @isFolder
@versionTag = metadata.hash
@mimeType = metadata.mime_type || 'inode/directory'
else
@versionTag = metadata.rev
@mimeType = metadata.mime_type || 'application/octet-stream'

View file

@ -1,59 +0,0 @@
# Tracks the progress of a resumable upload.
class Dropbox.UploadCursor
# Creates an UploadCursor instance from an API response.
#
# @param {?Object, ?String} cursorData the parsed JSON describing the status
# of a partial upload, or the upload ID string
@parse: (cursorData) ->
if cursorData and (typeof cursorData is 'object' or
typeof cursorData is 'string')
new Dropbox.UploadCursor cursorData
else
cursorData
# @property {String} the server-generated ID for this upload
tag: null
# @property {Number} number of bytes that have already been uploaded
offset: null
# @property {Date} deadline for finishing the upload
expiresAt: null
# JSON representation of this cursor.
#
# @return {Object} conforms to the JSON restrictions; can be passed to
# Dropbox.UploadCursor#parse to obtain an identical UploadCursor instance
json: ->
# NOTE: the assignment only occurs if
@_json ||= upload_id: @tag, offset: @offset, expires: @expiresAt.toString()
# Creates an UploadCursor instance from a raw reference or API response.
#
# This constructor should only be called directly to obtain a cursor for a
# new file upload. Dropbox.UploadCursor#parse should be called instead
#
# @param {?Object, ?String} cursorData the parsed JSON describing a copy
# reference, or the reference string
constructor: (cursorData) ->
@replace cursorData
# Replaces the current
#
# @private Called by Dropbox.Client#resumableUploadStep.
#
# @param {?Object, ?String} cursorData the parsed JSON describing a copy
# reference, or the reference string
# @return {Dropbox.UploadCursor} this
replace: (cursorData) ->
if typeof cursorData is 'object'
@tag = cursorData.upload_id or null
@offset = cursorData.offset or 0
@expiresAt = new Date(Date.parse(cursorData.expires) or Date.now())
@_json = cursorData
else
@tag = cursorData or null
@offset = 0
@expiresAt = new Date Math.floor(Date.now() / 1000) * 1000
@_json = null
@

View file

@ -1,88 +0,0 @@
# Information about a Dropbox user.
class Dropbox.UserInfo
# Creates a UserInfo instance from a raw API response.
#
# @param {?Object} userInfo the result of parsing a JSON API response that
# describes a user
# @return {Dropbox.UserInfo} a UserInfo instance wrapping the given API
# response; parameters that aren't parsed JSON objects are returned as
# the are
@parse: (userInfo) ->
if userInfo and typeof userInfo is 'object'
new Dropbox.UserInfo userInfo
else
userInfo
# @property {String} the user's name, in a form that is fit for display
name: null
# @property {?String} the user's email; this is not in the official API
# documentation, so it might not be supported
email: null
# @property {?String} two-letter country code, or null if unavailable
countryCode: null
# @property {String} unique ID for the user; this ID matches the unique ID
# returned by the authentication process
uid: null
# @property {String} the user's referral link; the user might benefit if
# others use the link to sign up for Dropbox
referralUrl: null
# Specific to applications whose access type is "public app folder".
#
# @property {String} prefix for URLs to the application's files
publicAppUrl: null
# @property {Number} the maximum amount of bytes that the user can store
quota: null
# @property {Number} the number of bytes taken up by the user's data
usedQuota: null
# @property {Number} the number of bytes taken up by the user's data that is
# not shared with other users
privateBytes: null
# @property {Number} the number of bytes taken up by the user's data that is
# shared with other users
sharedBytes: null
# JSON representation of this user's information.
#
# @return {Object} conforms to the JSON restrictions; can be passed to
# Dropbox.UserInfo#parse to obtain an identical UserInfo instance
json: ->
@_json
# Creates a UserInfo instance from a raw API response.
#
# @private
# This constructor is used by Dropbox.UserInfo.parse, and should not be
# called directly.
#
# @param {Object} userInfo the result of parsing a JSON API response that
# describes a user
constructor: (userInfo) ->
@_json = userInfo
@name = userInfo.display_name
@email = userInfo.email
@countryCode = userInfo.country or null
@uid = userInfo.uid.toString()
if userInfo.public_app_url
@publicAppUrl = userInfo.public_app_url
lastIndex = @publicAppUrl.length - 1
# Strip any trailing /, to make path joining predictable.
if lastIndex >= 0 and @publicAppUrl.substring(lastIndex) is '/'
@publicAppUrl = @publicAppUrl.substring 0, lastIndex
else
@publicAppUrl = null
@referralUrl = userInfo.referral_link
@quota = userInfo.quota_info.quota
@privateBytes = userInfo.quota_info.normal or 0
@sharedBytes = userInfo.quota_info.shared or 0
@usedQuota = @privateBytes + @sharedBytes

View file

@ -1,505 +0,0 @@
if window?
if window.XDomainRequest and not ('withCredentials' of new XMLHttpRequest())
DropboxXhrRequest = window.XDomainRequest
DropboxXhrIeMode = true
# IE's XDR doesn't allow setting requests' Content-Type to anything other
# than text/plain, so it can't send _any_ forms.
DropboxXhrCanSendForms = false
else
DropboxXhrRequest = window.XMLHttpRequest
DropboxXhrIeMode = false
# Firefox doesn't support adding named files to FormData.
# https://bugzilla.mozilla.org/show_bug.cgi?id=690659
DropboxXhrCanSendForms =
window.navigator.userAgent.indexOf('Firefox') is -1
DropboxXhrDoesPreflight = true
else
# Node.js needs an adapter for the XHR API.
DropboxXhrRequest = require('xmlhttprequest').XMLHttpRequest
DropboxXhrIeMode = false
# Node.js doesn't have FormData. We wouldn't want to bother putting together
# upload forms in node.js anyway, because it doesn't do CORS preflight
# checks, so we can use PUT requests without a performance hit.
DropboxXhrCanSendForms = false
# Node.js is a server so it doesn't do annoying browser checks.
DropboxXhrDoesPreflight = false
# ArrayBufferView isn't available in the global namespce.
#
# Using the hack suggested in
# https://code.google.com/p/chromium/issues/detail?id=60449
if typeof Uint8Array is 'undefined'
DropboxXhrArrayBufferView = null
DropboxXhrSendArrayBufferView = false
else
if Object.getPrototypeOf
DropboxXhrArrayBufferView = Object.getPrototypeOf(
Object.getPrototypeOf(new Uint8Array(0))).constructor
else if Object.__proto__
DropboxXhrArrayBufferView =
(new Uint8Array(0)).__proto__.__proto__.constructor
# Browsers that haven't implemented XHR#send(ArrayBufferView) also don't
# have a real ArrayBufferView prototype. (Safari, Firefox)
DropboxXhrSendArrayBufferView = DropboxXhrArrayBufferView isnt Object
# Dispatches low-level AJAX calls (XMLHttpRequests).
class Dropbox.Xhr
# The object used to perform AJAX requests (XMLHttpRequest).
@Request = DropboxXhrRequest
# Set to true when using the XDomainRequest API.
@ieXdr = DropboxXhrIeMode
# Set to true if the platform has proper support for FormData.
@canSendForms = DropboxXhrCanSendForms
# Set to true if the platform performs CORS preflight checks.
@doesPreflight = DropboxXhrDoesPreflight
# Superclass for all ArrayBufferView objects.
@ArrayBufferView = DropboxXhrArrayBufferView
# Set to true if we think we can send ArrayBufferView objects via XHR.
@sendArrayBufferView = DropboxXhrSendArrayBufferView
# Sets up an AJAX request.
#
# @param {String} method the HTTP method used to make the request ('GET',
# 'POST', 'PUT', etc.)
# @param {String} baseUrl the URL that receives the request; this URL might
# be modified, e.g. by appending parameters for GET requests
constructor: (@method, baseUrl) ->
@isGet = @method is 'GET'
@url = baseUrl
@headers = {}
@params = null
@body = null
@preflight = not (@isGet or (@method is 'POST'))
@signed = false
@responseType = null
@callback = null
@xhr = null
@onError = null
# @property {?XMLHttpRequest} the raw XMLHttpRequest object used to make the
# request; null until Dropbox.Xhr#prepare is called
xhr: null
# @property {?Dropbox.EventSource<Dropbox.ApiError>} if the XHR fails and
# this property is set, the Dropbox.ApiError instance that will be passed
# to the callback will be dispatched through the Dropbox.EventSource; the
# EventSource should be configured for non-cancelable events
onError: null
# Sets the parameters (form field values) that will be sent with the request.
#
# @param {?Object} params an associative array (hash) containing the HTTP
# request parameters
# @return {Dropbox.Xhr} this, for easy call chaining
setParams: (params) ->
if @signed
throw new Error 'setParams called after addOauthParams or addOauthHeader'
if @params
throw new Error 'setParams cannot be called twice'
@params = params
@
# Sets the function called when the XHR completes.
#
# This function can also be set when calling Dropbox.Xhr#send.
#
# @param {function(?Dropbox.ApiError, ?Object, ?Object)} callback called when
# the XHR completes; if an error occurs, the first parameter will be a
# Dropbox.ApiError instance; otherwise, the second parameter will be an
# instance of the required response type (e.g., String, Blob), and the
# third parameter will be the JSON-parsed 'x-dropbox-metadata' header
# @return {Dropbox.Xhr} this, for easy call chaining
setCallback: (@callback) ->
@
# Ammends the request parameters to include an OAuth signature.
#
# The OAuth signature will become invalid if the parameters are changed after
# the signing process.
#
# This method automatically decides the best way to add the OAuth signature
# to the current request. Modifying the request in any way (e.g., by adding
# headers) might result in a valid signature that is applied in a sub-optimal
# fashion. For best results, call this right before Dropbox.Xhr#prepare.
#
# @param {Dropbox.Oauth} oauth OAuth instance whose key and secret will be
# used to sign the request
# @param {Boolean} cacheFriendly if true, the signing process choice will be
# biased towards allowing the HTTP cache to work; by default, the choice
# attempts to avoid the CORS preflight request whenever possible
# @return {Dropbox.Xhr} this, for easy call chaining
signWithOauth: (oauth, cacheFriendly) ->
if Dropbox.Xhr.ieXdr
@addOauthParams oauth
else if @preflight or !Dropbox.Xhr.doesPreflight
@addOauthHeader oauth
else
if @isGet and cacheFriendly
@addOauthHeader oauth
else
@addOauthParams oauth
# Ammends the request parameters to include an OAuth signature.
#
# The OAuth signature will become invalid if the parameters are changed after
# the signing process.
#
# @param {Dropbox.Oauth} oauth OAuth instance whose key and secret will be
# used to sign the request
# @return {Dropbox.Xhr} this, for easy call chaining
addOauthParams: (oauth) ->
if @signed
throw new Error 'Request already has an OAuth signature'
@params or= {}
oauth.addAuthParams @method, @url, @params
@signed = true
@
# Adds an Authorize header containing an OAuth signature.
#
# The OAuth signature will become invalid if the parameters are changed after
# the signing process.
#
# @param {Dropbox.Oauth} oauth OAuth instance whose key and secret will be
# used to sign the request
# @return {Dropbox.Xhr} this, for easy call chaining
addOauthHeader: (oauth) ->
if @signed
throw new Error 'Request already has an OAuth signature'
@params or= {}
@signed = true
@setHeader 'Authorization', oauth.authHeader(@method, @url, @params)
# Sets the body (piece of data) that will be sent with the request.
#
# @param {String, Blob, ArrayBuffer} body the body to be sent in a request;
# GET requests cannot have a body
# @return {Dropbox.Xhr} this, for easy call chaining
setBody: (body) ->
if @isGet
throw new Error 'setBody cannot be called on GET requests'
if @body isnt null
throw new Error 'Request already has a body'
if typeof body is 'string'
# Content-Type will be set automatically.
else if (typeof FormData isnt 'undefined') and (body instanceof FormData)
# Content-Type will be set automatically.
else
@headers['Content-Type'] = 'application/octet-stream'
@preflight = true
@body = body
@
# Sends off an AJAX request and requests a custom response type.
#
# This method requires XHR Level 2 support, which is not available in IE
# versions <= 9. If these browsers must be supported, it is recommended to
# check whether window.Blob is truthy.
#
# @param {String} responseType the value that will be assigned to the XHR's
# responseType property
# @return {Dropbox.Xhr} this, for easy call chaining
setResponseType: (@responseType) ->
@
# Sets the value of a custom HTTP header.
#
# Custom HTTP headers require a CORS preflight in browsers, so requests that
# use them will take more time to complete, especially on high-latency mobile
# connections.
#
# @param {String} headerName the name of the HTTP header
# @param {String} value the value that the header will be set to
# @return {Dropbox.Xhr} this, for easy call chaining
setHeader: (headerName, value) ->
if @headers[headerName]
oldValue = @headers[headerName]
throw new Error "HTTP header #{headerName} already set to #{oldValue}"
if headerName is 'Content-Type'
throw new Error 'Content-Type is automatically computed based on setBody'
@preflight = true
@headers[headerName] = value
@
# Simulates having an <input type="file"> being sent with the request.
#
# @param {String} fieldName the name of the form field / parameter (not of
# the uploaded file)
# @param {String} fileName the name of the uploaded file (not the name of the
# form field / parameter)
# @param {String, Blob, File} fileData contents of the file to be uploaded
# @param {?String} contentType the MIME type of the file to be uploaded; if
# fileData is a Blob or File, its MIME type is used instead
setFileField: (fieldName, fileName, fileData, contentType) ->
if @body isnt null
throw new Error 'Request already has a body'
if @isGet
throw new Error 'setFileField cannot be called on GET requests'
if typeof(fileData) is 'object' and typeof Blob isnt 'undefined'
if typeof ArrayBuffer isnt 'undefined'
if fileData instanceof ArrayBuffer
# Convert ArrayBuffer -> ArrayBufferView on standard-compliant
# browsers, to avoid warnings from the Blob constructor.
if Dropbox.Xhr.sendArrayBufferView
fileData = new Uint8Array fileData
else
# Convert ArrayBufferView -> ArrayBuffer on older browsers, to avoid
# having a Blob that contains "[object Uint8Array]" instead of the
# actual data.
if !Dropbox.Xhr.sendArrayBufferView and fileData.byteOffset is 0 and
fileData.buffer instanceof ArrayBuffer
fileData = fileData.buffer
contentType or= 'application/octet-stream'
fileData = new Blob [fileData], type: contentType
# Workaround for http://crbug.com/165095
if typeof File isnt 'undefined' and fileData instanceof File
fileData = new Blob [fileData], type: fileData.type
#fileData = fileData
useFormData = fileData instanceof Blob
else
useFormData = false
if useFormData
@body = new FormData()
@body.append fieldName, fileData, fileName
else
contentType or= 'application/octet-stream'
boundary = @multipartBoundary()
@headers['Content-Type'] = "multipart/form-data; boundary=#{boundary}"
@body = ['--', boundary, "\r\n",
'Content-Disposition: form-data; name="', fieldName,
'"; filename="', fileName, "\"\r\n",
'Content-Type: ', contentType, "\r\n",
"Content-Transfer-Encoding: binary\r\n\r\n",
fileData,
"\r\n", '--', boundary, '--', "\r\n"].join ''
# @private
# @return {String} a nonce suitable for use as a part boundary in a multipart
# MIME message
multipartBoundary: ->
[Date.now().toString(36), Math.random().toString(36)].join '----'
# Moves this request's parameters to its URL.
#
# @private
# @return {Dropbox.Xhr} this, for easy call chaining
paramsToUrl: ->
if @params
queryString = Dropbox.Xhr.urlEncode @params
if queryString.length isnt 0
@url = [@url, '?', queryString].join ''
@params = null
@
# Moves this request's parameters to its body.
#
# @private
# @return {Dropbox.Xhr} this, for easy call chaining
paramsToBody: ->
if @params
if @body isnt null
throw new Error 'Request already has a body'
if @isGet
throw new Error 'paramsToBody cannot be called on GET requests'
@headers['Content-Type'] = 'application/x-www-form-urlencoded'
@body = Dropbox.Xhr.urlEncode @params
@params = null
@
# Sets up an XHR request.
#
# This method completely sets up a native XHR object and stops short of
# calling its send() method, so the API client has a chance of customizing
# the XHR. After customizing the XHR, Dropbox.Xhr#send should be called.
#
#
# @return {Dropbox.Xhr} this, for easy call chaining
prepare: ->
ieXdr = Dropbox.Xhr.ieXdr
if @isGet or @body isnt null or ieXdr
@paramsToUrl()
if @body isnt null and typeof @body is 'string'
@headers['Content-Type'] = 'text/plain; charset=utf8'
else
@paramsToBody()
@xhr = new Dropbox.Xhr.Request()
if ieXdr
@xhr.onload = => @onXdrLoad()
@xhr.onerror = => @onXdrError()
@xhr.ontimeout = => @onXdrError()
# NOTE: there are reports that XHR somtimes fails if onprogress doesn't
# have any handler
@xhr.onprogress = ->
else
@xhr.onreadystatechange = => @onReadyStateChange()
@xhr.open @method, @url, true
unless ieXdr
for own header, value of @headers
@xhr.setRequestHeader header, value
if @responseType
if @responseType is 'b'
if @xhr.overrideMimeType
@xhr.overrideMimeType 'text/plain; charset=x-user-defined'
else
@xhr.responseType = @responseType
@
# Fires off the prepared XHR request.
#
# Dropbox.Xhr#prepare should be called exactly once before this method.
#
# @param {function(?Dropbox.ApiError, ?Object, ?Object)} callback called when
# the XHR completes; if an error occurs, the first parameter will be a
# Dropbox.ApiError instance; otherwise, the second parameter will be an
# instance of the required response type (e.g., String, Blob), and the
# third parameter will be the JSON-parsed 'x-dropbox-metadata' header
# @return {Dropbox.Xhr} this, for easy call chaining
send: (callback) ->
@callback = callback or @callback
if @body isnt null
body = @body
# send() in XHR doesn't like naked ArrayBuffers
if Dropbox.Xhr.sendArrayBufferView and body instanceof ArrayBuffer
body = new Uint8Array body
try
@xhr.send body
catch e
# Node.js doesn't implement Blob.
if !Dropbox.Xhr.sendArrayBufferView and typeof Blob isnt 'undefined'
# Firefox doesn't support sending ArrayBufferViews.
body = new Blob [body], type: 'application/octet-stream'
@xhr.send body
else
throw e
else
@xhr.send()
@
# Encodes an associative array (hash) into a x-www-form-urlencoded String.
#
# For consistency, the keys are sorted in alphabetical order in the encoded
# output.
#
# @param {Object} object the JavaScript object whose keys will be encoded
# @return {String} the object's keys and values, encoded using
# x-www-form-urlencoded
@urlEncode: (object) ->
chunks = []
for key, value of object
chunks.push @urlEncodeValue(key) + '=' + @urlEncodeValue(value)
chunks.sort().join '&'
# Encodes an object into a x-www-form-urlencoded key or value.
#
# @param {Object} object the object to be encoded; the encoding calls
# toString() on the object to obtain its string representation
# @return {String} encoded string, suitable for use as a key or value in an
# x-www-form-urlencoded string
@urlEncodeValue: (object) ->
encodeURIComponent(object.toString()).replace(/\!/g, '%21').
replace(/'/g, '%27').replace(/\(/g, '%28').replace(/\)/g, '%29').
replace(/\*/g, '%2A')
# Decodes an x-www-form-urlencoded String into an associative array (hash).
#
# @param {String} string the x-www-form-urlencoded String to be decoded
# @return {Object} an associative array whose keys and values are all strings
@urlDecode: (string) ->
result = {}
for token in string.split '&'
kvp = token.split '='
result[decodeURIComponent(kvp[0])] = decodeURIComponent kvp[1]
result
# Handles the XHR readystate event.
onReadyStateChange: ->
return true if @xhr.readyState isnt 4 # XMLHttpRequest.DONE is 4
if @xhr.status < 200 or @xhr.status >= 300
apiError = new Dropbox.ApiError @xhr, @method, @url
@onError.dispatch apiError if @onError
@callback apiError
return true
metadataJson = @xhr.getResponseHeader 'x-dropbox-metadata'
if metadataJson?.length
try
metadata = JSON.parse metadataJson
catch e
# Make sure the app doesn't crash if the server goes crazy.
metadata = undefined
else
metadata = undefined
if @responseType
if @responseType is 'b'
dirtyText = if @xhr.responseText?
@xhr.responseText
else
@xhr.response
###
jsString = ['["']
for i in [0...dirtyText.length]
hexByte = (dirtyText.charCodeAt(i) & 0xFF).toString(16)
if hexByte.length is 2
jsString.push "\\u00#{hexByte}"
else
jsString.push "\\u000#{hexByte}"
jsString.push '"]'
console.log jsString
text = JSON.parse(jsString.join(''))[0]
###
bytes = []
for i in [0...dirtyText.length]
bytes.push String.fromCharCode(dirtyText.charCodeAt(i) & 0xFF)
text = bytes.join ''
@callback null, text, metadata
else
@callback null, @xhr.response, metadata
return true
text = if @xhr.responseText? then @xhr.responseText else @xhr.response
switch @xhr.getResponseHeader('Content-Type')
when 'application/x-www-form-urlencoded'
@callback null, Dropbox.Xhr.urlDecode(text), metadata
when 'application/json', 'text/javascript'
@callback null, JSON.parse(text), metadata
else
@callback null, text, metadata
true
# Handles the XDomainRequest onload event. (IE 8, 9)
onXdrLoad: ->
text = @xhr.responseText
switch @xhr.contentType
when 'application/x-www-form-urlencoded'
@callback null, Dropbox.Xhr.urlDecode(text), undefined
when 'application/json', 'text/javascript'
@callback null, JSON.parse(text), undefined
else
@callback null, text, undefined
true
# Handles the XDomainRequest onload event. (IE 8, 9)
onXdrError: ->
apiError = new Dropbox.ApiError @xhr, @method, @url
@onError.dispatch apiError if @onError
@callback apiError
return true

View file

@ -1,21 +0,0 @@
# All changes to the global namespace happen here.
# This file's name is set up in such a way that it will always show up last in
# the source directory. This makes coffee --join work as intended.
if module?.exports?
# We're a node.js module, so export the Dropbox class.
module.exports = Dropbox
else if window?
# We're in a browser, so add Dropbox to the global namespace.
window.Dropbox = Dropbox
else
throw new Error('This library only supports node.js and modern browsers.')
# These are mostly useful for testing. Clients shouldn't use internal stuff.
Dropbox.atob = atob
Dropbox.btoa = btoa
Dropbox.hmac = base64HmacSha1
Dropbox.sha1 = base64Sha1
Dropbox.encodeKey = dropboxEncodeKey

View file

@ -1,33 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100px" height="94.209px" viewBox="0 0 100 94.209" enable-background="new 0 0 100 94.209" xml:space="preserve">
<path d="M51.62,0c10.969,0.871,19.611,10.069,19.611,21.262c0,11.397-8.966,20.693-20.221,21.272v4.367
c3.071,0.485,5.426,3.154,5.426,6.362c0,0.784-0.145,1.536-0.4,2.232l3.992,2.305c6.17-9.318,18.615-12.359,28.422-6.696
c9.786,5.651,13.4,17.907,8.467,27.893c6.771-13.177,2.062-29.512-10.911-37.001c-3.243-1.873-6.7-3.014-10.2-3.486
c1.339-3.264,2.081-6.836,2.081-10.583C77.887,13.059,66.268,0.886,51.62,0L51.62,0z"/>
<path d="M48.044,0.016c-14.575,0.963-26.119,13.095-26.119,27.91c0,3.641,0.696,7.119,1.962,10.306
c-3.393,0.497-6.754,1.627-9.905,3.446C1.099,49.117-3.622,65.28,2.952,78.412c-4.747-9.94-1.106-22.032,8.594-27.634
c9.87-5.698,22.402-2.581,28.53,6.876l3.853-2.223c-0.242-0.677-0.366-1.409-0.366-2.168c0-3.071,2.155-5.643,5.032-6.281v-4.457
c-11.153-0.684-20.006-9.938-20.006-21.264C28.589,10.119,37.144,0.959,48.044,0.016L48.044,0.016z"/>
<path d="M49.29,26.803c-5.63,0.129-10.819,2.046-15.079,5.165c0.34,0.503,0.706,0.991,1.091,1.458
c0.386,0.468,0.794,0.916,1.222,1.344s0.876,0.836,1.344,1.222c0.343,0.284,0.712,0.539,1.075,0.799
c3.144-2.066,6.91-3.275,10.966-3.275c4.058,0,7.813,1.209,10.957,3.275c0.363-0.26,0.731-0.515,1.075-0.799
c0.468-0.386,0.916-0.794,1.344-1.222s0.836-0.876,1.222-1.344c0.386-0.468,0.752-0.956,1.093-1.458
c-4.415-3.23-9.826-5.165-15.69-5.165c-0.14,0-0.278-0.002-0.418,0C49.428,26.804,49.357,26.8,49.29,26.803L49.29,26.803z"/>
<path d="M23.628,50.297C23.035,55.735,24.068,61.39,27,66.47c2.933,5.08,7.314,8.809,12.316,11.014
c0.269-0.548,0.508-1.11,0.717-1.678c0.214-0.57,0.398-1.143,0.555-1.728s0.292-1.18,0.391-1.777
c0.075-0.438,0.115-0.883,0.154-1.329c-3.357-1.689-6.288-4.346-8.317-7.86C30.79,59.6,29.954,55.74,30.17,51.984
c-0.407-0.185-0.812-0.373-1.23-0.53c-0.568-0.211-1.142-0.396-1.728-0.553c-0.585-0.158-1.177-0.282-1.776-0.383
C24.839,50.417,24.234,50.34,23.628,50.297L23.628,50.297z"/>
<path d="M76.368,50.615c-0.604,0.043-1.21,0.121-1.807,0.221c-0.598,0.101-1.19,0.227-1.777,0.383
c-0.582,0.157-1.165,0.343-1.732,0.554c-0.418,0.156-0.816,0.354-1.225,0.538c0.217,3.753-0.617,7.616-2.648,11.128
c-2.025,3.512-4.958,6.163-8.318,7.854c0.045,0.445,0.082,0.888,0.157,1.328c0.102,0.598,0.227,1.19,0.383,1.775
c0.157,0.585,0.342,1.167,0.556,1.734c0.209,0.57,0.448,1.125,0.717,1.673c5.003-2.208,9.384-5.929,12.315-11.007
C75.92,61.716,76.961,56.053,76.368,50.615L76.368,50.615z"/>
<path d="M45.151,57.492l-3.878,2.248c4.985,10.003,1.401,22.296-8.408,27.959c-9.759,5.635-22.142,2.671-28.342-6.541
c8.046,12.395,24.497,16.459,37.442,8.985c3.136-1.81,5.782-4.145,7.903-6.817c2.163,2.81,4.9,5.251,8.164,7.136
c12.96,7.481,29.432,3.402,37.467-9.025c-6.188,9.249-18.588,12.243-28.367,6.596c-9.869-5.697-13.438-18.109-8.311-28.146
l-4.032-2.329C53.611,58.869,51.901,59.7,50,59.7C48.068,59.7,46.331,58.841,45.151,57.492L45.151,57.492z"/>
</svg>

Before

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

View file

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
width="100px" height="94.113px" viewBox="0 0 100 94.113" enable-background="new 0 0 100 94.113" xml:space="preserve">
<path d="M74.868,86.582l-16.531-28.37c-3.038,1.997-8.752,2.129-8.752,2.129s-5.779-0.132-8.817-2.129l-16.58,28.37
c0,0,9.743,7.531,25.612,7.531C65.619,94.113,74.868,86.582,74.868,86.582L74.868,86.582L74.868,86.582z"/>
<path d="M49.881,53.471c5.417,0,9.745-4.408,9.745-9.792c0-5.417-4.328-9.827-9.745-9.827s-9.791,4.41-9.791,9.827
C40.09,49.063,44.464,53.471,49.881,53.471L49.881,53.471L49.881,53.471z"/>
<path d="M74.539,0.578L58.254,29.114c3.237,1.618,6.16,6.49,6.16,6.49s2.823,5.201,2.575,8.868l32.864-0.033
c0,0,1.666-12.121-6.244-25.877C85.651,4.822,74.539,0.578,74.539,0.578L74.539,0.578L74.539,0.578z"/>
<path d="M0.191,43.845h32.897c-0.247-3.667,2.51-8.737,2.51-8.737s2.988-4.953,6.29-6.588L25.557,0c0,0-11.378,4.658-19.32,18.397
C-1.674,32.103,0.191,43.845,0.191,43.845L0.191,43.845L0.191,43.845z"/>
</svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 324 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 854 B

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="100" height="100" viewBox="0 0 100 100">
<path d="M 66.177,0 H 50 33.823 v 3.724 h 4.445 V 88.047 C 38.269,94.638 43.531,100 50,100 56.469,100 61.731,94.638 61.731,88.047 V 3.724 h 4.445 V 0 z M 50,96.28 c -4.416,0 -8.012,-3.695 -8.012,-8.233 V 36.559 H 58.011 V 88.047 C 58.012,92.585 54.416,96.28 50,96.28 z" />
</svg>

Before

Width:  |  Height:  |  Size: 439 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1,017 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 240 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 421 B

View file

@ -1,20 +0,0 @@
{
"name": "dropbox.js Test Suite",
"version": "1.0",
"manifest_version": 2,
"description": "Test suite for Chrome applications and extensions.",
"icons": {
"16": "images/icon16.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
},
"permissions": [
"storage",
"unlimitedStorage"
],
"app": {
"launch": {
"local_path": "test/html/browser_test.html"
}
}
}

View file

@ -1,22 +0,0 @@
{
"name": "dropbox.js Test Suite",
"version": "1.0",
"manifest_version": 2,
"description": "Test suite for Chrome applications and extensions.",
"icons": {
"16": "images/icon16.png",
"48": "images/icon48.png",
"128": "images/icon128.png"
},
"permissions": [
"storage",
"unlimitedStorage"
],
"app": {
"background": {
"scripts": [
"test/js/chrome_app_background.js"
]
}
}
}

View file

@ -1,10 +0,0 @@
# dropbox.js Test Automator
This is a Google Chrome extension that fully automates the dropbox.js test
suite. Read the
[dropbox.js development guide](https://github.com/dropbox/dropbox-js/tree/master/doc)
to learn how the extension fits into the testing process.
You're welcome to reuse the code to automate the testing of your own
application's integration with Dropbox.

View file

@ -1,84 +0,0 @@
# Background script orchestrating the dropbox.js testing automation.
class Automator
constructor: ->
@wired = false
chrome.storage.sync.get 'enabled', (values) =>
@lifeSwitch values.enabled is 'true'
# Activates or deactivates the extension.
# @param {Boolean} enabled if true, the extension's functionality is enabled
lifeSwitch: (enabled) ->
if enabled
chrome.browserAction.setIcon
path:
19: 'images/action_on19.png'
38: 'images/action_on38.png'
chrome.browserAction.setTitle title: '(on) dropbox.js Test Automator'
@wire()
else
chrome.browserAction.setIcon
path:
19: 'images/action_off19.png',
38: 'images/action_off38.png'
chrome.browserAction.setTitle title: '(off) dropbox.js Test Automator'
@unwire()
# Checks if Dropbox's authorization dialog should be auto-clicked.
# @param {String} url the URL of the Dropbox authorization dialog
# @return {Boolean} true if the "Authorize" button should be auto-clicked
shouldAutomateAuth: (url) ->
return false unless @wired
!!(/(\?|&)oauth_callback=https?%3A%2F%2Flocalhost%3A891[12]%2F/.exec(url))
# Checks if an OAuth receiver window should be auto-closed.
# @param {String} url the URL of the OAuth receiver window
# @return {Boolean} true if the "Authorize" button should be auto-clicked
shouldAutomateClose: (url) ->
return false unless @wired
!!(/^https?:\/\/localhost:8912\/oauth_callback\?/.exec(url))
# Sets up all the features that make dropbox.js testing easier.
wire: ->
return if @wired
chrome.contentSettings.popups.set(
primaryPattern: 'http://localhost:8911/*', setting: 'allow')
@wired = true
@
# Disables the features that automate dropbox.js testing.
unwire: ->
return unless @wired
chrome.contentSettings.popups.clear({})
@wired = false
@
# Global Automator instance.
automator = new Automator()
# Current extension id, used to validate incoming messages.
extensionId = chrome.i18n.getMessage "@@extension_id"
# Communicates with content scripts.
chrome.extension.onMessage.addListener (message, sender, sendResponse) ->
return unless sender.id is extensionId
switch message.type
when 'auth'
sendResponse automate: automator.shouldAutomateAuth(message.url)
when 'close'
if automator.shouldAutomateClose(message.url) and sender.tab
chrome.tabs.remove sender.tab.id
# Listen to pref changes and activate / deactivate the extension.
chrome.storage.onChanged.addListener (changes, namespace) ->
return unless namespace is 'sync'
for name, change of changes
continue unless name is 'enabled'
automator.lifeSwitch change.newValue is 'true'
# The browser action item flips the switch that activates the extension.
chrome.browserAction.onClicked.addListener ->
chrome.storage.sync.get 'enabled', (values) ->
enabled = values.enabled is 'true'
chrome.storage.sync.set enabled: (!enabled).toString()

View file

@ -1,18 +0,0 @@
# Content script for Dropbox authorization pages.
message = type: 'auth', url: window.location.href
chrome.extension.sendMessage message, (response) ->
return unless response.automate
button = document.querySelector('[name=allow_access]') or
document.querySelector '.freshbutton-blue'
event = document.createEvent 'MouseEvents'
clientX = button.clientWidth / 2
clientY = button.clientHeight / 2
screenX = window.screenX + button.offsetLeft + clientX
screenY = window.screenY + button.offsetTop + clientY
event.initMouseEvent 'click', true, true, window, 1,
screenX, screenY, clientX, clientY, false, false, false, false, 0, null
button.dispatchEvent event

View file

@ -1,4 +0,0 @@
# Content script for Dropbox OAuth receiver pages.
message = type: 'close', url: window.location.href
chrome.extension.sendMessage message

Binary file not shown.

Before

Width:  |  Height:  |  Size: 611 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 745 B

Some files were not shown because too many files have changed in this diff Show more