Merge pull request #3 from coderaiser/dev

Cloud Commander v0.1.9
This commit is contained in:
coderaiser 2013-03-01 00:56:04 -08:00
commit b70f8c5a84
208 changed files with 30745 additions and 13779 deletions

10
.gitignore vendored
View file

@ -10,12 +10,6 @@ min
#node modules folder
node_modules
#moving out minify from cloudcmd submodules
#minify module
#!node_modules/
#node_modules/*
#!node_modules/minify
#temporary files folder
tmp
@ -28,5 +22,5 @@ apache-status
#log file
log.txt
#hashes file
hashes.json
#cloudfoundry file
manifest.yml

194
ChangeLog
View file

@ -1,3 +1,197 @@
2012.03.01, Version 0.1.9
* Changed the way of getting github application id
(now it's just from config, rest api removed).
* Added ability to upload files to GDrive.
* Added functions DOM.getCurrentFileContent(pCallBack [, pCurrentFile])
and Util.setTimeout(pFunction [, pCallBack, pTime])
* Added functions in win.js for parsing diskpart output.
* Added function getCurrentDir to DOM module.
* Fixed bug with terminal load.
* From now api url on client readed from config.json.
* If choosen upload to -> Gist, and file in json have
not '.json' extension, it's added to the and of filename.
* Fixed bug in not fully functional browsers,
jquery loaded after ie.js, should be before.
* Fixed bug with closing terminal and opening viewer:
inside event function varible called event do not exist
(everithing ok on webkit).
* Added ability to upload files to dropbox.
* Added ability to authorize on github thru new window,
changed redirecting url to /auth and added rout function
to cloudcmd.js
* DropBox, GDrive and GitHub modules now look the same.
* Fixed bug with pressing Esc when menu showed.
We could not to now, when key was pressed becaouse of
stopPropogation function in jquery.contextMenu module.
Commented stopPropogration.
```
keyStop: function(e, opt) {
if (!opt.isInput) {
e.preventDefault();
}
//e.stopPropagation();
}
```
* Updated socket.io to v0.9.13.
* Updated jquery-terminal to v0.4.22.
* Refactore viewer module.
* Removed loading spinner by commenting jquery.fancybox.css block
#fancybox-loading div {
width: 44px;
height: 44px;
background: url('fancybox_loading.gif') center center no-repeat;
}
* Updated CodeMirror to v2.37.01.
* Updated jquery to v.1.8.3.
* Improved optimizing size of menu.js from 2539 to 2444.
* Fixed bug with height of terminal.
* Added ability to read gdrive key from config.
* Fixed bug with setting curent cursor when
clicked on menu item.
* Updated dropbox library to v.0.8.1.
* Added ability to authorize on dropbox thru popup.
* Added ability to authorize in vk.com.
* Added vk to menu.
* Updated funcyBox to v2.1.4.
* Updated jquery to v1.9.0.
* Improved modules.json format and parsing.
* Added ability to read storage modules information from menu module.
* Added easy template renderrer system Util.render.
* Added functions DOM.parseJSON and DOM.stringifyJSON
* Improved modules.json.
* Added migrate plugin for jquery.
* Removed getting data from Minify cache.
* Moved jsons to json folder.
* Fixed bug with working links in path panel and
with clicks on files.
* Changed icons font from octicons to foundicons
* Changed icons font from foundicons to fonteollo
* Removed Octicons font.
* Removed allowed from cache property in config.
* Added ability to hide "Upload To" menu item if
no storage modules setted up in modules.json.
* From now any file minifying only if last modified
time was changed.
* Removed ability to cache files in memory.
* Moved extensions from main.js to json/ext.json.
* Commander functions moved out to commander.js from server.js
* Fixed bug with old browsers support.
* Fixed bug with scrolling in opera and firefox.
* Fixed bug with (fake) deleting file.
* Added ability show context menu on right click
while menu is showing now.
* Added ability to remove /fs/no-js when go up to root
directory.
* Removed reflows maded by js.
* Fixed bug with setting current file
after refresh and refreshing dir content.
* Removed part of url thet says that js is disabled, from
now json data of file structure would be getted from
click event with ?json flag. "no-js" part of url
would not be supported anymore.
* Added ability to add ?json flag only if we work with dir.
* Added ability not to change url if we have no rights
for reading.
* Fixed bug with pressing enter on file.
* Updated jquery jQuery-contextMenu to v1.6.5.
* Fixed bug with getting showed menu in firefox.
* Added temporary redirection for old no-js urls.
* Changed the way clickProcessing of menu works,
for now it's much simplier.
* Fixed dblclick selection in firefox.
* Fixed the first show of menu in firefox.
* Added ability to change configs without restart
(works for js minification only).
* Added ability to disable browser cache from config.json.
* Totaly refactored and leave in commander.js just
generation of json of directory listing.
* Added ability to upload files.
* Added ability save files on Ctrl+S in CodeMirror.
* Added ability to delete files.
* Added rename menu item.
* Added to html templating.
* Added ability to rename files.
* Added RESTfull object to DOM for easy work with
CloudCmd REST API.
* Added help screen (on F1 after viewer loads).
2012.12.12, Version 0.1.8
* Added ability to shutdown Cloud Commander

94
ChangeLog.rus.md Normal file
View file

@ -0,0 +1,94 @@
**2012.03.01**
Сегодня, в первый день весны, вышла новая версия облачного менеджера файлов: 0.1.9.
В связи с этим, есть двойной повод порадоватся: наконец, пришла весна,
с полной готовностью согревать серца жителей, после зимних холодов,
а так же новая порция положительных изминений и исправленных ошибок в
Облачном Командире.
Теперь Cloud Commander гораздо стабильнее,
продуманее, внего гораздо проще вносить изминения и добавлять новые функции.
Скоро будет много всего интересного.
С прошлой версии произошло очень много перемен и для пользователей и
для программистов, некоторые части были полностью переписаны, и теперь программа
более стабильна, а исходный код более простой и логичный.
К примеру раньше был один баг, из-за которого при нескольких одновременных
соединениях людям могли не приходить запрошенные файлы. Сейчас подобные просчёты
позади. Рассмотрим подробнее, чем нас порадует новая версия Cloud Commanerа.
**Добавленна внешняя возможность:**
- загрузки файлов на Google Drive и DropBox;
- пункт VK в меню;
- прятать пункт меню Upload To, если ниодного storage модуля не задано в modules.json.
- показывать контекстное меню, правкой кнопкой мыши, даже если оно уже открыто
- не менять путь, если у нас нет прав на чтение папки.
- времнный редирект для старых no-js урлов.
- отключения кеширования файлов браузером в config.json
- загрузка файлов;
- сохранение файлов по <Ctrl> + <S> в CodeMirror;
- удаление файлов;
- пункт меню и возможность переименовать;
- экран помощи (<F1>, когда Viewer загружен);
**Убрана внешняя возможность:**
- Удалена часть URL, которая говорит о том, что JavaScript отключен. Теперь json-данные о структуре файлов будут получатся в собитии click с флагом ?json. Часть URL /no-js больше не будет поддерживатся;
**Исправленные ошибки:**
- загрузка терминала;
- в браузерах, которые поддерживают не все функции: jquery загружался после ie.js, должен загружатся — до;
- закрытием терминала и откритием Вьюера, внутри функции, которая возникает во-время возникновения события переменной event не существует (всё хорошо работало на вебките);
- высотой терминала;
- установка текущего курсора, когда выбрали пункт меню;
- ссылки в панели пути и с кликами на файлы.;
- поддержка старых браузеров;
- скроллинг в опере и Фаерфоксе;
- (ненастоящее) удаление файлов;
- установка текущего файла после обновления содержимого директории;
- нажатие на файле;
- первый и последующие показы меню, а так же выделение даблкликом в Firefox
**Обновлены:**
- jquery до версии v1.9.0
- jquery-terminal до версии v0.4.22.
- jQuery-contextMenu до версии v1.6.5.
- socket.io до версии 0.9.13.
- CodeMirror до версии 2.37.01.
- dropbox до версии v.0.8.1.
- funcyBox до версии v2.1.4.
**Внутренние изминения:**
- Изменен способ получения github id (теперь это делается через config.json, rest api были удалены).
- Добавлены функции DOM.getCurrentFileContent(pCallBack [, pCurrentFile]) для получения содержимого файла в ФС, а так же обёртка Util.setTimeout(pFunction [, pCallBack, pTime]).
- Добавлена функция в win.js для парсинга вывода команды diskpart (которая используется для опредиления локальных дисков).
- Добавлена функция DOM.getCurrentDir()
- API url на клиенте читается с файла config.json
- Если выбрана загрузка на Gist (GitHub) и у файла в формате json нет расширения — оно добавляется
- Добавлена возможность авторизовыватся на Гитхабе через новое окно. Изминен редирект на /auth и добавлена фнукция rout в cloudcmd.js
- DropBox, GDrive и GitHub модули теперь выглядят одинаково
- Рефакторинг в модуле Viewer
- Улучшена оптимизация файла menu.js с 2539 до 2444 байт
- Добавлена возможность читать GDrive key из конфига
- Добавлена возможность авторизовыватся на дропбоксе через popup
- Добавлена возможность авторизовыватся на vk.com.
- Улучшен формат и парсинг файла modules.json
- Добавлена возможность читать информацию о модулях сохранения из модуля меню
- Добавлена простая система рендеринга шаблонов Util.render
- Добавлены функции DOM.parseJSON и DOM.stringifyJSON, как обёртки над системными функциями
- Добавлен плагин для миграции к новой версии jquery
- Убрано получения данных из кеша Minify
- JSON-файлы перемещены в папки json
- Изминен шрифт с Octicons на Fonteollo
- Полностью удален шрифт Octicons.
- Удалено свойство allowed из свойства cache в конфиге.
- С этого момента оптимизироватся будут лишь те файлы, дата последнего изминения которых была изменена
- Убрана возможность кешировать файлы в памяти
- Перемещены расширения из main.js в json/ext.json
- Основная функциональность CloudCmd перемещена в commander.js из server.js
- Добавлена возможность удалять приставку к URL /fs когда мы в корневом каталоге
- Убраны перересовки создаваемые js
- Добавлена возможность добавлять флаг ?json только если мы работаем с папкой
- Изминен принцип работы функции clickProcessing в меню, теперь он гораздо проще
- Произведен тотальный рефакторинг в файле commander.js, теперь там только генерация
структуры каталогов в формате json
- добавлен обьект RESTfull в DOM модуль для упрощения работы с CloudCmd REST API.

View file

@ -1,14 +1,14 @@
Cloud Commander [![Build Status](https://secure.travis-ci.org/coderaiser/cloudcmd.png?branch=master)](http://travis-ci.org/coderaiser/cloudcmd)
===============
===============
**Cloud Commander** - user friendly cloud file manager.
DEMO:
[cloudfoundry] (http://cloudcmd.cloudfoundry.com "cloudfoundry"),
[appfog] (http://cloudcmd.aws.af.cm "appfog").
Google PageSpeed Score : [100](https://developers.google.com/speed/pagespeed/insights#url=http_3A_2F_2Fcloudcmd.cloudfoundry.com_2F&mobile=false "score") (out of 100)
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)
(or 96 if js or css minification disabled in config.json).
![Cloud Commander](https://raw.github.com/coderaiser/cloudcmd/dev/img/logo/cloudcmd.png)
![Cloud Commander](img/logo/cloudcmd.png "Cloud Commander")
Benefits
---------------
@ -41,19 +41,38 @@ There is a short list:
- **Alt + q** - disable key bindings
- **Alt + s** - get all key bindings back
- **up, down, enter** - filesystem navigation
- **Alt + g** - authorization
- **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
- **F2** - rename current file
- **Alt + g** - authorization in GitHub
Viewer's hot keys
---------------
- **Shift + F3** - open viewer window
- **Shift + F3** - open viewer window
- **Esc** - close viewer window
Editor's hot keys
---------------
- **F3** - open CodeMirror editor in read only mode
- **F4** - open CodeMirror editor
- **Ctrl + s** - save file
- **Esc** - close CodeMirror editor
Menu
---------------
Right mouse click button show context menu with items:
- View
- Edit
- 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/)
@ -76,19 +95,15 @@ All main configuration could be done thrue config.json.
```js
{
"api_url" :"/api/v1",
"appcache" : false, /* html5 feature appcache */
"cache" : {"allowed" : true}, /* cashing of js and css files in memory */
"appcache" : false, /* html5 feature appcache */
"cache" : true, /* cashing on a client */
"minification" : { /* minification of js,css,html and img */
"js" : false, /* minify module neaded */
"css" : false, /* npm i minify */
"html" : true,
"img" : false
},
"github_key" : "891c251b925e4e967fa9",
"github_secret" : "afe9bed1e810c5dc44c4c2a953fc6efb1e5b0545",
"dropbox_key" : "0nd3ssnp5fp7tqs",
"dropbox_chooser_key" : "o7d6llji052vijk"
"show_keys_panel" : true, /* show classic panel with buttons of keys */
"show_keys_panel" : true, /* show classic panel with buttons of keys */
"server" : true, /* server mode or testing mode */
"logs" : false, /* logs or console ouput */
"socket" : true /* enable web sockets */
@ -169,11 +184,15 @@ For extend main functionality Cloud Commander use next modules:
- [FancyBox] [FancyBoxURL]
- [jQuery-contextMenu] [jQuery-contextMenuURL]
- [jquery.terminal] [jquery.terminalURL]
- [github] [githubURL]
- [dropbox-js] [dropbox-jsURL]
[CodeMirrorURL]: https://github.com/marijnh/CodeMirror "CodeMirror"
[FancyBoxURL]: https://github.com/fancyapps/fancyBox "FancyBox"
[jQuery-contextMenuURL]: https://github.com/medialize/jQuery-contextMenu "jQuery-contextMenu"
[jquery.terminalURL]: https://github.com/jcubic/jquery.terminal "jquery.terminal"
[CodeMirrorURL]: //github.com/marijnh/CodeMirror "CodeMirror"
[FancyBoxURL]: //github.com/fancyapps/fancyBox "FancyBox"
[jQuery-contextMenuURL]: //github.com/medialize/jQuery-contextMenu "jQuery-contextMenu"
[jquery.terminalURL]: //github.com/jcubic/jquery.terminal "jquery.terminal"
[githubURL]: //github.com/michael/github
[dropbox-jsURL]: //github.com/dropbox/dropbox-js
Contributing
---------------

View file

@ -1,147 +1,318 @@
(function(){
"use strict";
var DIR = __dirname + '/',
main = require(DIR + 'lib/server/main'),
LIBDIR = main.LIBDIR,
SRVDIR = main.SRVDIR,
path = main.path,
fs = main.fs,
CloudFunc = main.cloudfunc,
Util = main.util,
update = main.update,
Server = main.require(LIBDIR + 'server'),
srv = Server.CloudServer,
Config = main.config;
/* reinit main dir os if we on
* Win32 should be backslashes */
DIR = main.DIR;
readConfig();
Server.start(Config, {
index : indexProcessing,
appcache : appCacheProcessing,
rest : rest
});
if(update)
update.get();
/**
* additional processing of index file
*/
function indexProcessing(pData){
var lReplace_s,
lData = pData.data,
lAdditional = pData.additional;
/*
* если выбрана опция минимизировать скрипты
* меняем в index.html обычные css на
* минифицированый
*/
if(srv.Minify._allowed.css){
var lPath = '/' + srv.Minify.MinFolder.replace(DIR, '');
lReplace_s = '<link rel=stylesheet href="/css/reset.css">';
lData = Util.removeStr(lData, lReplace_s)
.replace('/css/style.css', lPath + 'all.min.css');
}
lReplace_s = '<div id=fm class=no-js>';
/* меняем title */
lData = lData.replace(lReplace_s, lReplace_s + lAdditional)
.replace('<title>Cloud Commander</title>',
'<title>' + CloudFunc.getTitle() + '</title>');
if(!srv.Config.appcache)
lData = Util.removeStr(lData, ' manifest="/cloudcmd.appcache"');
if(!srv.Config.show_keys_panel){
var lKeysPanel = '<div id=keyspanel';
lData = lData.replace(lKeysPanel, lKeysPanel +' class=hidden');
}
return lData;
}
function appCacheProcessing(){
var lAppCache = srv.AppCache,
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'}];
if(srv.Minify._allowed.css)
lFiles.push('node_modules/minify/min/all.min.css');
lAppCache.addFiles(lFiles);
lAppCache.createManifest();
}
/**
* rest interface
* @pConnectionData {request, responce}
*/
function rest(pConnectionData){
return Util.exec(main.rest, pConnectionData);
}
function readConfig(){
/* Determining server.js directory
* and chang current process directory
* (usually /) to it.
* argv[1] - is always script name
*/
var lServerDir = path.dirname(process.argv[1]) + '/';
if( DIR !== lServerDir ){
console.log('current dir: ' + DIR);
process.chdir(lServerDir);
}
console.log('server dir: ' + lServerDir + '\n' +
'reading configuretion file config.json...');
if(Config){
console.log('config.json readed');
/* if command line parameter testing resolved
* setting config to testing, so server
* not created, just init and
* all logs writed to screen
*/
var lArg = process.argv;
lArg = lArg[lArg.length - 1];
if ( lArg === 'test' || lArg === 'test\r') {
console.log(process.argv);
Config.server =
Config.logs = false;
}
if (Config.logs) {
console.log('log param setted up in config.json\n' +
'from now all logs will be writed to log.txt');
writeLogsToFile();
}
}
}
/* function sets stdout to file log.txt */
function writeLogsToFile(){
var stdo = fs.createWriteStream('./log.txt');
process.stdout.write = (function(write) {
return function(string, encoding, fd) {
stdo.write(string);
};
})(process.stdout.write);
}
(function(){
'use strict';
var DIR = __dirname + '/',
main = require(DIR + 'lib/server/main'),
LIBDIR = main.LIBDIR,
SRVDIR = main.SRVDIR,
CLIENTDIR = LIBDIR + 'client',
HTMLDIR = main.HTMLDIR,
JSONDIR = main.JSONDIR,
path = main.path,
fs = main.fs,
CloudFunc = main.cloudfunc,
AppCache = main.appcache,
Util = main.util,
update = main.update,
server = main.librequire('server'),
Minify = main.minify,
Config = main.config,
INDEX = HTMLDIR + 'index.html',
CONFIG_PATH = JSONDIR + 'config.json',
FS = CloudFunc.FS;
/* reinit main dir os if we on
* Win32 should be backslashes */
DIR = main.DIR;
readConfig(init);
/**
* additional processing of index file
*/
function indexProcessing(pData){
var lReplace_s,
lData = pData.data,
lAdditional = pData.additional;
/*
* если выбрана опция минимизировать скрипты
* меняем в index.html обычные css на
* минифицированый
*/
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)
.replace('/css/style.css', lPath + 'all.min.css');
}
lData = Util.render(lData, {
title : CloudFunc.getTitle(),
fm : lAdditional
});
if(!Config.appcache)
lData = Util.removeStr(lData, ' manifest="/cloudcmd.appcache"');
if(!Config.show_keys_panel){
var lKeysPanel = '<div id=keyspanel';
lData = lData.replace(lKeysPanel, lKeysPanel +' class=hidden');
}
return lData;
}
/**
* 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'}];
if(Config.minification.css)
lFiles.push('node_modules/minify/min/all.min.css');
AppCache.addFiles(lFiles);
AppCache.createManifest();
}
/**
* Функция минимизирует css/js/html
* если установлены параметры минимизации
*/
function minimize(pAllowed){
var lOptimizeParams = [],
lStyleCSS = DIR + 'css/style.css',
lResetCSS = DIR + 'css/reset.css',
lCSSOptions = {
img : pAllowed.img,
merge : true
};
if (pAllowed.js)
lOptimizeParams.push(LIBDIR + 'client.js');
if (pAllowed.html)
lOptimizeParams.push(INDEX);
if (pAllowed.css) {
var lStyles = [{}, {}];
lStyles[0][lStyleCSS] = lCSSOptions;
lStyles[1][lResetCSS] = lCSSOptions;
lOptimizeParams.push(lStyles[0]);
lOptimizeParams.push(lStyles[1]);
}
if (lOptimizeParams.length)
Minify.optimize(lOptimizeParams, {
force: true
});
}
/**
* rest interface
* @pConnectionData {request, responce}
*/
function rest(pConnectionData){
return Util.exec(main.rest, pConnectionData);
}
function init(){
if(update)
update.get();
/* Determining server.js directory
* and chang current process directory
* (usually /) to it.
* argv[1] - is always script name
*/
var lServerDir = path.dirname(process.argv[1]) + '/';
if( DIR !== lServerDir ){
Util.log('current dir: ' + DIR);
process.chdir(lServerDir);
}
Util.log('server dir: ' + lServerDir);
Util.log('reading configuration file config.json...');
if(Config){
Util.log('config.json readed');
/* if command line parameter testing resolved
* setting config to testing, so server
* not created, just init and
* all logs writed to screen
*/
var lArg = process.argv;
lArg = lArg[lArg.length - 1];
if ( lArg === 'test' || lArg === 'test\r') {
Util.log(process.argv);
Config.server = false;
}
if (Config.logs) {
Util.log('log param setted up in config.json\n' +
'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);
});
server.start({
appcache : appCacheProcessing,
minimize : minimize,
rest : rest,
route : route
});
}
function readConfig(pCallBack){
fs.readFile(CONFIG_PATH, function(pError, pData){
if(!pError){
Util.log('config: readed');
var lStr = pData.toString(),
lReadedConf = Util.parseJSON(lStr);
if(!Config.minification)
main.config = Config = lReadedConf;
Util.tryCatchLog(function(){
Config.minification.js = lReadedConf.minification.js;
Config.cache = lReadedConf.cache;
Minify.setAllowed(Config.minification);
});
}
else
Util.log(pError);
Util.exec(pCallBack);
});
}
/**
* routing of server queries
*/
function route(pParams){
var lRet = main.checkParams(pParams);
if(lRet){
var p = pParams;
if( Util.strCmp(p.name, ['/auth', '/auth/github']) ){
Util.log('* Routing' +
'-> ' + p.name);
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);
lRet = sendCommanderContent(p);
}
/* termporary redirect for old urls */
else
lRet = false;
}
return lRet;
}
function sendCommanderContent(pParams){
var lRet = main.checkParams(pParams);
if(lRet){
var p = pParams;
p.name = Util.removeStr(p.name, CloudFunc.FS) || main.SLASH;
fs.stat(p.name, function(pError, pStat){
if(!pError)
if( pStat.isDirectory() )
processCommanderContent(pParams);
else
main.sendFile(p);
else
main.sendError(pParams, pError);
});
}
return lRet;
}
function processCommanderContent(pParams){
var lRet = main.checkParams(pParams);
if(lRet){
var p = pParams;
main.commander.getDirContent(p.name, function(pError, pJSON){
if(!pError){
var lQuery = main.getQuery(p.request);
if( Util.isContainStr(lQuery, 'json') ){
p.data = Util.stringifyJSON(pJSON);
p.name +='.json';
main.sendResponse(p);
}
else if(!lQuery){ /* 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),
lList = '<ul id=left class=panel>' + lPanel + '</ul>' +
'<ul id=right class=panel>' + lPanel + '</ul>';
main.sendResponse(p, indexProcessing({
additional : lList,
data : pData.toString(),
}));
}
else
main.sendError(pParams, pError);
});
}
}
else
main.sendError(pParams, pError);
});
}
}
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(){
var stdo = fs.createWriteStream('./log.txt');
process.stdout.write = (function(write) {
return function(string, encoding, fd) {
stdo.write(string);
};
})(process.stdout.write);
}
})();

View file

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

View file

@ -2,30 +2,21 @@
@import url(//fonts.googleapis.com/css?family=Droid+Sans+Mono);
*/
/* Foundation Icons General Enclosed */
/*
@font-face {
font-family: 'FoundationIconsGeneralEnclosed';
src: url('//dl.dropbox.com/u/78163899/mnemonia/fonts/foundation-icons-general-enclosed.woff') format('woff');
font-weight: normal;
font-style: normal;
}
*/
/* символьный шрифт от гитхаба*/
@font-face {
font-family: 'Fontello';
font-style: normal;
font-family: "GeneralFoundicons";
src: url('/font/general_foundicons.woff') format('woff');
font-weight: normal;
src: url('/font/fontello.woff') format('woff');
font-style: normal;
}
/* http://fontello.com/ */
@font-face {
font-family: 'Octicons Regular';
font-style: normal;
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;
src: local('Octicons Regular'), url('/font/Octicons.woff') format('woff');
font-style: normal;
}
@font-face {
@ -40,11 +31,6 @@ body{
background-color:white;
}
.menu{
margin-bottom: 0;
font: 16px 'Octicons Regular';
}
.path-icon{
position: relative;
top: 3px;
@ -59,9 +45,6 @@ body{
text-shadow:black 0 2px 1px;
}
.path-icon:hover{
/*
color:#45D827;
*/
cursor:pointer;
}
.path-icon:active{
@ -74,15 +57,15 @@ body{
width:16px;
height:16px;
margin-left:0.5%;
font-family: 'Octicons Regular';
font-size:16px;
/* font-family: 'GeneralFoundicons'; */
font-family: 'Fontello';
}
.error::before{
position: relative;
bottom : 4px;
color:rgb(222, 41, 41);
cursor:default;
content:'\f026';
font-size: 14px;
color: rgb(222, 41, 41);
cursor :default;
content: '\2757';
}
.loading{
position:relative;
@ -111,13 +94,14 @@ body{
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);
background-color: rgb(49,123,249);
}
.clear-cache{
@ -126,19 +110,13 @@ body{
background:url(/img/console_clear.png) -4px -4px no-repeat;
}
.clear-cache:active{
/*
background-position-y: -25px;
*/
top:5px;
}
.settings:before{
content:'k';
}
top:5px;
}
.links{
color:red;
}
.mini-icon {
position: relative;
top: 2px;
@ -158,7 +136,7 @@ body{
* установления курсора
*/
.current-file > .mini-icon{
left: -6px;
left: -6px;
}
/* freeupex */
.directory{
@ -177,7 +155,7 @@ body{
height: 90%;
margin: 26px 26px 0 26px;
}
.fm_header{
.fm-header{
font-weight: bold;
}
#path{
@ -233,8 +211,6 @@ body{
*/
text-align: right;
}
.owner{
}
.mode{
float: right;
width: 25%;
@ -252,14 +228,12 @@ button{
width:10%;
}
a{
text-decoration:none;
text-decoration:none;
}
a:hover, a:active {
color: #06e;
cursor:pointer;
outline: 0;
}
a:focus { outline: thin dotted; }
/* Если размер окна очень маленький
* располагаем имя и атрибуты файла
@ -276,6 +250,7 @@ a:focus { outline: thin dotted; }
}
/* текущий файл под курсором */
.current-file{
background-color: rgb(49, 123, 249);
background-color: rgba(49, 123, 249, .40);
color:white;
}
@ -287,24 +262,29 @@ a:focus { outline: thin dotted; }
color:white;
}
/* меняем иконки на шрифтовые*/
.fm-header{
display:none;
}
/* меняем иконки на шрифтовые*/
.mini-icon {
font: 60px 'Octicons Regular';
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: -17px;
color: rgba(246, 224, 124, 0.56);
top: 10px
}
.directory::before{
content: '\f216';
content: '\1f4c1';
}
.text-file::before{
color: rgb(26, 224, 124);
color: rgba(26, 224, 124, 0.56);
content: '\f211';
content: '\1f4c4';
}
.text-file{
background-image:none;
@ -353,7 +333,7 @@ a:focus { outline: thin dotted; }
}
}
@media only screen and (min-width: 601px) and (max-width: 785px){
.panel{
.panel{
width:94% !important;
}
#right{
@ -362,7 +342,7 @@ a:focus { outline: thin dotted; }
}
@media only screen and (min-width:786px) and (max-width: 1155px){
.panel{
.panel{
width:94% !important;
}
/* если правая панель не помещаеться - прячем её */

Binary file not shown.

BIN
font/fontello.eot Normal file

Binary file not shown.

40
font/fontello.svg Normal file
View file

@ -0,0 +1,40 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>
Created by FontForge 20100429 at Thu Jan 24 11:50:44 2013
By root
Copyright (C) 2012 by original authors @ fontello.com
</metadata>
<defs>
<font id="cloudcmd" horiz-adv-x="364" >
<font-face
font-family="cloudcmd"
font-weight="500"
font-stretch="normal"
units-per-em="1000"
panose-1="2 0 6 3 0 0 0 0 0 0"
ascent="850"
descent="-150"
bbox="14.6522 -100 1015.35 800"
underline-thickness="50"
underline-position="-100"
unicode-range="U+2757-1F4C4"
/>
<missing-glyph
d="M33 0v666h265v-666h-265zM66 33h199v600h-199v-600z" />
<glyph glyph-name=".notdef"
d="M33 0v666h265v-666h-265zM66 33h199v600h-199v-600z" />
<glyph glyph-name=".null" horiz-adv-x="0"
/>
<glyph glyph-name="nonmarkingreturn" horiz-adv-x="333"
/>
<glyph glyph-name="uni2757" unicode="&#x2757;" horiz-adv-x="887"
d="M15 350q0 123 60 219.5t153 151.5q101 58 216 58q123 0 219.5 -60t151.5 -154q57 -99 57 -215q0 -123 -60 -219.5t-153 -151.5q-101 -58 -215 -58q-123 0 -220 60t-152 154q-57 99 -57 215zM382 636q-7 0 -13 -4.5t-6 -10.5l10 -346q0 -6 6 -10t13 -4h103q17 0 19 14
l10 346q0 6 -6 10.5t-13 4.5h-123zM372 83q0 -7 6 -13t13 -6h107q7 0 12 5.5t5 13.5v106q0 18 -17 18h-107q-8 0 -13.5 -5.5t-5.5 -12.5v-106z" />
<glyph glyph-name="u1F4C1" unicode="&#x1f4c1;" horiz-adv-x="1031"
d="M969 500q29 0 38.5 -10.5t7.5 -37.5l-42 -452q-2 -27 -13.5 -38.5t-40.5 -11.5h-806q-51 0 -56 50l-42 452q-2 27 7.5 37.5t38.5 10.5h908zM935 610l10 -40h-846l14 132q4 19 20 33.5t36 14.5h164q52 0 86 -34l30 -30q31 -36 86 -36h340q20 0 38 -12t22 -28z" />
<glyph glyph-name="u1F4C4" unicode="&#x1f4c4;" horiz-adv-x="730"
d="M227 398h280v-90h-280v90zM615 800q42 0 71 -29t29 -71v-700q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-40 0 -70 30t-30 70v700q0 41 29.5 70.5t70.5 29.5h500zM615 700h-500v-700h500v700zM505 504h-280v88h280v-88zM505 200v-88h-280v88h280z" />
</font>
</defs></svg>

After

Width:  |  Height:  |  Size: 2 KiB

BIN
font/fontello.ttf Normal file

Binary file not shown.

BIN
font/fontello.woff Normal file

Binary file not shown.

Binary file not shown.

18
html/auth.html Normal file
View file

@ -0,0 +1,18 @@
<!doctype html>
<html>
<head></head>
<body>
<script>
(function(){
'use strict';
if(window.opener){
var lGitHub = window.opener.CloudCommander.GitHub;
lGitHub.autorize(lGitHub.callback, window.location.search);
window.close();
}
})();
</script>
</body>
</html>

11
html/auth/dropbox.html Normal file
View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<script src=http://cdnjs.cloudflare.com/ajax/libs/dropbox.js/0.8.1/dropbox.min.js></script>
</head>
<body>
<script>
Dropbox.Drivers.Popup.oauthReceiver();
</script>
</body>
</html>

27
html/auth/github.html Normal file
View file

@ -0,0 +1,27 @@
<!DOCTYPE html>
<html>
<head></head>
<body>
<script>
(function(){
"use strict";
var lOpener = window.opener;
if(lOpener){
var cloudcmd = lOpener.CloudCommander;
cloudcmd.getModules(function(pModules){
var Util = lOpener.Util,
lStorage = Util.findObjByNameInArr(pModules, 'storage'),
lGitHub = Util.findObjByNameInArr(lStorage, 'GitHub'),
GitHub_ID = lGitHub && lGitHub.key;
window.location =
'https://github.com/login/oauth/authorize?client_id=' +
GitHub_ID + '&&scope=repo,user,gist';
});
}
})();
</script>
</body>
</html>

View file

@ -4,26 +4,14 @@
<meta charset="utf-8">
<!-- mobile first design -->
<meta content="width=device-width,initial-scale=1" name="viewport" />
<title>Cloud Commander</title>
<title>{title}</title>
<link rel=stylesheet href="/css/reset.css">
<link rel=stylesheet href="/css/style.css">
</head>
<body>
<ul class="menu hidden">
<li>
<!-- http://css-tricks.com/examples/IconFont/ -->
<a href=settings onclick="return false;">
<span class="menu_icon settings hidden"></span>
</a>
<a href=refresh onclick="return false;">
<span class="menu_icon refresh" id=refresh></span>
</a>
</li>
</ul>
<div id=fm class=no-js>
</div>
<div id=fm class=no-js>{fm}</div>
<div id=keyspanel>
<button id=f1 class=cmd-button>F1 - help</button>
<button id=f2 class=cmd-button>F2 - rename</button>
@ -32,15 +20,16 @@
<button id=f5 class=cmd-button>F5 - copy</button>
<button id=f6 class=cmd-button>F6 - move</button>
<button id=f7 class=cmd-button>F7 - make dir</button>
<button id=f8 class=cmd-button>F8 - remove</button>
<button id=f8 class=cmd-button>F8 - delete</button>
</div>
<script src=/lib/util.js></script>
<script src=/lib/client/dom.js></script>
<script src=/lib/client.js></script>
<!--[if lt IE 9]>
<script src="//ie7-js.googlecode.com/svn/version/2.1(beta4)/IE9.js" async></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.0/jquery.min.js" id=jquery_min_js ></script>
<!--[if lt IE]>
<script src=//ie7-js.googlecode.com/svn/version/2.1(beta4)/IE9.js></script>
<script src=//code.jquery.com/jquery-1.9.0.min.js id=jquery-1_9_0_min_js ></script>
<script src=/lib/client/ie.js id=ie_js ></script>
<script src=//getfirebug.com/firebug-lite-debug.js></script>
<![endif]-->
<script src=/lib/client.js></script>
</body>
</html>

View file

@ -1,22 +1,18 @@
{
"api_url" : "/api/v1",
"appcache" : false,
"cache" : {"allowed" : false},
"minification" : {
"js" : true,
"css" : true,
"html" : true,
"img" : true
},
"github_key" : "891c251b925e4e967fa9",
"github_secret" : "afe9bed1e810c5dc44c4c2a953fc6efb1e5b0545",
"dropbox_key" : "0nd3ssnp5fp7tqs",
"dropbox_chooser_key" : "o7d6llji052vijk",
"logs" : false,
"show_keys_panel" : true,
"server" : true,
"socket" : true,
"port" : 80,
"ip" : null,
"rest" : true
{
"api_url" : "/api/v1",
"appcache" : false,
"minification" : {
"js" : true,
"css" : true,
"html" : true,
"img" : true
},
"cache" : true,
"logs" : false,
"show_keys_panel" : true,
"server" : true,
"socket" : true,
"port" : 80,
"ip" : null,
"rest" : true
}

169
json/ext.json Normal file
View file

@ -0,0 +1,169 @@
{
".3gp": "video/3gpp",
".a": "application/octet-stream",
".ai": "application/postscript",
".aif": "audio/x-aiff",
".aiff": "audio/x-aiff",
".appcache": "text/cache-manifest",
".asc": "application/pgp-signature",
".asf": "video/x-ms-asf",
".asm": "text/x-asm",
".asx": "video/x-ms-asf",
".atom": "application/atom+xml",
".au": "audio/basic",
".avi": "video/x-msvideo",
".bat": "application/x-msdownload",
".bin": "application/octet-stream",
".bmp": "image/bmp",
".bz2": "application/x-bzip2",
".c": "text/x-c",
".cab": "application/vnd.ms-cab-compressed",
".cc": "text/x-c",
".chm": "application/vnd.ms-htmlhelp",
".class": "application/octet-stream",
".com": "application/x-msdownload",
".conf": "text/plain",
".cpp": "text/x-c",
".crt": "application/x-x509-ca-cert",
".css": "text/css",
".csv": "text/csv",
".cxx": "text/x-c",
".deb": "application/x-debian-package",
".der": "application/x-x509-ca-cert",
".diff": "text/x-diff",
".djv": "image/vnd.djvu",
".djvu": "image/vnd.djvu",
".dll": "application/x-msdownload",
".dmg": "application/octet-stream",
".doc": "application/msword",
".dot": "application/msword",
".dtd": "application/xml-dtd",
".dvi": "application/x-dvi",
".ear": "application/java-archive",
".eml": "message/rfc822",
".eps": "application/postscript",
".exe": "application/x-msdownload",
".f": "text/x-fortran",
".f77": "text/x-fortran",
".f90": "text/x-fortran",
".flv": "video/x-flv",
".for": "text/x-fortran",
".gem": "application/octet-stream",
".gemspec": "text/x-script.ruby",
".gif": "image/gif",
".gz": "application/x-gzip",
".h": "text/x-c",
".hh": "text/x-c",
".htm": "text/html",
".html": "text/html",
".ico": "image/vnd.microsoft.icon",
".ics": "text/calendar",
".ifb": "text/calendar",
".iso": "application/octet-stream",
".jar": "application/java-archive",
".java": "text/x-java-source",
".jnlp": "application/x-java-jnlp-file",
".jpeg": "image/jpeg",
".jpg": "image/jpeg",
".js": "application/javascript",
".json": "application/json",
".log": "text/plain",
".m3u": "audio/x-mpegurl",
".m4v": "video/mp4",
".man": "text/troff",
".mathml": "application/mathml+xml",
".mbox": "application/mbox",
".mdoc": "text/troff",
".me": "text/troff",
".mid": "audio/midi",
".midi": "audio/midi",
".mime": "message/rfc822",
".mml": "application/mathml+xml",
".mng": "video/x-mng",
".mov": "video/quicktime",
".mp3": "audio/mpeg",
".mp4": "video/mp4",
".mp4v": "video/mp4",
".mpeg": "video/mpeg",
".mpg": "video/mpeg",
".ms": "text/troff",
".msi": "application/x-msdownload",
".odp": "application/vnd.oasis.opendocument.presentation",
".ods": "application/vnd.oasis.opendocument.spreadsheet",
".odt": "application/vnd.oasis.opendocument.text",
".ogg": "application/ogg",
".p": "text/x-pascal",
".pas": "text/x-pascal",
".pbm": "image/x-portable-bitmap",
".pdf": "application/pdf",
".pem": "application/x-x509-ca-cert",
".pgm": "image/x-portable-graymap",
".pgp": "application/pgp-encrypted",
".pkg": "application/octet-stream",
".pl": "text/x-script.perl",
".pm": "text/x-script.perl-module",
".png": "image/png",
".pnm": "image/x-portable-anymap",
".ppm": "image/x-portable-pixmap",
".pps": "application/vnd.ms-powerpoint",
".ppt": "application/vnd.ms-powerpoint",
".ps": "application/postscript",
".psd": "image/vnd.adobe.photoshop",
".py": "text/x-script.python",
".qt": "video/quicktime",
".ra": "audio/x-pn-realaudio",
".rake": "text/x-script.ruby",
".ram": "audio/x-pn-realaudio",
".rar": "application/x-rar-compressed",
".rb": "text/x-script.ruby",
".rdf": "application/rdf+xml",
".roff": "text/troff",
".rpm": "application/x-redhat-package-manager",
".rss": "application/rss+xml",
".rtf": "application/rtf",
".ru": "text/x-script.ruby",
".s": "text/x-asm",
".sgm": "text/sgml",
".sgml": "text/sgml",
".sh": "application/x-sh",
".sig": "application/pgp-signature",
".snd": "audio/basic",
".so": "application/octet-stream",
".svg": "image/svg+xml",
".svgz": "image/svg+xml",
".swf": "application/x-shockwave-flash",
".t": "text/troff",
".tar": "application/x-tar",
".tbz": "application/x-bzip-compressed-tar",
".tcl": "application/x-tcl",
".tex": "application/x-tex",
".texi": "application/x-texinfo",
".texinfo": "application/x-texinfo",
".text": "text/plain",
".tif": "image/tiff",
".tiff": "image/tiff",
".torrent": "application/x-bittorrent",
".tr": "text/troff",
".txt": "text/plain",
".vcf": "text/x-vcard",
".vcs": "text/x-vcalendar",
".vrml": "model/vrml",
".war": "application/java-archive",
".wav": "audio/x-wav",
".wma": "audio/x-ms-wma",
".wmv": "video/x-ms-wmv",
".wmx": "video/x-ms-wmx",
".woff": "font/woff",
".wrl": "model/vrml",
".wsdl": "application/wsdl+xml",
".xbm": "image/x-xbitmap",
".xhtml": "application/xhtml+xml",
".xls": "application/vnd.ms-excel",
".xml": "application/xml",
".xpm": "image/x-xpixmap",
".xsl": "application/xml",
".xslt": "application/xslt+xml",
".yaml": "text/yaml",
".yml": "text/yaml",
".zip": "application/zip"
}

40
json/modules.json Normal file
View file

@ -0,0 +1,40 @@
[
"editor/_codemirror",
"menu",
"viewer",
"terminal", {
"name": "storage",
"data": [{
"name": "DropBox",
"data":{
"key" : "0nd3ssnp5fp7tqs",
"secret" : "r61lxpchmk8l06o",
"encodedKey": "DkMz4FYHQTA=|GW6pf2dONkrGvckMwBsl1V1vysrCPktPiUWN7UpDjw==",
"chooserKey": "o7d6llji052vijk"
}
}, {
"name": "GitHub",
"data": {
"key" : "891c251b925e4e967fa9",
"secret" : "afe9bed1e810c5dc44c4c2a953fc6efb1e5b0545"
}
}, {
"name": "GDrive",
"data": {
"id" : "255175681917"
}
}, {
"name": "VK",
"data": {
"id" : "3336188"
}
},
{
"name": "SkyDrive",
"data": {
"id" : "00000000440E696F"
}
}
]
}
]

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -107,17 +107,12 @@ var CloudCommander, CloudFunc, ace;
/* if directory - load json
* not html data
*/
if (lSize === '<dir>'){
if (lA.indexOf(CloudFunc.NOJS) ===
CloudFunc.FS.length) {
lA = lA.replace(CloudFunc.NOJS, '');
/* when folder view
* is no need to edit
* data
*/
ReadOnly = true;
}
}
if (lSize === '<dir>')
/* when folder view
* is no need to edit
* data
*/
ReadOnly = true;
}
this.loading = true;

View file

@ -1,240 +1,214 @@
var CloudCommander, Util, DOM, CodeMirror;
/* object contains editors CodeMirror */
(function(){
"use strict";
var cloudcmd = CloudCommander,
KeyBinding = CloudCommander.KeyBinding,
CodeMirrorEditor = {},
FM,
CodeMirrorElement,
CodeMirrorLoaded = false,
/* indicator says CodeMirror still loads */
Loading = false,
ReadOnly = false,
CallBacks = [
hide,
initCodeMirror,
show,
load
];
cloudcmd.Editor = {
get : (function(){
return this.CodeMirror;
})
};
/* private functions */
function setCSS(){
var lPosition = DOM.getPanel().id,
lRet = DOM.cssSet({
id : 'editor',
inner : '.CodeMirror{' +
'font-family' + ': \'Droid Sans Mono\';' +
'font-size' + ': 15px;' +
'}' +
'.CodeMirror-scroll{' +
'height' + ':' + cloudcmd.HEIGHT + 'px' +
'}' +
'#CodeMirrorEditor{' +
'float' + ':' + lPosition +
'}'
});
return lRet;
}
/**
* function initialize CodeMirror
* @param {value, callback}
*/
function initCodeMirror(pData){
if(!pData)
pData = {};
if(!FM)
FM = DOM.getFM();
var lCSS = setCSS();
CodeMirrorElement = DOM.anyload({
name : 'div',
id : 'CodeMirrorEditor',
className : 'panel',
parent : FM
});
CodeMirrorEditor.CodeMirror = new CodeMirror(CodeMirrorElement,{
mode : 'javascript',
value : pData.data,
theme : 'night',
lineNumbers : true,
//переносим длинные строки
lineWrapping: false,
autofocus : true,
extraKeys: {
//Сохранение
'Esc': function(){
Util.exec(pData.callback);
DOM.remove(lCSS, document.head);
}
},
readOnly : ReadOnly
});
}
/**
* function loads CodeMirror js and css files
*/
function load(pCallBack){
console.time('codemirror load');
var lDir = cloudcmd.LIBDIRCLIENT + 'editor/codemirror/',
lFiles =
[
[
lDir + 'codemirror.css',
lDir + 'theme/night.css',
lDir + 'mode/javascript.js',
],
lDir + 'codemirror.js'
];
DOM.anyLoadOnLoad(lFiles, function(){
console.timeEnd('codemirror load');
CodeMirrorLoaded = true;
Util.exec(pCallBack);
});
}
/**
* function shows CodeMirror editor
*/
function show(pCallBack){
/* if CodeMirror function show already
* called do not call it again
* if f4 key pressed couple times
*/
if(Loading)
return;
/* getting link */
var lCurrentFile = DOM.getCurrentFile(),
lPath = DOM.getCurrentPath(lCurrentFile);
/* checking is this link is to directory */
var lSize = DOM.getByClass('size', lCurrentFile);
if(lSize){
lSize = lSize[0].textContent;
/* when folder view
* is no need to edit
* data
*/
if (lSize === '<dir>')
ReadOnly = true;
}
Loading = true;
setTimeout(function(){
Loading = false;
},
400);
/* reading data from current file */
DOM.ajax({
url:lPath,
error: function(jqXHR, textStatus, errorThrown){
Loading = false;
return DOM.Images.showError(jqXHR);
},
success:function(data, textStatus, jqXHR){
/* if we got json - show it */
if(typeof data === 'object')
data = JSON.stringify(data, null, 4);
var lHided = DOM.hidePanel();
if(lHided){
Util.exec(pCallBack, data);
/* removing keyBinding if set */
KeyBinding.unSet();
}
DOM.Images.hideLoad();
Loading = false;
}
});
}
/**
* function hides CodeMirror editor
*/
function hide() {
var lElem = CodeMirrorElement;
KeyBinding.set();
if(lElem && FM)
FM.removeChild(lElem);
DOM.showPanel();
}
/**
* function calls all CodeMirror editor functions
*/
CodeMirrorEditor.show = function(){
DOM.Images.showLoad();
Util.loadOnLoad( CallBacks );
};
/**
* function hides CodeMirror editor
*/
CodeMirrorEditor.hide = hide;
/**
* function bind keys
*/
cloudcmd.Editor.init = function(pReadOnly){
ReadOnly = pReadOnly;
CodeMirrorEditor.show();
CallBacks.pop();
var lKeyListener = function(pEvent){
/* если клавиши можно обрабатывать */
if( KeyBinding.get() ){
/* if f4 or f3 pressed */
var lF3 = cloudcmd.KEY.F3,
lF4 = cloudcmd.KEY.F4;
if(!pEvent.shiftKey)
switch(pEvent.keyCode)
{
case lF4:
ReadOnly = false;
CodeMirrorEditor.show();
break;
case lF3:
ReadOnly = true;
CodeMirrorEditor.show();
break;
}
}
};
/* добавляем обработчик клавишь */
DOM.addKeyListener( lKeyListener );
DOM.setButtonKey('f4', CodeMirrorEditor.show);
};
cloudcmd.Editor.CodeMirror = CodeMirrorEditor;
})();
var CloudCommander, Util, DOM, CloudFunc, CodeMirror;
/* object contains editors CodeMirror */
(function(CloudCmd, Util, DOM, CloudFunc){
'use strict';
var KeyBinding = CloudCommander.KeyBinding,
CodeMirrorEditor = {},
FM,
CodeMirrorElement,
CodeMirrorLoaded = false,
/* indicator says CodeMirror still loads */
Loading = false,
ReadOnly = false,
CallBacks = [
hide,
initCodeMirror,
show,
load
];
CloudCmd.Editor = {
get : (function(){
return this.CodeMirror;
})
};
/* private functions */
function setCSS(){
var lPosition = DOM.getPanel().id,
lRet = DOM.cssSet({
id : 'editor',
inner : '.CodeMirror{' +
'font-family' + ': \'Droid Sans Mono\';' +
'font-size' + ': 15px;' +
'}' +
'.CodeMirror-scroll{' +
'height' + ':' + CloudCmd.HEIGHT + 'px' +
'}' +
'#CodeMirrorEditor{' +
'float' + ':' + lPosition +
'}'
});
return lRet;
}
/**
* function initialize CodeMirror
* @param {value, callback}
*/
function initCodeMirror(pParams){
if(!FM)
FM = DOM.getFM();
var lCSS = setCSS();
CodeMirrorElement = DOM.anyload({
name : 'div',
id : 'CodeMirrorEditor',
className : 'panel',
parent : FM
});
var lEditor = CodeMirrorEditor.CodeMirror = new CodeMirror(CodeMirrorElement,{
mode : 'javascript',
value : pParams && pParams.data && pParams.data.data,
theme : 'night',
lineNumbers : true,
//переносим длинные строки
lineWrapping: false,
autofocus : true,
extraKeys: {
//Сохранение
'Esc': function(){
Util.exec(pParams);
DOM.remove(lCSS, document.head);
},
'Ctrl-S': function(){
DOM.RESTfull.save(DOM.getCurrentPath(), lEditor.getValue());
}
},
readOnly : ReadOnly
});
}
/**
* function loads CodeMirror js and css files
*/
function load(pCallBack){
console.time('codemirror load');
var lDir = CloudCmd.LIBDIRCLIENT + 'editor/codemirror/',
lFiles =
[
[
lDir + 'codemirror.css',
lDir + 'theme/night.css',
lDir + 'mode/javascript.js',
],
lDir + 'codemirror.js'
];
DOM.anyLoadOnLoad(lFiles, function(){
console.timeEnd('codemirror load');
CodeMirrorLoaded = true;
Util.exec(pCallBack);
});
}
/**
* function shows CodeMirror editor
*/
function show(pCallBack){
/* if CodeMirror function show already
* 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();
}
DOM.Images.hideLoad();
lFalseLoading();
}
});
}
/**
* function hides CodeMirror editor
*/
function hide() {
var lElem = CodeMirrorElement;
KeyBinding.set();
if(lElem && FM)
FM.removeChild(lElem);
DOM.showPanel();
}
/**
* function calls all CodeMirror editor functions
*/
CodeMirrorEditor.show = function(){
DOM.Images.showLoad();
Util.loadOnLoad( CallBacks );
};
/**
* function hides CodeMirror editor
*/
CodeMirrorEditor.hide = hide;
/**
* function bind keys
*/
CloudCmd.Editor.init = function(pReadOnly){
ReadOnly = pReadOnly;
CodeMirrorEditor.show();
CallBacks.pop();
var lKeyListener = function(pEvent){
/* если клавиши можно обрабатывать */
if( KeyBinding.get() ){
/* if f4 or f3 pressed */
var lF3 = CloudCmd.KEY.F3,
lF4 = CloudCmd.KEY.F4;
if(!pEvent.shiftKey)
switch(pEvent.keyCode)
{
case lF4:
ReadOnly = false;
CodeMirrorEditor.show();
break;
case lF3:
ReadOnly = true;
CodeMirrorEditor.show();
break;
}
}
};
/* добавляем обработчик клавишь */
DOM.addKeyListener( lKeyListener );
DOM.setButtonKey('f4', CodeMirrorEditor.show);
};
CloudCmd.Editor.CodeMirror = CodeMirrorEditor;
})(CloudCommander, Util, DOM, CloudFunc);

View file

@ -1,174 +1,176 @@
.CodeMirror {
line-height: 1em;
font-family: monospace;
/* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */
position: relative;
/* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */
overflow: hidden;
}
.CodeMirror-scroll {
overflow: auto;
height: 300px;
/* This is needed to prevent an IE[67] bug where the scrolled content
is visible outside of the scrolling box. */
position: relative;
outline: none;
}
/* Vertical scrollbar */
.CodeMirror-scrollbar {
position: absolute;
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
z-index: 5;
}
.CodeMirror-scrollbar-inner {
/* This needs to have a nonzero width in order for the scrollbar to appear
in Firefox and IE9. */
width: 1px;
}
.CodeMirror-scrollbar.cm-sb-overlap {
/* Ensure that the scrollbar appears in Lion, and that it overlaps the content
rather than sitting to the right of it. */
position: absolute;
z-index: 1;
float: none;
right: 0;
min-width: 12px;
}
.CodeMirror-scrollbar.cm-sb-nonoverlap {
min-width: 12px;
}
.CodeMirror-scrollbar.cm-sb-ie7 {
min-width: 18px;
}
.CodeMirror-gutter {
position: absolute; left: 0; top: 0;
z-index: 10;
background-color: #f7f7f7;
border-right: 1px solid #eee;
min-width: 2em;
height: 100%;
}
.CodeMirror-gutter-text {
color: #aaa;
text-align: right;
padding: .4em .2em .4em .4em;
white-space: pre !important;
cursor: default;
}
.CodeMirror-lines {
padding: .4em;
white-space: pre;
cursor: text;
}
.CodeMirror pre {
-moz-border-radius: 0;
-webkit-border-radius: 0;
-o-border-radius: 0;
border-radius: 0;
border-width: 0; margin: 0; padding: 0; background: transparent;
font-family: inherit;
font-size: inherit;
padding: 0; margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
overflow: visible;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: hidden;
}
.CodeMirror textarea {
outline: none !important;
}
.CodeMirror pre.CodeMirror-cursor {
z-index: 10;
position: absolute;
visibility: hidden;
border-left: 1px solid black;
border-right: none;
width: 0;
}
.cm-keymap-fat-cursor pre.CodeMirror-cursor {
width: auto;
border: 0;
background: transparent;
background: rgba(0, 200, 0, .4);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800);
}
/* Kludge to turn off filter in ie9+, which also accepts rgba */
.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) {
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
}
.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
.CodeMirror-focused pre.CodeMirror-cursor {
visibility: visible;
}
div.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* Default theme */
.cm-s-default span.cm-keyword {color: #708;}
.cm-s-default span.cm-atom {color: #219;}
.cm-s-default span.cm-number {color: #164;}
.cm-s-default span.cm-def {color: #00f;}
.cm-s-default span.cm-variable {color: black;}
.cm-s-default span.cm-variable-2 {color: #05a;}
.cm-s-default span.cm-variable-3 {color: #085;}
.cm-s-default span.cm-property {color: black;}
.cm-s-default span.cm-operator {color: black;}
.cm-s-default span.cm-comment {color: #a50;}
.cm-s-default span.cm-string {color: #a11;}
.cm-s-default span.cm-string-2 {color: #f50;}
.cm-s-default span.cm-meta {color: #555;}
.cm-s-default span.cm-error {color: #f00;}
.cm-s-default span.cm-qualifier {color: #555;}
.cm-s-default span.cm-builtin {color: #30a;}
.cm-s-default span.cm-bracket {color: #997;}
.cm-s-default span.cm-tag {color: #170;}
.cm-s-default span.cm-attribute {color: #00c;}
.cm-s-default span.cm-header {color: blue;}
.cm-s-default span.cm-quote {color: #090;}
.cm-s-default span.cm-hr {color: #999;}
.cm-s-default span.cm-link {color: #00c;}
span.cm-header, span.cm-strong {font-weight: bold;}
span.cm-em {font-style: italic;}
span.cm-emstrong {font-style: italic; font-weight: bold;}
span.cm-link {text-decoration: underline;}
span.cm-invalidchar {color: #f00;}
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
@media print {
/* Hide the cursor when printing */
.CodeMirror pre.CodeMirror-cursor {
visibility: hidden;
}
}
.CodeMirror {
line-height: 1em;
font-family: monospace;
/* Necessary so the scrollbar can be absolutely positioned within the wrapper on Lion. */
position: relative;
/* This prevents unwanted scrollbars from showing up on the body and wrapper in IE. */
overflow: hidden;
}
.CodeMirror-scroll {
overflow: auto;
height: 300px;
/* This is needed to prevent an IE[67] bug where the scrolled content
is visible outside of the scrolling box. */
position: relative;
outline: none;
}
/* Vertical scrollbar */
.CodeMirror-scrollbar {
position: absolute;
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
z-index: 5;
}
.CodeMirror-scrollbar-inner {
/* This needs to have a nonzero width in order for the scrollbar to appear
in Firefox and IE9. */
width: 1px;
}
.CodeMirror-scrollbar.cm-sb-overlap {
/* Ensure that the scrollbar appears in Lion, and that it overlaps the content
rather than sitting to the right of it. */
position: absolute;
z-index: 1;
float: none;
right: 0;
min-width: 12px;
}
.CodeMirror-scrollbar.cm-sb-nonoverlap {
min-width: 12px;
}
.CodeMirror-scrollbar.cm-sb-ie7 {
min-width: 18px;
}
.CodeMirror-gutter {
position: absolute; left: 0; top: 0;
z-index: 10;
background-color: #f7f7f7;
border-right: 1px solid #eee;
min-width: 2em;
height: 100%;
}
.CodeMirror-gutter-text {
color: #aaa;
text-align: right;
padding: .4em .2em .4em .4em;
white-space: pre !important;
cursor: default;
}
.CodeMirror-lines {
padding: .4em;
white-space: pre;
cursor: text;
}
.CodeMirror pre {
-moz-border-radius: 0;
-webkit-border-radius: 0;
-o-border-radius: 0;
border-radius: 0;
border-width: 0; margin: 0; padding: 0; background: transparent;
font-family: inherit;
font-size: inherit;
padding: 0; margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
overflow: visible;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-wrap .CodeMirror-scroll {
overflow-x: hidden;
}
.CodeMirror textarea {
outline: none !important;
}
.CodeMirror pre.CodeMirror-cursor {
z-index: 10;
position: absolute;
visibility: hidden;
border-left: 1px solid black;
border-right: none;
width: 0;
}
.cm-keymap-fat-cursor pre.CodeMirror-cursor {
width: auto;
border: 0;
background: transparent;
background: rgba(0, 200, 0, .4);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr=#6600c800, endColorstr=#4c00c800);
}
/* Kludge to turn off filter in ie9+, which also accepts rgba */
.cm-keymap-fat-cursor pre.CodeMirror-cursor:not(#nonsense_id) {
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
}
.CodeMirror pre.CodeMirror-cursor.CodeMirror-overwrite {}
.CodeMirror-focused pre.CodeMirror-cursor {
visibility: visible;
}
div.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused div.CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-searching {
background: #ffa;
background: rgba(255, 255, 0, .4);
}
/* Default theme */
.cm-s-default span.cm-keyword {color: #708;}
.cm-s-default span.cm-atom {color: #219;}
.cm-s-default span.cm-number {color: #164;}
.cm-s-default span.cm-def {color: #00f;}
.cm-s-default span.cm-variable {color: black;}
.cm-s-default span.cm-variable-2 {color: #05a;}
.cm-s-default span.cm-variable-3 {color: #085;}
.cm-s-default span.cm-property {color: black;}
.cm-s-default span.cm-operator {color: black;}
.cm-s-default span.cm-comment {color: #a50;}
.cm-s-default span.cm-string {color: #a11;}
.cm-s-default span.cm-string-2 {color: #f50;}
.cm-s-default span.cm-meta {color: #555;}
.cm-s-default span.cm-error {color: #f00;}
.cm-s-default span.cm-qualifier {color: #555;}
.cm-s-default span.cm-builtin {color: #30a;}
.cm-s-default span.cm-bracket {color: #997;}
.cm-s-default span.cm-tag {color: #170;}
.cm-s-default span.cm-attribute {color: #00c;}
.cm-s-default span.cm-header {color: blue;}
.cm-s-default span.cm-quote {color: #090;}
.cm-s-default span.cm-hr {color: #999;}
.cm-s-default span.cm-link {color: #00c;}
span.cm-negative {color: #d44;}
span.cm-positive {color: #292;}
span.cm-header, span.cm-strong {font-weight: bold;}
span.cm-em {font-style: italic;}
span.cm-emstrong {font-style: italic; font-weight: bold;}
span.cm-link {text-decoration: underline;}
span.cm-invalidchar {color: #f00;}
div.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}
@media print {
/* Hide the cursor when printing */
.CodeMirror pre.CodeMirror-cursor {
visibility: hidden;
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,409 +1,419 @@
// TODO actually recognize syntax of TypeScript constructs
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var jsonMode = parserConfig.json;
var isTS = parserConfig.typescript;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
var jsKeywords = {
"if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
};
// Extend the 'normal' keywords with the TypeScript language extensions
if (isTS) {
var type = {type: "variable", style: "variable-3"};
var tsKeywords = {
// object-like things
"interface": kw("interface"),
"class": kw("class"),
"extends": kw("extends"),
"constructor": kw("constructor"),
// scope modifiers
"public": kw("public"),
"private": kw("private"),
"protected": kw("protected"),
"static": kw("static"),
"super": kw("super"),
// types
"string": type, "number": type, "bool": type, "any": type
};
for (var attr in tsKeywords) {
jsKeywords[attr] = tsKeywords[attr];
}
}
return jsKeywords;
}();
var isOperatorChar = /[+\-*&%=<>!?|]/;
function chain(stream, state, f) {
state.tokenize = f;
return f(stream, state);
}
function nextUntilUnescaped(stream, end) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (next == end && !escaped)
return false;
escaped = !escaped && next == "\\";
}
return escaped;
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function jsTokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'")
return chain(stream, state, jsTokenString(ch));
else if (/[\[\]{}\(\),;\:\.]/.test(ch))
return ret(ch);
else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
}
else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
}
else if (ch == "/") {
if (stream.eat("*")) {
return chain(stream, state, jsTokenComment);
}
else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
}
else if (state.lastType == "operator" || state.lastType == "keyword c" ||
/^[\[{}\(,;:]$/.test(state.lastType)) {
nextUntilUnescaped(stream, "/");
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
return ret("regexp", "string-2");
}
else {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
}
else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
}
else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
else {
stream.eatWhile(/[\w\$_]/);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
function jsTokenString(quote) {
return function(stream, state) {
if (!nextUntilUnescaped(stream, quote))
state.tokenize = jsTokenBase;
return ret("string", "string");
};
}
function jsTokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = jsTokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
var state = cx.state;
if (state.context) {
cx.marked = "def";
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return;
state.localVars = {name: varname, next: state.localVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
cx.state.localVars = defaultVars;
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state;
state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
return function expecting(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(arguments.callee);
};
}
function statement(type) {
if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
poplex, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
if (type == "function") return cont(functiondef);
if (type == "keyword c") return cont(maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
if (type == "operator") return cont(expression);
if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeoperator(type, value) {
if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
if (type == "operator" && value == "?") return cont(expression, expect(":"), expression);
if (type == ";") return;
if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
if (type == ".") return cont(property, maybeoperator);
if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperator, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type) {
if (type == "variable") cx.marked = "property";
if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
}
function commasep(what, end) {
function proceed(type) {
if (type == ",") return cont(what, proceed);
if (type == end) return cont();
return cont(expect(end));
}
return function commaSeparated(type) {
if (type == end) return cont();
else return pass(what, proceed);
};
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type) {
if (type == ":") return cont(typedef);
return pass();
}
function typedef(type) {
if (type == "variable"){cx.marked = "variable-3"; return cont();}
return pass();
}
function vardef1(type, value) {
if (type == "variable") {
register(value);
return isTS ? cont(maybetype, vardef2) : cont(vardef2);
}
return pass();
}
function vardef2(type, value) {
if (value == "=") return cont(expression, vardef2);
if (type == ",") return cont(vardef1);
}
function forspec1(type) {
if (type == "var") return cont(vardef1, expect(";"), forspec2);
if (type == ";") return cont(forspec2);
if (type == "variable") return cont(formaybein);
return cont(forspec2);
}
function formaybein(type, value) {
if (value == "in") return cont(expression);
return cont(maybeoperator, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in") return cont(expression);
return cont(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
}
function funarg(type, value) {
if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();}
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: jsTokenBase,
lastType: null,
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
context: parserConfig.localVars && {vars: parserConfig.localVars},
indented: 0
};
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == jsTokenComment) return CodeMirror.Pass;
if (state.tokenize != jsTokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? indentUnit : 0);
else if (lexical.info == "switch" && !closing)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricChars: ":{}"
};
});
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });
// TODO actually recognize syntax of TypeScript constructs
CodeMirror.defineMode("javascript", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var jsonMode = parserConfig.json;
var isTS = parserConfig.typescript;
// Tokenizer
var keywords = function(){
function kw(type) {return {type: type, style: "keyword"};}
var A = kw("keyword a"), B = kw("keyword b"), C = kw("keyword c");
var operator = kw("operator"), atom = {type: "atom", style: "atom"};
var jsKeywords = {
"if": A, "while": A, "with": A, "else": B, "do": B, "try": B, "finally": B,
"return": C, "break": C, "continue": C, "new": C, "delete": C, "throw": C,
"var": kw("var"), "const": kw("var"), "let": kw("var"),
"function": kw("function"), "catch": kw("catch"),
"for": kw("for"), "switch": kw("switch"), "case": kw("case"), "default": kw("default"),
"in": operator, "typeof": operator, "instanceof": operator,
"true": atom, "false": atom, "null": atom, "undefined": atom, "NaN": atom, "Infinity": atom
};
// Extend the 'normal' keywords with the TypeScript language extensions
if (isTS) {
var type = {type: "variable", style: "variable-3"};
var tsKeywords = {
// object-like things
"interface": kw("interface"),
"class": kw("class"),
"extends": kw("extends"),
"constructor": kw("constructor"),
// scope modifiers
"public": kw("public"),
"private": kw("private"),
"protected": kw("protected"),
"static": kw("static"),
"super": kw("super"),
// types
"string": type, "number": type, "bool": type, "any": type
};
for (var attr in tsKeywords) {
jsKeywords[attr] = tsKeywords[attr];
}
}
return jsKeywords;
}();
var isOperatorChar = /[+\-*&%=<>!?|]/;
function chain(stream, state, f) {
state.tokenize = f;
return f(stream, state);
}
function nextUntilUnescaped(stream, end) {
var escaped = false, next;
while ((next = stream.next()) != null) {
if (next == end && !escaped)
return false;
escaped = !escaped && next == "\\";
}
return escaped;
}
// Used as scratch variables to communicate multiple values without
// consing up tons of objects.
var type, content;
function ret(tp, style, cont) {
type = tp; content = cont;
return style;
}
function jsTokenBase(stream, state) {
var ch = stream.next();
if (ch == '"' || ch == "'")
return chain(stream, state, jsTokenString(ch));
else if (/[\[\]{}\(\),;\:\.]/.test(ch))
return ret(ch);
else if (ch == "0" && stream.eat(/x/i)) {
stream.eatWhile(/[\da-f]/i);
return ret("number", "number");
}
else if (/\d/.test(ch) || ch == "-" && stream.eat(/\d/)) {
stream.match(/^\d*(?:\.\d*)?(?:[eE][+\-]?\d+)?/);
return ret("number", "number");
}
else if (ch == "/") {
if (stream.eat("*")) {
return chain(stream, state, jsTokenComment);
}
else if (stream.eat("/")) {
stream.skipToEnd();
return ret("comment", "comment");
}
else if (state.lastType == "operator" || state.lastType == "keyword c" ||
/^[\[{}\(,;:]$/.test(state.lastType)) {
nextUntilUnescaped(stream, "/");
stream.eatWhile(/[gimy]/); // 'y' is "sticky" option in Mozilla
return ret("regexp", "string-2");
}
else {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
}
else if (ch == "#") {
stream.skipToEnd();
return ret("error", "error");
}
else if (isOperatorChar.test(ch)) {
stream.eatWhile(isOperatorChar);
return ret("operator", null, stream.current());
}
else {
stream.eatWhile(/[\w\$_]/);
var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];
return (known && state.lastType != ".") ? ret(known.type, known.style, word) :
ret("variable", "variable", word);
}
}
function jsTokenString(quote) {
return function(stream, state) {
if (!nextUntilUnescaped(stream, quote))
state.tokenize = jsTokenBase;
return ret("string", "string");
};
}
function jsTokenComment(stream, state) {
var maybeEnd = false, ch;
while (ch = stream.next()) {
if (ch == "/" && maybeEnd) {
state.tokenize = jsTokenBase;
break;
}
maybeEnd = (ch == "*");
}
return ret("comment", "comment");
}
// Parser
var atomicTypes = {"atom": true, "number": true, "variable": true, "string": true, "regexp": true};
function JSLexical(indented, column, type, align, prev, info) {
this.indented = indented;
this.column = column;
this.type = type;
this.prev = prev;
this.info = info;
if (align != null) this.align = align;
}
function inScope(state, varname) {
for (var v = state.localVars; v; v = v.next)
if (v.name == varname) return true;
}
function parseJS(state, style, type, content, stream) {
var cc = state.cc;
// Communicate our context to the combinators.
// (Less wasteful than consing up a hundred closures on every call.)
cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = true;
while(true) {
var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;
if (combinator(type, content)) {
while(cc.length && cc[cc.length - 1].lex)
cc.pop()();
if (cx.marked) return cx.marked;
if (type == "variable" && inScope(state, content)) return "variable-2";
return style;
}
}
}
// Combinator utils
var cx = {state: null, column: null, marked: null, cc: null};
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function register(varname) {
function inList(list) {
for (var v = list; v; v = v.next)
if (v.name == varname) return true;
return false;
}
var state = cx.state;
if (state.context) {
cx.marked = "def";
if (inList(state.localVars)) return;
state.localVars = {name: varname, next: state.localVars};
} else {
if (inList(state.globalVars)) return;
state.globalVars = {name: varname, next: state.globalVars};
}
}
// Combinators
var defaultVars = {name: "this", next: {name: "arguments"}};
function pushcontext() {
cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};
cx.state.localVars = defaultVars;
}
function popcontext() {
cx.state.localVars = cx.state.context.vars;
cx.state.context = cx.state.context.prev;
}
function pushlex(type, info) {
var result = function() {
var state = cx.state;
state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info);
};
result.lex = true;
return result;
}
function poplex() {
var state = cx.state;
if (state.lexical.prev) {
if (state.lexical.type == ")")
state.indented = state.lexical.indented;
state.lexical = state.lexical.prev;
}
}
poplex.lex = true;
function expect(wanted) {
return function expecting(type) {
if (type == wanted) return cont();
else if (wanted == ";") return pass();
else return cont(arguments.callee);
};
}
function statement(type) {
if (type == "var") return cont(pushlex("vardef"), vardef1, expect(";"), poplex);
if (type == "keyword a") return cont(pushlex("form"), expression, statement, poplex);
if (type == "keyword b") return cont(pushlex("form"), statement, poplex);
if (type == "{") return cont(pushlex("}"), block, poplex);
if (type == ";") return cont();
if (type == "function") return cont(functiondef);
if (type == "for") return cont(pushlex("form"), expect("("), pushlex(")"), forspec1, expect(")"),
poplex, statement, poplex);
if (type == "variable") return cont(pushlex("stat"), maybelabel);
if (type == "switch") return cont(pushlex("form"), expression, pushlex("}", "switch"), expect("{"),
block, poplex, poplex);
if (type == "case") return cont(expression, expect(":"));
if (type == "default") return cont(expect(":"));
if (type == "catch") return cont(pushlex("form"), pushcontext, expect("("), funarg, expect(")"),
statement, poplex, popcontext);
return pass(pushlex("stat"), expression, expect(";"), poplex);
}
function expression(type) {
if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);
if (type == "function") return cont(functiondef);
if (type == "keyword c") return cont(maybeexpression);
if (type == "(") return cont(pushlex(")"), maybeexpression, expect(")"), poplex, maybeoperator);
if (type == "operator") return cont(expression);
if (type == "[") return cont(pushlex("]"), commasep(expression, "]"), poplex, maybeoperator);
if (type == "{") return cont(pushlex("}"), commasep(objprop, "}"), poplex, maybeoperator);
return cont();
}
function maybeexpression(type) {
if (type.match(/[;\}\)\],]/)) return pass();
return pass(expression);
}
function maybeoperator(type, value) {
if (type == "operator" && /\+\+|--/.test(value)) return cont(maybeoperator);
if (type == "operator" && value == "?") return cont(expression, expect(":"), expression);
if (type == ";") return;
if (type == "(") return cont(pushlex(")"), commasep(expression, ")"), poplex, maybeoperator);
if (type == ".") return cont(property, maybeoperator);
if (type == "[") return cont(pushlex("]"), expression, expect("]"), poplex, maybeoperator);
}
function maybelabel(type) {
if (type == ":") return cont(poplex, statement);
return pass(maybeoperator, expect(";"), poplex);
}
function property(type) {
if (type == "variable") {cx.marked = "property"; return cont();}
}
function objprop(type) {
if (type == "variable") cx.marked = "property";
if (atomicTypes.hasOwnProperty(type)) return cont(expect(":"), expression);
}
function commasep(what, end) {
function proceed(type) {
if (type == ",") return cont(what, proceed);
if (type == end) return cont();
return cont(expect(end));
}
return function commaSeparated(type) {
if (type == end) return cont();
else return pass(what, proceed);
};
}
function block(type) {
if (type == "}") return cont();
return pass(statement, block);
}
function maybetype(type) {
if (type == ":") return cont(typedef);
return pass();
}
function typedef(type) {
if (type == "variable"){cx.marked = "variable-3"; return cont();}
return pass();
}
function vardef1(type, value) {
if (type == "variable") {
register(value);
return isTS ? cont(maybetype, vardef2) : cont(vardef2);
}
return pass();
}
function vardef2(type, value) {
if (value == "=") return cont(expression, vardef2);
if (type == ",") return cont(vardef1);
}
function forspec1(type) {
if (type == "var") return cont(vardef1, expect(";"), forspec2);
if (type == ";") return cont(forspec2);
if (type == "variable") return cont(formaybein);
return cont(forspec2);
}
function formaybein(type, value) {
if (value == "in") return cont(expression);
return cont(maybeoperator, forspec2);
}
function forspec2(type, value) {
if (type == ";") return cont(forspec3);
if (value == "in") return cont(expression);
return cont(expression, expect(";"), forspec3);
}
function forspec3(type) {
if (type != ")") cont(expression);
}
function functiondef(type, value) {
if (type == "variable") {register(value); return cont(functiondef);}
if (type == "(") return cont(pushlex(")"), pushcontext, commasep(funarg, ")"), poplex, statement, popcontext);
}
function funarg(type, value) {
if (type == "variable") {register(value); return isTS ? cont(maybetype) : cont();}
}
// Interface
return {
startState: function(basecolumn) {
return {
tokenize: jsTokenBase,
lastType: null,
cc: [],
lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, "block", false),
localVars: parserConfig.localVars,
globalVars: parserConfig.globalVars,
context: parserConfig.localVars && {vars: parserConfig.localVars},
indented: 0
};
},
token: function(stream, state) {
if (stream.sol()) {
if (!state.lexical.hasOwnProperty("align"))
state.lexical.align = false;
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;
var style = state.tokenize(stream, state);
if (type == "comment") return style;
state.lastType = type;
return parseJS(state, style, type, content, stream);
},
indent: function(state, textAfter) {
if (state.tokenize == jsTokenComment) return CodeMirror.Pass;
if (state.tokenize != jsTokenBase) return 0;
var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical;
if (lexical.type == "stat" && firstChar == "}") lexical = lexical.prev;
var type = lexical.type, closing = firstChar == type;
if (type == "vardef") return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? 4 : 0);
else if (type == "form" && firstChar == "{") return lexical.indented;
else if (type == "form") return lexical.indented + indentUnit;
else if (type == "stat")
return lexical.indented + (state.lastType == "operator" || state.lastType == "," ? indentUnit : 0);
else if (lexical.info == "switch" && !closing)
return lexical.indented + (/^(?:case|default)\b/.test(textAfter) ? indentUnit : 2 * indentUnit);
else if (lexical.align) return lexical.column + (closing ? 0 : 1);
else return lexical.indented + (closing ? 0 : indentUnit);
},
electricChars: ":{}",
jsonMode: jsonMode
};
});
CodeMirror.defineMIME("text/javascript", "javascript");
CodeMirror.defineMIME("application/json", {name: "javascript", json: true});
CodeMirror.defineMIME("text/typescript", { name: "javascript", typescript: true });
CodeMirror.defineMIME("application/typescript", { name: "javascript", typescript: true });

View file

@ -1,318 +1,322 @@
CodeMirror.defineMode("xml", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var Kludges = parserConfig.htmlMode ? {
autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
'track': true, 'wbr': true},
implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
'th': true, 'tr': true},
contextGrabbers: {
'dd': {'dd': true, 'dt': true},
'dt': {'dd': true, 'dt': true},
'li': {'li': true},
'option': {'option': true, 'optgroup': true},
'optgroup': {'optgroup': true},
'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
'rp': {'rp': true, 'rt': true},
'rt': {'rp': true, 'rt': true},
'tbody': {'tbody': true, 'tfoot': true},
'td': {'td': true, 'th': true},
'tfoot': {'tbody': true},
'th': {'td': true, 'th': true},
'thead': {'tbody': true, 'tfoot': true},
'tr': {'tr': true}
},
doNotIndent: {"pre": true},
allowUnquoted: true,
allowMissing: true
} : {
autoSelfClosers: {},
implicitlyClosed: {},
contextGrabbers: {},
doNotIndent: {},
allowUnquoted: false,
allowMissing: false
};
var alignCDATA = parserConfig.alignCDATA;
// Return variables for tokenizers
var tagName, type;
function inText(stream, state) {
function chain(parser) {
state.tokenize = parser;
return parser(stream, state);
}
var ch = stream.next();
if (ch == "<") {
if (stream.eat("!")) {
if (stream.eat("[")) {
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
else return null;
}
else if (stream.match("--")) return chain(inBlock("comment", "-->"));
else if (stream.match("DOCTYPE", true, true)) {
stream.eatWhile(/[\w\._\-]/);
return chain(doctype(1));
}
else return null;
}
else if (stream.eat("?")) {
stream.eatWhile(/[\w\._\-]/);
state.tokenize = inBlock("meta", "?>");
return "meta";
}
else {
type = stream.eat("/") ? "closeTag" : "openTag";
stream.eatSpace();
tagName = "";
var c;
while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
state.tokenize = inTag;
return "tag";
}
}
else if (ch == "&") {
var ok;
if (stream.eat("#")) {
if (stream.eat("x")) {
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
} else {
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
}
} else {
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
}
return ok ? "atom" : "error";
}
else {
stream.eatWhile(/[^&<]/);
return null;
}
}
function inTag(stream, state) {
var ch = stream.next();
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
state.tokenize = inText;
type = ch == ">" ? "endTag" : "selfcloseTag";
return "tag";
}
else if (ch == "=") {
type = "equals";
return null;
}
else if (/[\'\"]/.test(ch)) {
state.tokenize = inAttribute(ch);
return state.tokenize(stream, state);
}
else {
stream.eatWhile(/[^\s\u00a0=<>\"\'\/?]/);
return "word";
}
}
function inAttribute(quote) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.next() == quote) {
state.tokenize = inTag;
break;
}
}
return "string";
};
}
function inBlock(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
state.tokenize = inText;
break;
}
stream.next();
}
return style;
};
}
function doctype(depth) {
return function(stream, state) {
var ch;
while ((ch = stream.next()) != null) {
if (ch == "<") {
state.tokenize = doctype(depth + 1);
return state.tokenize(stream, state);
} else if (ch == ">") {
if (depth == 1) {
state.tokenize = inText;
break;
} else {
state.tokenize = doctype(depth - 1);
return state.tokenize(stream, state);
}
}
}
return "meta";
};
}
var curState, setStyle;
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function pushContext(tagName, startOfLine) {
var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
curState.context = {
prev: curState.context,
tagName: tagName,
indent: curState.indented,
startOfLine: startOfLine,
noIndent: noIndent
};
}
function popContext() {
if (curState.context) curState.context = curState.context.prev;
}
function element(type) {
if (type == "openTag") {
curState.tagName = tagName;
return cont(attributes, endtag(curState.startOfLine));
} else if (type == "closeTag") {
var err = false;
if (curState.context) {
if (curState.context.tagName != tagName) {
if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
popContext();
}
err = !curState.context || curState.context.tagName != tagName;
}
} else {
err = true;
}
if (err) setStyle = "error";
return cont(endclosetag(err));
}
return cont();
}
function endtag(startOfLine) {
return function(type) {
if (type == "selfcloseTag" ||
(type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) {
maybePopContext(curState.tagName.toLowerCase());
return cont();
}
if (type == "endTag") {
maybePopContext(curState.tagName.toLowerCase());
pushContext(curState.tagName, startOfLine);
return cont();
}
return cont();
};
}
function endclosetag(err) {
return function(type) {
if (err) setStyle = "error";
if (type == "endTag") { popContext(); return cont(); }
setStyle = "error";
return cont(arguments.callee);
};
}
function maybePopContext(nextTagName) {
var parentTagName;
while (true) {
if (!curState.context) {
return;
}
parentTagName = curState.context.tagName.toLowerCase();
if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
!Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
return;
}
popContext();
}
}
function attributes(type) {
if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
if (type == "endTag" || type == "selfcloseTag") return pass();
setStyle = "error";
return cont(attributes);
}
function attribute(type) {
if (type == "equals") return cont(attvalue, attributes);
if (!Kludges.allowMissing) setStyle = "error";
return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
}
function attvalue(type) {
if (type == "string") return cont(attvaluemaybe);
if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
setStyle = "error";
return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
}
function attvaluemaybe(type) {
if (type == "string") return cont(attvaluemaybe);
else return pass();
}
return {
startState: function() {
return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null};
},
token: function(stream, state) {
if (stream.sol()) {
state.startOfLine = true;
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;
setStyle = type = tagName = null;
var style = state.tokenize(stream, state);
state.type = type;
if ((style || type) && style != "comment") {
curState = state;
while (true) {
var comb = state.cc.pop() || element;
if (comb(type || style)) break;
}
}
state.startOfLine = false;
return setStyle || style;
},
indent: function(state, textAfter, fullLine) {
var context = state.context;
if ((state.tokenize != inTag && state.tokenize != inText) ||
context && context.noIndent)
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
if (context && /^<\//.test(textAfter))
context = context.prev;
while (context && !context.startOfLine)
context = context.prev;
if (context) return context.indent + indentUnit;
else return 0;
},
electricChars: "/"
};
});
CodeMirror.defineMIME("text/xml", "xml");
CodeMirror.defineMIME("application/xml", "xml");
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});
CodeMirror.defineMode("xml", function(config, parserConfig) {
var indentUnit = config.indentUnit;
var Kludges = parserConfig.htmlMode ? {
autoSelfClosers: {'area': true, 'base': true, 'br': true, 'col': true, 'command': true,
'embed': true, 'frame': true, 'hr': true, 'img': true, 'input': true,
'keygen': true, 'link': true, 'meta': true, 'param': true, 'source': true,
'track': true, 'wbr': true},
implicitlyClosed: {'dd': true, 'li': true, 'optgroup': true, 'option': true, 'p': true,
'rp': true, 'rt': true, 'tbody': true, 'td': true, 'tfoot': true,
'th': true, 'tr': true},
contextGrabbers: {
'dd': {'dd': true, 'dt': true},
'dt': {'dd': true, 'dt': true},
'li': {'li': true},
'option': {'option': true, 'optgroup': true},
'optgroup': {'optgroup': true},
'p': {'address': true, 'article': true, 'aside': true, 'blockquote': true, 'dir': true,
'div': true, 'dl': true, 'fieldset': true, 'footer': true, 'form': true,
'h1': true, 'h2': true, 'h3': true, 'h4': true, 'h5': true, 'h6': true,
'header': true, 'hgroup': true, 'hr': true, 'menu': true, 'nav': true, 'ol': true,
'p': true, 'pre': true, 'section': true, 'table': true, 'ul': true},
'rp': {'rp': true, 'rt': true},
'rt': {'rp': true, 'rt': true},
'tbody': {'tbody': true, 'tfoot': true},
'td': {'td': true, 'th': true},
'tfoot': {'tbody': true},
'th': {'td': true, 'th': true},
'thead': {'tbody': true, 'tfoot': true},
'tr': {'tr': true}
},
doNotIndent: {"pre": true},
allowUnquoted: true,
allowMissing: true
} : {
autoSelfClosers: {},
implicitlyClosed: {},
contextGrabbers: {},
doNotIndent: {},
allowUnquoted: false,
allowMissing: false
};
var alignCDATA = parserConfig.alignCDATA;
// Return variables for tokenizers
var tagName, type;
function inText(stream, state) {
function chain(parser) {
state.tokenize = parser;
return parser(stream, state);
}
var ch = stream.next();
if (ch == "<") {
if (stream.eat("!")) {
if (stream.eat("[")) {
if (stream.match("CDATA[")) return chain(inBlock("atom", "]]>"));
else return null;
}
else if (stream.match("--")) return chain(inBlock("comment", "-->"));
else if (stream.match("DOCTYPE", true, true)) {
stream.eatWhile(/[\w\._\-]/);
return chain(doctype(1));
}
else return null;
}
else if (stream.eat("?")) {
stream.eatWhile(/[\w\._\-]/);
state.tokenize = inBlock("meta", "?>");
return "meta";
}
else {
var isClose = stream.eat("/");
tagName = "";
var c;
while ((c = stream.eat(/[^\s\u00a0=<>\"\'\/?]/))) tagName += c;
if (!tagName) return "error";
type = isClose ? "closeTag" : "openTag";
state.tokenize = inTag;
return "tag";
}
}
else if (ch == "&") {
var ok;
if (stream.eat("#")) {
if (stream.eat("x")) {
ok = stream.eatWhile(/[a-fA-F\d]/) && stream.eat(";");
} else {
ok = stream.eatWhile(/[\d]/) && stream.eat(";");
}
} else {
ok = stream.eatWhile(/[\w\.\-:]/) && stream.eat(";");
}
return ok ? "atom" : "error";
}
else {
stream.eatWhile(/[^&<]/);
return null;
}
}
function inTag(stream, state) {
var ch = stream.next();
if (ch == ">" || (ch == "/" && stream.eat(">"))) {
state.tokenize = inText;
type = ch == ">" ? "endTag" : "selfcloseTag";
return "tag";
}
else if (ch == "=") {
type = "equals";
return null;
}
else if (/[\'\"]/.test(ch)) {
state.tokenize = inAttribute(ch);
return state.tokenize(stream, state);
}
else {
stream.eatWhile(/[^\s\u00a0=<>\"\']/);
return "word";
}
}
function inAttribute(quote) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.next() == quote) {
state.tokenize = inTag;
break;
}
}
return "string";
};
}
function inBlock(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
state.tokenize = inText;
break;
}
stream.next();
}
return style;
};
}
function doctype(depth) {
return function(stream, state) {
var ch;
while ((ch = stream.next()) != null) {
if (ch == "<") {
state.tokenize = doctype(depth + 1);
return state.tokenize(stream, state);
} else if (ch == ">") {
if (depth == 1) {
state.tokenize = inText;
break;
} else {
state.tokenize = doctype(depth - 1);
return state.tokenize(stream, state);
}
}
}
return "meta";
};
}
var curState, setStyle;
function pass() {
for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);
}
function cont() {
pass.apply(null, arguments);
return true;
}
function pushContext(tagName, startOfLine) {
var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);
curState.context = {
prev: curState.context,
tagName: tagName,
indent: curState.indented,
startOfLine: startOfLine,
noIndent: noIndent
};
}
function popContext() {
if (curState.context) curState.context = curState.context.prev;
}
function element(type) {
if (type == "openTag") {
curState.tagName = tagName;
return cont(attributes, endtag(curState.startOfLine));
} else if (type == "closeTag") {
var err = false;
if (curState.context) {
if (curState.context.tagName != tagName) {
if (Kludges.implicitlyClosed.hasOwnProperty(curState.context.tagName.toLowerCase())) {
popContext();
}
err = !curState.context || curState.context.tagName != tagName;
}
} else {
err = true;
}
if (err) setStyle = "error";
return cont(endclosetag(err));
}
return cont();
}
function endtag(startOfLine) {
return function(type) {
if (type == "selfcloseTag" ||
(type == "endTag" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase()))) {
maybePopContext(curState.tagName.toLowerCase());
return cont();
}
if (type == "endTag") {
maybePopContext(curState.tagName.toLowerCase());
pushContext(curState.tagName, startOfLine);
return cont();
}
return cont();
};
}
function endclosetag(err) {
return function(type) {
if (err) setStyle = "error";
if (type == "endTag") { popContext(); return cont(); }
setStyle = "error";
return cont(arguments.callee);
};
}
function maybePopContext(nextTagName) {
var parentTagName;
while (true) {
if (!curState.context) {
return;
}
parentTagName = curState.context.tagName.toLowerCase();
if (!Kludges.contextGrabbers.hasOwnProperty(parentTagName) ||
!Kludges.contextGrabbers[parentTagName].hasOwnProperty(nextTagName)) {
return;
}
popContext();
}
}
function attributes(type) {
if (type == "word") {setStyle = "attribute"; return cont(attribute, attributes);}
if (type == "endTag" || type == "selfcloseTag") return pass();
setStyle = "error";
return cont(attributes);
}
function attribute(type) {
if (type == "equals") return cont(attvalue, attributes);
if (!Kludges.allowMissing) setStyle = "error";
else if (type == "word") setStyle = "attribute";
return (type == "endTag" || type == "selfcloseTag") ? pass() : cont();
}
function attvalue(type) {
if (type == "string") return cont(attvaluemaybe);
if (type == "word" && Kludges.allowUnquoted) {setStyle = "string"; return cont();}
setStyle = "error";
return (type == "endTag" || type == "selfCloseTag") ? pass() : cont();
}
function attvaluemaybe(type) {
if (type == "string") return cont(attvaluemaybe);
else return pass();
}
return {
startState: function() {
return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null};
},
token: function(stream, state) {
if (stream.sol()) {
state.startOfLine = true;
state.indented = stream.indentation();
}
if (stream.eatSpace()) return null;
setStyle = type = tagName = null;
var style = state.tokenize(stream, state);
state.type = type;
if ((style || type) && style != "comment") {
curState = state;
while (true) {
var comb = state.cc.pop() || element;
if (comb(type || style)) break;
}
}
state.startOfLine = false;
return setStyle || style;
},
indent: function(state, textAfter, fullLine) {
var context = state.context;
if ((state.tokenize != inTag && state.tokenize != inText) ||
context && context.noIndent)
return fullLine ? fullLine.match(/^(\s*)/)[0].length : 0;
if (alignCDATA && /<!\[CDATA\[/.test(textAfter)) return 0;
if (context && /^<\//.test(textAfter))
context = context.prev;
while (context && !context.startOfLine)
context = context.prev;
if (context) return context.indent + indentUnit;
else return 0;
},
electricChars: "/",
configuration: parserConfig.htmlMode ? "html" : "xml"
};
});
CodeMirror.defineMIME("text/xml", "xml");
CodeMirror.defineMIME("application/xml", "xml");
if (!CodeMirror.mimeModes.hasOwnProperty("text/html"))
CodeMirror.defineMIME("text/html", {name: "xml", htmlMode: true});

View file

@ -1,21 +1,21 @@
{
"name": "codemirror",
"version":"2.35.0",
"main": "codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
"url": "http://codemirror.net/LICENSE"}],
"directories": {"lib": "./lib"},
"scripts": {"test": "node ./test/run.js"},
"devDependencies": {"node-static": "0.6.0"},
"bugs": "http://github.com/marijnh/CodeMirror/issues",
"keywords": ["JavaScript", "CodeMirror", "Editor"],
"homepage": "http://codemirror.net",
"maintainers":[{"name": "Marijn Haverbeke",
"email": "marijnh@gmail.com",
"web": "http://marijnhaverbeke.nl"}],
"repositories": [{"type": "git",
"url": "http://marijnhaverbeke.nl/git/codemirror"},
{"type": "git",
"url": "https://github.com/marijnh/CodeMirror.git"}]
}
{
"name": "codemirror",
"version":"2.37.01",
"main": "codemirror.js",
"description": "In-browser code editing made bearable",
"licenses": [{"type": "MIT",
"url": "http://codemirror.net/LICENSE"}],
"directories": {"lib": "./lib"},
"scripts": {"test": "node ./test/run.js"},
"devDependencies": {"node-static": "0.6.0"},
"bugs": "http://github.com/marijnh/CodeMirror/issues",
"keywords": ["JavaScript", "CodeMirror", "Editor"],
"homepage": "http://codemirror.net",
"maintainers":[{"name": "Marijn Haverbeke",
"email": "marijnh@gmail.com",
"web": "http://marijnhaverbeke.nl"}],
"repositories": [{"type": "git",
"url": "http://marijnhaverbeke.nl/git/codemirror"},
{"type": "git",
"url": "https://github.com/marijnh/CodeMirror.git"}]
}

View file

@ -1,21 +1,21 @@
/* Loosely based on the Midnight Textmate theme */
.cm-s-night { background: #0a001f; color: #f8f8f8; }
.cm-s-night div.CodeMirror-selected { background: #447 !important; }
.cm-s-night .CodeMirror-gutter { background: #0a001f; border-right: 1px solid #aaa; }
.cm-s-night .CodeMirror-gutter-text { color: #f8f8f8; }
.cm-s-night .CodeMirror-cursor { border-left: 1px solid white !important; }
.cm-s-night span.cm-comment { color: #6900a1; }
.cm-s-night span.cm-atom { color: #845dc4; }
.cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; }
.cm-s-night span.cm-keyword { color: #599eff; }
.cm-s-night span.cm-string { color: #37f14a; }
.cm-s-night span.cm-meta { color: #7678e2; }
.cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; }
.cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { color: white; }
.cm-s-night span.cm-error { color: #9d1e15; }
.cm-s-night span.cm-bracket { color: #8da6ce; }
.cm-s-night span.cm-comment { color: #6900a1; }
.cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; }
.cm-s-night span.cm-link { color: #845dc4; }
/* Loosely based on the Midnight Textmate theme */
.cm-s-night { background: #0a001f; color: #f8f8f8; }
.cm-s-night div.CodeMirror-selected { background: #447 !important; }
.cm-s-night .CodeMirror-gutter { background: #0a001f; border-right: 1px solid #aaa; }
.cm-s-night .CodeMirror-gutter-text { color: #f8f8f8; }
.cm-s-night .CodeMirror-cursor { border-left: 1px solid white !important; }
.cm-s-night span.cm-comment { color: #6900a1; }
.cm-s-night span.cm-atom { color: #845dc4; }
.cm-s-night span.cm-number, .cm-s-night span.cm-attribute { color: #ffd500; }
.cm-s-night span.cm-keyword { color: #599eff; }
.cm-s-night span.cm-string { color: #37f14a; }
.cm-s-night span.cm-meta { color: #7678e2; }
.cm-s-night span.cm-variable-2, .cm-s-night span.cm-tag { color: #99b2ff; }
.cm-s-night span.cm-variable-3, .cm-s-night span.cm-def { color: white; }
.cm-s-night span.cm-error { color: #9d1e15; }
.cm-s-night span.cm-bracket { color: #8da6ce; }
.cm-s-night span.cm-comment { color: #6900a1; }
.cm-s-night span.cm-builtin, .cm-s-night span.cm-special { color: #ff9e59; }
.cm-s-night span.cm-link { color: #845dc4; }

View file

@ -1,23 +1,15 @@
var DOM, _gaq;
(function(){
"use strict";
(function(DOM, _gaq){
'use strict';
/* setting google analitics tracking code */
_gaq = [['_setAccount', 'UA-33536569-2'], ['_trackPageview']];
var lOnError_f = window.onerror;
window.onerror = function(msg, url, line) {
var preventErrorAlert = true;
_gaq.push(['_trackEvent',
'JS Error',
msg,
DOM.addErrorListener(function(msg, url, line) {
_gaq.push(['_trackEvent', 'JS Error', msg,
navigator.userAgent + ' -> ' + url + " : " + line]);
if(typeof lOnError_f === 'function')
lOnError_f();
return preventErrorAlert;
};
});
DOM.jsload('http://google-analytics.com/ga.js');
})();
DOM.jsload('//google-analytics.com/ga.js');
})(DOM, _gaq);

View file

@ -1,221 +1,273 @@
/* script, fixes ie */
var Util, DOM, $;
(function(){
"use strict";
/* setting head ie6 - ie8 */
if(!document.head){
document.head = document.getElementsByTagName("head")[0];
/*
{name: '', src: ' ',func: '', style: '', id: '', parent: '',
async: false, inner: 'id{color:red, }, class:'', not_append: false}
*/
DOM.cssSet = function(pParams_o){
var lElement = '<style ';
if (pParams_o.id) lElement += 'id=' + pParams_o.id + ' ';
if (pParams_o.style) lElement += 'style=' + pParams_o.style + ' ';
if (pParams_o.className) lElement += 'class=' + pParams_o.className;
if (pParams_o.inner)lElement += '>' + pParams_o.inner;
lElement +='</style>';
return $(lElement)
.appendTo(pParams_o.parent || document.head);
};
}
/* setting function context (this) */
Util.bind = function(pFunction, pContext){
return $.proxy(pFunction, pContext);
};
/*
* typeof callback === "function" should not be used,
* as older browsers may report objects to be a function,
* which they are not
*/
Util.isFunction = $.isFunction;
if (!document.addEventListener)
/**
* safe add event listener on ie
* @param pType
* @param pListener
*/
DOM.addListener = function(pType, pListener){
var lRet = this,
lType = 'on' + pType,
lFunc = document[lType];
document[lType] = function(){
Util.exec(lFunc);
pListener();
};
return lRet;
};
if(!document.getElementsByClassName){
DOM.getByClass = function(pClass, pElement){
var lClass = '.' + pClass,
lResult;
if(pElement)
lResult = $(pElement).find(lClass);
else lResult = $.find(lClass);
return lResult;
};
}
/* function polyfill webkit standart function */
DOM.scrollIntoViewIfNeeded = function(pElement, centerIfNeeded){
/*
https://gist.github.com/2581101
*/
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
var parent = pElement.parentNode,
parentComputedStyle = window.getComputedStyle(parent, null),
parentBorderTopWidth =
parseInt(parentComputedStyle.getPropertyValue('border-top-width')),
parentBorderLeftWidth =
parseInt(parentComputedStyle.getPropertyValue('border-left-width')),
overTop = pElement.offsetTop - parent.offsetTop < parent.scrollTop,
overBottom =
(pElement.offsetTop -
parent.offsetTop +
pElement.clientHeight -
parentBorderTopWidth) >
(parent.scrollTop + parent.clientHeight),
overLeft = pElement.offsetLeft -
parent.offsetLeft < parent.scrollLeft,
overRight =
(pElement.offsetLeft -
parent.offsetLeft +
pElement.clientWidth -
parentBorderLeftWidth) >
(parent.scrollLeft + parent.clientWidth),
alignWithTop = overTop && !overBottom;
if ((overTop || overBottom) && centerIfNeeded)
parent.scrollTop =
pElement.offsetTop -
parent.offsetTop -
parent.clientHeight / 2 -
parentBorderTopWidth +
pElement.clientHeight / 2;
if ((overLeft || overRight) && centerIfNeeded)
parent.scrollLeft =
pElement.offsetLeft -
parent.offsetLeft -
parent.clientWidth / 2 -
parentBorderLeftWidth +
pElement.clientWidth / 2;
if ( (overTop || overBottom || overLeft || overRight) &&
!centerIfNeeded)
pElement.scrollIntoView(alignWithTop);
};
if(!document.body.classList){
DOM.addClass = function(pElement, pClass){
var lSpaceChar = '',
lClassName = pElement.className,
lRet_b = true;
if(lClassName) lSpaceChar = ' ';
if( lClassName.indexOf(pClass) < 0 )
pElement.className += lSpaceChar + pClass;
else
lRet_b = false;
};
DOM.removeClass = function(pElement, pClass){
var lClassName = pElement.className;
if(lClassName.length > pClass.length)
pElement.className = lClassName.replace(pClass, '');
};
}
if(!window.XMLHttpRequest)
DOM.ajax = $.ajax;
if(!window.localStorage){
DOM.Cache = function(){
/* приватный переключатель возможности работы с кэшем */
var CacheAllowed,
Data = {};
/* функция проверяет возможно ли работать с кэшем каким-либо образом */
this.isAllowed = function(){
return CacheAllowed;
};
this.setAllowed = function(){
CacheAllowed = true;
};
this.UnSetAllowed = function(){
CacheAllowed = false;
};
/** remove element */
this.remove = function(pItem){
var lRet = this;
if(CacheAllowed)
delete Data[pItem];
return lRet;
};
/** если доступен localStorage и
* в нём есть нужная нам директория -
* записываем данные в него
*/
this.set = function(pName, pData){
var lRet = this;
if(CacheAllowed && pName && pData)
Data[pName] = pData;
return lRet;
},
/** Если доступен Cache принимаем из него данные*/
this.get = function(pName){
var lRet = false;
if(CacheAllowed)
lRet = Data[pName];
return lRet;
},
/** функция чистит весь кэш для всех каталогов*/
this.clear = function(){
var lRet = this;
if(CacheAllowed)
Data = {};
return lRet;
};
};
DOM.Cache = new DOM.Cache();
}
})();
/* script, fixes ie */
//var Util, DOM, jQuery;
(function(Util, DOM, $){
'use strict';
if(!window.XMLHttpRequest || !document.head)
DOM.ajax = $.ajax;
/* setting head ie6 - ie8 */
if(!document.head){
document.head = $('head')[0];
/*
{name: '', src: ' ',func: '', style: '', id: '', parent: '',
async: false, inner: 'id{color:red, }, class:'', not_append: false}
*/
DOM.cssSet = function(pParams_o){
var lElement = '<style ';
if (pParams_o.id) lElement += 'id=' + pParams_o.id + ' ';
if (pParams_o.style) lElement += 'style=' + pParams_o.style + ' ';
if (pParams_o.className) lElement += 'class=' + pParams_o.className;
if (pParams_o.inner)lElement += '>' + pParams_o.inner;
lElement +='</style>';
return $(lElement)
.appendTo(pParams_o.parent || document.head);
};
}
/* setting function context (this) */
Util.bind = function(pFunction, pContext){
var lRet;
lRet = $.proxy(pFunction, pContext);
return lRet;
};
/*
* typeof callback === "function" should not be used,
* as older browsers may report objects to be a function,
* which they are not
*/
Util.isFunction = $.isFunction;
if (!document.addEventListener)
/**
* safe add event listener on ie
* @param pType
* @param pListener
*/
DOM.addListener = function(pType, pListener, pCapture, pElement){
var lRet;
if(!pElement)
pElement = window;
lRet = $(pElement).bind(pType, null, pListener);
return lRet;
};
if(!document.removeEventListener){
DOM.removeListener = function(pType, pListener, pCapture, pElement){
var lRet;
if(!pElement)
pElement = window;
$(pElement).unbind(pType, pListener);
return lRet;
};
}
if(!document.getElementsByClassName){
DOM.getByClass = function(pClass, pElement){
var lClass = '.' + pClass,
lResult;
if(pElement)
lResult = $(pElement).find(lClass);
else lResult = $.find(lClass);
return lResult;
};
}
DOM.scrollByPages = Util.retFalse;
/* function polyfill webkit standart function */
DOM.scrollIntoViewIfNeeded = function(pElement, centerIfNeeded){
if(!window.getComputedStyle)
return;
/*
https://gist.github.com/2581101
*/
centerIfNeeded = arguments.length === 0 ? true : !!centerIfNeeded;
var parent = pElement.parentNode,
parentComputedStyle = window.getComputedStyle(parent, null),
parentBorderTopWidth =
parseInt(parentComputedStyle.getPropertyValue('border-top-width'), 10),
parentBorderLeftWidth =
parseInt(parentComputedStyle.getPropertyValue('border-left-width'), 10),
overTop = pElement.offsetTop - parent.offsetTop < parent.scrollTop,
overBottom =
(pElement.offsetTop -
parent.offsetTop +
pElement.clientHeight -
parentBorderTopWidth) >
(parent.scrollTop + parent.clientHeight),
overLeft = pElement.offsetLeft -
parent.offsetLeft < parent.scrollLeft,
overRight =
(pElement.offsetLeft -
parent.offsetLeft +
pElement.clientWidth -
parentBorderLeftWidth) >
(parent.scrollLeft + parent.clientWidth),
alignWithTop = overTop && !overBottom;
if ((overTop || overBottom) && centerIfNeeded)
parent.scrollTop =
pElement.offsetTop -
parent.offsetTop -
parent.clientHeight / 2 -
parentBorderTopWidth +
pElement.clientHeight / 2;
if ((overLeft || overRight) && centerIfNeeded)
parent.scrollLeft =
pElement.offsetLeft -
parent.offsetLeft -
parent.clientWidth / 2 -
parentBorderLeftWidth +
pElement.clientWidth / 2;
if ( (overTop || overBottom || overLeft || overRight) &&
!centerIfNeeded)
pElement.scrollIntoView(alignWithTop);
};
if(!document.body.classList){
DOM.addClass = function(pElement, pClass){
var lSpaceChar = '',
lClassName = pElement.className,
lRet_b = true;
if(lClassName) lSpaceChar = ' ';
if( lClassName.indexOf(pClass) < 0 )
pElement.className += lSpaceChar + pClass;
else
lRet_b = false;
};
DOM.removeClass = function(pElement, pClass){
var lClassName = pElement.className;
if(lClassName.length > pClass.length)
pElement.className = lClassName.replace(pClass, '');
};
}
if(!window.JSON){
Util.parseJSON = $.parseJSON;
/* https://gist.github.com/754454 */
Util.stringifyJSON = function(pObj){
var lRet;
if (!Util.isObject(pObj) || pObj === null) {
// simple data type
if (Util.isString(pObj)) pObj = '"' + pObj + '"';
lRet = String(pObj);
} else {
// recurse array or object
var n, v, json = [], isArray = Util.isArray(pObj);
for (n in pObj) {
v = pObj[n];
if (pObj.hasOwnProperty(n)) {
if (Util.isString(v))
v = '"' + v + '"';
else if (v && Util.isObject(v))
v = DOM.stringifyJSON(v);
json.push((isArray ? "" : '"' + n + '":') + String(v));
}
}
lRet = (isArray ? "[" : "{") + String(json) + (isArray ? "]" : "}");
}
return lRet;
};
}
if(!window.localStorage){
var Cache = function(){
/* приватный переключатель возможности работы с кэшем */
var CacheAllowed,
Data = {};
/* функция проверяет возможно ли работать с кэшем каким-либо образом */
this.isAllowed = function(){
return CacheAllowed;
};
this.setAllowed = function(){
CacheAllowed = true;
};
this.UnSetAllowed = function(){
CacheAllowed = false;
};
/** remove element */
this.remove = function(pItem){
var lRet = this;
if(CacheAllowed)
delete Data[pItem];
return lRet;
};
/** если доступен localStorage и
* в нём есть нужная нам директория -
* записываем данные в него
*/
this.set = function(pName, pData){
var lRet = this;
if(CacheAllowed && pName && pData)
Data[pName] = pData;
return lRet;
},
/** Если доступен Cache принимаем из него данные*/
this.get = function(pName){
var lRet = false;
if(CacheAllowed)
lRet = Data[pName];
return lRet;
},
/** функция чистит весь кэш для всех каталогов*/
this.clear = function(){
var lRet = this;
if(CacheAllowed)
Data = {};
return lRet;
};
};
DOM.Cache = new Cache();
}
})(Util, DOM, jQuery);

1969
lib/client/jquery.js vendored

File diff suppressed because it is too large Load diff

View file

@ -1,382 +1,310 @@
var CloudCommander, Util, DOM;
(function(){
"use strict";
DOM.Images.hideLoad();
var cloudcmd = CloudCommander,
/* private property set or set key binding */
keyBinded;
/* Key constants*/
cloudcmd.KEY = {
TAB : 9,
ENTER : 13,
ESC : 27,
PAGE_UP : 33,
PAGE_DOWN : 34,
END : 35,
HOME : 36,
UP : 38,
DOWN : 40,
Delete : 46,
D : 68,
G : 71,
O : 79,
Q : 81,
R : 82,
S : 83,
T : 84,
F2 : 113,
F3 : 114,
F4 : 115,
F10 : 121,
TRA : 192 /* Typewritten Reverse Apostrophe (`) */
};
var KEY = cloudcmd.KEY;
cloudcmd.KeyBinding = {};
var KeyBinding = cloudcmd.KeyBinding;
KeyBinding.get = function(){return keyBinded;};
KeyBinding.set = function(){keyBinded = true;};
KeyBinding.unSet = function(){keyBinded = false;};
KeyBinding.init = function(){
/* saving state of tabs varibles */
var lTabPanel = {
left : 0,
right : 0
};
var key_event = function(event){
/* получаем выдленный файл*/
var lCurrentFile = DOM.getCurrentFile(),
lName, i;
/* если клавиши можно обрабатывать*/
if(keyBinded && event){
var lKeyCode = event.keyCode;
/* open configuration window */
if(lKeyCode === KEY.O && event.altKey){
console.log('openning config window...');
DOM.Images.showLoad({top: true});
Util.exec(cloudcmd.Config);
}
else if(lKeyCode === KEY.G && event.altKey)
Util.exec(cloudcmd.GitHub);
else if(lKeyCode === KEY.D && event.altKey){
Util.exec(cloudcmd.DropBox);
event.preventDefault();
}
/* если нажали таб:
* переносим курсор на
* правую панель, если
* мы были на левой и
* наоборот
*/
else if(lKeyCode === KEY.TAB){
console.log('Tab pressed');
Util.tryCatchLog(function(){
/* changing parent panel of curent-file */
var lPanel = DOM.getPanel(),
lId = lPanel.id;
lTabPanel[lId] = lCurrentFile;
lPanel = DOM.getPanel({active:false});
lId = lPanel.id;
if(lTabPanel[lId])
DOM.setCurrentFile(lTabPanel[lId]);
else{
var lFirstFileOnList = DOM.getByTag('li', lPanel)[2];
DOM.setCurrentFile(lFirstFileOnList);
}
});
event.preventDefault();//запрет на дальнейшее действие
}
else if(lKeyCode === KEY.Delete)
DOM.promptRemoveCurrent(lCurrentFile);
/* if f3 or shift+f3 or alt+f3 pressed */
else if(lKeyCode === KEY.F3){
var lEditor = cloudcmd[event.shiftKey ?
'Viewer' : 'Editor'];
Util.exec(lEditor, true);
event.preventDefault();//запрет на дальнейшее действие
}
/* if f4 pressed */
else if(lKeyCode === KEY.F4) {
DOM.Images.showLoad();
Util.exec(cloudcmd.Editor);
event.preventDefault();//запрет на дальнейшее действие
}
else if(lKeyCode === KEY.F10 && event.shiftKey){
Util.exec(cloudcmd.Menu);
event.preventDefault();//запрет на дальнейшее действие
}
else if (lKeyCode === KEY.TRA){
DOM.Images.showLoad({top: true});
Util.exec(cloudcmd.Terminal);
}
/* навигация по таблице файлов*/
/* если нажали клавишу вверх*/
else if(lKeyCode === KEY.UP){
/* если ненайдены выделенные файлы - выходим*/
if(!lCurrentFile) return;
/* если это строка существет и
* если она не заголовок
* файловой таблицы
*/
lCurrentFile = lCurrentFile.previousSibling;
if(lCurrentFile){
/* выделяем предыдущую строку*/
DOM.setCurrentFile(lCurrentFile);
}
event.preventDefault();//запрет на дальнейшее действие
}
/* если нажали клавишу в низ*/
else if(lKeyCode === KEY.DOWN){
/* если ненайдены выделенные файлы - выходим*/
if(!lCurrentFile)return;
lCurrentFile = lCurrentFile.nextSibling;
/* если это не последняя строка */
if(lCurrentFile){
/* выделяем следующую строку*/
DOM.setCurrentFile(lCurrentFile);
event.preventDefault();//запрет на дальнейшее действие
}
}
/* если нажали клавишу Home
* переходим к самому верхнему
* элементу
*/
else if(lKeyCode === KEY.HOME){
/* получаем первый элемент
* пропускаем путь и заголовки столбиков
* выделяем верхий файл
*/
lCurrentFile = lCurrentFile.
parentElement.firstChild;
/* set current file and
* move scrollbar to top
*/
DOM.setCurrentFile(lCurrentFile);
event.preventDefault();//запрет на дальнейшее действие
}
/* если нажали клавишу End
* выделяем последний элемент
*/
else if(lKeyCode === KEY.END){
/* выделяем самый нижний файл */
lCurrentFile = lCurrentFile.
parentElement.lastElementChild;
/* set current file and
* move scrollbar to bottom
*/
DOM.setCurrentFile(lCurrentFile);
event.preventDefault();//запрет на дальнейшее действие
}
/* если нажали клавишу page down
* проматываем экран
*/
else if(lKeyCode === KEY.PAGE_DOWN){
DOM.getPanel().scrollByPages(1);
for(i=0; i<30; i++){
if(!lCurrentFile.nextSibling) break;
lCurrentFile = lCurrentFile.nextSibling;
}
DOM.setCurrentFile(lCurrentFile);
event.preventDefault();//запрет на дальнейшее действие
}
/* если нажали клавишу page up
* проматываем экран
*/
else if(lKeyCode === KEY.PAGE_UP){
DOM.getPanel().scrollByPages(-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);
event.preventDefault();//запрет на дальнейшее действие
}
/* если нажали Enter - открываем папку*/
else if(lKeyCode === KEY.ENTER){
/* если ненайдены выделенные файлы - выходим*/
if(!lCurrentFile)return;
/* из него достаём спан с именем файла*/
lName = DOM.getByClass('name', lCurrentFile);
/* если нету (что вряд ли) - выходим*/
if(!lName)return false;
/* достаём все ссылки*/
var lATag = DOM.getByTag('a', lName[0]);
/* если нету - выходим */
if(!lATag)return false;
/* вызываем ajaxload привязанный через changelinks
* пробуем нажать на ссылку, если не получиться
* (opera, ie), вызываем событие onclick,
*/
if(lCurrentFile.onclick)
lCurrentFile.onclick(true);
else try{
lATag[0].click();
}
catch(error){
console.log(error);
}
event.preventDefault();//запрет на дальнейшее действие
}
/* если нажали <ctr>+r
* обновляем страницу,
* загружаем содержимое каталога
* при этом данные берём всегда с
* сервера, а не из кэша
* (обновляем кэш)
*/
else if(lKeyCode === KEY.R &&
event.ctrlKey){
console.log('<ctrl>+r pressed\n' +
'reloading page...\n' +
'press <alt>+q to remove all key-handlers');
/* Программно нажимаем на кнопку перезагрузки
* содержимого каталога
*/
var lRefreshIcon = DOM.getRefreshButton();
if(lRefreshIcon){
/* получаем название файла*/
var lSelectedName = DOM.getCurrentName();
/* если нашли элемент нажимаем него
* а если не можем - нажимаем на
* ссылку, на которую повешен eventHandler
* onclick
*/
lRefreshIcon.onclick();
cloudcmd._currentToParent(lSelectedName);
event.preventDefault();//запрет на дальнейшее действие
}
}
/* если нажали <ctrl>+d чистим кэш */
else if(lKeyCode === KEY.D &&
event.ctrlKey){
console.log('<ctrl>+d pressed\n' +
'clearing cache...\n' +
'press <alt>+q to remove all key-handlers');
var lClearCache = DOM.getById('clear-cache');
if(lClearCache && lClearCache.onclick)
lClearCache.onclick();
event.preventDefault();//запрет на дальнейшее действие
}
/* если нажали <alt>+q
* убираем все обработчики
* нажатий клавиш
*/
else if(lKeyCode === KEY.Q && event.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;
event.preventDefault();//запрет на дальнейшее действие
}
}
/* если нажали <alt>+s
* устанавливаем все обработчики
* нажатий клавиш
*/
else if(event.keyCode === KEY.S && event.altKey){
/* обрабатываем нажатия на клавиши*/
keyBinded = true;
console.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');
event.preventDefault();//запрет на дальнейшее действие
}
};
/* добавляем обработчик клавишь */
DOM.addKeyListener(key_event);
/* клавиши назначены*/
keyBinded = true;
};
})();
var CloudCommander, Util, DOM;
(function(CloudCmd, Util, DOM){
'use strict';
/* private property set or set key binding */
var keyBinded;
/* Key constants*/
CloudCmd.KEY = {
TAB : 9,
ENTER : 13,
ESC : 27,
PAGE_UP : 33,
PAGE_DOWN : 34,
END : 35,
HOME : 36,
UP : 38,
DOWN : 40,
Delete : 46,
D : 68,
G : 71,
O : 79,
Q : 81,
R : 82,
S : 83,
T : 84,
F1 : 112,
F2 : 113,
F3 : 114,
F4 : 115,
F10 : 121,
TRA : 192 /* Typewritten Reverse Apostrophe (`) */
};
var KEY = CloudCmd.KEY;
CloudCmd.KeyBinding = {};
var KeyBinding = CloudCmd.KeyBinding;
KeyBinding.get = function(){return keyBinded;};
KeyBinding.set = function(){keyBinded = true;};
KeyBinding.unSet = function(){keyBinded = false;};
KeyBinding.init = function(){
/* saving state of tabs varibles */
var lTabPanel = {
left : 0,
right : 0
};
var key_event = function(pEvent){
/* получаем выдленный файл*/
var lCurrentFile = DOM.getCurrentFile(), i;
/* если клавиши можно обрабатывать*/
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(){
/* changing parent panel of curent-file */
var lPanel = DOM.getPanel(),
lId = lPanel.id;
lTabPanel[lId] = lCurrentFile;
lPanel = DOM.getPanel({active:false});
lId = lPanel.id;
if(lTabPanel[lId])
DOM.setCurrentFile(lTabPanel[lId]);
else{
var lFirstFileOnList = DOM.getByTag('li', lPanel)[2];
DOM.setCurrentFile(lFirstFileOnList);
}
});
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'];
Util.exec(lEditor, true);
DOM.preventDefault(pEvent);
}
/* if f4 pressed */
else if(lKeyCode === KEY.F4) {
DOM.Images.showLoad();
Util.exec(CloudCmd.Editor);
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) );
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');
DOM.Cache.clear();
DOM.preventDefault();
}
/* если нажали <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();//запрет на дальнейшее действие
}
}
/* если нажали <alt>+s
* устанавливаем все обработчики
* нажатий клавиш
*/
else if(pEvent.keyCode === KEY.S && pEvent.altKey){
/* обрабатываем нажатия на клавиши*/
keyBinded = true;
console.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.addKeyListener(key_event);
/* клавиши назначены*/
keyBinded = true;
};
})(CloudCommander, Util, DOM);

View file

@ -1,263 +1,309 @@
/* object contains jQuery-contextMenu
* https://github.com/medialize/jQuery-contextMenu
*/
var CloudCommander, Util, DOM, CloudFunc, $;
(function(){
"use strict";
var cloudcmd = CloudCommander,
KeyBinding = cloudcmd.KeyBinding,
MenuSeted = false,
Menu = {},
Position;
Menu.dir = '/lib/client/menu/';
/* enable and disable menu constant */
Menu.ENABLED = false;
/* PRIVATE FUNCTIONS */
/** function shows editor
* @param pReadOnly
*/
function showEditor(pReadOnly){
DOM.Images.showLoad();
var lEditor = cloudcmd[pReadOnly ? 'Viewer' : 'Editor'],
lResult = Util.exec(lEditor, pReadOnly);
if(!lResult){
lEditor = lEditor.get();
if(lEditor)
Util.exec(lEditor.show);
}
}
/** function return configureation for menu */
function getConfig (){
return{
// define which elements trigger this menu
selector: 'li',
callback: function(key, options) {
var m = "clicked: " + key;
console.log(m, options);
KeyBinding.set();
},
// define the elements of the menu
items: {
view: {name: 'View', callback: function(key, opt){
showEditor(true);
}},
edit: {name: 'Edit', callback: function(key, opt){
showEditor();
}},
delete: {name: 'Delete',
callback: function(key, opt){
DOM.promptRemoveCurrent();
}},
upload: {
name: "Upload to",
items: {
'upload_to_gist': {name: 'Gist',
callback: function(key, opt){
var lCurrent = DOM.getCurrentFile(),
lPath = DOM.getCurrentPath(),
lName = DOM.getCurrentName(lCurrent);
DOM.ajax({
url : lPath,
error : DOM.Images.showError,
success : function(data, textStatus, jqXHR){
if( Util.isObject(data) )
data = JSON.stringify(data, null, 4);
var lGitHub = cloudcmd.GitHub;
if('init' in lGitHub)
lGitHub.createGist(data, lName);
else
Util.exec(cloudcmd.GitHub,
function(){
lGitHub.createGist(data, lName);
});
}
});
Util.log('Uploading to gist...');
}
},
},
},
download: {name: 'Download',callback: function(key, opt){
DOM.Images.showLoad();
var lCurrent = DOM.getCurrentFile(),
lLink = DOM.getByTag('a', lCurrent)[0].href;
console.log('downloading file ' + lLink +'...');
lLink = lLink + '?download';
lLink = Util.removeStr( lLink, CloudFunc.NOJS );
var lId = DOM.getIdBySrc(lLink);
if(!DOM.getById(lId)){
var lDownload = DOM.anyload({
name : 'iframe',
async : false,
className : 'hidden',
src : lLink,
func : function(){
DOM.Images.hideLoad();
}
});
DOM.Images.hideLoad();
setTimeout(function() {
document.body.removeChild(lDownload);
}, 10000);
}
else
DOM.Images.showError({
responseText: 'Error: You trying to' +
'download same file to often'});
}}
}
};
}
/** function loads css and js of Menu
* @param pCallBack
*/
function load(pCallBack){
console.time('menu load');
var lDir = Menu.dir,
lFiles = [
lDir + 'contextMenu.js',
lDir + 'contextMenu.css'
];
DOM.anyLoadInParallel(lFiles, function(){
console.timeEnd('menu load');
Util.exec(pCallBack);
});
}
function set(){
if(!MenuSeted){
$.contextMenu(getConfig());
var lFunc_f = document.onclick;
/*
* Menu works in some crazy way so need a
* little hack to get every thing work out.
* When menu shows up, it drawing invisible
* layer wich hides all elements of
* Cloud Commander so it could not handle
* onclick events. To get every thing work
* how expected we hide out invisible layer
* so for observer it is nothing special
* is not going on. All magic happening in
* DOM tree
*/
document.onclick = function(pEvent){
if(pEvent && pEvent.x && pEvent.y){
var lLayer = DOM.getById('context-menu-layer');
if(lLayer){
var lStyle;
if(lLayer)
lStyle = lLayer.style.cssText;
/* hide invisible menu layer */
if(lStyle)
lLayer.style.cssText = lStyle
.replace('z-index: 1', 'z-index:-1');
/* get element by point */
var lElement = document.elementFromPoint(pEvent.x, pEvent.y),
lTag = lElement.tagName,
lParent;
if(lTag === 'A' || lTag === 'SPAN'){
if (lElement.tagName === 'A')
lParent = lElement.parentElement.parentElement;
else if(lElement.tagName === 'SPAN')
lParent = lElement.parentElement;
if(lParent.className === '')
DOM.setCurrentFile(lParent);
}
/* show invisible menu layer */
if(lLayer && lStyle)
lLayer.style.cssText = lStyle;
/* if document.onclick was set up
* before us, it's best time to call it
*/
if(typeof lFunc_f === 'function')
lFunc_f();
KeyBinding.set();
}
}
};
MenuSeted = true;
}
}
/** function shows menu for the first time
* right away after loading
*/
Menu.show = function(){
set();
DOM.Images.hideLoad();
if(Position && Position.x && Position.y)
$('li').contextMenu(Position);
else
$('li').contextMenu();
};
/* key binding function */
Menu.init = function(pPosition){
Position = pPosition;
DOM.jqueryLoad( Util.retLoadOnLoad([
Menu.show,
load
]));
var key_event = function(pEvent){
/* если клавиши можно обрабатывать */
if( KeyBinding.get() ){
/* if shift + F10 pressed */
if(pEvent.keyCode === cloudcmd.KEY.F10 && pEvent.shiftKey){
var lCurrent = DOM.getCurrentFile();
if(lCurrent)
$(lCurrent).contextMenu();
pEvent.preventDefault();
}
}
else if (pEvent.keyCode === cloudcmd.KEY.ESC)
KeyBinding.set();
};
/* добавляем обработчик клавишь */
DOM.addKeyListener( key_event );
};
cloudcmd.Menu = Menu;
})();
/* object contains jQuery-contextMenu
* https://github.com/medialize/jQuery-contextMenu
*/
var CloudCommander, Util, DOM, CloudFunc, $;
(function(CloudCmd, Util, DOM, CloudFunc){
'use strict';
var KeyBinding = CloudCmd.KeyBinding,
MenuSeted = false,
Menu = {},
Position,
UploadToItemNames;
Menu.dir = '/lib/client/menu/';
/* enable and disable menu constant */
Menu.ENABLED = false;
/* PRIVATE FUNCTIONS */
/** function shows editor
* @param pReadOnly
*/
function showEditor(pReadOnly){
DOM.Images.showLoad();
var lEditor = CloudCmd[pReadOnly ? 'Viewer' : 'Editor'],
lResult = Util.exec(lEditor, pReadOnly);
if(!lResult){
lEditor = lEditor.get();
if(lEditor)
Util.exec(lEditor.show);
}
}
/* function read data from modules.json
* and build array of menu items of "upload to"
* menu
*/
function setUploadToItemNames(pCallBack){
CloudCmd.getModules(function(pModules){
var lStorageObj = Util.findObjByNameInArr( pModules, 'storage' );
UploadToItemNames = Util.getNamesFromObjArray( lStorageObj ) || [];
Util.exec(pCallBack);
});
}
/**
* function get menu item object for Upload To
*/
function getUploadToItems(pObjectName){
var lObj = {};
if( Util.isArray(pObjectName) ){
var n = pObjectName.length;
for(var i = 0; i < n; i++){
var lStr = pObjectName[i];
lObj[lStr] = getUploadToItems( lStr );
}
}
else if( Util.isString(pObjectName) ){
lObj.name = pObjectName;
lObj.callback = 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);
});
});
Util.log('Uploading to ' + pObjectName+ '...');
};
}
return lObj;
}
/**
* get menu item
*/
function getItem(pName, pCallBack){
var lRet = {
name : pName
};
if(Util.isFunction(pCallBack) )
lRet.callback = pCallBack;
else if (Util.isObject(pCallBack))
lRet.items = pCallBack;
return lRet;
}
/**
* get all menu items
* pItems = [{pName, pFunc}]
*/
function getAllItems(pItems){
var lRet = {},
lName,
lFunc;
if(pItems)
for(lName in pItems){
lFunc = pItems[lName];
lRet[lName] = getItem(lName, lFunc);
}
return lRet;
}
/**
* download menu item callback
*/
function downloadFromMenu(key, opt){
DOM.Images.showLoad();
var lPath = DOM.getCurrentPath(),
lId = DOM.getIdBySrc(lPath);
Util.log('downloading file ' + lPath +'...');
var lDir = DOM.isCurrentIsDir() ? '&&json' : '';
lPath = CloudFunc.FS + lPath + '?download' + lDir;
if(!DOM.getById(lId)){
var lDownload = DOM.anyload({
name : 'iframe',
async : false,
className : 'hidden',
src : lPath,
func : Util.retFunc(DOM.Images.hideLoad)
});
DOM.Images.hideLoad();
setTimeout(function() {
document.body.removeChild(lDownload);
}, 10000);
}
else
DOM.Images.showError({
responseText: 'Error: You trying to' +
'download same file to often'});
}
/**
* function return configureation for menu
*/
function getConfig (){
var lRet,
lMenuItems = {
'View' : Util.retExec(showEditor, true),
'Edit' : Util.retExec(showEditor, false),
'Rename' : function(){
setTimeout( Util.retExec(DOM.renameCurrentFile), 100);
},
'Delete' : Util.retExec(DOM.promptDeleteCurrent),
};
if(UploadToItemNames.length)
lMenuItems['Upload to'] = getUploadToItems(UploadToItemNames);
lMenuItems.Download = Util.retExec(downloadFromMenu);
lRet = {
// define which elements trigger this menu
selector: 'li',
callback: function(key, options) {
var m = "clicked: " + key;
Util.log(m, options);
KeyBinding.set();
},
// define the elements of the menu
items : getAllItems(lMenuItems),
events :{
hide: clickProcessing
}
};
return lRet;
}
/** function loads css and js of Menu
* @param pCallBack
*/
function load(pCallBack){
console.time('menu load');
var lDir = Menu.dir,
lFiles = [
lDir + 'contextMenu.js',
lDir + 'contextMenu.css'
];
DOM.anyLoadInParallel(lFiles, function(){
console.timeEnd('menu load');
$.contextMenu.handle.keyStop = $.noop;
Util.exec(pCallBack);
});
}
/*
* Menu works in some crazy way so need a
* little hack to get every thing work out.
* When menu shows up, it draws invisible
* layer wich hides all elements of
* Cloud Commander so it could not handle
* onclick events. To get every thing work
* how expected we remove invisible layer
* so for observer it is nothing special
* is not going on. All magic happening in
* DOM tree.
*/
function clickProcessing(){
var lLayer = DOM.getById('context-menu-layer');
if(lLayer){
document.body.removeChild(lLayer);
var lElement = document.elementFromPoint(Position.x, Position.y),
lTag = lElement.tagName;
if(lTag === 'A' || lTag === 'SPAN'){
if (lElement.tagName === 'A')
lElement = lElement.parentElement.parentElement;
else if(lElement.tagName === 'SPAN')
lElement = lElement.parentElement;
if(lElement.className === '')
DOM.setCurrentFile(lElement);
}
KeyBinding.set();
}
}
function set(){
if(!MenuSeted){
$.contextMenu(getConfig());
MenuSeted = true;
//DOM.addListener('mousedown', clickProcessing);
DOM.addListener('mousemove', function(pEvent){
Position = {
x : pEvent.clientX,
y : pEvent.clientY
};
});
}
}
/** function shows menu for the first time
* right away after loading
*/
Menu.show = function(){
set();
DOM.Images.hideLoad();
if(Position && !Position.x )
Position = undefined;
$('li').contextMenu(Position);
};
/* key binding function */
Menu.init = function(pPosition){
Position = pPosition;
Util.loadOnLoad([
Menu.show,
setUploadToItemNames,
load,
DOM.jqueryLoad
]);
var key_event = function(pEvent){
var lKEY = CloudCmd.KEY,
lKeyCode = pEvent.keyCode;
/* если клавиши можно обрабатывать */
if( KeyBinding.get() ){
/* if shift + F10 pressed */
if(lKeyCode === lKEY.F10 && pEvent.shiftKey){
$( DOM.getCurrentFile() ).contextMenu();
DOM.preventDefault(pEvent);
}
}
else if (lKeyCode === lKEY.ESC)
KeyBinding.set();
};
/* добавляем обработчик клавишь */
DOM.addKeyListener( key_event );
};
CloudCmd.Menu = Menu;
})(CloudCommander, Util, DOM, CloudFunc);

View file

@ -69,7 +69,7 @@ You're (obviously) able to use the context menu with your mouse. Once it is open
* ⇞ (page up) captured and ignore to avoid page scrolling (for consistency with native menus)
* ⇟ (page down) captured and ignore to avoid page scrolling (for consistency with native menus)
* ↖ (home) first item in list, will skip disabled elements
* ↘ (end) last item in, will skip disabled elements
* ↘ (end) last item in list, will skip disabled elements
Besides the obvious, browser also react to alphanumeric key strokes. Hitting <code>r</code> in a context menu will make Firefox (8) reload the page immediately. Chrome selects the option to see infos on the page, Safari selects the option to print the document. Awesome, right? Until trying the same on Windows I did not realize that the browsers were using the access-key for this. I would've preferred typing the first character of something, say "s" for "save" and then iterate through all the commands beginning with s. But that's me - what do I know about UX? Anyways, $.contextMenu now also supports accesskey handling.
@ -82,9 +82,9 @@ use [Google Closure Compiler](http://closure-compiler.appspot.com/home):
// ==ClosureCompiler==
// @compilation_level SIMPLE_OPTIMIZATIONS
// @output_file_name contextMenu.js
// @code_url http://medialize.github.com/jQuery-contextMenu/jquery-1.7.1.min.js
// @code_url http://medialize.github.com/jQuery-contextMenu/jquery.ui.position.js
// @code_url http://medialize.github.com/jQuery-contextMenu/jquery.contextMenu.js
// @code_url http://medialize.github.com/jQuery-contextMenu/jquery-1.8.2.min.js
// @code_url http://medialize.github.com/jQuery-contextMenu/src/jquery.ui.position.js
// @code_url http://medialize.github.com/jQuery-contextMenu/src/jquery.contextMenu.js
// ==/ClosureCompiler==
```
@ -103,155 +103,213 @@ $.contextMenu is published under the [MIT license](http://www.opensource.org/lic
## Changelog ##
### 1.5.22 ###
### 1.6.5 (January 20th 2013) ###
* Fixing issue with animation and remove on hide (Issue #64)
* fixing "opening a second menu can break the layer" - ([Issue #105](https://github.com/medialize/jQuery-contextMenu/issues/105))
### 1.5.21 ###
### 1.6.4 (January 19th 2013) ###
* Fixing backdrop would not remove on destroy (Issue #63)
* fixing [jQuery plugin manifest](https://github.com/medialize/jQuery-contextMenu/commit/413b1ecaba0aeb4e50f97cee35f7c367435e7830#commitcomment-2465216), again. yep. I'm that kind of a guy. :(
### 1.5.20 ###
### 1.6.3 (January 19th 2013) ###
* Fixing backdrop would not position properly in IE6 (Issue #59)
* Fixing nested input elements not accessible in Chrome / Safari (Issue #58)
* fixing [jQuery plugin manifest](https://github.com/medialize/jQuery-contextMenu/commit/413b1ecaba0aeb4e50f97cee35f7c367435e7830#commitcomment-2465216)
### 1.6.2 (January 19th 2013) ###
* fixing "menu won't close" regression introduced by 1.6.1
### 1.6.1 (January 19th 2013) ###
* fixing potential html parsing problem
* upgrading to jQuery UI position v1.10.0
* replaced `CRLF` by `LF` (no idea how this happened in the first place...)
* adding `options.reposition` to dis/allow simply relocating a menu instead of rebuilding it ([Issue #104](https://github.com/medialize/jQuery-contextMenu/issues/104))
### 1.6.0 (December 29th 2012) ###
* adding [DOM Element bound context menus](http://medialize.github.com/jQuery-contextMenu/demo/on-dom-element.html) - ([Issue 88](https://github.com/medialize/jQuery-contextMenu/issues/88))
* adding class `context-menu-active` to define state on active trigger element - ([Issue 92](https://github.com/medialize/jQuery-contextMenu/issues/92))
* adding [demo for TouchSwipe](http://medialize.github.com/jQuery-contextMenu/demo/trigger-swipe.html) activation
* adding export of internal functions and event handlers - ([Issue 101](https://github.com/medialize/jQuery-contextMenu/issues/101))
* fixing key "watch" might translate to Object.prototype.watch in callbacks map - ([Issue 93](https://github.com/medialize/jQuery-contextMenu/issues/93))
* fixing menu and submenu width calculation - ([Issue 18](https://github.com/medialize/jQuery-contextMenu/issues/18))
* fixing unused variables - ([Issue 100](https://github.com/medialize/jQuery-contextMenu/issues/100))
* fixing iOS "click" compatibility problem - ([Issue 83](https://github.com/medialize/jQuery-contextMenu/issues/83))
* fixing separators to not be clickable - ([Issue 85](https://github.com/medialize/jQuery-contextMenu/issues/85))
* fixing issues with fixed positioned triggers ([Issue 95](https://github.com/medialize/jQuery-contextMenu/issues/95))
* fixing word break problem - ([Issue 80](https://github.com/medialize/jQuery-contextMenu/issues/80))
### 1.5.25 (October 8th 2012) ###
* upgrading to jQuery 1.8.2 ([Issue 78](https://github.com/medialize/jQuery-contextMenu/issues/78))
* upgrading to jQuery UI position 1.9.0 RC1 ([Issue 78](https://github.com/medialize/jQuery-contextMenu/issues/78))
### 1.5.24 (August 30th 2012) ###
* adding context menu options to input command events ([Issue 72](https://github.com/medialize/jQuery-contextMenu/issues/72), dtex)
* code cosmetics for JSLint
### 1.5.23 (August 22nd 2012) ###
* fixing reposition/close issue on scrolled documents ([Issue 69](https://github.com/medialize/jQuery-contextMenu/issues/69))
* fixing jQuery reference ([Issue 68](https://github.com/medialize/jQuery-contextMenu/issues/68))
### 1.5.22 (July 16th 2012) ###
* fixing issue with animation and remove on hide (Issue #64)
### 1.5.21 (July 14th 2012) ###
* fixing backdrop would not remove on destroy (Issue #63)
### 1.5.20 (June 26th 2012) ###
Note: git tag of version is `v1.6.20`?!
* fixing backdrop would not position properly in IE6 (Issue #59)
* fixing nested input elements not accessible in Chrome / Safari (Issue #58)
### 1.5.19 ###
Note: git tag of version is missing...?!
* fixing sub-menu positioning when `$.ui.position` is not available (Issue #56)
### 1.5.18 ###
Note: git tag of version is missing...?!
* fixing html5 `<menu>` import (Issue #53)
### 1.5.17 ###
### 1.5.17 (June 4th 2012) ###
* fixing `options` to default to `options.trigger = "right"`
* fixing variable name typo (Within Issue #51)
* fixing menu not closing while opening other menu (Within Issue #51)
* adding workaround for `contextmenu`-bug in Firefox 12 (Within Issue #51)
### 1.5.16 ###
### 1.5.16 (May 29th 2012) ###
* added vendor-prefixed user-select to CSS
* fixed issue with z-indexing when `<body>` is used as a trigger (Issue #49)
### 1.5.15 ###
### 1.5.15 (May 26th 2012) ###
* allowing to directly open another element's menu while a menu is shown (Issue #48)
* fixing autohide option that would not properly hide the menu
### 1.5.14 ###
### 1.5.14 (May 22nd 2012) ###
* options.build() would break default options (Issue #47)
* $.contextMenu('destroy') would not remove backdrop
### 1.5.13 ###
### 1.5.13 (May 4th 2012) ###
* exposing $trigger to dynamically built custom menu-item types (Issue #42)
* fixing repositioning of open menu (formerly accidental re-open)
* adding asynchronous example
* dropping ignoreRightClick in favor of proper event-type detection
### 1.5.12 ###
### 1.5.12 (May 2nd 2012) ###
* prevent invoking callback of first item of a sub-menu when clicking on the sub-menu-item (Issue #41)
### 1.5.11 ###
### 1.5.11 (April 27th 2012) ###
* providing `opt.$trigger` to show event (Issue #39)
### 1.5.10 ###
### 1.5.10 (April 21st 2012) ###
* ignoreRightClick would not prevent right click when menu is already open (Issue #38)
### 1.5.9 ###
### 1.5.9 (March 10th 2012) ###
* If build() did not return any items, an empty menu was shown (Issue #33)
### 1.5.8 ###
### 1.5.8 (January 28th 2012) ###
* Capturing Page Up and Page Down keys to ignore like space (Issue #30)
* Added Home / End keys to jump to first / last command of menu (Issue #29)
* Bug hitting enter in an &lt;input&gt; would yield an error (Issue #28)
### 1.5.7 ###
### 1.5.7 (January 21st 2012) ###
* Non-ASCII character in jquery.contextMenu.js caused compatibility issues in Rails (Issue #27)
### 1.5.6 ###
### 1.5.6 (January 8th 2012) ###
* Bug contextmenu event was not passed to build() callback (Issue #24)
* Bug sub-menu markers would not display properly in Safari and Chrome (Issue #25)
### 1.5.5 ###
### 1.5.5 (January 6th 2012) ###
* Bug Internet Explorer would not close menu when giving input elements focus (Issue #23)
### 1.5.4 ###
### 1.5.4 (January 5th 2012) ##
* Bug not set z-index of sub-menus might not overlap the main menu correctly (Issue #22)
### 1.5.3 ###
### 1.5.3 (January 1st 2012) ###
* Bug `console.log is undefined`
### 1.5.2 ###
### 1.5.2 (December 25th 2012) ###
* Bug sub-menus would not properly update their disabled states (Issue #16) [again…]
* Bug sub-menus would not properly adjust width accoring to min-width and max-width (Issue #18)
### 1.5.1 ###
### 1.5.1 (December 18th 2011) ###
* Bug sub-menus would not properly update their disabled states (Issue #16)
### 1.5 ###
### 1.5 (December 13th 2011) ###
* Added [dynamic menu creation](http://medialize.github.com/jQuery-contextMenu/demo/dynamic-create.html) (Issue #15)
### 1.4.4 ###
### 1.4.4 (December 12th 2011) ###
* Bug positioning &lt;menu&gt; when trigger element is `position:fixed` (Issue #14)
### 1.4.3 ###
### 1.4.3 (December 11th 2011) ###
* Bug key handler would caputure all key strokes while menu was visible (essentially disabling F5 and co.)
### 1.4.2 ###
### 1.4.2 (December 6th 2011) ###
* Bug opt.$trigger was not available to disabled callbacks
* jQuery bumped to 1.7.1
### 1.4.1 ###
### 1.4.1 (November 9th 2011) ###
* Bug where &lt;menu&gt; imports would not pass action (click event) properly
### 1.4 ###
### 1.4 (November 7th 2011) ###
* Upgraded to jQuery 1.7 (changed dependecy!)
* Added internal events `contextmenu:focus`, `contextmenu:blur` and `contextmenu:hide`
* Added custom &lt;command&gt; types
* Bug where `className` wasn't properly set on &lt;menu&gt;
### 1.3 ###
### 1.3 (September 5th 2011) ###
* Added support for accesskeys
* Bug where two sub-menus could be open simultaneously
### 1.2.2 ###
### 1.2.2 (August 24th 2011) ###
* Bug in HTML5 import
### 1.2.1 ###
### 1.2.1 (August 24th 2011) ###
* Bug in HTML5 detection
### 1.2 ###
### 1.2 (August 24th 2011) ###
* Added compatibility to &lt;menuitem&gt; for Firefox 8
* Upgraded to jQuery 1.6.2
### 1.1 ###
### 1.1 (August 11th 2011) ###
* Bug #1 TypeError on HTML5 action passthru
* Bug #2 disbaled callback not invoked properly
@ -259,6 +317,6 @@ $.contextMenu is published under the [MIT license](http://www.opensource.org/lic
* Feature #4 option to use a single callback for all commands, rather than registering the same function for each item
* Option to ignore right-click (original "contextmenu" event trigger) for non-right-click triggers
### 1.0 ###
### 1.0 (July 7th 2011) ###
* Initial $.contextMenu handler

View file

@ -1,142 +1,142 @@
/*!
* jQuery contextMenu - Plugin for simple contextMenu handling
*
* Version: 1.5.22
*
* Authors: Rodney Rehm, Addy Osmani (patches for FF)
* Web: http://medialize.github.com/jQuery-contextMenu/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
* GPL v3 http://opensource.org/licenses/GPL-3.0
*
*/
.context-menu-list {
margin:0;
padding:0;
min-width: 120px;
max-width: 250px;
display: inline-block;
position: absolute;
list-style-type: none;
border: 1px solid #DDD;
background: #EEE;
-webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
-moz-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
-ms-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
-o-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 11px;
}
.context-menu-item {
padding: 2px 2px 2px 24px;
background-color: #EEE;
position: relative;
-webkit-user-select: none;
-moz-user-select: -moz-none;
-ms-user-select: none;
user-select: none;
}
.context-menu-separator {
padding-bottom:0;
border-bottom: 1px solid #DDD;
}
.context-menu-item > label > input,
.context-menu-item > label > textarea {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.context-menu-item.hover {
cursor: pointer;
background-color: #39F;
}
.context-menu-item.disabled {
color: #666;
}
.context-menu-input.hover,
.context-menu-item.disabled.hover {
cursor: default;
background-color: #EEE;
}
.context-menu-submenu:after {
content: ">";
color: #666;
position: absolute;
top: 0;
right: 3px;
z-index: 1;
}
/* icons
#protip:
In case you want to use sprites for icons (which I would suggest you do) have a look at
http://css-tricks.com/13224-pseudo-spriting/ to get an idea of how to implement
.context-menu-item.icon:before {}
*/
.context-menu-item.icon { min-height: 18px; background-repeat: no-repeat; background-position: 4px 2px; }
.context-menu-item.icon-edit { background-image: url(images/page_white_edit.png); }
.context-menu-item.icon-cut { background-image: url(images/cut.png); }
.context-menu-item.icon-copy { background-image: url(images/page_white_copy.png); }
.context-menu-item.icon-paste { background-image: url(images/page_white_paste.png); }
.context-menu-item.icon-delete { background-image: url(images/page_white_delete.png); }
.context-menu-item.icon-add { background-image: url(images/page_white_add.png); }
.context-menu-item.icon-quit { background-image: url(images/door.png); }
/* vertically align inside labels */
.context-menu-input > label > * { vertical-align: top; }
/* position checkboxes and radios as icons */
.context-menu-input > label > input[type="checkbox"],
.context-menu-input > label > input[type="radio"] {
margin-left: -17px;
}
.context-menu-input > label > span {
margin-left: 5px;
}
.context-menu-input > label,
.context-menu-input > label > input[type="text"],
.context-menu-input > label > textarea,
.context-menu-input > label > select {
display: block;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
}
.context-menu-input > label > textarea {
height: 100px;
}
.context-menu-item > .context-menu-list {
display: none;
/* re-positioned by js */
right: -5px;
top: 5px;
}
.context-menu-item.hover > .context-menu-list {
display: block;
}
.context-menu-accesskey {
text-decoration: underline;
}
/*!
* jQuery contextMenu - Plugin for simple contextMenu handling
*
* Version: git-master
*
* Authors: Rodney Rehm, Addy Osmani (patches for FF)
* Web: http://medialize.github.com/jQuery-contextMenu/
*
* Licensed under
* MIT License http://www.opensource.org/licenses/mit-license
* GPL v3 http://opensource.org/licenses/GPL-3.0
*
*/
.context-menu-list {
margin:0;
padding:0;
min-width: 120px;
max-width: 250px;
display: inline-block;
position: absolute;
list-style-type: none;
border: 1px solid #DDD;
background: #EEE;
-webkit-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
-moz-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
-ms-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
-o-box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.5);
font-family: Verdana, Arial, Helvetica, sans-serif;
font-size: 11px;
}
.context-menu-item {
padding: 2px 2px 2px 24px;
background-color: #EEE;
position: relative;
-webkit-user-select: none;
-moz-user-select: -moz-none;
-ms-user-select: none;
user-select: none;
}
.context-menu-separator {
padding-bottom:0;
border-bottom: 1px solid #DDD;
}
.context-menu-item > label > input,
.context-menu-item > label > textarea {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.context-menu-item.hover {
cursor: pointer;
background-color: #39F;
}
.context-menu-item.disabled {
color: #666;
}
.context-menu-input.hover,
.context-menu-item.disabled.hover {
cursor: default;
background-color: #EEE;
}
.context-menu-submenu:after {
content: ">";
color: #666;
position: absolute;
top: 0;
right: 3px;
z-index: 1;
}
/* icons
#protip:
In case you want to use sprites for icons (which I would suggest you do) have a look at
http://css-tricks.com/13224-pseudo-spriting/ to get an idea of how to implement
.context-menu-item.icon:before {}
*/
.context-menu-item.icon { min-height: 18px; background-repeat: no-repeat; background-position: 4px 2px; }
.context-menu-item.icon-edit { background-image: url(images/page_white_edit.png); }
.context-menu-item.icon-cut { background-image: url(images/cut.png); }
.context-menu-item.icon-copy { background-image: url(images/page_white_copy.png); }
.context-menu-item.icon-paste { background-image: url(images/page_white_paste.png); }
.context-menu-item.icon-delete { background-image: url(images/page_white_delete.png); }
.context-menu-item.icon-add { background-image: url(images/page_white_add.png); }
.context-menu-item.icon-quit { background-image: url(images/door.png); }
/* vertically align inside labels */
.context-menu-input > label > * { vertical-align: top; }
/* position checkboxes and radios as icons */
.context-menu-input > label > input[type="checkbox"],
.context-menu-input > label > input[type="radio"] {
margin-left: -17px;
}
.context-menu-input > label > span {
margin-left: 5px;
}
.context-menu-input > label,
.context-menu-input > label > input[type="text"],
.context-menu-input > label > textarea,
.context-menu-input > label > select {
display: block;
width: 100%;
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
-ms-box-sizing: border-box;
-o-box-sizing: border-box;
box-sizing: border-box;
}
.context-menu-input > label > textarea {
height: 100px;
}
.context-menu-item > .context-menu-list {
display: none;
/* re-positioned by js */
right: -5px;
top: 5px;
}
.context-menu-item.hover > .context-menu-list {
display: block;
}
.context-menu-accesskey {
text-decoration: underline;
}

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
{
"name": "jQuery contextMenu",
"version": "1.5.22",
"version": "git-master",
"title": "jQuery.contextMenu()",
"author": {
"name": "Rodney Rehm",

View file

@ -1,517 +0,0 @@
/*!
* jQuery UI Position @VERSION
* http://jqueryui.com
*
* Copyright 2012 jQuery Foundation and other contributors
* Released under the MIT license.
* http://jquery.org/license
*
* http://docs.jquery.com/UI/Position
*/
(function( $, undefined ) {
$.ui = $.ui || {};
var cachedScrollbarWidth,
max = Math.max,
abs = Math.abs,
round = Math.round,
rhorizontal = /left|center|right/,
rvertical = /top|center|bottom/,
roffset = /[\+\-]\d+%?/,
rposition = /^\w+/,
rpercent = /%$/,
_position = $.fn.position;
function getOffsets( offsets, width, height ) {
return [
parseInt( offsets[ 0 ], 10 ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
parseInt( offsets[ 1 ], 10 ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
];
}
function parseCss( element, property ) {
return parseInt( $.css( element, property ), 10 ) || 0;
}
$.position = {
scrollbarWidth: function() {
if ( cachedScrollbarWidth !== undefined ) {
return cachedScrollbarWidth;
}
var w1, w2,
div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
innerDiv = div.children()[0];
$( "body" ).append( div );
w1 = innerDiv.offsetWidth;
div.css( "overflow", "scroll" );
w2 = innerDiv.offsetWidth;
if ( w1 === w2 ) {
w2 = div[0].clientWidth;
}
div.remove();
return (cachedScrollbarWidth = w1 - w2);
},
getScrollInfo: function( within ) {
var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ),
overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ),
hasOverflowX = overflowX === "scroll" ||
( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
hasOverflowY = overflowY === "scroll" ||
( overflowY === "auto" && within.height < within.element[0].scrollHeight );
return {
width: hasOverflowX ? $.position.scrollbarWidth() : 0,
height: hasOverflowY ? $.position.scrollbarWidth() : 0
};
},
getWithinInfo: function( element ) {
var withinElement = $( element || window ),
isWindow = $.isWindow( withinElement[0] );
return {
element: withinElement,
isWindow: isWindow,
offset: withinElement.offset() || { left: 0, top: 0 },
scrollLeft: withinElement.scrollLeft(),
scrollTop: withinElement.scrollTop(),
width: isWindow ? withinElement.width() : withinElement.outerWidth(),
height: isWindow ? withinElement.height() : withinElement.outerHeight()
};
}
};
$.fn.position = function( options ) {
if ( !options || !options.of ) {
return _position.apply( this, arguments );
}
// make a copy, we don't want to modify arguments
options = $.extend( {}, options );
var atOffset, targetWidth, targetHeight, targetOffset, basePosition,
target = $( options.of ),
within = $.position.getWithinInfo( options.within ),
scrollInfo = $.position.getScrollInfo( within ),
targetElem = target[0],
collision = ( options.collision || "flip" ).split( " " ),
offsets = {};
if ( targetElem.nodeType === 9 ) {
targetWidth = target.width();
targetHeight = target.height();
targetOffset = { top: 0, left: 0 };
} else if ( $.isWindow( targetElem ) ) {
targetWidth = target.width();
targetHeight = target.height();
targetOffset = { top: target.scrollTop(), left: target.scrollLeft() };
} else if ( targetElem.preventDefault ) {
// force left top to allow flipping
options.at = "left top";
targetWidth = targetHeight = 0;
targetOffset = { top: targetElem.pageY, left: targetElem.pageX };
} else {
targetWidth = target.outerWidth();
targetHeight = target.outerHeight();
targetOffset = target.offset();
}
// clone to reuse original targetOffset later
basePosition = $.extend( {}, targetOffset );
// force my and at to have valid horizontal and vertical positions
// if a value is missing or invalid, it will be converted to center
$.each( [ "my", "at" ], function() {
var pos = ( options[ this ] || "" ).split( " " ),
horizontalOffset,
verticalOffset;
if ( pos.length === 1) {
pos = rhorizontal.test( pos[ 0 ] ) ?
pos.concat( [ "center" ] ) :
rvertical.test( pos[ 0 ] ) ?
[ "center" ].concat( pos ) :
[ "center", "center" ];
}
pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
// calculate offsets
horizontalOffset = roffset.exec( pos[ 0 ] );
verticalOffset = roffset.exec( pos[ 1 ] );
offsets[ this ] = [
horizontalOffset ? horizontalOffset[ 0 ] : 0,
verticalOffset ? verticalOffset[ 0 ] : 0
];
// reduce to just the positions without the offsets
options[ this ] = [
rposition.exec( pos[ 0 ] )[ 0 ],
rposition.exec( pos[ 1 ] )[ 0 ]
];
});
// normalize collision option
if ( collision.length === 1 ) {
collision[ 1 ] = collision[ 0 ];
}
if ( options.at[ 0 ] === "right" ) {
basePosition.left += targetWidth;
} else if ( options.at[ 0 ] === "center" ) {
basePosition.left += targetWidth / 2;
}
if ( options.at[ 1 ] === "bottom" ) {
basePosition.top += targetHeight;
} else if ( options.at[ 1 ] === "center" ) {
basePosition.top += targetHeight / 2;
}
atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
basePosition.left += atOffset[ 0 ];
basePosition.top += atOffset[ 1 ];
return this.each(function() {
var collisionPosition, using,
elem = $( this ),
elemWidth = elem.outerWidth(),
elemHeight = elem.outerHeight(),
marginLeft = parseCss( this, "marginLeft" ),
marginTop = parseCss( this, "marginTop" ),
collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
position = $.extend( {}, basePosition ),
myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
if ( options.my[ 0 ] === "right" ) {
position.left -= elemWidth;
} else if ( options.my[ 0 ] === "center" ) {
position.left -= elemWidth / 2;
}
if ( options.my[ 1 ] === "bottom" ) {
position.top -= elemHeight;
} else if ( options.my[ 1 ] === "center" ) {
position.top -= elemHeight / 2;
}
position.left += myOffset[ 0 ];
position.top += myOffset[ 1 ];
// if the browser doesn't support fractions, then round for consistent results
if ( !$.support.offsetFractions ) {
position.left = round( position.left );
position.top = round( position.top );
}
collisionPosition = {
marginLeft: marginLeft,
marginTop: marginTop
};
$.each( [ "left", "top" ], function( i, dir ) {
if ( $.ui.position[ collision[ i ] ] ) {
$.ui.position[ collision[ i ] ][ dir ]( position, {
targetWidth: targetWidth,
targetHeight: targetHeight,
elemWidth: elemWidth,
elemHeight: elemHeight,
collisionPosition: collisionPosition,
collisionWidth: collisionWidth,
collisionHeight: collisionHeight,
offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
my: options.my,
at: options.at,
within: within,
elem : elem
});
}
});
if ( $.fn.bgiframe ) {
elem.bgiframe();
}
if ( options.using ) {
// adds feedback as second argument to using callback, if present
using = function( props ) {
var left = targetOffset.left - position.left,
right = left + targetWidth - elemWidth,
top = targetOffset.top - position.top,
bottom = top + targetHeight - elemHeight,
feedback = {
target: {
element: target,
left: targetOffset.left,
top: targetOffset.top,
width: targetWidth,
height: targetHeight
},
element: {
element: elem,
left: position.left,
top: position.top,
width: elemWidth,
height: elemHeight
},
horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
};
if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
feedback.horizontal = "center";
}
if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
feedback.vertical = "middle";
}
if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
feedback.important = "horizontal";
} else {
feedback.important = "vertical";
}
options.using.call( this, props, feedback );
};
}
elem.offset( $.extend( position, { using: using } ) );
});
};
$.ui.position = {
fit: {
left: function( position, data ) {
var within = data.within,
withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
outerWidth = within.width,
collisionPosLeft = position.left - data.collisionPosition.marginLeft,
overLeft = withinOffset - collisionPosLeft,
overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
newOverRight;
// element is wider than within
if ( data.collisionWidth > outerWidth ) {
// element is initially over the left side of within
if ( overLeft > 0 && overRight <= 0 ) {
newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
position.left += overLeft - newOverRight;
// element is initially over right side of within
} else if ( overRight > 0 && overLeft <= 0 ) {
position.left = withinOffset;
// element is initially over both left and right sides of within
} else {
if ( overLeft > overRight ) {
position.left = withinOffset + outerWidth - data.collisionWidth;
} else {
position.left = withinOffset;
}
}
// too far left -> align with left edge
} else if ( overLeft > 0 ) {
position.left += overLeft;
// too far right -> align with right edge
} else if ( overRight > 0 ) {
position.left -= overRight;
// adjust based on position and margin
} else {
position.left = max( position.left - collisionPosLeft, position.left );
}
},
top: function( position, data ) {
var within = data.within,
withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
outerHeight = data.within.height,
collisionPosTop = position.top - data.collisionPosition.marginTop,
overTop = withinOffset - collisionPosTop,
overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
newOverBottom;
// element is taller than within
if ( data.collisionHeight > outerHeight ) {
// element is initially over the top of within
if ( overTop > 0 && overBottom <= 0 ) {
newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
position.top += overTop - newOverBottom;
// element is initially over bottom of within
} else if ( overBottom > 0 && overTop <= 0 ) {
position.top = withinOffset;
// element is initially over both top and bottom of within
} else {
if ( overTop > overBottom ) {
position.top = withinOffset + outerHeight - data.collisionHeight;
} else {
position.top = withinOffset;
}
}
// too far up -> align with top
} else if ( overTop > 0 ) {
position.top += overTop;
// too far down -> align with bottom edge
} else if ( overBottom > 0 ) {
position.top -= overBottom;
// adjust based on position and margin
} else {
position.top = max( position.top - collisionPosTop, position.top );
}
}
},
flip: {
left: function( position, data ) {
var within = data.within,
withinOffset = within.offset.left + within.scrollLeft,
outerWidth = within.width,
offsetLeft = within.isWindow ? 0 : within.offset.left,
collisionPosLeft = position.left - data.collisionPosition.marginLeft,
overLeft = collisionPosLeft - offsetLeft,
overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
myOffset = data.my[ 0 ] === "left" ?
-data.elemWidth :
data.my[ 0 ] === "right" ?
data.elemWidth :
0,
atOffset = data.at[ 0 ] === "left" ?
data.targetWidth :
data.at[ 0 ] === "right" ?
-data.targetWidth :
0,
offset = -2 * data.offset[ 0 ],
newOverRight,
newOverLeft;
if ( overLeft < 0 ) {
newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
position.left += myOffset + atOffset + offset;
}
}
else if ( overRight > 0 ) {
newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
position.left += myOffset + atOffset + offset;
}
}
},
top: function( position, data ) {
var within = data.within,
withinOffset = within.offset.top + within.scrollTop,
outerHeight = within.height,
offsetTop = within.isWindow ? 0 : within.offset.top,
collisionPosTop = position.top - data.collisionPosition.marginTop,
overTop = collisionPosTop - offsetTop,
overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
top = data.my[ 1 ] === "top",
myOffset = top ?
-data.elemHeight :
data.my[ 1 ] === "bottom" ?
data.elemHeight :
0,
atOffset = data.at[ 1 ] === "top" ?
data.targetHeight :
data.at[ 1 ] === "bottom" ?
-data.targetHeight :
0,
offset = -2 * data.offset[ 1 ],
newOverTop,
newOverBottom;
if ( overTop < 0 ) {
newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
position.top += myOffset + atOffset + offset;
}
}
else if ( overBottom > 0 ) {
newOverTop = position.top - data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
position.top += myOffset + atOffset + offset;
}
}
}
},
flipfit: {
left: function() {
$.ui.position.flip.left.apply( this, arguments );
$.ui.position.fit.left.apply( this, arguments );
},
top: function() {
$.ui.position.flip.top.apply( this, arguments );
$.ui.position.fit.top.apply( this, arguments );
}
}
};
// fraction support test
(function () {
var testElement, testElementParent, testElementStyle, offsetLeft, i,
body = document.getElementsByTagName( "body" )[ 0 ],
div = document.createElement( "div" );
//Create a "fake body" for testing based on method used in jQuery.support
testElement = document.createElement( body ? "div" : "body" );
testElementStyle = {
visibility: "hidden",
width: 0,
height: 0,
border: 0,
margin: 0,
background: "none"
};
if ( body ) {
$.extend( testElementStyle, {
position: "absolute",
left: "-1000px",
top: "-1000px"
});
}
for ( i in testElementStyle ) {
testElement.style[ i ] = testElementStyle[ i ];
}
testElement.appendChild( div );
testElementParent = body || document.documentElement;
testElementParent.insertBefore( testElement, testElementParent.firstChild );
div.style.cssText = "position: absolute; left: 10.7432222px;";
offsetLeft = $( div ).offset().left;
$.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
testElement.innerHTML = "";
testElementParent.removeChild( testElement );
})();
// DEPRECATED
if ( $.uiBackCompat !== false ) {
// offset option
(function( $ ) {
var _position = $.fn.position;
$.fn.position = function( options ) {
if ( !options || !options.offset ) {
return _position.call( this, options );
}
var offset = options.offset.split( " " ),
at = options.at.split( " " );
if ( offset.length === 1 ) {
offset[ 1 ] = offset[ 0 ];
}
if ( /^\d/.test( offset[ 0 ] ) ) {
offset[ 0 ] = "+" + offset[ 0 ];
}
if ( /^\d/.test( offset[ 1 ] ) ) {
offset[ 1 ] = "+" + offset[ 1 ];
}
if ( at.length === 1 ) {
if ( /left|center|right/.test( at[ 0 ] ) ) {
at[ 1 ] = "center";
} else {
at[ 1 ] = at[ 0 ];
at[ 0 ] = "center";
}
}
return _position.call( this, $.extend( options, {
at: at[ 0 ] + offset[ 0 ] + " " + at[ 1 ] + offset[ 1 ],
offset: undefined
} ) );
};
}( jQuery ) );
}
}( jQuery ) );

View file

@ -1,22 +1,21 @@
/* module make possible connectoin thrue socket.io on a client */
var CloudCommander, DOM, Util, io;
(function(){
"use strict";
(function(CloudCmd, DOM, Util){
'use strict';
var cloudcmd = CloudCommander,
Messages = [],
var Messages = [],
socket,
JqueryTerminal;
function getJqueryTerminal(){
return cloudcmd.Terminal.JqueryTerminal;
return CloudCmd.Terminal.JqueryTerminal;
}
DOM.jsload('/socket.io/lib/socket.io.js', {
onload : function(){
socket = io.connect(document.location.hostname);
cloudcmd.Socket = socket;
CloudCmd.Socket = socket;
socket.on('connect', function () {
JqueryTerminal = getJqueryTerminal();
@ -98,11 +97,10 @@ var CloudCommander, DOM, Util, io;
*/
Messages.push(pMsg);
lResult = false;
}
}
console.log(pMsg);
return lResult;
}
})();
})(CloudCommander, DOM, Util);

View file

@ -1,55 +1,111 @@
var CloudCommander, DOM, Dropbox;
var CloudCommander, Util, DOM, Dropbox, cb, Client;
/* module for work with github */
(function(){
"use strict";
(function(CloudCmd, Util, DOM){
'use strict';
var DropBoxStore = {};
/* temporary callback function for work with github */
cb = function (err, data){ Util.log(err || data);};
var cloudcmd = CloudCommander,
CHOOSER_API = 'https://www.dropbox.com/static/api/1/dropbox.js',
CLIENT_ID,
DropBoxStore = {},
options = {
linkType: "direct",
success: function(files) {
console.log("Here's the file link:" + files[0].link);
},
cancel: function() {
console.log('Chose something');
}
};
/* PRIVATE FUNCTIONS */
/**
* function loads dropbox.js
*/
function load(){
function load(pCallBack){
console.time('dropbox load');
cloudcmd.getConfig(function(pConfig){
var lElement = DOM.anyload({
src : CHOOSER_API,
not_append : true,
id : 'dropboxjs',
func : DropBoxStore.choose
});
var lDropBoxId = pConfig.dropbox_chooser_key;
lElement.setAttribute('data-app-key', lDropBoxId);
document.body.appendChild(lElement);
console.timeEnd('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)
});
}
DropBoxStore.choose = function(){
Dropbox.choose(options);
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(){
load();
DropBoxStore.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
getUserData,
DropBoxStore.login,
load
]);
CloudCmd.DropBox.init = null;
};
cloudcmd.DropBox = DropBoxStore;
})();
CloudCmd.DropBox = DropBoxStore;
})(CloudCommander, Util, DOM);

View file

@ -0,0 +1,55 @@
var CloudCommander, DOM, Dropbox;
/* module for work with github */
(function(){
"use strict";
var cloudcmd = CloudCommander,
CHOOSER_API = 'https://www.dropbox.com/static/api/1/dropbox.js',
CLIENT_ID,
DropBoxStore = {},
options = {
linkType: "direct",
success: function(files) {
console.log("Here's the file link:" + files[0].link);
},
cancel: function() {
console.log('Chose something');
}
};
/* PRIVATE FUNCTIONS */
/**
* function loads dropbox.js
*/
function load(){
console.time('dropbox load');
cloudcmd.getConfig(function(pConfig){
var lElement = DOM.anyload({
src : CHOOSER_API,
not_append : true,
id : 'dropboxjs',
func : DropBoxStore.choose
});
var lDropBoxId = pConfig.dropbox_chooser_key;
lElement.setAttribute('data-app-key', lDropBoxId);
document.body.appendChild(lElement);
console.timeEnd('dropbox load');
});
}
DropBoxStore.choose = function(){
Dropbox.choose(options);
};
DropBoxStore.init = function(){
load();
};
cloudcmd.DropBox = DropBoxStore;
})();

View file

@ -0,0 +1,109 @@
var CloudCommander, Util, DOM, gapi;
(function(CloudCmd, Util, DOM){
'use strict';
var GDrive = {};
/* PRIVATE FUNCTIONS */
/**
* load google api library
*/
function load(pCallBack){
/* https://code.google.com/p/google-api-javascript-client/ */
var lUrl = 'https://apis.google.com/js/client.js';
DOM.jsload(lUrl, function(){
CloudCmd.getModules(function(pModules){
var lStorage = Util.findObjByNameInArr(pModules, 'storage'),
lGDrive = Util.findObjByNameInArr(lStorage, 'GDrive'),
GDriveId = lGDrive && lGDrive.id;
/* https://developers.google.com/drive/credentials */
var lCLIENT_ID = GDriveId + '.apps.googleusercontent.com',
lSCOPES = 'https://www.googleapis.com/auth/drive',
lParams = {
'client_id' : lCLIENT_ID,
'scope' : lSCOPES,
'immediate' : false
};
setTimeout(function() {
gapi.auth.authorize(lParams, function(pAuthResult){
if (pAuthResult && !pAuthResult.error)
gapi.client.load('drive', 'v2', function() {
Util.exec(pCallBack);
});
});
}, 500);
});
});
}
/**
* Insert new file.
*
* @param {File} fileData {name, data} File object to read data from.
* @param {Function} callback Function to call when the request is complete.
*/
GDrive.uploadFile = function(pParams, pCallBack) {
var lContent = pParams.data,
lName = pParams.name,
boundary = '-------314159265358979323846',
delimiter = "\r\n--" + boundary + "\r\n",
close_delim = "\r\n--" + boundary + "--",
contentType = pParams.type || 'application/octet-stream',
metadata = {
'title' : lName,
'mimeType' : contentType
},
base64Data = btoa(lContent),
multipartRequestBody =
delimiter +
'Content-Type: application/json\r\n\r\n' +
JSON.stringify(metadata) +
delimiter +
'Content-Type: ' + contentType + '\r\n' +
'Content-Transfer-Encoding: base64\r\n' +
'\r\n' +
base64Data +
close_delim;
var request = gapi.client.request({
'path': '/upload/drive/v2/files',
'method': 'POST',
'params': {'uploadType': 'multipart'},
'headers': {
'Content-Type': 'multipart/mixed; boundary="' + boundary + '"'
},
'body': multipartRequestBody
});
if (!pCallBack)
pCallBack = function(file) {
Util.log(file);
};
request.execute(pCallBack);
};
GDrive.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
load
]);
};
CloudCmd.GDrive = GDrive;
})(CloudCommander, Util, DOM);

View file

@ -1,23 +1,16 @@
var CloudCommander, Util, DOM, $, Github, cb;
/* module for work with github */
(function(){
(function(CloudCmd, Util, DOM){
"use strict";
var cloudcmd = CloudCommander,
Cache = DOM.Cache,
APIURL = '/api/v1',
AuthURL = APIURL + '/auth',
GitHubIdURL = APIURL + '/github_key',
GitHub_ID,
var Cache = DOM.Cache,
GithubLocal,
User,
GithubStore = {};
GitHubStore = {};
/* temporary callback function for work with github */
cb = function (err, data){ console.log(err || data);};
cb = function (err, data){ Util.log(err || data);};
/* PRIVATE FUNCTIONS */
@ -37,78 +30,61 @@ var CloudCommander, Util, DOM, $, Github, cb;
DOM.anyLoadInParallel(lFiles, function(){
console.timeEnd('github load');
DOM.Images.hideLoad();
Util.exec(pCallBack);
});
}
function setConfig(pCallBack){
DOM.ajax({
url : GitHubIdURL,
success : function(pData){
GitHub_ID = pData;
Util.exec(pCallBack);
}
});
}
function init(pCallBack){
GitHubStore.autorize = function(pCallBack, pCode){
var lToken = Cache.get('token');
if(lToken){
GithubStore.Login(lToken);
GitHubStore.Login(lToken);
Util.exec(pCallBack);
}
else{
var lCode = window.location.search;
if ( Util.isContainStr(lCode, '?code=') ){
lCode = lCode.replace('?code=','');
DOM.ajax({
type : 'put',
url : AuthURL,
data: lCode,
success: function(pData){
if(pData && pData.token){
lToken = pData.token;
GithubStore.Login(lToken);
Cache.set('token', lToken);
Util.exec(pCallBack);
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
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
//window.open('welcome.html', 'welcome','width=300,height=200,menubar=yes,status=yes')">
window.location =
'https://github.com/login/oauth/authorize?client_id=' +
GitHub_ID + '&&scope=repo,user,gist';
}
}
function getUserData(pCallBack){
var lName,
lShowUserInfo = function(pError, pData){
if(!pError){
lName = pData.name;
console.log('Hello ' + lName + ' :)!');
}
else
DOM.Cache.remove('token');
};
User.show(null, lShowUserInfo);
DOM.Cache.remove('token');
});
Util.exec(pCallBack);
}
};
/* PUBLICK FUNCTIONS */
GithubStore.basicLogin = function(pUser, pPasswd){
/* PUBLIC FUNCTIONS */
GitHubStore.basicLogin = function(pUser, pPasswd){
GithubLocal = new Github({
username: pUser,
password: pPasswd,
@ -116,7 +92,7 @@ var CloudCommander, Util, DOM, $, Github, cb;
});
};
GithubStore.Login = function(pToken){
GitHubStore.Login = function(pToken){
Github = GithubLocal = new Github({
token : pToken,
auth : 'oauth'
@ -128,43 +104,56 @@ var CloudCommander, Util, DOM, $, Github, cb;
/**
* function creates gist
*/
cloudcmd.GitHub.createGist = function(pContent, pFileName){
if(pContent){
if(!pFileName)
pFileName = Util.getDate();
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,
description: 'Uplouded by Cloud Commander from ' + lHost,
public: true
};
lFiles[pFileName] ={
content: pContent
lFiles[lName] ={
content: lContent
};
lOptions.files = lFiles;
lGist.create(lOptions, cb);
lGist.create(lOptions, function(pError, pData){
DOM.Images.hideLoad();
Util.log(pError || pData);
Util.log(pData && pData.html_url);
Util.exec(pCallBack);
});
}
return pContent;
return lContent;
};
cloudcmd.GitHub.init = function(pCallBack){
GitHubStore.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
getUserData,
init,
setConfig,
GitHubStore.getUserData,
GitHubStore.autorize,
load
]);
cloudcmd.GitHub.init = null;
GitHubStore.callback = function(){
Util.loadOnLoad([
Util.retExec(pCallBack),
GitHubStore.getUserData,
]);
};
};
cloudcmd.Github = GithubStore;
})();
CloudCmd.GitHub = GitHubStore;
})(CloudCommander, Util, DOM);

97
lib/client/storage/_vk.js Normal file
View file

@ -0,0 +1,97 @@
var CloudCommander, Util, DOM, VK;
(function(CloudCmd, Util, DOM){
'use strict';
var VKStorage = {};
/* PRIVATE FUNCTIONS */
/**
* load google api library
*/
function load(pCallBack){
console.time('vk');
var lUrl = 'http://vkontakte.ru/js/api/openapi.js',
lLocal = CloudCmd.LIBDIRCLIENT + 'storage/vk/open.js',
lOnload = function(){
console.timeEnd('vk load');
DOM.Images.hideLoad();
Util.exec(pCallBack);
};
DOM.jsload(lUrl, {
onload : lOnload,
error : DOM.retJSLoad(lLocal, lOnload)
});
}
function auth(pCallBack){
CloudCmd.getConfig(function(pConfig){
var lDOCUMENTS_ACCESS = 131072;
VK.init({ apiId: pConfig.vk_id});
VK.Auth.login(function(){
var lNAME = 1281;
VK.Api.call('getVariable', {key: lNAME}, function(r) {
var lName = r.response;
if(lName)
Util.log ('Hello, ' + lName + ':)');
});
Util.exec(pCallBack);
}, lDOCUMENTS_ACCESS); /* Доступ к документам пользователя */
});
}
/**
* Insert new file.
*
* @param {File} fileData {name, data} File object to read data from.
* @param {Function} callback Function to call when the request is complete.
*/
VKStorage.uploadFile = function(pParams, pCallBack) {
/* http://vk.com/developers.php?oid=-1&p=docs.getUploadServer */
VK.Api.call('docs.getUploadServer', {}, function(pResult){
var lURL = pResult.response.upload_url,
lData = pParams.data,
lName = pParams.name;
DOM.ajax({
type : "POST",
url : lURL,
data : {
file: lData,
name: lName
},
dataType: 'application/x-www-form-urlencoded',
success : function(pData){
Util.log(pData);
VK.Api.call('docs.save', {}, Util.log);
},
error : Util.log
});
});
};
VKStorage.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
auth,
load
]);
};
CloudCmd.VK = VKStorage;
})(CloudCommander, Util, DOM);

37
lib/client/storage/dropbox/.gitignore vendored Normal file
View file

@ -0,0 +1,37 @@
# 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

@ -0,0 +1,45 @@
# 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

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

View file

@ -0,0 +1,215 @@
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

@ -0,0 +1,19 @@
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

@ -0,0 +1,73 @@
# 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

@ -0,0 +1,203 @@
# 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

@ -0,0 +1,84 @@
# 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

@ -0,0 +1,195 @@
# 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

@ -0,0 +1,280 @@
# 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

@ -0,0 +1,10 @@
# 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

@ -0,0 +1,48 @@
{
"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

@ -0,0 +1,67 @@
# 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

@ -0,0 +1,249 @@
# 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

@ -0,0 +1,298 @@
// 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.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

@ -0,0 +1,10 @@
<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>

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 326 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

View file

@ -0,0 +1,68 @@
<!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

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

View file

@ -0,0 +1,59 @@
# 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

@ -0,0 +1,72 @@
# 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

@ -0,0 +1,342 @@
# 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

@ -0,0 +1,202 @@
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

@ -0,0 +1,87 @@
# 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

@ -0,0 +1,59 @@
# 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

@ -0,0 +1,71 @@
# 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

@ -0,0 +1,180 @@
# 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

@ -0,0 +1,162 @@
# 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

@ -0,0 +1,36 @@
# 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

@ -0,0 +1,106 @@
# 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

@ -0,0 +1,119 @@
# 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

@ -0,0 +1,135 @@
# 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

@ -0,0 +1,59 @@
# 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

@ -0,0 +1,88 @@
# 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

@ -0,0 +1,505 @@
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

@ -0,0 +1,21 @@
# 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

@ -0,0 +1,33 @@
<?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>

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 428 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

View file

@ -0,0 +1,14 @@
<?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>

After

Width:  |  Height:  |  Size: 1.3 KiB

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