10
.gitignore
vendored
|
|
@ -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
|
|
@ -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
|
|
@ -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.
|
||||
51
README.md
|
|
@ -1,14 +1,14 @@
|
|||
Cloud Commander [](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).
|
||||
|
||||

|
||||

|
||||
|
||||
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
|
||||
---------------
|
||||
|
|
|
|||
463
cloudcmd.js
|
|
@ -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);
|
||||
}
|
||||
})();
|
||||
|
|
@ -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
|
||||
========================================================================== */
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
/* если правая панель не помещаеться - прячем её */
|
||||
|
|
|
|||
BIN
font/fontello.eot
Normal file
40
font/fontello.svg
Normal 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="❗" 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="📁" 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="📄" 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
BIN
font/fontello.woff
Normal file
BIN
font/general_foundicons.woff
Normal file
18
html/auth.html
Normal 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
|
|
@ -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
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
1542
lib/client.js
2592
lib/client/dom.js
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
828
lib/client/editor/codemirror/mode/javascript.js
vendored
|
|
@ -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 });
|
||||
640
lib/client/editor/codemirror/mode/xml.js
vendored
|
|
@ -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});
|
||||
|
|
@ -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"}]
|
||||
}
|
||||
42
lib/client/editor/codemirror/theme/night.css
vendored
|
|
@ -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; }
|
||||
|
|
@ -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);
|
||||
494
lib/client/ie.js
|
|
@ -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
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
@ -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 <input> 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 <menu> 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 <menu> 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 <command> types
|
||||
* Bug where `className` wasn't properly set on <menu>
|
||||
|
||||
### 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 <menuitem> 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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "jQuery contextMenu",
|
||||
"version": "1.5.22",
|
||||
"version": "git-master",
|
||||
"title": "jQuery.contextMenu()",
|
||||
"author": {
|
||||
"name": "Rodney Rehm",
|
||||
|
|
|
|||
|
|
@ -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 ) );
|
||||
|
|
@ -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);
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
55
lib/client/storage/_dropbox_chooser.js
Normal 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;
|
||||
})();
|
||||
109
lib/client/storage/_gdrive.js
Normal 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);
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
||||
45
lib/client/storage/dropbox/.npmignore
Normal 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
|
||||
|
||||
6
lib/client/storage/dropbox/.vimrc
Normal 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
|
||||
|
||||
215
lib/client/storage/dropbox/Cakefile
Normal 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
|
||||
19
lib/client/storage/dropbox/LICENSE.txt
Normal 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.
|
||||
73
lib/client/storage/dropbox/README.md
Normal 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.
|
||||
203
lib/client/storage/dropbox/doc/auth_drivers.md
Normal 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.
|
||||
|
||||
84
lib/client/storage/dropbox/doc/coffee_faq.md
Normal 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.
|
||||
195
lib/client/storage/dropbox/doc/development.md
Normal 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).
|
||||
280
lib/client/storage/dropbox/doc/getting_started.md
Normal 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.
|
||||
|
||||
10
lib/client/storage/dropbox/lib/README.md
Normal 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.
|
||||
2925
lib/client/storage/dropbox/lib/dropbox.js
Normal file
2
lib/client/storage/dropbox/lib/dropbox.min.js
vendored
Normal file
48
lib/client/storage/dropbox/package.json
Normal 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"
|
||||
}
|
||||
}
|
||||
67
lib/client/storage/dropbox/samples/checkbox.js/README.md
Normal 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 task’s 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.
|
||||
|
|
@ -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'
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
After Width: | Height: | Size: 1 KiB |
|
After Width: | Height: | Size: 1.3 KiB |
|
|
@ -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 |
|
After Width: | Height: | Size: 597 B |
|
After Width: | Height: | Size: 124 B |
|
After Width: | Height: | Size: 326 B |
|
After Width: | Height: | Size: 1.6 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
|
@ -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"> </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>
|
||||
2
lib/client/storage/dropbox/samples/checkbox.js/public/lib/jquery.js
vendored
Normal file
6
lib/client/storage/dropbox/src/000-dropbox.coffee
Normal 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.
|
||||
59
lib/client/storage/dropbox/src/api_error.coffee
Normal 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()
|
||||
72
lib/client/storage/dropbox/src/base64.coffee
Normal 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'
|
||||
1270
lib/client/storage/dropbox/src/client.coffee
Normal file
342
lib/client/storage/dropbox/src/drivers-browser.coffee
Normal 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()
|
||||
202
lib/client/storage/dropbox/src/drivers-chrome.coffee
Normal 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
|
||||
87
lib/client/storage/dropbox/src/drivers-node.coffee
Normal 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
|
||||
59
lib/client/storage/dropbox/src/drivers.coffee
Normal 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 = {}
|
||||
71
lib/client/storage/dropbox/src/event_source.coffee
Normal 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
|
||||
180
lib/client/storage/dropbox/src/hmac.coffee
Normal 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
|
||||
|
||||
162
lib/client/storage/dropbox/src/oauth.coffee
Normal 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()
|
||||
36
lib/client/storage/dropbox/src/prod.coffee
Normal 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 '|'
|
||||
|
||||
106
lib/client/storage/dropbox/src/pulled_changes.coffee
Normal 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
|
||||
119
lib/client/storage/dropbox/src/references.coffee
Normal 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
|
||||
135
lib/client/storage/dropbox/src/stat.coffee
Normal 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'
|
||||
59
lib/client/storage/dropbox/src/upload_cursor.coffee
Normal 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
|
||||
@
|
||||
88
lib/client/storage/dropbox/src/user_info.coffee
Normal 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
|
||||
|
||||
505
lib/client/storage/dropbox/src/xhr.coffee
Normal 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
|
||||
21
lib/client/storage/dropbox/src/zzz-export.coffee
Normal 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
|
||||
|
||||
33
lib/client/storage/dropbox/test/app_icon/hazard.svg
Normal 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 |
BIN
lib/client/storage/dropbox/test/app_icon/hazard128.png
Normal file
|
After Width: | Height: | Size: 4.3 KiB |
BIN
lib/client/storage/dropbox/test/app_icon/hazard16.png
Normal file
|
After Width: | Height: | Size: 428 B |
BIN
lib/client/storage/dropbox/test/app_icon/hazard64.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
14
lib/client/storage/dropbox/test/app_icon/radiation.svg
Normal 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 |