Merge pull request #10 from coderaiser/dev

Cloud Commander v0.8.0
This commit is contained in:
coderaiser 2014-02-13 14:38:19 +02:00
commit b01de63b23
101 changed files with 41928 additions and 3919 deletions

191
ChangeLog
View file

@ -1,3 +1,194 @@
2013.02.13, v0.8.0
fix:
- (test) template
- (cloudfunc) "draggable" -> "draggable "
- (cloudfunc) buildFromJSON: lHeader -> header
- (test) add ""
- (test) add draggable
- (test) li -> div
- (files) p.error -> error
- (test) add "
- (path) rm " "
- (test) JSON_Files -> JSON_FILES
- (polyfill) args not defined
- (server) lHost -> host
- (test) change template
- (util) retExec: add unshift
- (rest) onPut: lFiles.password -> lFiles && lFiles.password
- (console) access to socket before it loads
- (cloudcmd) pParams -> params
- (util) Util.exec -> Util.retExec.apply
- (main) getQuery
- (css) error position: add left
- (listeners) changeLinks: pPanelID -> panelId
- (menu) if menu could not load keys do not working
- (terminal) resizing
- (terminal) CHANNEL_RESIZE -> CHANNEL_TERMINAL_RESIZE
- (terminal) new line
- (terminal) maxSize
- (terminal) when back to term, first simbol wasn't wrote
- (rest) onFS: Util.log -> Util.log.bind(Util)
- (terminal) onConnection bind: add context
- (terminal) add rmKeys
- (polyfill) bind
- (console) chat do not broadcast
- (dom) getNotCurrentDirPath: add this
- (console) io - > exec
- (users) File -> FILE
- (config) init: change order
- (filepicker) change to proto
- (menu) set key bind when close
- (github) api_url -> apiURL
- (loader) anyLoadOnLoad: lParam -> param, callback -> func
- (console) connNum -> conNum
- (console) onMessageFunc -> Clients
- (socket) ret -> io
- (socket) change onDisconect
- (socket) lRet -> ret
- (socket) add removeListeners
- (cloudfunc) RIGH_TPANE -> RIGHT_PANEL
- (menu) if ESC current file is one mouse pointer on it
- (dom) scrollIntoViewIfNeeded: add false
- (dom) getPanel isLeft check: class -> id
- (view) ',' -> ';'
- (client) getJSONfromFileTable: owner -> uid
- (client) getJSONfromFileTable: i: 2 -> 0
- (client) baseInit: add 'js-'
- (client) createFileTable: found -> !found
- (view) add margin only when view text documents
- (dom) selectByPattern: show message if not esc
- (dom) selectByPattern: "." -> "\."
- (dom) selectByPattern: DOM.alert -> Dialog.alert
- chore(cloudcmd) fix lint warnings
- (socket) prevent memory leak
- (socket) getSpawn: add tryCachLog
- (edit) add emmet
- (cloudcmd) minify -> Minify
- (loader) add Images
- (client) baseInit: mv setCurrentFile to top
- (dom) add max length for storage file
- (express) if username or password is change, auth do not work
- (listeners) changeLinks: get back current
- (dom) getById: rm pElement
- (dom) udpateCurrentInfo: files -> files.children
- (css) .keyspanel: rm height
- (css) .delete-button: change icon
- (key) insert
- (dom) first 2 files not select
- (dom) CurrentInfo: prev, next not updates
- (config) if view is load config do not show
- (key) ";" -> ","
- (config) init
- (dom) getCurrentData: this -> Cmd
- (edit) DOM.getData -> Info.getData
- (dom) getFiles: add panel
- (client) baseInit pCallBack: mv to end
feature:
- (util) spead improvement; exec: rm condition
- (cloudfunc) buildFromJSON: rm draggable from header
- (package) update minify to v0.2.5
- (util) rm setTimeout
- (util) rm setValue
- (util) rm call
- (main) rm gzipData
- (rest) create recursive dir
- (console) update to v2.10.0
- (rest) add recursiv copy dir (rimraf -> fs-extra)
- (main) add getPathName
- (express) add join as express middleware
- (dom) Images: improved access speed
- (util) copyObj: from, to -> to, from
- (listeners) onConextMenu: rm showLoad
- (style) user-select : rm path
- (rest) add ability to remove directory with files
- (util) rm bind
- (terminal) add info if pty not installed
- (console) (dis)connected: add "console"
- (terminal) add connect/disconnect events
- (terminal) add Size
- (terminal) dir : home -> DIR
- (terminal) add update
- (terminal) add cursor style
- (main) mrequire: tryCatchLog - > tryCatch
- (main) mrequire: tryCatch -> tryCatchLog
- (terminal) add
- (view) rm afterShow, mv css to style
- (util) rm retFunc
- (console) update to v2.8.0
- (console) rm unused vars
- (commander) rm file.sort: no use
- (css) add media print
- (style) .files: rm cursor
- (util) add slice
- (util) loadOnLoad: rm data
- (key) <shift> + <f7> - add new file
- (loader) pop -> shift
- (util) loadOnLoad: rm data
- (util) loadOnLoad: pop -> shift - change params order
- (socket) getSpawn: add error sending
- (socket) addListener: first parameter could be object
- (console) addEvenets: add connect, disconect
- (panel) add
- (cloudfunc) rm Path
- (key) add Ctrl + \ -> go to root
- (key) go to parent dir on backspace
- (style) add .reduce-text
- (style) prevent text from wrapping in .size and .owner
- (style) if size to long it reduce
- (style) if owner name to long - it's reduce
- (users) add
- (key) add help call on "?"
- (commander) uid -> owner
- (dom) scrollIntViewIfNeeded: add second param
- (dom) CurrentInfo: add filesPassive
- (dom) getByClass -> getByClassAll
- (util) rm bind
- (files) readPipe: pop -> shift
- (dom) add getCurrentOwner
- (style) rm #path
- (view) add margin
- (dom) add extended selection (*, +, -)
- (package) http-auth: 2.1.x
- (package) minify: v0.2.4
- (edit) add autocomletion
- (edit) emmet: add css suport
- (edit) add emmet
- (notify) add
- (loader) add
- (path) add title with full path
- (css) style: new line when path to long
- (listeners) open file on new tab on <ctrl> + click
- (style) max-width 600px: hide .size, .owner, .mode
- (style) .cmd-button color : rgb(49,123,249) -> #222
- (listeners) initKeysPanel: rm i === 12
- (menu) From Filepicker -> From Cloud
- (css) icons: add menu icons
- (menu) getItem: add icon processing
- (css) mv icons to icons.css
- (css) add .icon-file
- (menu) add icons
- (util) add getStrSmallfirst
- (menu) getConfig: rm callback
- (css) .cmd-button width: 9% -> 5%
- (html) buttons: mv name to title
- (css) keyspanel: add rename, copy and move icons
- (css) keyspanel: add icons for: help, view, edit, delete, menu, console and contact
- (style) cmd-button: add directory icon
- (font) add config icon
- (css) style height: .fm 85%, .keyspanel 15%
- (css) .cmd-button hover: rm border color
- (config) auth: false
- (cloudcmd) add http authorization
- (listeners) ListenersProto: add Info
- (dom) add CurrentInfo
- (key) rm lCurrent
- (config) add Loading
- (style) .cmd-button focus: add outline
- (contact) add
- (cloudfunc) getJoinURL: rm first "/"
2013.12.09, v0.7.0
fix:

119
HELP.md
View file

@ -1,11 +1,11 @@
Cloud Commander v0.7.0 [![NPM version][NPMIMGURL]][NPMURL] [![Dependency Status][DependencyStatusIMGURL]][DependencyStatusURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL]
Cloud Commander v0.8.0 [![NPM version][NPMIMGURL]][NPMURL] [![Dependency Status][DependencyStatusIMGURL]][DependencyStatusURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL]
===============
###[Main][MainURL] [Blog][BlogURL] Live(![IO][IO_LIVE_IMG] [IO][IOURL], ![JitSu][JitSu_LIVE_IMG] [JitSu][JitSuURL], ![Heroku][Heroku_LIVE_IMG] [Heroku][HerokuURL] ![RunKite][RunKite_LIVE_IMG] [RunKite][RunKiteURL])
###[Main][MainURL] [Blog][BlogURL] Live(![IO][IO_LIVE_IMG] [IO][IOURL], ![JitSu][JitSu_LIVE_IMG] [JitSu][JitSuURL], ![Heroku][Heroku_LIVE_IMG] [Heroku][HerokuURL])
[NPMIMGURL]: https://badge.fury.io/js/cloudcmd.png
[BuildStatusIMGURL]: https://secure.travis-ci.org/coderaiser/cloudcmd.png?branch=master
[DependencyStatusIMGURL]: https://gemnasium.com/coderaiser/cloudcmd.png
[FlattrIMGURL]: https://api.flattr.com/button/flattr-badge-large.png
[NPM_INFO_IMG]: https://nodei.co/npm/cloudcmd.png?downloads=true&&stars
[NPM_INFO_IMG]: https://nodei.co/npm/cloudcmd.png
[NPMURL]: https://npmjs.org/package/cloudcmd "npm"
[BuildStatusURL]: http://travis-ci.org/coderaiser/cloudcmd "Build Status"
[DependencyStatusURL]: https://gemnasium.com/coderaiser/cloudcmd "Dependency Status"
@ -15,11 +15,9 @@ Cloud Commander v0.7.0 [![NPM version][NPMIMGURL]][NPMURL] [![Dependency Status]
[IOURL]: http://io.cloudcmd.io "IO"
[JitSuURL]: http://cloudcmd.jit.su "JitSu"
[HerokuURL]: http://cloudcmd.herokuapp.com/ "Heroku"
[RunKiteURL]: http://cloudcmd.apps.runkite.com/ "RunKite"
[IO_LIVE_IMG]: https://status-ok.cloudcmd.io/host/io.cloudcmd.io/fs?json "IO"
[JitSu_LIVE_IMG]: https://status-ok.cloudcmd.io/host/cloudcmd.jit.su/fs?json "JitSu"
[HEROKU_LIVE_IMG]: https://status-ok.cloudcmd.io/host/cloudcmd.herokuapp.com/fs?json "Heroku"
[RunKite_LIVE_IMG]: https://status-ok.cloudcmd.io/host/cloudcmd.apps.runkite.com/fs?json "RunKite"
[IO_LIVE_IMG]: http://status-ok.cloudcmd.io/host/io.cloudcmd.io/fs?json "IO"
[JitSu_LIVE_IMG]: http://status-ok.cloudcmd.io/host/cloudcmd.jit.su/fs?json "JitSu"
[HEROKU_LIVE_IMG]: http://status-ok.cloudcmd.io/host/cloudcmd.herokuapp.com/fs?json "Heroku"
**Cloud Commander** - cloud file manager with console and editor.
@ -49,28 +47,25 @@ All you need is
and unpack or just clone repository from github:
```
git clone git://github.com/coderaiser/cloudcmd.git
cd cloudcmd
node cloudcmd
git clone git://github.com/coderaiser/cloudcmd.git
cd cloudcmd
npm install
node cloudcmd
```
or install in npm:
```
npm i cloudcmd -g
cloudcmd
npm install cloudcmd -g
cloudcmd
```
Additional modules
---------------
**Cloud Commander's Server Side** not using additional modules for main functionality.
But for console and minification and optimization tricks optional can be
assingned (and installed) modules: [Minify] (https://github.com/coderaiser/minify "Minify")
and [socket.io] (https://github.com/LearnBoost/socket.io "Socket.IO").
Install addtitional modules (type in **Cloud Commander** directory):
npm i
**Cloud Commander** could work without any modules installed.
But for console, minification, file system operations etc, recommended
install additional modules with next commnad (type in **Cloud Commander**'s directory):
npm install
Hot keys
---------------
@ -82,15 +77,20 @@ Hot keys
- **F5** - copy
- **F6** - rename/move
- **F7** - new dir
- **F7** + **shift** = new file
- **F8, Delete** - remove current file
- **F9** - menu
- **F10** - config
- **(*)** - select/unselect all
- **(+)** - expand selection
- **(-)** - shrink selection
- **Ctrl + r** - reload dir content
- **Ctrl + d** - clear local cache (wich contains dir contents)
- **Alt + q** - disable key bindings
- **Alt + s** - get all key bindings back
- **Ctrl + a** - select all files in a panel
- **up, down, enter** - filesystem navigation
- **Ctrl + \** - go to the root directory
- **Tab** - move via panels
- **Page Up** - up on one page
- **Page Down** - down on one page
@ -100,6 +100,7 @@ Hot keys
- **Insert** - select current file
- **Shift + F10** - context menu
- **~** - console
- **Ctrl + Click** - open file on new tab
Edit
---------------
@ -160,6 +161,9 @@ All main configuration could be done via [config.json](json/config.json "Config"
```js
{
"auth" : false, /* enable http authentication */
"username" : "root", /* username for authentication */
"password" : "toor", /* password hash in sha-1 for authentication*/
"apiURL" :"/api/v1",
"appCache" : false, /* cache files for offline use */
"analytics" : true, /* google analytics suport */
@ -241,7 +245,7 @@ server {
server_name io.cloudcmd.io;
access_log /var/log/nginx/io.cloudcmd.io.access.log;
location / {
proxy_pass http://127.0.0.1:8000/;
proxy_pass http://127.0.0.1:8000/;
}
}
```
@ -263,6 +267,19 @@ server {
}
```
For websocket suport (nginx >= v1.3.13) modify server block:
```sh
location / {
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_pass http://127.0.0.1:8000/;
}
```
If you need redirection from **http** to **https**, it's simple:
```sh
@ -286,15 +303,6 @@ do something like this:
nohup node cloudcmd
Authorization
---------------
Cloud Commander could authorize clients on GitHub via openID.
All things that should be done is must be added **id** and **secret** of application
from github settings page and added to [modules.json](json/modules.json) (id just) and env variable (secret)
with names: *github_id*, *github_secret*, *dropbox_key*, *dropbox_secret* etc in
[secret.bat](shell/secret.bat) *(on win32)* or [secret.sh](shell/secret.sh) *(on nix)*.
Start
---------------
To start **Cloud Commander** only one command needed:
@ -327,10 +335,9 @@ and then, if there is new version
npm r cloudcmd
npm i cloudcmd
Extensions
Additional modules list
---------------
**Cloud Commander** desinged to easily porting extensions.
For extend main functionality Cloud Commander use next modules:
To extend capabilities of file manager next modules used:
- [Ace] [AceURL]
- [FancyBox] [FancyBoxURL]
@ -339,14 +346,20 @@ For extend main functionality Cloud Commander use next modules:
- [github] [githubURL]
- [dropbox-js] [dropbox-jsURL]
- [jquery] [jqueryURL]
- [socket.io] [socketIOURL]
- [http-auth] [httpAuthURL]
- [fs-extra] [fs-extraURL]
[AceURL]: //ace.ajax.org/ "Ace"
[AceURL]: http://ace.ajax.org/ "Ace"
[FancyBoxURL]: //github.com/fancyapps/fancyBox "FancyBox"
[jQuery-contextMenuURL]: //github.com/medialize/jQuery-contextMenu "jQuery-contextMenu"
[jq-consoleURL]: //github.com/replit/jq-console "jq-console"
[jq-consoleURL]: //github.com/replit/jq-console "jq-console"
[githubURL]: //github.com/michael/github "github"
[dropbox-jsURL]: //github.com/dropbox/dropbox-js "dropbox-js"
[jqueryURL]: //jquery.com
[socketIOURL]: http://socket.io
[httpAuthURL]: //github.com/gevorg/http-auth
[fs-extraURL]: //github.com/jprichardson/node-fs-extra "fs-extra"
Contributing
---------------
@ -366,23 +379,23 @@ so to get it you should type a couple more commands:
Version history
---------------
- *2013.12.09*, **[v0.7.0](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.7.0.zip)**
- *2013.11.08*, **[v0.6.0](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.6.0.zip)**
- *2013.10.17*, **[v0.5.0](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.5.0.zip)**
- *2013.09.27*, **[v0.4.0](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.4.0.zip)**
- *2013.08.01*, **[v0.3.0](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.3.0.zip)**
- *2013.04.22*, **[v0.2.0](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.2.0.zip)**
- *2013.03.01*, **[v0.1.9](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.9.zip)**
- *2012.12.12*, **[v0.1.8](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.8.zip)**
- *2012.10.01*, **[v0.1.7](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.7.zip)**
- *2012.08.24*, **[v0.1.6](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.6.zip)**
- *2012.08.06*, **[v0.1.5](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.5.zip)**
- *2012.07.27*, **[v0.1.4](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.4.zip)**
- *2012.07.19*, **[v0.1.3](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.3.zip)**
- *2012.07.14*, **[v0.1.2](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.2.zip)**
- *2012.07.11*, **[v0.1.1](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.1.zip)**
- *2012.07.09*, **[v0.1.0](//github.com/coderaiser/cloudcmd-archive/raw/master/cloudcmd-v0.1.0.zip)**
- *2014.02.13*, **[v0.8.0](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.8.0.zip)**
- *2013.12.09*, **[v0.7.0](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.7.0.zip)**
- *2013.11.08*, **[v0.6.0](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.6.0.zip)**
- *2013.10.17*, **[v0.5.0](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.5.0.zip)**
- *2013.09.27*, **[v0.4.0](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.4.0.zip)**
- *2013.08.01*, **[v0.3.0](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.3.0.zip)**
- *2013.04.22*, **[v0.2.0](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.2.0.zip)**
- *2013.03.01*, **[v0.1.9](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.1.9.zip)**
- *2012.12.12*, **[v0.1.8](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.1.8.zip)**
- *2012.10.01*, **[v0.1.7](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.1.7.zip)**
- *2012.08.24*, **[v0.1.6](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.1.6.zip)**
- *2012.08.06*, **[v0.1.5](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.1.5.zip)**
- *2012.07.27*, **[v0.1.4](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.1.4.zip)**
- *2012.07.19*, **[v0.1.3](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.1.3.zip)**
- *2012.07.14*, **[v0.1.2](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.1.2.zip)**
- *2012.07.11*, **[v0.1.1](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.1.1.zip)**
- *2012.07.09*, **[v0.1.0](//github.com/cloudcmd/archive/raw/master/cloudcmd-v0.1.0.zip)**
License
---------------

View file

@ -1,6 +1,6 @@
Cloud Commander v0.7.0 [![NPM version][NPMIMGURL]][NPMURL] [![Dependency Status][DependencyStatusIMGURL]][DependencyStatusURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL]
Cloud Commander v0.8.0 [![NPM version][NPMIMGURL]][NPMURL] [![Dependency Status][DependencyStatusIMGURL]][DependencyStatusURL] [![Build Status][BuildStatusIMGURL]][BuildStatusURL]
===============
###[Main][MainURL] [Blog][BlogURL] Live(![IO][IO_LIVE_IMG] [IO][IOURL], ![JitSu][JitSu_LIVE_IMG] [JitSu][JitSuURL], ![Heroku][Heroku_LIVE_IMG] [Heroku][HerokuURL] ![RunKite][RunKite_LIVE_IMG] [RunKite][RunKiteURL])
###[Main][MainURL] [Blog][BlogURL] Live(![IO][IO_LIVE_IMG] [IO][IOURL], ![JitSu][JitSu_LIVE_IMG] [JitSu][JitSuURL], ![Heroku][Heroku_LIVE_IMG] [Heroku][HerokuURL])
[NPMIMGURL]: https://badge.fury.io/js/cloudcmd.png
[BuildStatusIMGURL]: https://secure.travis-ci.org/coderaiser/cloudcmd.png?branch=master
[DependencyStatusIMGURL]: https://gemnasium.com/coderaiser/cloudcmd.png
@ -15,14 +15,12 @@ Cloud Commander v0.7.0 [![NPM version][NPMIMGURL]][NPMURL] [![Dependency Status]
[IOURL]: http://io.cloudcmd.io "IO"
[JitSuURL]: http://cloudcmd.jit.su "JitSu"
[HerokuURL]: http://cloudcmd.herokuapp.com/ "Heroku"
[RunKiteURL]: http://cloudcmd.apps.runkite.com/ "RunKite"
[IO_LIVE_IMG]: https://status-ok.cloudcmd.io/host/io.cloudcmd.io/fs?json "IO"
[JitSu_LIVE_IMG]: https://status-ok.cloudcmd.io/host/cloudcmd.jit.su/fs?json "JitSu"
[HEROKU_LIVE_IMG]: https://status-ok.cloudcmd.io/host/cloudcmd.herokuapp.com/fs?json "Heroku"
[RunKite_LIVE_IMG]: https://status-ok.cloudcmd.io/host/cloudcmd.apps.runkite.com/fs?json "RunKite"
[IO_LIVE_IMG]: http://status-ok.cloudcmd.io/host/io.cloudcmd.io/fs?json "IO"
[JitSu_LIVE_IMG]: http://status-ok.cloudcmd.io/host/cloudcmd.jit.su/fs?json "JitSu"
[HEROKU_LIVE_IMG]: http://status-ok.cloudcmd.io/host/cloudcmd.herokuapp.com/fs?json "Heroku"
**Cloud Commander** - cloud file manager with console and editor.
![Cloud Commander](/img/logo/cloudcmd.png "Cloud Commander")
[![Flattr][FlattrIMGURL]][FlattrURL]
[![Flattr][FlattrIMGURL]][FlattrURL]

View file

@ -29,10 +29,11 @@
CERT = DIR + 'ssl/ssl.crt',
FILE_TMPL = HTMLDIR + 'file.html',
PANEL_TMPL = HTMLDIR + 'panel.html',
PATH_TMPL = HTMLDIR + 'path.html',
LINK_TMPL = HTMLDIR + 'link.html',
FileTemplate, PathTemplate, LinkTemplate,
FileTemplate, PanelTemplate, PathTemplate, LinkTemplate,
FS = CloudFunc.FS;
/* reinit main dir os if we on Win32 should be backslashes */
@ -44,13 +45,15 @@
/**
* additional processing of index file
*/
function indexProcessing(pData) {
var lPath, lReplace, lKeysPanel,
lData = pData.data,
lAdditional = pData.additional;
function indexProcessing(options) {
var keysPanel, left, right,
LEFT = CloudFunc.PANEL_LEFT,
RIGHT = CloudFunc.PANEL_RIGHT,
data = options.data,
panel = options.panel;
if (!Config.appCache)
lData = Util.removeStr(lData, [
data = Util.removeStr(data, [
/* min */
' manifest=/cloudcmd.appcache',
/* normal */
@ -59,15 +62,27 @@
if (!Config.showKeysPanel) {
lKeysPanel = '<div class="keyspanel';
lData = lData.replace(lKeysPanel + '"', lKeysPanel +' hidden"');
data = data.replace(keysPanel + '"', keysPanel +' hidden"');
}
lData = Util.render(lData, {
title : CloudFunc.getTitle(),
fm : lAdditional
left = Util.render(PanelTemplate, {
id : LEFT,
side : 'left',
content : panel
});
return lData;
right = Util.render(PanelTemplate, {
id : RIGHT,
side : 'right',
content : panel
});
data = Util.render(data, {
title : CloudFunc.getTitle(),
fm : left + right
});
return data;
}
@ -88,13 +103,6 @@
AppCache.createManifest();
}
/**
* rest interface
* @pParams pConnectionData {request, responce}
*/
function rest(pConnectionData) {
return Util.exec(main.rest, pConnectionData);
}
function init() {
var lServerDir, lArg, lParams, lFiles;
@ -136,11 +144,11 @@
lParams = {
appcache : appCacheProcessing,
rest : rest,
rest : main.rest,
route : route
},
lFiles = [FILE_TMPL, PATH_TMPL, LINK_TMPL];
lFiles = [FILE_TMPL, PANEL_TMPL, PATH_TMPL, LINK_TMPL];
if (Config.ssl)
lFiles.push(KEY, CERT);
@ -150,6 +158,7 @@
Util.log(pErrors);
else {
FileTemplate = pFiles[FILE_TMPL];
PanelTemplate = pFiles[PANEL_TMPL];
PathTemplate = pFiles[PATH_TMPL];
LinkTemplate = pFiles[LINK_TMPL];
@ -164,16 +173,15 @@
});
}
function readConfig(pCallBack) {
fs.readFile(CONFIG_PATH, function(pError, pData) {
var msg, status, str, readed;
function readConfig(callback) {
fs.readFile(CONFIG_PATH, 'utf8', function(error, data) {
var msg, status, readed;
if (pError)
if (error)
status = 'error';
else {
status = 'ok';
str = pData.toString(),
readed = Util.parseJSON(str);
readed = Util.parseJSON(data);
main.config = Config = readed;
}
@ -181,34 +189,42 @@
msg = CloudFunc.formatMsg('read', 'config', status);
Util.log(msg);
Util.exec(pCallBack);
Util.exec(callback);
});
}
/**
* routing of server queries
*/
function route(pParams) {
var lRet = main.checkParams(pParams);
function route(request, response, callback) {
var ret, name, params, isAuth, isFS;
if (lRet) {
var p = pParams;
if (request && response) {
ret = true;
name = main.getPathName(request);
isAuth = Util.strCmp(name, ['/auth', '/auth/github']);
isFS = Util.strCmp(name, '/') || Util.isContainStrAtBegin(name, FS);
if ( Util.strCmp(p.name, ['/auth', '/auth/github']) ) {
Util.log('* Routing' +
'-> ' + p.name);
params = {
request : request,
response : response,
name : name
};
if (isAuth) {
Util.log('* Routing' + '-> ' + name);
pParams.name = main.HTMLDIR + p.name + '.html';
lRet = main.sendFile( pParams );
params.name = main.HTMLDIR + name + '.html';
main.sendFile(params);
} else if (isFS)
sendContent(params);
else {
ret = false;
Util.exec(callback);
}
else if ( Util.isContainStrAtBegin(p.name, FS) || Util.strCmp( p.name, '/') )
lRet = sendContent( pParams );
else
lRet = false;
}
return lRet;
return ret;
}
function sendContent(pParams) {
@ -218,56 +234,53 @@
p = pParams;
p.name = Util.removeStrOneTime(p.name, CloudFunc.FS) || main.SLASH;
fs.stat(p.name, function(pError, pStat) {
if (!pError)
if ( pStat.isDirectory() )
fs.stat(p.name, function(error, stat) {
if (error)
main.sendError(pParams, error);
else
if (stat.isDirectory())
processContent(pParams);
else
main.sendFile( pParams );
else
main.sendError(pParams, pError);
main.sendFile(pParams);
});
}
return lRet;
}
function processContent(pParams) {
var p,
lRet = main.checkParams(pParams);
function processContent(params) {
var p = params,
ret = main.checkParams(params);
if (lRet) {
p = pParams;
main.commander.getDirContent(p.name, function(pError, pJSON) {
var lQuery, isJSON;
if (ret)
main.commander.getDirContent(p.name, function(error, json) {
var query, isJSON;
if (pError)
main.sendError(pParams, pError);
if (error)
main.sendError(params, error);
else {
lQuery = main.getQuery(p.request);
isJSON = Util.isContainStr(lQuery, 'json');
query = main.getQuery(p.request);
isJSON = Util.isContainStr(query, 'json');
if (!isJSON)
readIndex(pJSON, pParams);
readIndex(json, params);
else {
p.data = Util.stringifyJSON(pJSON);
p.data = Util.stringifyJSON(json);
p.name +='.json';
main.sendResponse(p, null, true);
main.sendResponse(params, null, true);
}
}
});
}
}
function readIndex(pJSON, params) {
var p = params;
Util.ifExec(!minify, function(params) {
Util.ifExec(!Minify, function(params) {
var name = params && params.name;
fs.readFile(name || INDEX_PATH, 'utf8', function(error, template) {
var lPanel, lList,
var panel,
config = main.config,
minify = config.minify;
@ -275,13 +288,11 @@
main.sendError(p, error);
else {
p.name = INDEX_PATH,
lPanel = CloudFunc.buildFromJSON(pJSON, FileTemplate, PathTemplate, LinkTemplate),
lList = '<div id=left class=panel>' + lPanel + '</div>' +
'<div id=right class=panel>' + lPanel + '</div>';
panel = CloudFunc.buildFromJSON(pJSON, FileTemplate, PathTemplate, LinkTemplate),
main.sendResponse(p, indexProcessing({
additional : lList,
data : template,
panel : panel,
data : template,
}), true);
}
});

View file

@ -2,13 +2,13 @@
white-space: normal;
}
.list li{
.list li {
list-style-type: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
list-style-type: none;
}
.config .form-control{

94
css/icons.css Normal file
View file

@ -0,0 +1,94 @@
.icon-help:before {
font-family : 'Fontello';
content : '\e801 ';
}
.icon-rename:before {
font-family : 'Fontello';
content : '\e802 ';
}
.icon-view:before {
font-family : 'Fontello';
content : '\e803 ';
}
.icon-edit:before {
font-family : 'Fontello';
content : '\e804 ';
}
.icon-copy:before {
font-family : 'Fontello';
content : '\e805 ';
}
.icon-move:before {
font-family : 'Fontello';
content : '\e806 ';
}
.icon-directory:before {
font-family : 'Fontello';
content : '\e807 ';
}
.icon-delete:before {
font-family : 'Fontello';
content : '\e808 ';
}
.icon-menu:before {
font-family : 'Fontello';
content : '\e809 ';
}
.icon-config:before {
font-family : 'Fontello';
content : '\e80a ';
}
.icon-console:before {
font-family : 'Fontello';
content : '\e80b ';
}
.icon-contact:before {
font-family : 'Fontello';
content : '\e80c ';
}
.icon-file::before {
font-family : 'Fontello';
content : '\e80d ';
}
.icon-upload-to::before {
font-family : 'Fontello';
content : '\e80e ';
}
.icon-from-cloud::before {
font-family : 'Fontello';
content : '\e80f ';
}
.icon-download::before {
font-family : 'Fontello';
content : '\e810 ';
}
.icon-new::before {
font-family : 'Fontello';
content : '\e811 ';
}
.icon-unselect-all::before {
font-family : 'Fontello';
content : '\e812 ';
}
.icon-zip-file::before {
font-family : 'Fontello';
content : '\e813 ';
}

View file

@ -29,44 +29,7 @@ body {
Links
========================================================================== */
a{
color: #00e;
text-decoration:none;
}
a:visited { color: #551a8b; }
a:hover { color: #06e; }
a:focus { outline: thin dotted; }
/* Improve readability when focused and hovered in all browsers: h5bp.com/h */
a:hover, a:active { outline: 0; }
/*
* 1. Display hand cursor for clickable form elements
* 2. Allow styling of clickable form elements in iOS
* 3. Correct inner spacing displayed oddly in IE7 (doesn't effect IE6)
*/
/* ==== non-semantic helper classes ========================================
Please define your styles before this section.
========================================================================== */
/* Hide from both screenreaders and browsers: h5bp.com/u */
.hidden { display: none !important; visibility: hidden; }
/* preformatted text */
.pre {white-space:pre}
/* ==== print styles =======================================================
Print styles.
Inlined to avoid required HTTP connection: h5bp.com/r
========================================================================== */
@media print {
* { background: transparent !important;
color: black !important; box-shadow:none !important; text-shadow: none !important;
filter:none !important; -ms-filter: none !important; } /* Black prints faster: h5bp.com/s */
a, a:visited { text-decoration: underline; }
a[href]:after { content: " (" attr(href) ")"; }
.ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; } /* Don't show links for images, or javascript/internal links */
@page { margin: 0.5cm; }
}

View file

@ -21,13 +21,21 @@
src: local('Droid Sans Mono'), local('DroidSansMono'), url('//themes.googleusercontent.com/static/fonts/droidsansmono/v4/ns-m2xQYezAtqh7ai59hJUYuTAAIFFn5GTWtryCmBQ4.woff') format('woff');
}
html {
height: 100%;
}
body {
height: 95%;
font:16px "Droid Sans Mono";
background-color:white;
}
.fm, #left>li, #right>li, .path, .fm-header,
.mini-icon, .name, .size, .owner, .mode, .keyspanel, .cmd-button {
.hidden {
visibility: hidden;
}
.fm, .keyspanel {
-webkit-user-select : none;
-moz-user-select : none;
-ms-user-select : none;
@ -35,6 +43,14 @@ body {
user-select : none;
}
.links {
-webkit-user-select : initial;
-moz-user-select : initial;
-ms-user-select : initial;
-o-user-select : initial;
user-select : text;
}
.path-icon {
position : relative;
top : 3px;
@ -42,8 +58,6 @@ body {
display : inline-block;
width : 15px;
height : 15px;
color : #46A4C3;/*#55BF3F; green*/
text-shadow :black 0 2px 1px;
}
.path-icon:hover {
@ -62,11 +76,12 @@ body {
.error::before {
position : relative;
left : -15px;
font-family : 'Fontello';
font-size : 14px;
color : rgb(222, 41, 41);
cursor : default;
content : '\2757';
content : '\e800';
}
.loading {
@ -79,7 +94,7 @@ body {
}
.refresh-icon {
background:url(/img/panel_refresh.png) no-repeat;
background:url(/img/refresh.png) no-repeat;
}
.refresh-icon:active {
@ -87,12 +102,9 @@ body {
}
.cmd-button {
width: 9%;
width: 5%;
margin: 20px 2px 0 2px;
overflow: hidden;
color: rgb(49,123,249);
text-overflow: ellipsis;
white-space: nowrap;
color: #222;
background-color: white;
border: 1.5px solid;
border-color: rgb(49,123,249);
@ -101,7 +113,7 @@ body {
}
.cmd-button:hover {
border: 1.5px solid rgb(0,0,0);
border: 1.5px solid;
transition: ease 0.5s;
}
@ -111,16 +123,20 @@ body {
transition: ease 0.1s;
}
.cmd-button:focus {
outline: 0;
}
.clear-storage {
margin-right: 6px;
margin-left: 7px;
background:url(/img/console_clear.png) -4px -4px no-repeat;
background:url(/img/clear.png) -4px -4px no-repeat;
}
.clear-storage:active {
top:5px;
}
.links {
.links a {
color:red;
}
@ -147,16 +163,14 @@ body {
background-image:url('/img/txt.png');
}
.fm {
height: 90%;
height: 85%;
margin: 26px 26px 0 26px;
}
.fm-header {
font-weight: bold;
}
#path{
margin-left:1.5%;
}
.left, #left {
.panel-left {
float:left;
}
@ -169,11 +183,12 @@ body {
color: rgb(254,159,224);
}
.right, #right {
.panel-right {
float:right;
}
.panel {
width: 46%;
height: 90%;
padding: 20px;
margin: 0;
border: 1.5px solid;
@ -193,9 +208,10 @@ body {
.name {
float: left;
width: 35%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.name a:hover {
cursor: default;
}
.size {
@ -204,17 +220,35 @@ body {
margin-right: 27px;
text-align: right;
}
.owner {
display : inline-block;
width : 14%;
/* when inline-block
* vertical align should be
* set top to prevent additional
* spaces behind lines
*/
vertical-align : top;
}
.mode {
float: right;
width: 23%;
}
.reduce-text {
overflow : hidden;
text-overflow : ellipsis;
white-space : nowrap;
}
.files {
overflow-y: auto;
height: 95%;
padding: 0;
margin: 0;
overflow-y: auto;
list-style-type: none;
cursor: default;
}
a {
@ -222,7 +256,7 @@ a {
}
a:hover, a:active {
color : #06e;
cursor: pointer;
text-decoration:none;
}
/* Если размер окна очень маленький
@ -251,23 +285,32 @@ a:hover, a:active {
/* меняем иконки на шрифтовые*/
.mini-icon {
color: rgb(246, 224, 124);
color: rgba(246, 224, 124, 0.56);
font: 60px 'Fontello';
width: 40%;
height: 0;
margin-left: 0;
float: right;
position: relative;
top: 10px
color : rgb(246, 224, 124);
color : rgba(246, 224, 124, 0.56);
font : 16px 'Fontello';
width : 6%;
margin-left : 10px;
float : left;
background-image: none;
}
.size, .owner, .mode {
display: none;
}
.name {
float: none;
width: 100%;
font-size: 18px;
}
.directory::before {
content: '\1f4c1';
content: '\e807';
}
.text-file::before {
color: rgb(26, 224, 124);
color: rgba(26, 224, 124, 0.56);
content: '\1f4c4';
content: '\e80d';
}
.text-file {
background-image:none;
@ -278,43 +321,7 @@ a:hover, a:active {
display:none;
}
.mode,.size,.owner {
/* располагаем элементы
* один под другим
*/
display: table;
float: none;
width: 0;
text-align: left;
}
/* выводим заголовки рядом с полями */
.name::before {
content: 'name:';
font-weight: bold;
font-size: 13px;
}
.mode::before {
content: 'mode:';
font-weight: bold;
font-size: 13px;
}
.size::before {
content: 'size:';
font-weight: bold;
font-size: 13px;
}
.owner::before {
content: 'owner:';
font-weight: bold;
font-size: 13px;
}
.name {
float: none;
width:100%;
font-size: 18px;
}
.cmd-button {
width: 20%;
@ -337,7 +344,36 @@ a:hover, a:active {
width:94%;
}
/* если правая панель не помещаеться - прячем её */
#right, .cmd-button#f5, .cmd-button#f6 {
.panel-right, .cmd-button#f5, .cmd-button#f6 {
display: none;
}
}
@media print {
.panel {
width: 94%;
margin: 0;
padding: 0;
border: none;
}
.keyspanel, .panel-right {
display: none;
}
.files {
overflow-y: visible;
}
.current-file {
box-shadow: none;
}
.path-icon {
display: none;
}
.mini-icon {
display: none;
}
}

View file

View file

@ -1,201 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Binary file not shown.

186
font/fontello.json Normal file
View file

@ -0,0 +1,186 @@
{
"name": "",
"css_prefix_text": "icon-",
"css_use_suffix": false,
"hinting": true,
"units_per_em": 1000,
"ascent": 850,
"glyphs": [
{
"uid": "60617c8adc1e7eb3c444a5491dd13f57",
"css": "attention-circled-1",
"code": 59395,
"src": "custom_icons",
"selected": false,
"svg": {
"path": "M429 71Q545 71 644 129T800 285T857 500T800 715T644 871T429 929T213 871T58 715T0 500T58 285T213 129T429 71ZM500 767V661Q500 653 495 648T483 643H376Q368 643 363 648T357 661V767Q357 775 363 780T376 786H483Q490 786 495 780T500 767ZM499 575L509 229Q509 222 503 219Q498 214 490 214H367Q359 214 354 219Q348 222 348 229L358 575Q358 581 363 585T377 589H480Q488 589 493 585T499 575Z",
"width": 857.1
},
"search": [
"attention-circled"
]
},
{
"uid": "9347a40928618e76958fa67e5333f1c5",
"css": "doc-text-1",
"code": 59397,
"src": "custom_icons",
"selected": false,
"svg": {
"path": "M212 542L212 452L492 452L492 542L212 542ZM600 50Q642 50 671 79T700 150L700 850Q700 890 671 920T600 950L100 950Q60 950 30 920T0 850L0 150Q0 108 30 79T100 50L600 50ZM600 850L600 150L100 150L100 850L600 850ZM490 258L490 346L210 346L210 258L490 258ZM490 650L490 738L210 738L210 650L490 650Z",
"width": 700
},
"search": [
"doc-text"
]
},
{
"uid": "6405321f27abab4a505476c917d8c748",
"css": "folder-1",
"code": 59398,
"src": "custom_icons",
"selected": false,
"svg": {
"path": "M954 350Q986 350 994 362T1000 398L958 850Q956 874 946 887T904 900L98 900Q46 900 42 850L0 398Q-2 374 6 362T46 350L954 350ZM920 240L930 280L84 280L98 148Q102 128 118 114T154 100L318 100Q370 100 404 134L434 164Q466 200 520 200L860 200Q880 200 898 212T920 240Z",
"width": 1001
},
"search": [
"folder"
]
},
{
"uid": "2ba2063e74ee7b74f6c46a949d37a130",
"css": "cog-2",
"code": 59399,
"src": "custom_icons",
"selected": false,
"svg": {
"path": "M0 498Q0 460 7 415Q57 419 102 396T172 328Q197 283 195 233T165 142Q229 86 315 56Q338 97 379 122T469 146T558 122T622 56Q707 86 772 142Q751 181 751 227T774 313Q798 354 839 377T926 400Q937 449 937 498Q937 530 930 572Q885 573 846 597T783 660Q758 704 760 752T786 841Q720 901 641 934Q618 892 577 867T487 842Q434 842 391 870T327 944Q245 918 174 862Q202 821 204 771T181 676Q154 630 107 606T9 589Q0 548 0 498ZM293 498Q293 571 344 622T469 673T593 622T644 498T593 374T469 322T344 374T293 498Z",
"width": 937.5
},
"search": [
"cog-1"
]
},
{
"uid": "17ebadd1e3f274ff0205601eef7b9cc4",
"css": "help-circled",
"code": 59393,
"src": "fontawesome"
},
{
"uid": "c5fd349cbd3d23e4ade333789c29c729",
"css": "eye",
"code": 59395,
"src": "fontawesome"
},
{
"uid": "9a76bc135eac17d2c8b8ad4a5774fc87",
"css": "download",
"code": 59408,
"src": "fontawesome"
},
{
"uid": "f5999a012fc3752386635ec02a858447",
"css": "download-cloud",
"code": 59407,
"src": "fontawesome"
},
{
"uid": "de2fc7a5c986ab8c622f63455d7cf814",
"css": "upload-cloud",
"code": 59406,
"src": "fontawesome"
},
{
"uid": "41087bc74d4b20b55059c60a33bf4008",
"css": "edit",
"code": 59396,
"src": "fontawesome"
},
{
"uid": "85528017f1e6053b2253785c31047f44",
"css": "comment",
"code": 59404,
"src": "fontawesome"
},
{
"uid": "b035c28eba2b35c6ffe92aee8b0df507",
"css": "attention-circled",
"code": 59392,
"src": "fontawesome"
},
{
"uid": "f48ae54adfb27d8ada53d0fd9e34ee10",
"css": "trash",
"code": 59400,
"src": "fontawesome"
},
{
"uid": "c8585e1e5b0467f28b70bce765d5840c",
"css": "docs",
"code": 59397,
"src": "fontawesome"
},
{
"uid": "559647a6f430b3aeadbecd67194451dd",
"css": "menu",
"code": 59401,
"src": "fontawesome"
},
{
"uid": "3c24ee33c9487bbf18796ca6dffa1905",
"css": "resize-small",
"code": 59411,
"src": "fontawesome"
},
{
"uid": "6020aff067fc3c119cdd75daa5249220",
"css": "exchange",
"code": 59398,
"src": "fontawesome"
},
{
"uid": "823a9e02e643318116fea40a00190e4e",
"css": "asterisk",
"code": 59410,
"src": "fontawesome"
},
{
"uid": "fa10777b2d88cc64cd6e4f26ef0e5264",
"css": "terminal",
"code": 59403,
"src": "fontawesome"
},
{
"uid": "1189604bf305b6b03a74685ce60e6632",
"css": "doc-text",
"code": 59405,
"src": "entypo"
},
{
"uid": "0ccb084ddeeae372673793ed0b45bb4a",
"css": "folder",
"code": 59399,
"src": "entypo"
},
{
"uid": "67f793f91864e379458a92ccc61d9ccf",
"css": "sort-alphabet",
"code": 59394,
"src": "typicons"
},
{
"uid": "7336247ba3db350dec8d6c0a47cef966",
"css": "cog-1",
"code": 59402,
"src": "mfglabs"
},
{
"uid": "9e0404ba55575a540164db9a5ad511df",
"css": "doc-new",
"code": 59409,
"src": "elusive"
}
]
}

View file

@ -1,40 +1,31 @@
<?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" >
<!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>
<metadata>Copyright (C) 2013 by original authors @ fontello.com</metadata>
<defs>
<font id="cloudcmd" horiz-adv-x="364" >
<font-face
font-family="cloudcmd"
font-weight="500"
font-stretch="normal"
units-per-em="1000"
panose-1="2 0 6 3 0 0 0 0 0 0"
ascent="850"
descent="-150"
bbox="14.6522 -100 1015.35 800"
underline-thickness="50"
underline-position="-100"
unicode-range="U+2757-1F4C4"
/>
<missing-glyph
d="M33 0v666h265v-666h-265zM66 33h199v600h-199v-600z" />
<glyph glyph-name=".notdef"
d="M33 0v666h265v-666h-265zM66 33h199v600h-199v-600z" />
<glyph glyph-name=".null" horiz-adv-x="0"
/>
<glyph glyph-name="nonmarkingreturn" horiz-adv-x="333"
/>
<glyph glyph-name="uni2757" unicode="&#x2757;" horiz-adv-x="887"
d="M15 350q0 123 60 219.5t153 151.5q101 58 216 58q123 0 219.5 -60t151.5 -154q57 -99 57 -215q0 -123 -60 -219.5t-153 -151.5q-101 -58 -215 -58q-123 0 -220 60t-152 154q-57 99 -57 215zM382 636q-7 0 -13 -4.5t-6 -10.5l10 -346q0 -6 6 -10t13 -4h103q17 0 19 14
l10 346q0 6 -6 10.5t-13 4.5h-123zM372 83q0 -7 6 -13t13 -6h107q7 0 12 5.5t5 13.5v106q0 18 -17 18h-107q-8 0 -13.5 -5.5t-5.5 -12.5v-106z" />
<glyph glyph-name="u1F4C1" unicode="&#x1f4c1;" horiz-adv-x="1031"
d="M969 500q29 0 38.5 -10.5t7.5 -37.5l-42 -452q-2 -27 -13.5 -38.5t-40.5 -11.5h-806q-51 0 -56 50l-42 452q-2 27 7.5 37.5t38.5 10.5h908zM935 610l10 -40h-846l14 132q4 19 20 33.5t36 14.5h164q52 0 86 -34l30 -30q31 -36 86 -36h340q20 0 38 -12t22 -28z" />
<glyph glyph-name="u1F4C4" unicode="&#x1f4c4;" horiz-adv-x="730"
d="M227 398h280v-90h-280v90zM615 800q42 0 71 -29t29 -71v-700q0 -41 -29.5 -70.5t-70.5 -29.5h-500q-40 0 -70 30t-30 70v700q0 41 29.5 70.5t70.5 29.5h500zM615 700h-500v-700h500v700zM505 504h-280v88h280v-88zM505 200v-88h-280v88h280z" />
</font>
</defs></svg>
<font id="fontello" horiz-adv-x="1000" >
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
<missing-glyph horiz-adv-x="1000" />
<glyph glyph-name="help-circled" unicode="&#xe801;" d="m500 82v107q0 8-5 13t-13 5h-107q-8 0-13-5t-5-13v-107q0-8 5-13t13-5h107q8 0 13 5t5 13z m143 375q0 49-31 91t-77 65t-95 23q-136 0-207-119q-9-14 4-24l74-55q4-4 10-4q9 0 14 7q30 38 48 51q19 14 48 14q27 0 48-15t21-33q0-21-11-34t-38-25q-35-16-65-48t-29-70v-20q0-8 5-13t13-5h107q8 0 13 5t5 13q0 10 12 27t30 28q18 10 28 16t25 19t25 27t16 34t7 45z m214-107q0-117-57-215t-156-156t-215-58t-216 58t-155 156t-58 215t58 215t155 156t216 58t215-58t156-156t57-215z" horiz-adv-x="857.1" />
<glyph glyph-name="doc-new" unicode="&#xe811;" d="m0-150l0 818l188 182l519 0l0-348l-86 0l0 260l-369 0l0-156l-166 0l0-668l418 0l0-88l-504 0z m373 207l0 162l209 0l0 207l160 0l0-207l207 0l0-162l-207 0l0-207l-160 0l0 207l-209 0z" horiz-adv-x="949" />
<glyph glyph-name="download" unicode="&#xe810;" d="m714 100q0 15-10 25t-25 11t-26-11t-10-25t10-25t26-11t25 11t10 25z m143 0q0 15-10 25t-26 11t-25-11t-10-25t10-25t25-11t26 11t10 25z m72 125v-179q0-22-16-37t-38-16h-821q-23 0-38 16t-16 37v179q0 22 16 38t38 16h259l75-76q33-32 76-32t76 32l76 76h259q22 0 38-16t16-38z m-182 318q10-23-8-40l-250-250q-10-10-25-10t-25 10l-250 250q-17 17-8 40q10 21 33 21h143v250q0 15 11 25t25 11h143q14 0 25-11t10-25v-250h143q24 0 33-21z" horiz-adv-x="928.6" />
<glyph glyph-name="download-cloud" unicode="&#xe80f;" d="m714 332q0 8-5 13t-13 5h-125v196q0 8-5 13t-12 5h-108q-7 0-12-5t-5-13v-196h-125q-8 0-13-5t-5-13q0-8 5-13l196-196q5-5 13-5t13 5l196 196q5 6 5 13z m357-125q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 72 39 134t105 92q-1 17-1 24q0 118 84 202t202 84q87 0 159-49t105-129q40 35 93 35q59 0 101-42t42-101q0-43-23-77q72-17 119-76t46-133z" horiz-adv-x="1071.4" />
<glyph glyph-name="upload-cloud" unicode="&#xe80e;" d="m714 368q0 8-5 13l-196 196q-5 5-13 5t-13-5l-196-196q-5-6-5-13q0-8 5-13t13-5h125v-196q0-8 5-13t12-5h108q7 0 12 5t5 13v196h125q8 0 13 5t5 13z m357-161q0-89-62-151t-152-63h-607q-103 0-177 73t-73 177q0 72 39 134t105 92q-1 17-1 24q0 118 84 202t202 84q87 0 159-49t105-129q40 35 93 35q59 0 101-42t42-101q0-43-23-77q72-17 119-76t46-133z" horiz-adv-x="1071.4" />
<glyph glyph-name="edit" unicode="&#xe804;" d="m496 189l64 65l-85 85l-64-65v-31h53v-54h32z m245 402q-9 9-18 0l-196-196q-9-9 0-18t18 0l196 196q9 9 0 18z m45-331v-106q0-67-47-114t-114-47h-464q-67 0-114 47t-47 114v464q0 66 47 113t114 48h464q35 0 65-14q9-4 10-13q2-10-5-16l-27-28q-8-8-18-4q-13 3-25 3h-464q-37 0-63-26t-27-63v-464q0-37 27-63t63-27h464q37 0 63 27t26 63v70q0 7 5 12l36 36q8 8 20 4t11-16z m-54 411l161-160l-375-375h-161v160z m248-73l-51-52l-161 161l51 51q16 16 38 16t38-16l85-84q16-16 16-38t-16-38z" horiz-adv-x="1000" />
<glyph glyph-name="comment" unicode="&#xe80c;" d="m1000 350q0-97-67-179t-182-130t-251-48q-39 0-81 4q-110-97-257-135q-27-8-63-12q-10-1-17 5t-10 16v1q-2 2 0 6t1 6t2 5l4 5t4 5t4 5q4 5 17 19t20 22t17 22t18 28t15 33t15 42q-88 50-138 123t-51 157q0 73 40 139t106 114t160 76t194 28q136 0 251-48t182-130t67-179z" horiz-adv-x="1000" />
<glyph glyph-name="attention-circled" unicode="&#xe800;" d="m429 779q116 0 215-58t156-156t57-215t-57-215t-156-156t-215-58t-216 58t-155 156t-58 215t58 215t155 156t216 58z m71-696v106q0 8-5 13t-12 5h-107q-8 0-13-5t-6-13v-106q0-8 6-13t13-6h107q7 0 12 6t5 13z m-1 192l10 346q0 7-6 10q-5 5-13 5h-123q-8 0-13-5q-6-3-6-10l10-346q0-6 5-10t14-4h103q8 0 13 4t6 10z" horiz-adv-x="857.1" />
<glyph glyph-name="trash" unicode="&#xe808;" d="m286 439v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m143 0v-321q0-8-5-13t-13-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q8 0 13-5t5-13z m142 0v-321q0-8-5-13t-12-5h-36q-8 0-13 5t-5 13v321q0 8 5 13t13 5h36q7 0 12-5t5-13z m72-404v529h-500v-529q0-12 4-22t8-15t6-5h464q2 0 6 5t8 15t4 22z m-375 601h250l-27 65q-4 5-9 6h-177q-6-1-10-6z m518-18v-36q0-8-5-13t-13-5h-54v-529q0-46-26-80t-63-34h-464q-37 0-63 33t-27 79v531h-53q-8 0-13 5t-5 13v36q0 8 5 13t13 5h172l39 93q9 21 31 35t44 15h178q22 0 44-15t30-35l39-93h173q8 0 13-5t5-13z" horiz-adv-x="785.7" />
<glyph glyph-name="docs" unicode="&#xe805;" d="m946 636q23 0 38-16t16-38v-678q0-23-16-38t-38-16h-535q-23 0-38 16t-16 38v160h-303q-23 0-38 16t-16 38v375q0 22 11 49t27 42l228 228q15 16 42 27t49 11h232q23 0 38-16t16-38v-183q38 23 71 23h232z m-303-119l-167-167h167v167z m-357 214l-167-167h167v167z m109-361l176 176v233h-214v-233q0-22-15-38t-38-15h-233v-357h286v143q0 22 11 49t27 42z m534-449v643h-215v-232q0-22-15-38t-38-15h-232v-358h500z" horiz-adv-x="1000" />
<glyph glyph-name="eye" unicode="&#xe803;" d="m929 314q-85 132-213 197q34-58 34-125q0-104-73-177t-177-73t-177 73t-73 177q0 67 34 125q-128-65-213-197q75-114 187-182t242-68t242 68t187 182z m-402 215q0 11-8 19t-19 7q-70 0-120-50t-50-119q0-12 8-19t19-8t19 8t8 19q0 48 34 82t82 34q11 0 19 8t8 19z m473-215q0-19-11-38q-78-129-210-206t-279-77t-279 77t-210 206q-11 19-11 38t11 39q78 128 210 205t279 78t279-78t210-205q11-20 11-39z" horiz-adv-x="1000" />
<glyph glyph-name="resize-small" unicode="&#xe813;" d="m429 314v-250q0-14-11-25t-25-10t-25 10l-81 81l-185-186q-5-5-13-5t-13 5l-63 64q-6 5-6 13t6 13l185 185l-80 80q-11 11-11 25t11 25t25 11h250q14 0 25-11t11-25z m421 375q0-7-6-13l-185-185l80-80q11-11 11-25t-11-25t-25-11h-250q-14 0-25 11t-10 25v250q0 14 10 25t25 10t25-10l81-81l185 186q6 5 13 5t13-5l63-64q6-5 6-13z" horiz-adv-x="857.1" />
<glyph glyph-name="exchange" unicode="&#xe806;" d="m1000 189v-107q0-7-5-12t-13-6h-768v-107q0-7-5-12t-13-6q-6 0-13 6l-178 178q-5 5-5 13q0 8 5 13l179 178q5 5 12 5q8 0 13-5t5-13v-107h768q7 0 13-5t5-13z m0 304q0-8-5-13l-179-179q-5-5-12-5q-8 0-13 6t-5 12v107h-768q-7 0-13 6t-5 12v107q0 8 5 13t13 5h768v107q0 8 5 13t13 5q6 0 13-5l178-178q5-5 5-13z" horiz-adv-x="1000" />
<glyph glyph-name="asterisk" unicode="&#xe812;" d="m827 264q26-14 33-43t-7-55l-35-61q-15-26-44-33t-54 7l-149 85v-171q0-29-21-50t-50-22h-71q-29 0-51 22t-21 50v171l-148-85q-26-15-55-7t-43 33l-36 61q-14 26-7 55t34 43l148 86l-148 86q-26 14-34 43t7 55l36 61q15 26 43 33t55-7l148-85v171q0 29 21 50t51 22h71q29 0 50-22t21-50v-171l149 85q26 15 54 7t44-33l35-62q15-25 7-54t-33-43l-148-86z" horiz-adv-x="928.6" />
<glyph glyph-name="terminal" unicode="&#xe80b;" d="m326 301l-260-260q-5-5-12-5t-13 5l-28 28q-6 6-6 13t6 13l219 219l-219 220q-6 5-6 12t6 13l28 28q5 6 13 6t12-6l260-260q6-5 6-13t-6-13z m603-255v-35q0-8-5-13t-13-5h-536q-8 0-13 5t-5 13v35q0 8 5 13t13 5h536q8 0 13-5t5-13z" horiz-adv-x="928.6" />
<glyph glyph-name="doc-text" unicode="&#xe80d;" d="m212 308l0 90l280 0l0-90l-280 0z m388 492q42 0 71-29t29-71l0-700q0-40-29-70t-71-30l-500 0q-40 0-70 30t-30 70l0 700q0 42 30 71t70 29l500 0z m0-800l0 700l-500 0l0-700l500 0z m-110 592l0-88l-280 0l0 88l280 0z m0-392l0-88l-280 0l0 88l280 0z" horiz-adv-x="700" />
<glyph glyph-name="folder" unicode="&#xe807;" d="m954 500q32 0 40-12t6-36l-42-452q-2-24-12-37t-42-13l-806 0q-52 0-56 50l-42 452q-2 24 6 36t40 12l908 0z m-34 110l10-40l-846 0l14 132q4 20 20 34t36 14l164 0q52 0 86-34l30-30q32-36 86-36l340 0q20 0 38-12t22-28z" horiz-adv-x="1001" />
<glyph glyph-name="sort-alphabet" unicode="&#xe802;" d="m516 165q12-25-2-50t-44-25q-33 0-47 29l-37 75l-249 0l-37-75q-9-20-30-27t-40 3q-20 9-27 29t3 41l209 417q13 27 47 27t46-27z m-326 134l143 0l-72 143z m905-209l-313 0q-30 0-45 27t4 57l250 332l-209 0q-21 0-37 16t-15 37t15 36t37 16l313 0q30 0 45-28t-4-56l-250-333l209 0q21 0 37-15t15-37t-15-37t-37-15z m-417 209l-105 0q-21 0-36 15t-15 36t15 37t36 15l105 0q22 0 37-15t15-37t-15-36t-37-15z" horiz-adv-x="1147" />
<glyph glyph-name="cog-1" unicode="&#xe80a;" d="m0 352q0 38 7 83q50-4 95 19t70 68q25 45 23 95t-30 91q64 56 150 86q23-41 64-66t90-24t89 24t64 66q85-30 150-86q-21-39-21-85t23-86q24-41 65-64t87-23q11-49 11-98q0-32-7-74q-45-1-84-25t-63-63q-25-44-23-92t26-89q-66-60-145-93q-23 42-64 67t-90 25q-53 0-96-28t-64-74q-82 26-153 82q28 41 30 91t-23 95q-27 46-74 70t-98 17q-9 41-9 91z m293 0q0-73 51-124t125-51t124 51t51 124t-51 124t-124 52t-125-52t-51-124z" horiz-adv-x="937.5" />
<glyph glyph-name="menu" unicode="&#xe809;" d="m857 100v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 25t25 11h785q15 0 26-11t10-25z m0 286v-72q0-14-10-25t-26-10h-785q-15 0-25 10t-11 25v72q0 14 11 25t25 10h785q15 0 26-10t10-25z m0 285v-71q0-15-10-25t-26-11h-785q-15 0-25 11t-11 25v71q0 15 11 26t25 10h785q15 0 26-10t10-26z" horiz-adv-x="857.1" />
</font>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 2 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Before After
Before After

Binary file not shown.

Binary file not shown.

View file

@ -6,9 +6,11 @@
(function(){
'use strict';
if(window.opener){
var lGitHub = window.opener.CloudCommander.GitHub;
lGitHub.autorize(lGitHub.callback, window.location.search);
var opener = window.opener;
if (opener){
var GitHub = opener.CloudCmd.GitHub;
GitHub.autorize(GitHub.callback, window.location.search);
window.close();
}
})();

View file

@ -3,18 +3,19 @@
<head></head>
<body>
<script>
(function(){
(function() {
"use strict";
var lOpener = window.opener;
if(lOpener){
var CloudCmd = lOpener.CloudCmd;
var opener = window.opener;
if (opener) {
var CloudCmd = opener.CloudCmd;
CloudCmd.getModules(function(pModules){
var Util = lOpener.Util,
lStorage = Util.findObjByNameInArr(pModules, 'storage'),
lGitHub = Util.findObjByNameInArr(lStorage, 'GitHub'),
GitHub_ID = lGitHub && lGitHub.key;
CloudCmd.getModules(function(pModules) {
var Util = opener.Util,
Storage = Util.findObjByNameInArr(pModules, 'storage'),
GitHub = Util.findObjByNameInArr(Storage, 'GitHub'),
GitHub_ID = GitHub && GitHub.key;
window.location =
'https://github.com/login/oauth/authorize?client_id=' +

View file

@ -1,5 +1,8 @@
<div class="border">
<ul class="list" >
<li><span><input type="checkbox" id="auth" {{ auth }} ></input></span><label for="auth"> Auth</label></li>
<li><span><input type="text" class="form-control" placeholder="username" id="username" value="{{ username }}"></input></span></li>
<li><span><input type="password" class="form-control" placeholder="password" id="password" value="{{ password }}" ></input></span></li>
<li><span><input type="checkbox" id="appCache" {{ appCache }} ></input></span><label for="appCache"> App cache</label></li>
<li><span><input type="checkbox" id="analytics" {{ analytics }} ></input></span><label for="analytics"> Analytics</label></li>
<li><span><input type="checkbox" id="diff" {{ diff }} ></input></span><label for="diff"> Diff</label></li>

View file

@ -1,6 +1,7 @@
<li draggable class="{{ className }}">
<span class="mini-icon {{ type }}"></span>
<span class=name>{{ name }}</span>
<span class=size>{{ size }}</span><span class=owner>{{ owner }}</span>
<span class=mode>{{ mode }}</span>
<span class="name reduce-text">{{ name }}</span>
<span class="size reduce-text">{{ size }}</span>
<span class="owner reduce-text">{{ owner }}</span>
<span class="mode reduce-text">{{ mode }}</span>
</li>

View file

@ -10,41 +10,45 @@
<link href=/img/favicon/favicon.png rel=icon type=image/png />
<title>{{ title }}</title>
<link rel=stylesheet href=/combine/css/reset.css:css/style.css>
<link rel=stylesheet href=/join/css/reset.css:css/style.css:css/icons.css>
</head>
<body>
<div class=fm>{{ fm }}</div>
<div class="keyspanel">
<button id=f1 class=cmd-button>F1 - help</button>
<button id=f2 class=cmd-button>F2 - rename</button>
<button id=f3 class=cmd-button>F3 - view</button>
<button id=f4 class=cmd-button>F4 - edit</button>
<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 - delete</button>
<button id=f9 class=cmd-button>F9 - menu</button>
<button id=f10 class=cmd-button>F10 - config</button>
<button id=~ class=cmd-button>~ - console</button>
<button id=f1 class="cmd-button reduce-text icon-help" title="help">F1</button>
<button id=f2 class="cmd-button reduce-text icon-rename" title="rename">F2</button>
<button id=f3 class="cmd-button reduce-text icon-view" title="view">F3</button>
<button id=f4 class="cmd-button reduce-text icon-edit " title="edit">F4</button>
<button id=f5 class="cmd-button reduce-text icon-copy" title="copy">F5</button>
<button id=f6 class="cmd-button reduce-text icon-move" title="move">F6</button>
<button id=f7 class="cmd-button reduce-text icon-directory" title="make directory">F7</button>
<button id=f8 class="cmd-button reduce-text icon-delete" title="delete">F8</button>
<button id=f9 class="cmd-button reduce-text icon-menu" title="menu">F9</button>
<button id=f10 class="cmd-button reduce-text icon-config" title="config">F10</button>
<button id=~ class="cmd-button reduce-text icon-console" title="console">~</button>
<button id=contact class="cmd-button reduce-text icon-contact" title="contact"></button>
</div>
<script>
(function() {
!(function() {
'use strict';
var script, Height,
var script,
lib = 'lib/',
client = lib + 'client/',
files = [
'lib/client/key.js',
'lib/client/listeners.js',
'lib/client.js',
'lib/client/dom.js',
'lib/cloudfunc.js',
'lib/util.js'
lib + 'util.js',
lib + 'cloudfunc.js',
client + 'dom.js',
client + 'loader.js',
client + 'notify.js',
lib + 'client.js',
client + 'listeners.js',
client + 'key.js'
],
url = getCombineURL(files);
url = getJoinURL(files);
window.addEventListener('load', createScript);
setPanelHeight();
function createScript() {
script = document.createElement('script');
@ -55,9 +59,9 @@
window.removeEventListener('load', createScript);
}
function getCombineURL(files) {
function getJoinURL(files) {
var regExp = new RegExp(',', 'g'),
url = '/combine/' + files;
url = '/join/' + files;
url = url.replace(regExp, ':');
@ -68,29 +72,6 @@
CloudCmd.init();
script.removeEventListener('load', scriptLoad);
}
function setPanelHeight() {
var style;
/* устанавливаем размер высоты таблицы файлов
* исходя из размеров разрешения экрана
*
* формируем и округляем высоту экрана
* при разрешениии 1024x1280:
* 658 -> 700
*/
Height = window.screen.height;
Height -= (Height / 3).toFixed();
Height = (Height / 100).toFixed() * 100;
style = document.createElement('style');
style.innerText = '.files {' +
'height:' + Height +'px;' +
'}';
document.head.appendChild(style);
}
})();
</script>
</body>

1
html/panel.html Normal file
View file

@ -0,0 +1 @@
<div id="{{ id }}" class="panel panel-{{ side }}">{{ content }}</div>

View file

@ -1 +1 @@
<div class=path><span class="path-icon clear-storage" id=clear-storage title="clear storage (Ctrl+D)"></span><span class="path-icon refresh-icon" title="refresh (Ctrl+R)"><a href="{{ link }}"></a></span><span>{{ path }}</span></div>
<div id="js-path" class="reduce-text" title="{{ fullPath }}"><span class="path-icon clear-storage" title="clear storage (Ctrl+D)"></span><span class="path-icon refresh-icon" title="refresh (Ctrl+R)"><a href="{{ link }}"></a></span><span class=links>{{ path }}</span></div>

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

Before After
Before After

View file

Before

Width:  |  Height:  |  Size: 872 B

After

Width:  |  Height:  |  Size: 872 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 59 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 70 KiB

After

Width:  |  Height:  |  Size: 49 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 86 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Before After
Before After

View file

@ -1,4 +1,7 @@
{
"auth": false,
"username": "root",
"password": "435b41068e8665513a20070c033b08b9c66e4332",
"apiURL": "/api/v1",
"appСache": false,
"analytics": true,

View file

@ -4,6 +4,9 @@
"view",
"help",
"config",
"contact",
"socket",
"terminal",
"console", {
"name": "storage",
"data": [{

View file

@ -1,18 +1,19 @@
var Util, DOM, CloudFunc;
/* Функция которая возвратит обьект CloudCmd
* @CloudFunc - обьект содержащий общий функционал
* клиентский и серверный
*/
var Util, DOM, CloudFunc, CloudCmd;
(function(scope, Util, DOM) {
(function(scope, Util, DOM, CloudFunc) {
'use strict';
scope.CloudCmd = new CloudCmdProto(Util, DOM);
scope.CloudCmd = new CloudCmdProto(Util, DOM, CloudFunc);
function CloudCmdProto(Util, DOM) {
function CloudCmdProto(Util, DOM, CloudFunc) {
var Key, Config, Modules, Extensions,
FileTemplate, PathTemplate, LinkTemplate, Listeners,
CloudCmd = this;
Images = DOM.Images,
Info = DOM.CurrentInfo,
CloudCmd = this,
Storage = DOM.Storage;
this.LIBDIR = '/lib/';
@ -45,7 +46,7 @@ var Util, DOM, CloudFunc, CloudCmd;
lLink += '?json';
if (lLink || lCurrentLink.target !== '_blank') {
DOM.Images.showLoad(pNeedRefresh ? {top:true} : null);
Images.showLoad(pNeedRefresh ? {top:true} : null);
/* загружаем содержимое каталога */
CloudCmd.ajaxLoad(lLink, { refresh: pNeedRefresh });
@ -79,34 +80,40 @@ var Util, DOM, CloudFunc, CloudCmd;
* @pParams = {name, path, func, dobefore, arg}
*/
function loadModule(pParams) {
var lName, lPath, lFunc, lDoBefore, lSlash, lAfterSlash,
isContain;
if (pParams) {
var lName = pParams.name,
lPath = pParams.path,
lFunc = pParams.func,
lDoBefore = pParams.dobefore;
lName = pParams.name,
lPath = pParams.path,
lFunc = pParams.func,
lDoBefore = pParams.dobefore;
if ( Util.isString(pParams) )
if (Util.isString(pParams))
lPath = pParams;
if (lPath && !lName) {
lName = Util.getStrBigFirst(lPath);
lName = Util.removeStr(lName, '.js');
var lSlash = lName.indexOf('/');
lSlash = lName.indexOf('/');
if (lSlash > 0) {
var lAfterSlash = lName.substr(lSlash);
lAfterSlash = lName.substr(lSlash);
lName = Util.removeStr(lName, lAfterSlash);
}
}
if ( !Util.isContainStr(lPath, '.js') )
isContain = Util.isContainStr(lPath, '.js');
if (!isContain)
lPath += '.js';
if (!CloudCmd[lName])
if (!CloudCmd[lName]) {
CloudCmd[lName] = function(pArg) {
var path = CloudCmd.LIBDIRCLIENT + lPath;
Util.exec(lDoBefore);
return DOM.jsload(CloudCmd.LIBDIRCLIENT + lPath, lFunc ||
return DOM.jsload(path, lFunc ||
function() {
var Proto = CloudCmd[lName];
@ -114,6 +121,9 @@ var Util, DOM, CloudFunc, CloudCmd;
CloudCmd[lName] = new Proto(pArg);
});
};
CloudCmd[lName].show = CloudCmd[lName];
}
}
}
@ -126,9 +136,9 @@ var Util, DOM, CloudFunc, CloudCmd;
lCallBack = function() {
Util.loadOnLoad([
Util.retFunc(CloudCmd.route, location.hash),
baseInit,
initModules,
baseInit,
Util.bind(CloudCmd.route, location.hash)
]);
},
lFunc = function(pCallBack) {
@ -178,45 +188,47 @@ var Util, DOM, CloudFunc, CloudCmd;
});
CloudCmd.getModules(function(pModules) {
pModules = pModules || [];
pModules = pModules || [];
var i, n, lStorage = 'storage',
lShowLoadFunc = Util.retFunc( DOM.Images.showLoad, {top:true} ),
var i, n, module, storageObj, mod, name, path,
STORAGE = 'storage',
showLoadFunc = Util.bind(Images.showLoad, {
top:true
}),
lDoBefore = {
'edit' : lShowLoadFunc,
'view' : lShowLoadFunc,
'menu' : lShowLoadFunc
doBefore = {
'edit' : showLoadFunc,
'view' : showLoadFunc,
'menu' : showLoadFunc
},
lLoad = function(pName, pPath, pDoBefore) {
load = function(name, path, func) {
loadModule({
path : pPath,
name : pName,
dobefore : pDoBefore
name : name,
path : path,
dobefore : func
});
};
for (i = 0, n = pModules.length; i < n ; i++) {
var lModule = pModules[i];
module = pModules[i];
if ( Util.isString(lModule) )
lLoad(null, lModule, lDoBefore[lModule]);
if (Util.isString(module))
load(null, module, doBefore[module]);
}
var lStorageObj = Util.findObjByNameInArr( pModules, lStorage ),
lMod = Util.getNamesFromObjArray( lStorageObj );
storageObj = Util.findObjByNameInArr(pModules, STORAGE),
mod = Util.getNamesFromObjArray(storageObj);
for (i = 0, n = lMod.length; i < n; i++) {
var lName = lMod[i],
lPath = lStorage + '/_' + lName.toLowerCase();
for (i = 0, n = mod.length; i < n; i++) {
name = mod[i],
path = STORAGE + '/_' + name.toLowerCase();
lLoad(lName, lPath);
load(name, path);
}
Util.exec(pCallBack);
});
}
@ -224,42 +236,39 @@ var Util, DOM, CloudFunc, CloudCmd;
function baseInit(pCallBack) {
var LIB = CloudCmd.LIBDIR,
LIBCLIENT = CloudCmd.LIBDIRCLIENT,
files = [
LIB + 'cloudfunc.js',
LIBCLIENT + 'listeners.js'
];
files = DOM.getFiles(),
LEFT = CloudFunc.PANEL_LEFT,
RIGHT = CloudFunc.PANEL_RIGHT;
/* выделяем строку с первым файлом */
if (files)
DOM.setCurrentFile(files[0]);
Listeners = CloudCmd.Listeners;
Listeners.init();
/* загружаем Google Analytics */
Listeners.analytics();
Listeners.changeLinks(CloudFunc.LEFTPANEL);
Listeners.changeLinks(CloudFunc.RIGHTPANEL);
Listeners.changeLinks(LEFT);
Listeners.changeLinks(RIGHT);
CloudCmd.KeysPanel = Listeners.initKeysPanel();
Listeners.initKeysPanel();
CloudCmd.getConfig(function(config) {
var localStorage = config.localStorage;
var localStorage = config.localStorage,
dirPath = DOM.getCurrentDirPath();
/* устанавливаем переменную доступности кэша */
Storage.setAllowed(localStorage);
/* Устанавливаем кэш корневого каталога */
var lDirPath = DOM.getCurrentDirPath();
lDirPath = CloudFunc.removeLastSlash(lDirPath) || '/';
dirPath = CloudFunc.rmLastSlash(dirPath) || '/';
if (!Storage.get(lDirPath))
Storage.set(lDirPath, getJSONfromFileTable());
if (!Storage.get(dirPath))
Storage.set(dirPath, getJSONfromFileTable());
});
Util.exec(pCallBack);
/* выделяем строку с первым файлом */
var lFiles = DOM.getFiles();
if (lFiles)
DOM.setCurrentFile(lFiles[0]);
Util.exec(CloudCmd.Key);
Util.exec(pCallBack);
}
function getSystemFile(pGlobal, pURL) {
@ -305,7 +314,7 @@ var Util, DOM, CloudFunc, CloudCmd;
lPanel = pCurrent && pCurrent.parentElement,
lPath = DOM.getCurrentDirPath(lPanel),
lLink = CloudFunc.FS + lPath,
lNotSlashlLink = CloudFunc.removeLastSlash(lLink),
lNotSlashlLink = CloudFunc.rmLastSlash(lLink),
lLoad = CloudCmd.loadDir(lNotSlashlLink, lNEEDREFRESH);
lLoad();
@ -324,6 +333,7 @@ var Util, DOM, CloudFunc, CloudCmd;
/* Отображаем красивые пути */
var lSLASH = '/',
title = CloudFunc.getTitle(lCleanPath),
lFSPath = decodeURI(pPath),
lNOJSPath = Util.removeStr( lFSPath, '?json' ),
lCleanPath = Util.removeStrOneTime( lNOJSPath, CloudFunc.FS ) || lSLASH,
@ -338,7 +348,7 @@ var Util, DOM, CloudFunc, CloudCmd;
if (!pOptions.nohistory)
DOM.setHistory(lNOJSPath, null, lNOJSPath);
DOM.setTitle( CloudFunc.getTitle(lCleanPath) );
DOM.setTitle(title);
/* если доступен localStorage и
* в нём есть нужная нам директория -
@ -399,7 +409,7 @@ var Util, DOM, CloudFunc, CloudCmd;
lDir = DOM.getCurrentDirName(),
lName = DOM.getCurrentName(lCurrent),
lWasRefresh_b = lPath === pJSON.path,
wasRefresh = lPath === pJSON.path,
lFuncs = [
CloudCmd.getFileTemplate,
CloudCmd.getPathTemplate,
@ -408,7 +418,8 @@ var Util, DOM, CloudFunc, CloudCmd;
Util.asyncCall(lFuncs, function(pTemplate, pPathTemplate, pLinkTemplate) {
/* очищаем панель */
var i = panel.childNodes.length;
var n, found,
i = panel.childNodes.length;
while(i--)
panel.removeChild(panel.lastChild);
@ -416,24 +427,24 @@ var Util, DOM, CloudFunc, CloudCmd;
panel.innerHTML = CloudFunc.buildFromJSON(pJSON, pTemplate, pPathTemplate, pLinkTemplate);
files = DOM.getFiles(panel);
/* если нажали на ссылку на верхний каталог*/
var lFound;
/* searching current file */
if (lWasRefresh_b) {
var n = files.length;
for(i = 0; i < n ; i++) {
if (wasRefresh) {
n = files.length;
for (i = 0; i < n ; i++) {
var lVarCurrent = files[i],
lVarName = DOM.getCurrentName(lVarCurrent);
lFound = lVarName === lName;
found = lVarName === lName;
if (lFound) {
if (found) {
lCurrent = files[i];
break;
}
}
}
if (!lFound) /* .. */
if (!found) /* .. */
lCurrent = files[0];
DOM.setCurrentFile(lCurrent);
@ -443,49 +454,60 @@ var Util, DOM, CloudFunc, CloudCmd;
if (lName === '..' && lDir !== '/')
currentToParent(lDir);
});
}
};
/**
* Функция генерирует JSON из html-таблицы файлов и
* используеться при первом заходе в корень
*/
function getJSONfromFileTable() {
var lLeft = DOM.getById('left'),
lPath = DOM.getByClass('path')[0].textContent,
var current, name, size, owner, mode, ret,
path = DOM.getCurrentDirPath(),
infoFiles = Info.files,
lFileTable = {
path : lPath,
fileTable = {
path : path,
files : []
},
files = lFileTable.files,
files = fileTable.files,
lLI = DOM.getByTag('li', lLeft),
i, n = lLI.length;
i, n = infoFiles.length;
for (i = 0; i < n; i++) {
current = infoFiles[i];
name = DOM.getCurrentName(current);
size = DOM.getCurrentSize(current);
owner = DOM.getCurrentOwner(current);
mode = DOM.getCurrentMode(current);
/* счётчик элементов файлов в DOM
* Если путь отличный от корневного
* второй элемент li - это ссылка на верхний
* каталог '..'
*/
/* пропускам Path и Header*/
for (i = 2; i < n; i++) {
var lCurrent = lLI[i],
lName = DOM.getCurrentName(lCurrent),
lSize = DOM.getCurrentSize(lCurrent),
mode = CloudFunc.getNumericPermissions(mode);
lMode = DOM.getCurrentMode(lCurrent);
lMode = CloudFunc.getNumericPermissions(lMode);
if (lName !== '..')
lFileTable.files.push({
name: lName,
size: lSize,
mode: lMode
if (name !== '..')
files.push({
name : name,
size : size,
mode : mode,
owner : owner
});
}
return Util.stringifyJSON(lFileTable);
ret = Util.stringifyJSON(fileTable);
return ret;
}
this.goToParentDir = function() {
var path = Info.dirPath,
parentPath = Info.parentDirPath;
if (path !== parentPath) {
path = parentPath;
path = CloudFunc.FS + CloudFunc.rmLastSlash(path);
Util.exec(CloudCmd.loadDir(path));
}
};
}
})(this, Util, DOM);
})(this, Util, DOM, CloudFunc);

View file

@ -1,11 +1,13 @@
var CloudCmd, Util, DOM;
(function(CloudCmd, Util, DOM){
(function(CloudCmd, Util, DOM) {
'use strict';
CloudCmd.Config = ConfigProto;
function ConfigProto() {
var Key = CloudCmd.Key,
var Loading = true,
Key = CloudCmd.Key,
Images = DOM.Images,
Events = DOM.Events,
INPUT = 'INPUT',
@ -15,13 +17,16 @@ var CloudCmd, Util, DOM;
Config = this;
function init(pCallBack) {
Util.loadOnLoad([
Config.show,
CloudCmd.View,
]);
Loading = true;
DOM.Events.addKey(listener);
DOM.setButtonKey('f10', Config.show);
Util.loadOnLoad([
CloudCmd.View,
function(callback) {
Loading = false;
Util.exec(callback);
},
Config.show
]);
}
this.show = function() {
@ -30,8 +35,10 @@ var CloudCmd, Util, DOM;
cssLoad
];
Images.showLoad({top:true});
Util.asyncCall(funcs, fillTemplate);
if (!Loading) {
Images.showLoad({top:true});
Util.asyncCall(funcs, fillTemplate);
}
};
function cssLoad(callback) {
@ -90,15 +97,6 @@ var CloudCmd, Util, DOM;
CloudCmd.View.hide();
};
function listener(pEvent){
var f10 = Key.F10,
isBind = Key.isBind(),
key = pEvent.keyCode;
/* если клавиши можно обрабатывать */
if (isBind && key === f10)
Config.show();
}
function changeConfig(config) {
var name;

View file

@ -2,47 +2,49 @@ var CloudCmd, Util, DOM, CloudFunc, $;
(function(CloudCmd, Util, DOM, CloudFunc) {
'use strict';
var Buffer = {
log : '',
error : ''
};
CloudCmd.Console = ConsoleProto;
function ConsoleProto(CallBack) {
var Name = 'Console',
Buffer = {
log : '',
error : ''
},
Loading,
jqconsole,
Element,
MouseBinded,
Socket,
Key = CloudCmd.Key,
Images = DOM.Images,
Notify = DOM.Notify,
CHANNEL = CloudFunc.CHANNEL_CONSOLE,
Console = this;
function init() {
var lFunc, lIsFunc = Util.isFunction(CloudCmd.View);
Loading = true;
if (lIsFunc)
lFunc = CloudCmd.View;
else
lFunc = Util.exec;
Util.loadOnLoad([
Console.show,
load,
lFunc,
DOM.jqueryLoad,
DOM.socketLoad
CloudCmd.View,
load,
CloudCmd.Socket,
function(callback) {
Socket = CloudCmd.Socket;
Util.exec(callback);
},
Console.show,
addListeners,
]);
DOM.Events.addKey(listener);
DOM.setButtonKey('~', Console.show);
}
this.show = function() {
this.show = show;
this.log = log;
this.error = error;
function show(callback) {
if (!Loading) {
Images.showLoad({top:true});
@ -61,12 +63,9 @@ var CloudCmd, Util, DOM, CloudFunc, $;
// Handle a command.
var handler = function(command) {
var lSocket = CloudCmd.Socket;
if (command) {
Images.showLoad({ top:true });
if(lSocket)
lSocket.send(command);
Socket.emit(CHANNEL, command);
}
jqconsole.Prompt(true, handler);
@ -77,55 +76,57 @@ var CloudCmd, Util, DOM, CloudFunc, $;
}
CloudCmd.View.show(Element, function() {
var l$Console = jqconsole.$console,
l$Input = jqconsole.$input_source,
lFocus = function() {
var console = jqconsole.$console,
input = jqconsole.$input_source,
focus = function() {
var x = window.scrollX,
y = window.scrollY;
l$Input.focus();
input.focus();
window.scrollTo(x,y);
};
lFocus();
focus();
if (!MouseBinded) {
MouseBinded = true;
$(l$Console).unbind('mouseup');
$(l$Console).mouseup(function() {
if( !window.getSelection().toString() ) {
var lTop = l$Console.scrollTop();
console.unbind('mouseup');
console.mouseup(function() {
var top,
isSelection = '' + window.getSelection();
if (!isSelection) {
top = console.scrollTop();
lFocus();
l$Console.scrollTop(lTop);
focus();
console.scrollTop(top);
}
});
}
Util.exec(callback);
});
}
};
}
this.hide = function() {
CloudCmd.View.hide();
};
function log(pText) {
write(pText, 'log');
}
this.log = function(pText) {
log(pText, 'log');
};
function error(pText) {
write(pText, 'error');
}
this.error = function(pText) {
log(pText, 'error');
};
function log(msg, status) {
var ret;
function write(msg, status) {
var isContain;
if (msg) {
Buffer[status] += msg;
ret = Util.isContainStr(Buffer[status], '\n');
isContain = Util.isContainStr(Buffer[status], '\n');
if (jqconsole && ret) {
if (jqconsole && isContain) {
jqconsole.Write(Buffer[status], status + '-msg');
Notify.send(Buffer[status]);
Buffer[status] = '';
@ -137,13 +138,15 @@ var CloudCmd, Util, DOM, CloudFunc, $;
Util.time(Name + ' load');
var lDir = CloudCmd.LIBDIRCLIENT + 'console/',
cssPath = lDir + 'css/',
jsPath = lDir + 'lib/',
lCSS = [
lDir + 'jqconsole.css',
lDir + 'ansi.css'
cssPath + 'jqconsole.css',
cssPath + 'ansi.css'
],
lAllCSS = CloudFunc.getCombineURL(lCSS),
lAllCSS = CloudFunc.getJoinURL(lCSS),
lFiles = [
lDir + 'jqconsole.js',
jsPath + 'jqconsole.js',
lAllCSS
];
@ -155,24 +158,32 @@ var CloudCmd, Util, DOM, CloudFunc, $;
});
}
function listener(pEvent) {
var lTRA = Key.TRA,
lESC = Key.ESC,
lIsBind = Key.isBind(),
lKey = pEvent.keyCode;
switch(lKey) {
case lTRA:
if (lIsBind) {
Console.show();
DOM.preventDefault(pEvent);
function addListeners(callback) {
var options = {
'connect' : function() {
log(Socket.CONNECTED);
},
'disconnect': function() {
error(Socket.DISCONNECTED);
}
break;
case lESC:
Console.hide();
break;
};
options[CHANNEL] = onMessage;
Socket.on(options);
Util.exec(callback);
}
function onMessage(json) {
if (json) {
Util.log(json);
log(json.stdout);
error(json.stderr);
}
DOM.Images.hideLoad();
}
init();

View file

@ -9,7 +9,7 @@ task 'watch', 'Build and watch the CoffeeScript source files', ->
task 'build', 'Build minified file with uglify', ->
console.log 'building...'
exec 'uglifyjs -o jqconsole.min.js lib/jqconsole.js', (err, res)->
exec 'uglifyjs -m -o jqconsole.min.js lib/jqconsole.js', (err, res)->
if err
console.error 'failed with', err
else

View file

@ -66,3 +66,19 @@ info.
2013.1.26 Version 2.7.7
* Support for middle click paste on linux. #47.
2013.8.5 Version 2.7.8
* Fix issue #51.
2014.2.3 Version 2.8.0
* Remove browser sniffing. Fix #37 #42 #53
* Update android support. Fix #41
* Add wrapper div for padding/styling of console. Fix #40
2014.2.8 Version 2.9.0
* Fixed `GetColumn` method to return the column number up until the cursor.
* `Dump`: Add header contents in console dump and remove extra spaces.
2014.2.8 Version 2.10.0
* Add `Clear` method to clear the console

View file

@ -15,10 +15,11 @@ queueing.
The plugin has been tested on the following browsers:
* IE 9+
* Chrome 10+
* Firefox 4+
* Opera 11+
* iOS 4+
* Chrome
* Firefox
* Opera
* iOS Safari and Chrome
* Android Chrome
##Getting Started
@ -134,8 +135,7 @@ Takes two arguments:
Sets the number of spaces inserted when indenting and removed when unindenting.
Takes one argument:
* __int__ *width*: The code of the key pressing which (when Ctrl is held) will
trigger this shortcut.
* __int__ *width*: The number of spaces in each indentation level.
Example:
@ -469,6 +469,9 @@ Resets the character matching configuration.
Resets the shortcut configuration.
###jqconsole.Clear
Clears the console's content excluding the current prompt
##Default Key Config
The console responds to the followind keys and key combinations by default:
@ -720,4 +723,8 @@ further customization.
##Contributors
[Max Shawabkeh](http://max99x.com/)
[Amjad Masad](http://twitter.com/amjad_masad)
[Amjad Masad](http://twitter.com/amasad)
## License
[MIT](http://opensource.org/licenses/MIT)

View file

@ -1,4 +1,3 @@
.jqconsole-ansi-bold {
/* font-weight: bold; */
}
@ -121,10 +120,10 @@
color: red;
}
.jqconsole-ansi-color-green {
color: #65b04b;
color: green;
}
.jqconsole-ansi-color-yellow {
color: #fed563;
color: yellow;
}
.jqconsole-ansi-color-blue {
color: rgb(49,123,249);
@ -146,10 +145,10 @@
background-color: red;
}
.jqconsole-ansi-background-color-green {
background-color: #65b04b;
background-color: green;
}
.jqconsole-ansi-background-color-yellow {
background-color: #fed563;
background-color: yellow;
}
.jqconsole-ansi-background-color-blue {
background-color: blue;

View file

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html>
<head>
<style>
html, body {
background-color: #333;
color: white;
font-family: monospace;
margin: 0;
padding: 0;
}
/* The console container element */
#console {
height: 400px;
width: 750px;
position:relative;
background-color: black;
border: 2px solid #CCC;
margin: 0 auto;
margin-top: 50px;
}
/* The inner console element. */
.jqconsole {
padding: 10px;
}
/* The cursor. */
.jqconsole-cursor {
background-color: gray;
}
/* The cursor color when the console looses focus. */
.jqconsole-blurred .jqconsole-cursor {
background-color: #666;
}
/* The current prompt text color */
.jqconsole-prompt {
color: #0d0;
}
/* The command history */
.jqconsole-old-prompt {
color: #0b0;
font-weight: normal;
}
/* The text color when in input mode. */
.jqconsole-input {
color: #dd0;
}
/* Previously entered input. */
.jqconsole-old-input {
color: #bb0;
font-weight: normal;
}
/* The text color of the output. */
.jqconsole-output {
color: white;
}
</style>
</head>
<body>
<div id="console"></div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../lib/jqconsole.js" type="text/javascript" charset="utf-8"></script>
<script>
$(function () {
var jqconsole = $('#console').jqconsole('Hi\n', '>>> ');
var startPrompt = function () {
// Start the prompt with history enabled.
jqconsole.Prompt(true, function (input) {
// Output input with the class jqconsole-output.
jqconsole.Write(input + '\n', 'jqconsole-output');
// Restart the prompt.
startPrompt();
});
};
startPrompt();
});
</script>
</body>
</html>

View file

@ -0,0 +1,120 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
<head>
<link rel="stylesheet" href="../css/ansi.css" type="text/css" media="all" />
<style>
html, body {
background-color: #333;
color: white;
font-family: monospace;
margin: 0;
padding: 0;
}
#console {
height: 400px;
width: 750px;
position:relative;
background-color: black;
border: 2px solid #CCC;
margin: 0 auto;
margin-top: 50px;
}
.jqconsole {
padding: 10px;
padding-bottom: 10px;
}
.jqconsole-cursor {
background-color: #999;
}
.jqconsole-blurred .jqconsole-cursor {
background-color: #666;
}
.jqconsole-prompt {
color: #0d0;
}
.jqconsole-old-prompt {
color: #0b0;
font-weight: normal;
}
.jqconsole-input {
color: #dd0;
}
.jqconsole-old-input {
color: #bb0;
font-weight: normal;
}
.brace {
color: #00FFFF;
}
.paran {
color: #FF00FF;
}
.bracket {
color: #FFFF00;
}
.jqconsole-composition {
background-color: red;
}
</style>
</head>
<body>
<div id="console"></div>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../lib/jqconsole.js"></script>
<script>
$(function() {
// Creating the console.
var header = 'Welcome to JQConsole!\n' +
'Use jqconsole.Write() to write and ' +
'jqconsole.Input() to read.\n';
window.jqconsole = $('#console').jqconsole(header, 'JS> ');
// Abort prompt on Ctrl+Z.
jqconsole.RegisterShortcut('Z', function() {
jqconsole.AbortPrompt();
handler();
});
// Move to line start Ctrl+A.
jqconsole.RegisterShortcut('A', function() {
jqconsole.MoveToStart();
handler();
});
// Move to line end Ctrl+E.
jqconsole.RegisterShortcut('E', function() {
jqconsole.MoveToEnd();
handler();
});
jqconsole.RegisterMatching('{', '}', 'brace');
jqconsole.RegisterMatching('(', ')', 'paran');
jqconsole.RegisterMatching('[', ']', 'bracket');
// Handle a command.
var handler = function(command) {
if (command) {
try {
jqconsole.Write('==> ' + window.eval(command) + '\n');
} catch (e) {
jqconsole.Write('ERROR: ' + e.message + '\n');
}
}
jqconsole.Prompt(true, handler, function(command) {
// Continue line if can't compile the command.
try {
Function(command);
} catch (e) {
if (/[\[\{\(]$/.test(command)) {
return 1;
} else {
return 0;
}
}
return false;
});
};
// Initiate the first prompt.
handler();
});
</script>
</body>
</html>

View file

@ -1,6 +1,6 @@
{
"name": "jqconsole",
"version": "2.7.7",
"version": "2.8.0",
"title": "Feature complete web terminal.",
"author": {
"name": "Amjad Masad",
@ -19,4 +19,4 @@
"dependencies": {
"jquery": ">=1.5"
}
}
}

View file

@ -0,0 +1,33 @@
module.exports = function(config) {
config.set({
frameworks: ["mocha"],
files: [
'test/vendor/*.js',
'lib/*.js',
'test/setup.coffee',
'test/ansi-test.coffee',
'test/jqconsole-test.coffee',
'test/prompt-test.coffee',
'test/shortcuts-test.coffee',
'test/history-test.coffee',
'test/matching-test.coffee',
'test/misc-test.coffee',
],
browsers: ['Chrome'],
reporters: ['dots', 'coverage'],
preprocessors: {
'lib/*.js': ['coverage'],
'test/*.coffee': ['coffee']
},
coffeePreprocessor: {
// options passed to the coffee compiler
options: {
bare: false,
}
},
coverageReporter: {
type : 'html',
dir : 'coverage/'
}
});
};

View file

@ -1,72 +1,9 @@
// Generated by CoffeeScript 1.3.3
// Generated by CoffeeScript 1.6.3
/*
Copyrights 2011, the repl.it project.
Licensed under the MIT license
*/
// Limit scope pollution from any deprecated API
(function() {
var matched, browser;
// Use of jQuery.browser is frowned upon.
// More details: http://api.jquery.com/jQuery.browser
// jQuery.uaMatch maintained for back-compat
jQuery.uaMatch = function( ua ) {
ua = ua.toLowerCase();
var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
/(msie) ([\w.]+)/.exec( ua ) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
[];
return {
browser: match[ 1 ] || "",
version: match[ 2 ] || "0"
};
};
matched = jQuery.uaMatch( navigator.userAgent );
browser = {};
if ( matched.browser ) {
browser[ matched.browser ] = true;
browser.version = matched.version;
}
// Chrome is Webkit, but Webkit is also Safari.
if ( browser.chrome ) {
browser.webkit = true;
} else if ( browser.webkit ) {
browser.safari = true;
}
jQuery.browser = browser;
jQuery.sub = function() {
function jQuerySub( selector, context ) {
return new jQuerySub.fn.init( selector, context );
}
jQuery.extend( true, jQuerySub, this );
jQuerySub.superclass = this;
jQuerySub.fn = jQuerySub.prototype = this();
jQuerySub.fn.constructor = jQuerySub;
jQuerySub.sub = this.sub;
jQuerySub.fn.init = function init( selector, context ) {
if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
context = jQuerySub( context );
}
return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
};
jQuerySub.fn.init.prototype = jQuerySub.fn;
var rootjQuerySub = jQuerySub(document);
return jQuerySub;
};
})();
(function() {
var $, Ansi, CLASS_ANSI, CLASS_BLURRED, CLASS_CURSOR, CLASS_HEADER, CLASS_INPUT, CLASS_OLD_PROMPT, CLASS_PREFIX, CLASS_PROMPT, DEFAULT_INDENT_WIDTH, DEFAULT_PROMPT_CONINUE_LABEL, DEFAULT_PROMPT_LABEL, EMPTY_DIV, EMPTY_SELECTOR, EMPTY_SPAN, ESCAPE_CHAR, ESCAPE_SYNTAX, E_KEYPRESS, JQConsole, KEY_BACKSPACE, KEY_DELETE, KEY_DOWN, KEY_END, KEY_ENTER, KEY_HOME, KEY_LEFT, KEY_PAGE_DOWN, KEY_PAGE_UP, KEY_RIGHT, KEY_TAB, KEY_UP, NEWLINE, STATE_INPUT, STATE_OUTPUT, STATE_PROMPT, spanHtml,
@ -142,24 +79,16 @@ Licensed under the MIT license
ESCAPE_SYNTAX = /\[(\d*)(?:;(\d*))*m/;
Ansi = (function() {
Ansi.prototype.COLORS = ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white'];
function Ansi() {
this.stylize = __bind(this.stylize, this);
this._closeSpan = __bind(this._closeSpan, this);
this._openSpan = __bind(this._openSpan, this);
this.getClasses = __bind(this.getClasses, this);
this._style = __bind(this._style, this);
this._color = __bind(this._color, this);
this._remove = __bind(this._remove, this);
this._append = __bind(this._append, this);
this.klasses = [];
}
@ -344,27 +273,16 @@ Licensed under the MIT license
};
JQConsole = (function() {
function JQConsole(container, header, prompt_label, prompt_continue_label) {
this.container = container;
function JQConsole(outer_container, header, prompt_label, prompt_continue_label) {
this._HideComposition = __bind(this._HideComposition, this);
this._ShowComposition = __bind(this._ShowComposition, this);
this._UpdateComposition = __bind(this._UpdateComposition, this);
this._EndComposition = __bind(this._EndComposition, this);
this._StartComposition = __bind(this._StartComposition, this);
this._CheckComposition = __bind(this._CheckComposition, this);
this._ProcessMatch = __bind(this._ProcessMatch, this);
this._HandleKey = __bind(this._HandleKey, this);
this._HandleChar = __bind(this._HandleChar, this);
this.isMobile = !!navigator.userAgent.match(/iPhone|iPad|iPod|Android/i);
this.isIos = !!navigator.userAgent.match(/iPhone|iPad|iPod/i);
this.isAndroid = !!navigator.userAgent.match(/Android/i);
@ -382,26 +300,41 @@ Licensed under the MIT license
this.history_new = '';
this.history_active = false;
this.shortcuts = {};
this.$console = $('<pre class="jqconsole"/>').appendTo(this.container);
this.$container = $('<div/>').appendTo(outer_container);
this.$container.css({
'top': 0,
'left': 0,
'right': 0,
'bottom': 0,
'position': 'absolute',
'overflow': 'auto'
});
this.$console = $('<pre class="jqconsole"/>').appendTo(this.$container);
this.$console.css({
position: 'absolute',
top: 0,
bottom: 0,
right: 0,
left: 0,
margin: 0,
overflow: 'auto'
'margin': 0,
'position': 'relative',
'min-height': '100%',
'box-sizing': 'border-box',
'-moz-box-sizing': 'border-box',
'-webkit-box-sizing': 'border-box'
});
this.$console_focused = true;
this.$input_container = $(EMPTY_DIV).appendTo(this.container);
this.$input_container = $(EMPTY_DIV).appendTo(this.$container);
this.$input_container.css({
position: 'relative',
position: 'absolute',
width: 1,
height: 0,
overflow: 'hidden'
});
this.$input_source = $('<textarea/>');
this.$input_source.attr('wrap', 'off').css({
this.$input_source = this.isAndroid ? $('<input/>') : $('<textarea/>');
this.$input_source.attr({
wrap: 'off',
autocapitalize: 'off',
autocorrect: 'off',
spellcheck: 'false',
autocomplete: 'off'
});
this.$input_source.css({
position: 'absolute',
width: 2
});
@ -421,7 +354,7 @@ Licensed under the MIT license
this._InitPrompt();
this._SetupEvents();
this.Write(this.header, CLASS_HEADER);
$(this.container).data('jqconsole', this);
$(outer_container).data('jqconsole', this);
}
JQConsole.prototype.ResetHistory = function() {
@ -455,7 +388,7 @@ Licensed under the MIT license
this.$input_container.detach();
this.$console.html('');
this.$prompt.appendTo(this.$console);
this.$input_container.appendTo(this.container);
this.$input_container.appendTo(this.$container);
this.Write(this.header, CLASS_HEADER);
return void 0;
};
@ -469,8 +402,7 @@ Licensed under the MIT license
return this.history_index = this.history.length;
};
/*------------------------ Shortcut Methods -----------------------------
*/
/*------------------------ Shortcut Methods -----------------------------*/
JQConsole.prototype._CheckKeyCode = function(key_code) {
@ -529,15 +461,16 @@ Licensed under the MIT license
return void 0;
};
/*---------------------- END Shortcut Methods ---------------------------
*/
/*---------------------- END Shortcut Methods ---------------------------*/
JQConsole.prototype.GetColumn = function() {
var lines;
this.$prompt_right.detach();
this.$prompt_cursor.text('');
lines = this.$console.text().split(NEWLINE);
this.$prompt_cursor.html('&nbsp;');
this.$prompt_cursor.after(this.$prompt_right);
return lines[lines.length - 1].length;
};
@ -728,7 +661,7 @@ Licensed under the MIT license
JQConsole.prototype.Dump = function() {
var $elems, elem;
$elems = this.$console.find("." + CLASS_HEADER).nextUntil("." + CLASS_PROMPT);
$elems = this.$console.find("." + CLASS_HEADER).nextUntil("." + CLASS_PROMPT).addBack();
return ((function() {
var _i, _len, _results;
_results = [];
@ -741,7 +674,7 @@ Licensed under the MIT license
}
}
return _results;
})()).join(' ');
})()).join('');
};
JQConsole.prototype.GetState = function() {
@ -777,8 +710,13 @@ Licensed under the MIT license
return void 0;
};
/*------------------------ Private Methods -------------------------------
*/
JQConsole.prototype.Clear = function() {
this.$console.find("." + CLASS_HEADER).nextUntil("." + CLASS_PROMPT).addBack().text('');
this.$prompt_cursor.detach();
return this.$prompt_after.before(this.$prompt_cursor);
};
/*------------------------ Private Methods -------------------------------*/
JQConsole.prototype._CheckInputQueue = function() {
@ -811,8 +749,7 @@ Licensed under the MIT license
};
JQConsole.prototype._SetupEvents = function() {
var cb, paste_event,
_this = this;
var _this = this;
if (this.isMobile) {
this.$console.click(function(e) {
e.preventDefault();
@ -865,8 +802,7 @@ Licensed under the MIT license
};
return setTimeout(addClass, 100);
});
paste_event = $.browser.opera ? 'input' : 'paste';
this.$input_source.bind(paste_event, function() {
this.$input_source.bind('paste', function() {
var handlePaste;
handlePaste = function() {
if (_this.in_composition) {
@ -881,21 +817,17 @@ Licensed under the MIT license
this.$input_source.keypress(this._HandleChar);
this.$input_source.keydown(this._HandleKey);
this.$input_source.keydown(this._CheckComposition);
if ($.browser.mozilla != null) {
this.$input_source.bind('compositionstart', this._StartComposition);
this.$input_source.bind('compositionend', this._EndCommposition);
this.$input_source.bind('text', this._UpdateComposition);
}
if ($.browser.opera != null) {
cb = function() {
if (_this.in_composition) {
return;
}
if (_this.$input_source.val().length) {
return _this._StartComposition();
}
};
return setInterval(cb, 200);
this.$input_source.bind('compositionstart', this._StartComposition);
this.$input_source.bind('compositionend', function(e) {
return setTimeout((function() {
return _this._EndComposition(e);
}), 0);
});
if (this.isAndroid) {
this.$input_source.bind('input', this._StartComposition);
return this.$input_source.bind('input', this._UpdateComposition);
} else {
return this.$input_source.bind('text', this._UpdateComposition);
}
};
@ -908,16 +840,6 @@ Licensed under the MIT license
if (char_code === 8 || char_code === 9 || char_code === 13) {
return false;
}
if ($.browser.mozilla) {
if (event.keyCode || event.altKey) {
return true;
}
}
if ($.browser.opera) {
if (event.altKey) {
return true;
}
}
this.$prompt_left.text(this.$prompt_left.text() + String.fromCharCode(char_code));
this._ScrollToEnd();
return false;
@ -949,10 +871,10 @@ Licensed under the MIT license
this._MoveDown();
break;
case KEY_PAGE_UP:
this._ScrollUp();
this._ScrollPage('up');
break;
case KEY_PAGE_DOWN:
this._ScrollDown();
this._ScrollPage('down');
break;
default:
return true;
@ -991,10 +913,10 @@ Licensed under the MIT license
this.MoveToEnd(false);
break;
case KEY_PAGE_UP:
this._ScrollUp();
this._ScrollPage('up');
break;
case KEY_PAGE_DOWN:
this._ScrollDown();
this._ScrollPage('down');
break;
default:
return true;
@ -1048,6 +970,7 @@ Licensed under the MIT license
JQConsole.prototype._HandleEnter = function(shift) {
var continuation, text,
_this = this;
this._EndComposition();
if (shift) {
return this._InsertNewLine(true);
} else {
@ -1301,52 +1224,49 @@ Licensed under the MIT license
return _results;
};
JQConsole.prototype._ScrollUp = function() {
JQConsole.prototype._ScrollPage = function(dir) {
var target;
target = this.$console[0].scrollTop - this.$console.height();
return this.$console.stop().animate({
scrollTop: target
}, 'fast');
};
JQConsole.prototype._ScrollDown = function() {
var target;
target = this.$console[0].scrollTop + this.$console.height();
return this.$console.stop().animate({
target = this.$container[0].scrollTop;
if (dir === 'up') {
target -= this.$container.height();
} else {
target += this.$container.height();
}
return this.$container.stop().animate({
scrollTop: target
}, 'fast');
};
JQConsole.prototype._ScrollToEnd = function() {
var cont,
_this = this;
this.$console.scrollTop(this.$console[0].scrollHeight);
cont = function() {
var doc_height, line_height, optimal_pos, pos, rel_pos, screen_left, screen_top;
line_height = _this.$prompt_cursor.height();
screen_top = _this.$window.scrollTop();
screen_left = _this.$window.scrollLeft();
doc_height = document.documentElement.clientHeight;
pos = _this.$prompt_cursor.offset();
rel_pos = _this.$prompt_cursor.position();
_this.$input_container.css({
left: rel_pos.left,
top: rel_pos.top
});
optimal_pos = pos.top - (2 * line_height);
if (_this.isMobile && (typeof orientation !== "undefined" && orientation !== null)) {
if (screen_top < pos.top || screen_top > pos.top) {
return _this.$window.scrollTop(optimal_pos);
}
} else {
if (screen_top + doc_height < pos.top) {
return _this.$window.scrollTop(pos.top - doc_height + line_height);
} else if (screen_top > optimal_pos) {
return _this.$window.scrollTop(pos.top);
}
var pos;
this.$container.scrollTop(this.$container[0].scrollHeight);
pos = this.$prompt_cursor.position();
this.$input_container.css({
left: pos.left,
top: pos.top
});
return setTimeout(this.ScrollWindowToPrompt.bind(this), 50);
};
JQConsole.prototype.ScrollWindowToPrompt = function() {
var doc_height, line_height, optimal_pos, pos, screen_left, screen_top;
line_height = this.$prompt_cursor.height();
screen_top = this.$window.scrollTop();
screen_left = this.$window.scrollLeft();
doc_height = document.documentElement.clientHeight;
pos = this.$prompt_cursor.offset();
optimal_pos = pos.top - (2 * line_height);
if (this.isMobile && (typeof orientation !== "undefined" && orientation !== null)) {
if (screen_top < pos.top || screen_top > pos.top) {
return this.$window.scrollTop(optimal_pos);
}
};
return setTimeout(cont, 0);
} else {
if (screen_top + doc_height < pos.top) {
return this.$window.scrollTop(pos.top - doc_height + line_height);
} else if (screen_top > optimal_pos) {
return this.$window.scrollTop(pos.top);
}
}
};
JQConsole.prototype._SelectPromptLabel = function(continuation) {
@ -1365,14 +1285,6 @@ Licensed under the MIT license
}
};
JQConsole.prototype._outerHTML = function($elem) {
if (document.body.outerHTML) {
return $elem.get(0).outerHTML;
} else {
return $(EMPTY_DIV).append($elem.eq(0).clone()).html();
}
};
JQConsole.prototype._Wrap = function($elem, index, cls) {
var html, text;
text = $elem.html();
@ -1511,9 +1423,6 @@ Licensed under the MIT license
JQConsole.prototype._CheckComposition = function(e) {
var key;
key = e.keyCode || e.which;
if (($.browser.opera != null) && this.in_composition) {
this._UpdateComposition();
}
if (key === 229) {
if (this.in_composition) {
return this._UpdateComposition();
@ -1524,17 +1433,23 @@ Licensed under the MIT license
};
JQConsole.prototype._StartComposition = function() {
this.$input_source.bind(E_KEYPRESS, this._EndComposition);
if (this.in_composition) {
return;
}
this.in_composition = true;
this._ShowComposition();
return setTimeout(this._UpdateComposition, 0);
};
JQConsole.prototype._EndComposition = function() {
this.$input_source.unbind(E_KEYPRESS, this._EndComposition);
this.in_composition = false;
if (!this.in_composition) {
return;
}
this._HideComposition();
return this.$input_source.val('');
this.$prompt_left.text(this.$prompt_left.text() + this.$composition.text());
this.$composition.text('');
this.$input_source.val('');
return this.in_composition = false;
};
JQConsole.prototype._UpdateComposition = function(e) {

View file

@ -0,0 +1,41 @@
{
"name": "jq-console",
"version": "2.10.0",
"description": "Feature complete web terminal",
"main": "jqconsole.min.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "./node_modules/karma/bin/karma start --single-run"
},
"repository": {
"type": "git",
"url": "git://github.com/replit/jq-console.git"
},
"keywords": [
"jq-consoel",
"console",
"terminal"
],
"author": "amasad",
"license": "MIT",
"bugs": {
"url": "https://github.com/replit/jq-console/issues"
},
"devDependencies": {
"karma-coffee-preprocessor": "~0.1.2",
"mocha": "~1.17.1",
"karma-script-launcher": "~0.1.0",
"karma-chrome-launcher": "~0.1.2",
"karma-firefox-launcher": "~0.1.3",
"karma-html2js-preprocessor": "~0.1.0",
"karma-jasmine": "~0.1.5",
"requirejs": "~2.1.10",
"karma-requirejs": "~0.2.1",
"karma-phantomjs-launcher": "~0.1.1",
"karma": "~0.10.9",
"karma-mocha": "~0.1.1",
"karma-coverage": "~0.1.5"
}
}

View file

@ -48,21 +48,21 @@ DEFAULT_PROMPT_CONINUE_LABEL = '... '
# The default number of spaces inserted when indenting.
DEFAULT_INDENT_WIDTH = 2
CLASS_ANSI = "#{CLASS_PREFIX}ansi-"
CLASS_ANSI = "#{CLASS_PREFIX}ansi-"
ESCAPE_CHAR = '\x1B'
ESCAPE_SYNTAX = /\[(\d*)(?:;(\d*))*m/
class Ansi
COLORS: ['black', 'red', 'green', 'yellow', 'blue', 'magenta', 'cyan', 'white']
constructor: ->
@klasses = [];
_append: (klass) =>
klass = "#{CLASS_ANSI}#{klass}"
if @klasses.indexOf(klass) is -1
@klasses.push klass
_remove: (klasses...) =>
for klass in klasses
if klass in ['fonts', 'color', 'background-color']
@ -70,15 +70,15 @@ class Ansi
else
klass = "#{CLASS_ANSI}#{klass}"
@klasses = (cls for cls in @klasses when cls isnt klass)
_color: (i) => @COLORS[i]
_style: (code) =>
code = 0 if code == ''
code = parseInt code
return if isNaN code
switch code
when 0 then @klasses = []
when 1 then @_append 'bold'
@ -113,27 +113,27 @@ class Ansi
when 53 then @_append 'overline'
when 54 then @_remove 'framed'
when 55 then @_remove 'overline'
getClasses: => @klasses.join ' '
_openSpan: (text) => "<span class=\"#{@getClasses()}\">#{text}"
_closeSpan: (text) => "#{text}</span>"
stylize: (text) =>
text = @_openSpan text
i = 0
while (i = text.indexOf(ESCAPE_CHAR ,i)) and i isnt -1
if d = text[i...].match ESCAPE_SYNTAX
@_style code for code in d[1...]
text = @_closeSpan(text[0...i]) + @_openSpan text[i + 1 + d[0].length...]
else i++
return @_closeSpan text
return @_closeSpan text
# Helper functions
spanHtml = (klass, content) -> "<span class=\"#{klass}\">#{content or ''}</span>"
class JQConsole
# Creates a console.
# @arg container: The DOM element into which the console is inserted.
@ -143,21 +143,21 @@ class JQConsole
# Defaults to DEFAULT_PROMPT_LABEL.
# @arg prompt_continue: The label to show before continuation lines of the
# command prompt. Optional. Defaults to DEFAULT_PROMPT_CONINUE_LABEL.
constructor: (@container, header, prompt_label, prompt_continue_label) ->
constructor: (outer_container, header, prompt_label, prompt_continue_label) ->
# Mobile devices supported sniff.
@isMobile = !!navigator.userAgent.match /iPhone|iPad|iPod|Android/i
@isIos = !!navigator.userAgent.match /iPhone|iPad|iPod/i
@isAndroid = !!navigator.userAgent.match /Android/i
@$window = $(window)
# The header written when the console is reset.
@header = header or ''
# The prompt label used by Prompt().
@prompt_label_main = if typeof prompt_label == 'string'
prompt_label
else
prompt_label
else
DEFAULT_PROMPT_LABEL
@prompt_label_continue = (prompt_continue_label or
DEFAULT_PROMPT_CONINUE_LABEL)
@ -193,60 +193,76 @@ class JQConsole
# A table of custom shortcuts, mapping character codes to callbacks.
@shortcuts = {}
@$container = $('<div/>').appendTo outer_container
@$container.css
'top': 0
'left': 0
'right': 0
'bottom': 0
'position': 'absolute'
'overflow': 'auto'
# The main console area. Everything else happens inside this.
@$console = $('<pre class="jqconsole"/>').appendTo @container
@$console.css
position: 'absolute'
top: 0
bottom: 0
right: 0
left: 0
margin: 0
overflow: 'auto'
@$console = $('<pre class="jqconsole"/>').appendTo @$container
@$console.css
'margin': 0
'position': 'relative'
'min-height': '100%'
'box-sizing': 'border-box'
'-moz-box-sizing': 'border-box'
'-webkit-box-sizing': 'border-box'
# Whether the console currently has focus.
@$console_focused = true
# On screen somehow invisible textbox for input.
# Copied from codemirror2, this works for both mobile and desktop browsers.
@$input_container = $(EMPTY_DIV).appendTo @container
@$input_container = $(EMPTY_DIV).appendTo @$container
@$input_container.css
position: 'relative'
position: 'absolute'
width: 1
height: 0
overflow: 'hidden'
@$input_source = $('<textarea/>')
@$input_source.attr('wrap', 'off').css
# On android autocapitlize works for input.
@$input_source = if @isAndroid then $('<input/>') else $('<textarea/>')
@$input_source.attr
wrap: 'off'
autocapitalize: 'off'
autocorrect: 'off'
spellcheck: 'false'
autocomplete: 'off'
@$input_source.css
position: 'absolute'
width: 2
@$input_source.appendTo @$input_container
@$composition = $(EMPTY_DIV)
@$composition.addClass "#{CLASS_PREFIX}composition"
@$composition.css
display: 'inline'
position: 'relative'
# Hash containing all matching settings
# openings/closings[char] = matching_config
# openings/closings[char] = matching_config
# where char is the opening/closing character.
# clss is an array of classes for fast unhighlighting
# for matching_config see Match method
@matchings =
@matchings =
openings: {}
closings: {}
clss: []
@ansi = new Ansi()
# Prepare console for interaction.
@_InitPrompt()
@_SetupEvents()
@Write @header, CLASS_HEADER
# Save this instance to be accessed if lost.
$(@container).data 'jqconsole', this
$(outer_container).data 'jqconsole', this
#### Reset methods
@ -260,7 +276,7 @@ class JQConsole
# Resets the matching configuration.
ResetMatchings: ->
@matchings =
@matchings =
openings: {}
closings: {}
clss: []
@ -279,23 +295,23 @@ class JQConsole
@$input_container.detach()
@$console.html ''
@$prompt.appendTo @$console
@$input_container.appendTo @container
@$input_container.appendTo @$container
@Write @header, CLASS_HEADER
return undefined
#### History Methods
# Get the current history
GetHistory: ->
@history
# Set the history
SetHistory: (history) ->
@history = history.slice()
@history_index = @history.length
###------------------------ Shortcut Methods -----------------------------###
# Checks the type/value of key codes passed in for registering/unregistering
# shortcuts and handles accordingly.
_CheckKeyCode: (key_code) ->
@ -306,16 +322,16 @@ class JQConsole
if not (0 < key_code < 256) or isNaN key_code
throw new Error 'Key code must be a number between 0 and 256 exclusive.'
return key_code
# A helper function responsible for calling the register/unregister callback
# twice passing in both the upper and lower case letters.
_LetterCaseHelper: (key_code, callback)->
callback key_code
if 65 <= key_code <= 90 then callback key_code + 32
if 97 <= key_code <= 122 then callback key_code - 32
# Registers a Ctrl+Key shortcut.
# @arg key_code: The code of the key pressing which (when Ctrl is held) will
# trigger this shortcut. If a string is provided, the character code of
@ -330,10 +346,10 @@ class JQConsole
addShortcut = (key) =>
if key not of @shortcuts then @shortcuts[key] = []
@shortcuts[key].push callback
@_LetterCaseHelper key_code, addShortcut
return undefined
# Removes a Ctrl+Key shortcut from shortcut registry.
# @arg key_code: The code of the key pressing which (when Ctrl is held) will
# trigger this shortcut. If a string is provided, the character code of
@ -343,24 +359,26 @@ class JQConsole
# would be removed.
UnRegisterShortcut: (key_code, handler) ->
key_code = @_CheckKeyCode key_code
removeShortcut = (key)=>
if key of @shortcuts
if handler
@shortcuts[key].splice @shortcuts[key].indexOf(handler), 1
else
delete @shortcuts[key]
@_LetterCaseHelper key_code, removeShortcut
return undefined
###---------------------- END Shortcut Methods ---------------------------###
# Returns the 0-based number of the column on which the cursor currently is.
GetColumn: ->
@$prompt_right.detach()
@$prompt_cursor.text ''
lines = @$console.text().split NEWLINE
@$prompt_cursor.html '&nbsp;'
@$prompt_cursor.after @$prompt_right
return lines[lines.length - 1].length
# Returns the 0-based number of the line on which the cursor currently is.
@ -415,7 +433,7 @@ class JQConsole
@_AppendPromptText text
@_ScrollToEnd()
return undefined
# Replaces the main prompt label.
# @arg main_label: The new main label for the next prompt.
# @arg continue_label: The new continuation label for the next prompt. Optional.
@ -431,12 +449,12 @@ class JQConsole
Write: (text, cls, escape=true) ->
if escape
text = @ansi.stylize $(EMPTY_SPAN).text(text).html()
span = $(EMPTY_SPAN).html text
if cls? then span.addClass cls
@Append span
# Adds a dom node, where any text would have been inserted
# Adds a dom node, where any text would have been inserted
# @arg node: The node to insert.
Append: (node) ->
$node = $(node).insertBefore @$prompt
@ -444,7 +462,7 @@ class JQConsole
# Force reclaculation of the cursor's position.
@$prompt_cursor.detach().insertAfter @$prompt_left
return $node
# Starts an input operation. If another input or prompt operation is currently
# underway, the new input operation is enqueued and will be called when the
# current operation and all previously enqueued operations finish.
@ -536,26 +554,29 @@ class JQConsole
# @arg open: the openning character
# @arg close: the closing character
# @arg cls: the html class to add to the matched characters
RegisterMatching: (open, close, cls) ->
match_config =
RegisterMatching: (open, close, cls) ->
match_config =
opening_char: open
closing_char: close
cls: cls
@matchings.clss.push(cls)
@matchings.openings[open] = match_config
@matchings.closings[close] = match_config
# Unregisters a character matching. cls is optional.
UnRegisterMatching: (open, close) ->
cls = @matchings.openings[open].cls
delete @matchings.openings[open]
delete @matchings.closings[close]
@matchings.clss.splice @matchings.clss.indexOf(cls), 1
# Dumps the content of the console before the current prompt.
Dump: ->
$elems = @$console.find(".#{CLASS_HEADER}").nextUntil(".#{CLASS_PROMPT}")
$elems = @$console
.find(".#{CLASS_HEADER}")
.nextUntil(".#{CLASS_PROMPT}")
.addBack()
return (
for elem in $elems
@ -563,7 +584,7 @@ class JQConsole
$(elem).text().replace /^\s+/, '>>> '
else
$(elem).text()
).join ' '
).join ''
# Gets the current prompt state.
GetState: ->
@ -573,12 +594,12 @@ class JQConsole
'output'
else
'prompt'
# Disables focus and input on the console.
Disable: ->
@$input_source.attr 'disabled', on
@$input_source.blur();
# Enables focus and input on the console.
Enable: ->
@$input_source.attr 'disabled', off
@ -599,6 +620,17 @@ class JQConsole
@_MoveTo all_lines, false
return undefined
# Clear the console keeping only the prompt.
Clear: ->
@$console
.find(".#{CLASS_HEADER}")
.nextUntil(".#{CLASS_PROMPT}")
.addBack()
.text ''
# Bug in Chrome were the cursor's position is not recalculated
@$prompt_cursor.detach()
@$prompt_after.before @$prompt_cursor
###------------------------ Private Methods -------------------------------###
_CheckInputQueue: ->
@ -660,7 +692,7 @@ class JQConsole
# Binds all the required input and focus events.
_SetupEvents: ->
# Click to focus.
if @isMobile
@$console.click (e) =>
@ -672,14 +704,14 @@ class JQConsole
# paste on linux desktop.
if e.which == 2
@Focus()
else
else
fn = =>
if not window.getSelection().toString()
e.preventDefault()
@Focus()
# Force selection update.
setTimeout fn, 0
# Mark the console with a style when it loses focus.
@$input_source.focus =>
@_ScrollToEnd()
@ -691,17 +723,16 @@ class JQConsole
hideTextInput = =>
if @isIos and @$console_focused then @$input_source.hide()
setTimeout hideTextInput, 500
@$input_source.blur =>
@$console_focused = false
if @isIos then @$input_source.show()
addClass = =>
if not @$console_focused then @$console.addClass CLASS_BLURRED
setTimeout addClass, 100
# Intercept pasting.
paste_event = if $.browser.opera then 'input' else 'paste'
@$input_source.bind paste_event, =>
@$input_source.bind 'paste', =>
handlePaste = =>
# Opera fires input on composition end.
return if @in_composition
@ -715,24 +746,25 @@ class JQConsole
@$input_source.keypress @_HandleChar
@$input_source.keydown @_HandleKey
@$input_source.keydown @_CheckComposition
# Firefox don't fire any key event for composition characters, so we listen
# for the unstandard composition-events.
if $.browser.mozilla?
@$input_source.bind 'compositionstart', @_StartComposition
@$input_source.bind 'compositionend', @_EndCommposition
@$input_source.bind 'compositionstart', @_StartComposition
@$input_source.bind 'compositionend', (e) =>
# Wait for the input element to update so we don't rely on buggy e.data
setTimeout((=> @_EndComposition(e)), 0)
# Android has an out of screen text input for autocorrect and autocomplete
# and since it doesn't allow disabling we use composition events and more
# hacks to get input to work.
if @isAndroid
# Text is handled via composition events but for things like spaces
# we need to emulate a composition start.
@$input_source.bind 'input', @_StartComposition
@$input_source.bind 'input', @_UpdateComposition
else
@$input_source.bind 'text', @_UpdateComposition
# There is no way to detect compositionstart in opera so we poll for it.
if $.browser.opera?
cb = =>
return if @in_composition
# if there was characters that actually escaped to the input source
# then its most probably a multibyte char.
if @$input_source.val().length
@_StartComposition()
setInterval cb, 200
# Handles a character key press.
# @arg event: The jQuery keyboard Event object to handle.
_HandleChar: (event) =>
@ -741,26 +773,17 @@ class JQConsole
# Allow alt key to pass through for unicode & multibyte characters.
if @state == STATE_OUTPUT or event.metaKey or event.ctrlKey
return true
# IE & Chrome capture non-control characters and Enter.
# Mozilla and Opera capture everything.
# This is the most reliable cross-browser; charCode/keyCode break on Opera.
char_code = event.which
# Skip Enter on IE and Chrome and Tab & backspace on Opera.
# Skip Enter on IE and Chrome and Tab & backspace on Opera.
# These are handled in _HandleKey().
if char_code in [8, 9, 13] then return false
# Pass control characters which are captured on Mozilla/Safari.
if $.browser.mozilla
if event.keyCode or event.altKey
return true
# Pass control characters which are captured on Opera.
if $.browser.opera
if event.altKey
return true
@$prompt_left.text @$prompt_left.text() + String.fromCharCode char_code
@_ScrollToEnd()
return false
@ -770,12 +793,12 @@ class JQConsole
_HandleKey: (event) =>
# We let the browser take over during output mode.
if @state == STATE_OUTPUT then return true
key = event.keyCode or event.which
# Check for char matching next time the callstack unwinds.
setTimeout $.proxy(@_CheckMatchings, this), 0
# Don't care about alt-modifier.
if event.altKey
return true
@ -789,8 +812,8 @@ class JQConsole
when KEY_TAB then @_Unindent()
when KEY_UP then @_MoveUp()
when KEY_DOWN then @_MoveDown()
when KEY_PAGE_UP then @_ScrollUp()
when KEY_PAGE_DOWN then @_ScrollDown()
when KEY_PAGE_UP then @_ScrollPage 'up'
when KEY_PAGE_DOWN then @_ScrollPage 'down'
# Allow other Shift shortcuts to pass through to the browser.
else return true
return false
@ -807,8 +830,8 @@ class JQConsole
when KEY_DOWN then @_HistoryNext()
when KEY_HOME then @MoveToStart false
when KEY_END then @MoveToEnd false
when KEY_PAGE_UP then @_ScrollUp()
when KEY_PAGE_DOWN then @_ScrollDown()
when KEY_PAGE_UP then @_ScrollPage 'up'
when KEY_PAGE_DOWN then @_ScrollPage 'down'
# Let any other key continue its way to keypress.
else return true
return false
@ -839,6 +862,7 @@ class JQConsole
# Handles the user pressing the Enter key.
# @arg shift: Whether the shift key is held.
_HandleEnter: (shift) ->
@_EndComposition()
if shift
@_InsertNewLine true
else
@ -863,7 +887,7 @@ class JQConsole
@input_callback = null
if callback then callback text
@_CheckInputQueue()
if @multiline_callback
if @async_multiline
@multiline_callback text, continuation
@ -871,8 +895,8 @@ class JQConsole
continuation @multiline_callback text
else
continuation false
# Returns the appropriate variables for usage in methods that depends on the
# direction of the interaction with the console.
_GetDirectionals: (back) ->
@ -882,11 +906,11 @@ class JQConsole
$prompt_rel_opposite = if back then @$prompt_after else @$prompt_before
MoveToLimit = if back
$.proxy @MoveToStart, @
else
else
$.proxy @MoveToEnd, @
MoveDirection = if back
$.proxy @_MoveLeft, @
else
$.proxy @_MoveLeft, @
else
$.proxy @_MoveRight, @
which_end = if back then 'last' else 'first'
where_append = if back then 'prependTo' else 'appendTo'
@ -900,7 +924,7 @@ class JQConsole
which_end
where_append
}
# Moves the cursor vertically in the current prompt,
# in the same column. (Used by _MoveUp, _MoveDown)
_VerticalMove: (up) ->
@ -911,7 +935,7 @@ class JQConsole
MoveToLimit
MoveDirection
} = @_GetDirectionals(up)
if $prompt_relative.is EMPTY_SELECTOR then return
pos = @$prompt_left.text().length
MoveToLimit()
@ -919,8 +943,8 @@ class JQConsole
text = $prompt_which.text()
$prompt_opposite.text if up then text[pos..] else text[...pos]
$prompt_which.text if up then text[...pos] else text[pos..]
# Moves the cursor to the line above the current one, in the same column.
_MoveUp: ->
@_VerticalMove true
@ -928,7 +952,7 @@ class JQConsole
# Moves the cursor to the line below the current one, in the same column.
_MoveDown: ->
@_VerticalMove()
# Moves the cursor horizontally in the current prompt.
# Used by _MoveLeft, _MoveRight
_HorizontalMove: (whole_word, back) ->
@ -941,7 +965,7 @@ class JQConsole
where_append
} = @_GetDirectionals(back)
regexp = if back then /\w*\W*$/ else /^\w*\W*/
text = $prompt_which.text()
if text
if whole_word
@ -960,12 +984,12 @@ class JQConsole
$which_line = $(EMPTY_SPAN)[where_append] $prompt_rel_opposite
$which_line.append $(EMPTY_SPAN).text @$prompt_label.text()
$which_line.append $(EMPTY_SPAN).text $prompt_opposite.text()
$opposite_line = $prompt_relative.children()[which_end]().detach()
@$prompt_label.text $opposite_line.children().first().text()
$prompt_which.text $opposite_line.children().last().text()
$prompt_opposite.text ''
# Moves the cursor to the left.
# @arg whole_word: Whether to move by a whole word rather than a character.
_MoveLeft: (whole_word) ->
@ -975,7 +999,7 @@ class JQConsole
# @arg whole_word: Whether to move by a whole word rather than a character.
_MoveRight: (whole_word) ->
@_HorizontalMove whole_word
# Moves the cursor either to the start or end of the current prompt line(s).
_MoveTo: (all_lines, back) ->
{
@ -985,7 +1009,7 @@ class JQConsole
MoveToLimit
MoveDirection
} = @_GetDirectionals(back)
if all_lines
# Warning! FF 3.6 hangs on is(EMPTY_SELECTOR)
until $prompt_relative.is(EMPTY_SELECTOR) and $prompt_which.text() == ''
@ -1069,57 +1093,58 @@ class JQConsole
@_InsertNewLine()
@$prompt_left.text line
# Scrolls the console area up one page (with animation).
_ScrollUp: ->
target = @$console[0].scrollTop - @$console.height()
@$console.stop().animate {scrollTop: target}, 'fast'
# Scrolls the console area down one page (with animation).
_ScrollDown: ->
target = @$console[0].scrollTop + @$console.height()
@$console.stop().animate {scrollTop: target}, 'fast'
# Scrolls the console area down/up one page (with animation).
_ScrollPage: (dir) ->
target = @$container[0].scrollTop
if dir == 'up'
target -= @$container.height()
else
target += @$container.height()
@$container.stop().animate {scrollTop: target}, 'fast'
# Scrolls the console area to its bottom;
# Scrolls the window to the cursor vertical position.
# Called with every input/output to the console.
_ScrollToEnd: ->
# Scroll console to the bottom.
@$console.scrollTop @$console[0].scrollHeight
# The cursor's top position is effected by the scroll-top of the console
# so we need to this asynchronously to give the browser a chance to
@$container.scrollTop @$container[0].scrollHeight
# Move the input element to the cursor position.
pos = @$prompt_cursor.position()
@$input_container.css
left: pos.left
top: pos.top
# Give time for mobile browsers to zoom in on textarea
setTimeout @ScrollWindowToPrompt.bind(@), 50
ScrollWindowToPrompt: ->
# The cursor's top position is effected by the scroll-top of the console
# so we need to this asynchronously to give the browser a chance to
# reflow and recaluclate the cursor's possition.
cont = =>
line_height = @$prompt_cursor.height()
screen_top = @$window.scrollTop()
screen_left = @$window.scrollLeft()
doc_height = document.documentElement.clientHeight
pos = @$prompt_cursor.offset()
rel_pos = @$prompt_cursor.position()
# Move the input element to the cursor position.
@$input_container.css
left: rel_pos.left
top: rel_pos.top
optimal_pos = pos.top - (2 * line_height)
# Follow the cursor vertically on mobile and desktop.
if @isMobile and orientation?
# Since the keyboard takes up most of the screen, we don't care about how
# far the the cursor position from the screen top is. We just follow it.
if screen_top < pos.top or screen_top > pos.top
@$window.scrollTop optimal_pos
else
if screen_top + doc_height < pos.top
# Scroll just to a place where the cursor is in the view port.
@$window.scrollTop pos.top - doc_height + line_height
else if screen_top > optimal_pos
# If the window is scrolled beyond the cursor, scroll to the cursor's
# position and give two line to the top.
@$window.scrollTop pos.top
setTimeout cont, 0
line_height = @$prompt_cursor.height()
screen_top = @$window.scrollTop()
screen_left = @$window.scrollLeft()
doc_height = document.documentElement.clientHeight
pos = @$prompt_cursor.offset()
optimal_pos = pos.top - (2 * line_height)
# Follow the cursor vertically on mobile and desktop.
if @isMobile and orientation?
# Since the keyboard takes up most of the screen, we don't care about how
# far the the cursor position from the screen top is. We just follow it.
if screen_top < pos.top or screen_top > pos.top
@$window.scrollTop optimal_pos
else
if screen_top + doc_height < pos.top
# Scroll just to a place where the cursor is in the view port.
@$window.scrollTop pos.top - doc_height + line_height
else if screen_top > optimal_pos
# If the window is scrolled beyond the cursor, scroll to the cursor's
# position and give two line to the top.
@$window.scrollTop pos.top
# Selects the prompt label appropriate to the current mode.
# @arg continuation: If true, returns the continuation prompt rather than
# the main one.
@ -1128,14 +1153,7 @@ class JQConsole
return if continuation then (' \n' + @prompt_label_continue) else @prompt_label_main
else
return if continuation then '\n ' else ' '
# Cross-browser outerHTML
_outerHTML: ($elem) ->
if document.body.outerHTML
return $elem.get(0).outerHTML
else
return $(EMPTY_DIV).append($elem.eq(0).clone()).html()
# Wraps a single character in an element with a <span> having a class
# @arg $elem: The JqDom element in question
# @arg index: the index of the character to be wrapped
@ -1146,7 +1164,7 @@ class JQConsole
spanHtml(cls, text[index])+
text[index + 1...]
$elem.html html
# Walks a string of characters incrementing current_count each time a char is found
# and decrementing each time an opposing char is found.
# @arg text: the text in question
@ -1170,11 +1188,11 @@ class JQConsole
current_count++
else if ch is opposing_char
current_count--
if current_count is 0
if current_count is 0
return {index: index, current_count: current_count}
return {index: -1, current_count: current_count}
_ProcessMatch: (config, back, before_char) =>
[char, opposing_char] = if back
[
@ -1187,7 +1205,7 @@ class JQConsole
config['closing_char']
]
{$prompt_which, $prompt_relative} = @_GetDirectionals(back)
current_count = 1
found = false
# check current line first
@ -1210,40 +1228,40 @@ class JQConsole
text = $elem.html()
{index, current_count} = @_WalkCharacters text, char, opposing_char, current_count, back
if index > -1
# When checking for matchings ona different line going forward we must decrement
# When checking for matchings ona different line going forward we must decrement
# the index since the current char is not included
if !back then index--
@_Wrap $elem, index, config.cls
found = true
return false
return found
# Unrwaps all prevoisly matched characters.
# Checks if the cursor's current character is one to be matched, then walks
# the following/preceeding characters to look for the opposing character that
# would satisfy the match. If found both characters would be wrapped with a
# would satisfy the match. If found both characters would be wrapped with a
# span and applied the html class that was found in the match_config.
_CheckMatchings: (before_char) ->
current_char = if before_char then @$prompt_left.text()[@$prompt_left.text().length - 1...] else @$prompt_right.text()[0]
# on every move unwrap all matched elements
# TODO(amasad): cache previous matched elements since this must be costly
$('.' + cls, @$console).contents().unwrap() for cls in @matchings.clss
if config = @matchings.closings[current_char]
found = @_ProcessMatch config, true, before_char
else if config = @matchings.openings[current_char]
found = @_ProcessMatch config, false, before_char
else if not before_char
@_CheckMatchings true
if before_char
@_Wrap @$prompt_left, @$prompt_left.html().length - 1, config.cls if found
else
# Wrap current element when a matching was found
@_Wrap @$prompt_right, 0, config.cls if found
# Sets the prompt to the previous history item.
_HistoryPrevious: ->
if not @history_active then return
@ -1261,48 +1279,48 @@ class JQConsole
@SetPromptText @history_new
else
@SetPromptText @history[++@history_index]
# Check if this could be the start of a composition or an update to it.
_CheckComposition: (e) =>
key = e.keyCode or e.which
if $.browser.opera? and @in_composition
@_UpdateComposition()
if key == 229
if @in_composition then @_UpdateComposition() else @_StartComposition()
# Starts a multibyte character composition.
_StartComposition: =>
@$input_source.bind E_KEYPRESS, @_EndComposition
return if @in_composition
@in_composition = true
@_ShowComposition()
setTimeout @_UpdateComposition, 0
# Ends a multibyte character composition.
_EndComposition: =>
@$input_source.unbind E_KEYPRESS, @_EndComposition
@in_composition = false
return if not @in_composition
@_HideComposition()
@$prompt_left.text @$prompt_left.text() + @$composition.text()
@$composition.text ''
@$input_source.val ''
@in_composition = false
# Updates a multibyte character composition.
_UpdateComposition: (e) =>
cb = =>
return if not @in_composition
@$composition.text @$input_source.val()
setTimeout cb, 0
# Shows a multibyte character composition.
_ShowComposition: =>
@$composition.css 'height', @$prompt_cursor.height()
@$composition.empty()
@$composition.appendTo @$prompt_left
# Hides a multibyte character composition.
_HideComposition: =>
# We just detach the element because by now the text value of this element
# is already extracted and has been put on the left of the prompt.
@$composition.detach()
$.fn.jqconsole = (header, prompt_main, prompt_continue) ->
new JQConsole this, header, prompt_main, prompt_continue

View file

@ -0,0 +1,50 @@
Ansi = $().jqconsole.Ansi
CssPre = 'jqconsole-ansi-'
describe 'Ansi', ->
describe '#_style', ->
ansi = null
beforeEach -> ansi = new Ansi()
it 'applies and replaces font', ->
ansi._style '11'
assert.deepEqual ["#{CssPre}fonts-1"], ansi.klasses
ansi._style '19'
assert.deepEqual ["#{CssPre}fonts-9"], ansi.klasses
it 'applies and replaces color', ->
ansi._style '31'
assert.deepEqual ["#{CssPre}color-#{ansi._color(1)}"], ansi.klasses
ansi._style '37'
assert.deepEqual ["#{CssPre}color-#{ansi._color(7)}"], ansi.klasses
ansi._style '39'
assert.deepEqual [], ansi.klasses
it 'applies and replaces background-color', ->
ansi._style '41'
assert.deepEqual ["#{CssPre}background-color-#{ansi._color(1)}"], ansi.klasses
ansi._style '47'
assert.deepEqual ["#{CssPre}background-color-#{ansi._color(7)}"], ansi.klasses
ansi._style '49'
assert.deepEqual [], ansi.klasses
describe '#stylize', ->
ansi = null
beforeEach -> ansi = new Ansi()
it 'appends a styles', ->
html = ansi.stylize 'test\x1B[1mhello'
assert.equal html, "<span class=\"\">test</span><span class=\"#{CssPre}bold\">hello</span>"
it 'appends multiple styles', ->
html = ansi.stylize 'test\x1B[1;2mhello'
assert.equal html, "<span class=\"\">test</span><span class=\"#{CssPre}bold #{CssPre}lighter\">hello</span>"
it 'appends multiple styles at different intervals', ->
html = ansi.stylize 'test\x1B[1mhello\x1B[2mhi'
assert.equal html, "<span class=\"\">test</span><span class=\"#{CssPre}bold\">hello</span><span class=\"#{CssPre}bold #{CssPre}lighter\">hi</span>"
html = ansi.stylize 'a\x1B[53mb\x1B[21mc\x1B[md'
assert.equal html, "<span class=\"#{CssPre}bold #{CssPre}lighter\">a</span><span class=\"#{CssPre}bold #{CssPre}lighter #{CssPre}overline\">b</span><span class=\"#{CssPre}overline\">c</span><span class=\"\">d</span>"

View file

@ -0,0 +1,49 @@
{jqconsole, typer: {typeA, keyDown, type}} = jqconsoleSetup()
describe 'History', ->
describe '#GetHistory', ->
it 'gets the history', ->
jqconsole.Prompt true, ->
typeA()
keyDown 13
deepEqual ['a'], jqconsole.GetHistory()
describe '#SetHistory', ->
it 'sets history', ->
h = ['a', 'b']
jqconsole.SetHistory h
deepEqual h, jqconsole.GetHistory()
describe '#ResetHistory', ->
it 'resets the history', ->
jqconsole.ResetHistory()
deepEqual jqconsole.history, []
describe 'History interaction in the prompt', ->
it 'gets the prev history item', ->
jqconsole.Prompt true, ->
type 'foo'
equal jqconsole.GetPromptText(), 'foo'
keyDown 13
jqconsole.Prompt true, ->
equal jqconsole.GetPromptText(), ''
keyDown 38
equal jqconsole.GetPromptText(), 'foo'
jqconsole.AbortPrompt()
it 'gets the next history item', ->
jqconsole.Prompt true, ->
type 'foo'
keyDown 13
jqconsole.Prompt true, ->
type 'bar'
keyDown 13
jqconsole.Prompt true, ->
keyDown 38
equal jqconsole.GetPromptText(), 'bar'
keyDown 38
equal jqconsole.GetPromptText(), 'foo'
keyDown 40
equal jqconsole.GetPromptText(), 'bar'
keyDown 40
equal jqconsole.GetPromptText(), ''

View file

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html>
<head>
<title>Mocha</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="vendor/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="vendor/mocha.js"></script>
<script>mocha.setup('bdd')</script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js" type="text/javascript" charset="utf-8"></script>
<script src="../lib/jqconsole.js"></script>
<script src="vendor/assert.js"></script>
<script src="vendor/coffeescript.js"></script>
<script>
var tests = [ 'setup.coffee'
, 'ansi-test.coffee'
, 'jqconsole-test.coffee'
, 'prompt-test.coffee'
, 'shortcuts-test.coffee'
, 'history-test.coffee'
]
tests.forEach(function (test) {
CoffeeScript.load(test, start)
})
function start () {
if (start.called == null) start.called = 0;
if (++start.called == tests.length) {
mocha.run();
}
}
</script>
</body>
</html>

View file

@ -0,0 +1,29 @@
describe 'JQConsole', ->
{$container, jqconsole} = jqconsoleSetup()
describe '#constructor', ->
it 'instantiates', ->
equal jqconsole.header, 'header'
equal jqconsole.prompt_label_main, 'prompt_label'
equal jqconsole.prompt_label_continue, 'prompt_continue'
equal jqconsole.indent_width, 2
equal jqconsole.GetState(), 'output'
deepEqual jqconsole.input_queue, []
deepEqual jqconsole.history, []
ok jqconsole.$console.length
ok jqconsole.$console instanceof jQuery
equal $container.text().trim(), 'header'
strictEqual $container.data('jqconsole'), jqconsole
ok jqconsole.$prompt.length
ok jqconsole.$input_source.length
it 'setup events', (done)->
counter = 0
jqconsole.$input_source.focus ->
counter++
jqconsole.$console.mouseup()
fn = ->
ok counter
done()
setTimeout fn, 10

View file

@ -0,0 +1,29 @@
{jqconsole, createScroll, typer: {typeA, keyDown, type}} = jqconsoleSetup()
describe 'Matching', ->
afterEach ->
jqconsole.AbortPrompt()
describe '#RegisterMatching', ->
it 'Adds matching', (done) ->
jqconsole.Prompt true, ->
jqconsole.RegisterMatching '(', ')', 'parens'
type 'foo ( bar )'
keyDown 39
check = ->
$parens = jqconsole.$prompt.find '.parens'
assert.equal $parens.first().text(), '('
assert.equal $parens.last().text(), ')'
done()
setTimeout check, 0
describe '#UnRegsiterMatching', ->
it 'Removes matching', (done) ->
jqconsole.Prompt true, ->
jqconsole.UnRegisterMatching '(', ')'
type 'foo ( bar )'
keyDown 39
check = ->
assert.ok !jqconsole.$prompt.find('.parens').length
done()
setTimeout check, 0

View file

@ -0,0 +1,65 @@
jqconsole = type = keyDown = null
describe 'Misc methods', ->
beforeEach ->
{jqconsole, typer: {keyDown, type}} = jqconsoleSetup()
jqconsole.Prompt true, ->
describe '#GetColumn', ->
it 'should get the column number of the cursor', ->
label_length = 'headerprompt_label'.length
assert.equal jqconsole.GetColumn(), label_length
type ' '
assert.equal jqconsole.GetColumn(), label_length + 3
describe '#GetLine', ->
it 'should get the line number of the cursor', ->
assert.equal jqconsole.GetLine(), 0
keyDown 13, shiftKey: on
assert.equal jqconsole.GetLine(), 1
keyDown 13, shiftKey: on
assert.equal jqconsole.GetLine(), 2
describe '#SetIndentWidth', ->
it 'changes the indent width', ->
l = jqconsole.GetColumn()
jqconsole.SetIndentWidth 10
keyDown 9
assert.equal jqconsole.GetColumn(), l + 10
describe '#GetIndentWidth', ->
it 'gets the indent width', ->
jqconsole.SetIndentWidth 20
assert.equal jqconsole.GetIndentWidth(), 20
describe '#Dump', ->
it 'dumps the console content', ->
type 'foo'
keyDown 13
jqconsole.Write('wat')
assert.equal jqconsole.Dump(), 'headerprompt_labelfoo\nwat'
describe '#SetPromptLabel', ->
it 'Sets the prompt label for the next prompt', ->
jqconsole.SetPromptLabel 'foobar123', 'shitmang'
keyDown 13, shiftKey: on
jqconsole.AbortPrompt()
jqconsole.Prompt true, ->
assert.ok jqconsole.Dump().indexOf 'foobar123' > -1
assert.ok jqconsole.Dump().indexOf 'shitmang' > -1
describe '#Disable', ->
it 'disables the console', ->
jqconsole.Disable()
assert.ok jqconsole.$input_source.attr 'disabled'
describe '#Enable', ->
it 'enables the console', ->
jqconsole.Disable()
jqconsole.Enable()
assert.ok not jqconsole.$input_source.attr 'disabled'
describe '#Clear', ->
it 'clears the console', ->
jqconsole.Clear()
assert.equal jqconsole.Dump(), ''

View file

@ -0,0 +1,400 @@
{jqconsole, createScroll, typer: {typeA, keyDown, type}} = jqconsoleSetup()
describe 'Prompt Interaction', ->
describe '#Prompt', ->
after ->
jqconsole.AbortPrompt()
it 'inits prompt and auto-focuses', ->
counter = 0
jqconsole.$input_source.focus ->
counter++
resultCb = ->
jqconsole.Prompt true, resultCb
equal jqconsole.GetState(), 'prompt'
ok counter
ok jqconsole.history_active
strictEqual jqconsole.input_callback, resultCb
equal jqconsole.$prompt.text().trim(), 'prompt_label'
describe '#AbortPrompt', ->
it 'aborts the prompt', ->
jqconsole.Prompt true, ->
jqconsole.AbortPrompt()
equal jqconsole.$prompt.text().trim(), ''
it 'restarts queued prompts', ->
aCb = ->
jqconsole.Prompt false, aCb
bCb = ->
jqconsole.Prompt true, bCb
strictEqual jqconsole.input_callback, aCb
strictEqual jqconsole.history_active, false
jqconsole.AbortPrompt()
strictEqual jqconsole.input_callback, bCb
strictEqual jqconsole.history_active, true
jqconsole.AbortPrompt()
describe '#GetPromptText', ->
beforeEach -> jqconsole.Prompt true, ->
afterEach -> jqconsole.AbortPrompt()
it 'gets the current prompt text', ->
type 'foo'
equal jqconsole.$prompt.text().trim(), 'prompt_labelfoo'
equal jqconsole.GetPromptText(), 'foo'
it 'gets the current prompt text with the label', ->
type 'foo'
equal jqconsole.$prompt.text().trim(), 'prompt_labelfoo'
equal jqconsole.GetPromptText(true), 'prompt_labelfoo'
describe '#ClearPromptText', ->
beforeEach -> jqconsole.Prompt true, ->
afterEach -> jqconsole.AbortPrompt()
it 'Clears the current prompt text', ->
type 'foo'
equal jqconsole.GetPromptText(), 'foo'
jqconsole.ClearPromptText()
equal jqconsole.GetPromptText(), ''
it 'Clears prompt text with label', ->
type 'foo'
equal jqconsole.GetPromptText(), 'foo'
jqconsole.ClearPromptText true
equal jqconsole.GetPromptText(true), ''
describe '#SetPromptText', ->
beforeEach -> jqconsole.Prompt true, ->
afterEach -> jqconsole.AbortPrompt()
it 'sets the current prompt text', ->
type 'bar'
jqconsole.SetPromptText('foo')
equal jqconsole.GetPromptText(), 'foo'
describe 'Moving', ->
beforeEach -> jqconsole.Prompt true, ->
afterEach -> jqconsole.AbortPrompt()
it 'moves to the left', ->
type 'xyz'
keyDown 37
equal jqconsole.$prompt_left.text().trim(), 'xy'
keyDown 37
equal jqconsole.$prompt_left.text().trim(), 'x'
keyDown 37
equal jqconsole.$prompt_left.text().trim(), ''
keyDown 37
equal jqconsole.$prompt_left.text().trim(), ''
it 'moves to the right', ->
type 'xyz'
keyDown 37
keyDown 37
equal jqconsole.$prompt_left.text().trim(), 'x'
keyDown 39
equal jqconsole.$prompt_left.text().trim(), 'xy'
keyDown 39
equal jqconsole.$prompt_left.text().trim(), 'xyz'
keyDown 39
equal jqconsole.$prompt_left.text().trim(), 'xyz'
it 'moves to the prev line when at the first char of the line moving left', ->
type 'xyz'
keyDown 13, shiftKey: on
type 'abc'
equal jqconsole.$prompt_left.text().trim(), 'abc'
keyDown 37
keyDown 37
keyDown 37
keyDown 37
equal jqconsole.$prompt_left.text().trim(), 'xyz'
it 'moves to the next line when at the last char of the line moving right', ->
type 'xyz'
keyDown 13, shiftKey: on
type 'abc'
equal jqconsole.$prompt_left.text().trim(), 'abc'
keyDown 37
keyDown 37
keyDown 37
keyDown 37
equal jqconsole.$prompt_left.text().trim(), 'xyz'
keyDown 39
equal jqconsole.$prompt_right.text().trim(), 'abc'
it 'moves to the start of the word', ->
type 'xyz abc'
keyDown 37, metaKey: on
equal jqconsole.$prompt_right.text().trim(), 'abc'
equal jqconsole.$prompt_left.text().trim(), 'xyz'
it 'moves to the end of the word', ->
type 'xyz abc'
keyDown 37, metaKey: on
keyDown 37, metaKey: on
keyDown 39, metaKey: on
equal jqconsole.$prompt_right.text().trim(), 'abc'
equal jqconsole.$prompt_left.text().trim(), 'xyz'
it 'moves to the end of the word', ->
type 'xyz abc'
keyDown 37, metaKey: on
keyDown 37, metaKey: on
keyDown 39, metaKey: on
equal jqconsole.$prompt_right.text().trim(), 'abc'
equal jqconsole.$prompt_left.text().trim(), 'xyz'
it 'moves to the start of the line', ->
type 'xyz abc'
keyDown 36
equal jqconsole.$prompt_right.text().trim(), 'xyz abc'
it 'moves to the end of the line', ->
type 'xyz abc'
keyDown 36
equal jqconsole.$prompt_right.text().trim(), 'xyz abc'
keyDown 35
equal jqconsole.$prompt_right.text().trim(), ''
equal jqconsole.$prompt_left.text().trim(), 'xyz abc'
it 'moves to the start of the prompt', ->
type 'xyz abc'
keyDown 13, shiftKey: on
type 'hafm olim'
keyDown 36, metaKey: on
equal jqconsole.$prompt_right.text().trim(), 'xyz abc'
equal jqconsole.$prompt_after.text().trim(), 'prompt_continuehafm olim'
it 'moves to the end of the prompt', ->
type 'xyz abc'
keyDown 13, shiftKey: on
type 'hafm olim'
keyDown 36, metaKey: on
equal jqconsole.$prompt_right.text().trim(), 'xyz abc'
equal jqconsole.$prompt_after.text().trim(), 'prompt_continuehafm olim'
keyDown 35, metaKey: on
equal jqconsole.$prompt_left.text().trim(), 'hafm olim'
equal jqconsole.$prompt_before.text().trim(), 'prompt_labelxyz abc'
it 'moves up one line', ->
type 'xyz'
keyDown 13, shiftKey: on
type 'a'
keyDown 38, shiftKey: on
equal jqconsole.$prompt_right.text().trim(), 'yz'
it 'moves down one line', ->
type 'xyz'
keyDown 13, shiftKey: on
type 'a'
# Meta key also works.
keyDown 38, metaKey: on
equal jqconsole.$prompt_right.text().trim(), 'yz'
keyDown 40, metaKey: on
equal jqconsole.$prompt_right.text().trim(), ''
it 'respects the column when moving vertically', ->
type 'xyz'
keyDown 13, shiftKey: on
type 'ab'
keyDown 38, shiftKey: on
equal jqconsole.$prompt_right.text().trim(), 'z'
keyDown 40, shiftKey: on
keyDown 37
keyDown 37
equal jqconsole.$prompt_right.text().trim(), 'ab'
keyDown 38, shiftKey: on
equal jqconsole.$prompt_right.text().trim(), 'xyz'
describe 'Control Keys', ->
beforeEach -> jqconsole.Prompt true, ->
afterEach -> jqconsole.AbortPrompt()
it 'handles enter', ->
jqconsole.AbortPrompt()
counter = 0
jqconsole.Prompt true, -> counter++
typeA()
keyDown 13
ok counter
equal jqconsole.$console.find('.jqconsole-old-prompt').last().text().trim(), 'prompt_labela'
# Restart the prompt for other tests.
jqconsole.Prompt true, ->
it 'handles shift+enter', ->
keyDown 13, shiftKey: on
equal jqconsole.$prompt.text().trim(), 'prompt_label \nprompt_continue'
it 'handles tab', ->
typeA()
keyDown 9
equal jqconsole.$prompt.text().trim(), 'prompt_label a'
it 'handles shift+tab', ->
typeA()
keyDown 9, shiftKey: on
equal jqconsole.$prompt.text().trim(), 'prompt_labela'
it 'backspace', ->
typeA()
keyDown 8
equal jqconsole.$prompt.text().trim(), 'prompt_label'
it 'cntrl+backspace', ->
typeA()
typeA()
keyDown 8, metaKey: on
equal jqconsole.$prompt.text().trim(), 'prompt_label'
it 'deletes a char', ->
type 'xyz'
keyDown 37
equal jqconsole.$prompt_right.text().trim(), 'z'
keyDown 46
equal jqconsole.$prompt_right.text().trim(), ''
it 'deletes a word', ->
type 'xyz abc'
keyDown 37
keyDown 37
keyDown 37
equal jqconsole.$prompt_right.text().trim(), 'abc'
keyDown 46, metaKey: on
equal jqconsole.$prompt_right.text().trim(), ''
describe 'scrolling', ->
console_height = null
_fast = null
before ->
jQuery.fx.speeds.fast = 10
after ->
jQuery.fx.speeds.fast = _fast
beforeEach ->
jqconsole.Reset()
jqconsole.Prompt true, ->
# Make sure the console has a scroll.
{ console_height } = createScroll()
it 'scrolls up', (done) ->
before = jqconsole.$container[0].scrollTop
keyDown 33
cb = ->
equal jqconsole.$container[0].scrollTop, before - console_height
done()
# * 2 is some arbitrary number otherwise it fails.
setTimeout cb, jQuery.fx.speeds.fast * 2
it 'scrolls up twice', (done) ->
before = jqconsole.$container[0].scrollTop
keyDown 33
cb = ->
keyDown 33
cb = ->
equal jqconsole.$container[0].scrollTop, before - (console_height * 2)
done()
setTimeout cb, jQuery.fx.speeds.fast * 2
# * 2 is some arbitrary number otherwise it fails.
setTimeout cb, jQuery.fx.speeds.fast * 2
it 'scrolls down', (done) ->
before = jqconsole.$container[0].scrollTop
keyDown 33
cb = ->
keyDown 34
cb = ->
equal jqconsole.$container[0].scrollTop, before
done()
setTimeout cb, jQuery.fx.speeds.fast * 2
# * 2 is some arbitrary number otherwise it fails.
setTimeout cb, jQuery.fx.speeds.fast * 2
describe 'Typing', ->
beforeEach -> jqconsole.Prompt true, ->
afterEach -> jqconsole.AbortPrompt()
it 'handles chars', ->
str = ''
test = (ch) ->
str += ch
e = $.Event('keypress')
e.which = ch.charCodeAt(0)
jqconsole.$input_source.trigger e
equal jqconsole.$prompt.text().trim(), 'prompt_label' + str
test 'a'
test 'Z'
test '$'
test 'ƒ'
it 'scrolls all the way down when typing', (done) ->
createScroll()
keyDown 33
cb = ->
before = jqconsole.$container[0].scrollTop
type('a')
cb = ->
notEqual jqconsole.$container[0].scrollTop, before
done()
setTimeout cb, 0
setTimeout cb, jQuery.fx.speeds.fast * 2
describe 'Multiline', ->
beforeEach ->
if jqconsole.GetState() is 'prompt'
jqconsole.AbortPrompt()
it 'executes multiline callback', (done) ->
jqconsole.Prompt true, (-> ), ->
done()
type('foo')
keyDown 13
it 'indents', ->
jqconsole.Prompt true, (-> ), ->
return 2
type('foo')
keyDown 13
equal jqconsole.GetState(), 'prompt'
equal jqconsole.GetPromptText(), 'foo\n '
it 'keeps indentation on shift+enter', ->
jqconsole.Prompt true, (-> ), ->
return 2
type('foo')
keyDown 9
keyDown 13, shiftKey: on
equal jqconsole.GetState(), 'prompt'
equal jqconsole.GetPromptText(), ' foo\n '
it 'unindents', ->
jqconsole.Prompt true, (-> ), ->
return -2
type('foo')
keyDown 9
keyDown 13
equal jqconsole.GetPromptText(), ' foo\n'
it 'skip indent callback', (done) ->
jqconsole.Prompt true, done.bind(null, null), ->
return false
type('foo')
keyDown 13
it 'handles async treatment', (done) ->
jqconsole.Prompt true, done.bind(null, null), ((text, cb) -> cb false), on
type('foo')
keyDown 13
describe '#Input', ->
it 'should enable history', (done) ->
jqconsole.Prompt true, done.bind(null, null)
jqconsole.Input (text) ->
assert.equal text, 'foo'
setTimeout (-> keyDown(13)), 0
type 'foo'
keyDown 13

View file

@ -0,0 +1,44 @@
window.equal = assert.equal
window.notEqual = assert.notEqual
window.deepEqual = assert.deepEqual
window.strictEqual = assert.strictEqual
window.ok = assert.ok
JQConsole = $().jqconsole.JQConsole
window.jqconsoleSetup = ->
$container = $('<div/>').css
height: '100px'
widht: '200px'
position: 'relative'
$container.appendTo('body')
jqconsole = new JQConsole($container, 'header', 'prompt_label', 'prompt_continue')
typer =
typeA: ->
e = $.Event('keypress')
e.which = 'a'.charCodeAt(0)
jqconsole.$input_source.trigger e
keyDown: (code, options = {}) ->
e = $.Event('keydown')
e.which = code
e[k] = v for k, v of options
jqconsole.$input_source.trigger e
type: (str) ->
type = (chr) ->
e = $.Event('keypress')
e.which = chr.charCodeAt(0)
jqconsole.$input_source.trigger(e)
type chr for chr in str
createScroll = ->
line_height = jqconsole.$prompt.height()
console_height = jqconsole.$container.height()
lines_per_page = Math.ceil(console_height / line_height)
for i in [0..lines_per_page * 5]
jqconsole.SetPromptText('foo')
jqconsole._HandleEnter()
jqconsole.Prompt true, ->
{line_height, console_height, lines_per_page}
{$container, jqconsole, typer, createScroll}

View file

@ -0,0 +1,60 @@
{jqconsole, typer: {keyDown}} = jqconsoleSetup()
describe 'Shortcuts', ->
describe '#RegisterShortcut', ->
# Fails in v2.7.7
it 'throws if callback not function', ->
assert.throws ->
jqconsole.RegisterShortcut 'b', 'c'
it 'registers shortcut by string', ->
cb = ->
jqconsole.RegisterShortcut 'a', cb
deepEqual jqconsole.shortcuts['a'.charCodeAt(0)], [cb]
deepEqual jqconsole.shortcuts['A'.charCodeAt(0)], [cb]
it 'registers shortcut by charcode', ->
cb = ->
jqconsole.RegisterShortcut 'c'.charCodeAt(0), cb
deepEqual jqconsole.shortcuts['c'.charCodeAt(0)], [cb]
deepEqual jqconsole.shortcuts['C'.charCodeAt(0)], [cb]
it 'shortcuts must be ascii', ->
assert.throws ->
jqconsole.RegisterShortcut 'ƒ', ->
describe '#UnRegisterShortcut', ->
it 'removes all callback for a shortcut', ->
cb = ->
jqconsole.RegisterShortcut 'a', cb
jqconsole.UnRegisterShortcut 'a'
deepEqual jqconsole.shortcuts['a'.charCodeAt(0)], undefined
it 'removes specific callback', ->
aCb = ->
bCb = ->
jqconsole.RegisterShortcut 'a', aCb
jqconsole.RegisterShortcut 'a', bCb
jqconsole.UnRegisterShortcut 'a', aCb
deepEqual jqconsole.shortcuts['a'.charCodeAt(0)], [bCb]
describe '#ResetShortcuts', ->
it 'resets all shortcuts', ->
cb1 = ->
cb2 = ->
jqconsole.RegisterShortcut 'a', cb1
jqconsole.RegisterShortcut 'b', cb2
jqconsole.ResetShortcuts()
deepEqual jqconsole.shortcuts, {}
describe 'Invoking Shortcuts', ->
it 'invokes shortcuts', ->
jqconsole.Prompt true, ->
counter = 0
jqconsole.RegisterShortcut 'a', ->
strictEqual this, jqconsole
counter++
keyDown 'a'.charCodeAt(0), metaKey: on
ok counter

369
lib/client/console/test/vendor/assert.js vendored Normal file
View file

@ -0,0 +1,369 @@
// http://wiki.commonjs.org/wiki/Unit_Testing/1.0
//
// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!
//
// Copyright (c) 2011 Jxck
//
// Originally from node.js (http://nodejs.org)
// Copyright Joyent, Inc.
//
// 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 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.
(function(global) {
// Object.create compatible in IE
var create = Object.create || function(p) {
if (!p) throw Error('no type');
function f() {};
f.prototype = p;
return new f();
};
// UTILITY
var util = {
inherits: function(ctor, superCtor) {
ctor.super_ = superCtor;
ctor.prototype = create(superCtor.prototype, {
constructor: {
value: ctor,
enumerable: false,
writable: true,
configurable: true
}
});
}
};
var pSlice = Array.prototype.slice;
// 1. The assert module provides functions that throw
// AssertionError's when particular conditions are not met. The
// assert module must conform to the following interface.
var assert = ok;
global['assert'] = assert;
if (typeof module === 'object' && typeof module.exports === 'object') {
module.exports = assert;
};
// 2. The AssertionError is defined in assert.
// new assert.AssertionError({ message: message,
// actual: actual,
// expected: expected })
assert.AssertionError = function AssertionError(options) {
this.name = 'AssertionError';
this.message = options.message || (options.actual + ' ' + options.operator + ' ' +options.expected)
this.actual = options.actual;
this.expected = options.expected;
this.operator = options.operator;
var stackStartFunction = options.stackStartFunction || fail;
if (Error.captureStackTrace) {
Error.captureStackTrace(this, stackStartFunction);
}
};
util.inherits(assert.AssertionError, Error);
function replacer(key, value) {
if (value === undefined) {
return '' + value;
}
if (typeof value === 'number' && (isNaN(value) || !isFinite(value))) {
return value.toString();
}
if (typeof value === 'function' || value instanceof RegExp) {
return value.toString();
}
return value;
}
function truncate(s, n) {
if (typeof s == 'string') {
return s.length < n ? s : s.slice(0, n);
} else {
return s;
}
}
assert.AssertionError.prototype.toString = function() {
if (this.message) {
return [this.name + ':', this.message].join(' ');
} else {
return [
this.name + ':',
truncate(JSON.stringify(this.actual, replacer), 128),
this.operator,
truncate(JSON.stringify(this.expected, replacer), 128)
].join(' ');
}
};
// assert.AssertionError instanceof Error
assert.AssertionError.__proto__ = Error.prototype;
// At present only the three keys mentioned above are used and
// understood by the spec. Implementations or sub modules can pass
// other keys to the AssertionError's constructor - they will be
// ignored.
// 3. All of the following functions must throw an AssertionError
// when a corresponding condition is not met, with a message that
// may be undefined if not provided. All assertion methods provide
// both the actual and expected values to the assertion error for
// display purposes.
function fail(actual, expected, message, operator, stackStartFunction) {
throw new assert.AssertionError({
message: message,
actual: actual,
expected: expected,
operator: operator,
stackStartFunction: stackStartFunction
});
}
// EXTENSION! allows for well behaved errors defined elsewhere.
assert.fail = fail;
// 4. Pure assertion tests whether a value is truthy, as determined
// by !!guard.
// assert.ok(guard, message_opt);
// This statement is equivalent to assert.equal(true, !!guard,
// message_opt);. To test strictly for the value true, use
// assert.strictEqual(true, guard, message_opt);.
function ok(value, message) {
if (!!!value) fail(value, true, message, '==', assert.ok);
}
assert.ok = ok;
// 5. The equality assertion tests shallow, coercive equality with
// ==.
// assert.equal(actual, expected, message_opt);
assert.equal = function equal(actual, expected, message) {
if (actual != expected) fail(actual, expected, message, '==', assert.equal);
};
// 6. The non-equality assertion tests for whether two objects are not equal
// with != assert.notEqual(actual, expected, message_opt);
assert.notEqual = function notEqual(actual, expected, message) {
if (actual == expected) {
fail(actual, expected, message, '!=', assert.notEqual);
}
};
// 7. The equivalence assertion tests a deep equality relation.
// assert.deepEqual(actual, expected, message_opt);
assert.deepEqual = function deepEqual(actual, expected, message) {
if (!_deepEqual(actual, expected)) {
fail(actual, expected, message, 'deepEqual', assert.deepEqual);
}
};
function _deepEqual(actual, expected) {
// 7.1. All identical values are equivalent, as determined by ===.
if (actual === expected) {
return true;
// } else if (Buffer.isBuffer(actual) && Buffer.isBuffer(expected)) {
// if (actual.length != expected.length) return false;
//
// for (var i = 0; i < actual.length; i++) {
// if (actual[i] !== expected[i]) return false;
// }
//
// return true;
//
// 7.2. If the expected value is a Date object, the actual value is
// equivalent if it is also a Date object that refers to the same time.
} else if (actual instanceof Date && expected instanceof Date) {
return actual.getTime() === expected.getTime();
// 7.3 If the expected value is a RegExp object, the actual value is
// equivalent if it is also a RegExp object with the same source and
// properties (`global`, `multiline`, `lastIndex`, `ignoreCase`).
} else if (actual instanceof RegExp && expected instanceof RegExp) {
return actual.source === expected.source &&
actual.global === expected.global &&
actual.multiline === expected.multiline &&
actual.lastIndex === expected.lastIndex &&
actual.ignoreCase === expected.ignoreCase;
// 7.4. Other pairs that do not both pass typeof value == 'object',
// equivalence is determined by ==.
} else if (typeof actual != 'object' && typeof expected != 'object') {
return actual == expected;
// 7.5 For all other Object pairs, including Array objects, equivalence is
// determined by having the same number of owned properties (as verified
// with Object.prototype.hasOwnProperty.call), the same set of keys
// (although not necessarily the same order), equivalent values for every
// corresponding key, and an identical 'prototype' property. Note: this
// accounts for both named and indexed properties on Arrays.
} else {
return objEquiv(actual, expected);
}
}
function isUndefinedOrNull(value) {
return value === null || value === undefined;
}
function isArguments(object) {
return Object.prototype.toString.call(object) == '[object Arguments]';
}
function objEquiv(a, b) {
if (isUndefinedOrNull(a) || isUndefinedOrNull(b))
return false;
// an identical 'prototype' property.
if (a.prototype !== b.prototype) return false;
//~~~I've managed to break Object.keys through screwy arguments passing.
// Converting to array solves the problem.
if (isArguments(a)) {
if (!isArguments(b)) {
return false;
}
a = pSlice.call(a);
b = pSlice.call(b);
return _deepEqual(a, b);
}
try {
var ka = Object.keys(a),
kb = Object.keys(b),
key, i;
} catch (e) {//happens when one is a string literal and the other isn't
return false;
}
// having the same number of owned properties (keys incorporates
// hasOwnProperty)
if (ka.length != kb.length)
return false;
//the same set of keys (although not necessarily the same order),
ka.sort();
kb.sort();
//~~~cheap key test
for (i = ka.length - 1; i >= 0; i--) {
if (ka[i] != kb[i])
return false;
}
//equivalent values for every corresponding key, and
//~~~possibly expensive deep test
for (i = ka.length - 1; i >= 0; i--) {
key = ka[i];
if (!_deepEqual(a[key], b[key])) return false;
}
return true;
}
// 8. The non-equivalence assertion tests for any deep inequality.
// assert.notDeepEqual(actual, expected, message_opt);
assert.notDeepEqual = function notDeepEqual(actual, expected, message) {
if (_deepEqual(actual, expected)) {
fail(actual, expected, message, 'notDeepEqual', assert.notDeepEqual);
}
};
// 9. The strict equality assertion tests strict equality, as determined by ===.
// assert.strictEqual(actual, expected, message_opt);
assert.strictEqual = function strictEqual(actual, expected, message) {
if (actual !== expected) {
fail(actual, expected, message, '===', assert.strictEqual);
}
};
// 10. The strict non-equality assertion tests for strict inequality, as
// determined by !==. assert.notStrictEqual(actual, expected, message_opt);
assert.notStrictEqual = function notStrictEqual(actual, expected, message) {
if (actual === expected) {
fail(actual, expected, message, '!==', assert.notStrictEqual);
}
};
function expectedException(actual, expected) {
if (!actual || !expected) {
return false;
}
if (expected instanceof RegExp) {
return expected.test(actual);
} else if (actual instanceof expected) {
return true;
} else if (expected.call({}, actual) === true) {
return true;
}
return false;
}
function _throws(shouldThrow, block, expected, message) {
var actual;
if (typeof expected === 'string') {
message = expected;
expected = null;
}
try {
block();
} catch (e) {
actual = e;
}
message = (expected && expected.name ? ' (' + expected.name + ').' : '.') +
(message ? ' ' + message : '.');
if (shouldThrow && !actual) {
fail('Missing expected exception' + message);
}
if (!shouldThrow && expectedException(actual, expected)) {
fail('Got unwanted exception' + message);
}
if ((shouldThrow && actual && expected &&
!expectedException(actual, expected)) || (!shouldThrow && actual)) {
throw actual;
}
}
// 11. Expected to throw an error:
// assert.throws(block, Error_opt, message_opt);
assert.throws = function(block, /*optional*/error, /*optional*/message) {
_throws.apply(this, [true].concat(pSlice.call(arguments)));
};
// EXTENSION! This is annoying to write outside this module.
assert.doesNotThrow = function(block, /*optional*/error, /*optional*/message) {
_throws.apply(this, [false].concat(pSlice.call(arguments)));
};
assert.ifError = function(err) { if (err) {throw err;}};
})(this);

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

241
lib/client/console/test/vendor/mocha.css vendored Normal file
View file

@ -0,0 +1,241 @@
@charset "utf-8";
body {
font: 20px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif;
padding: 60px 50px;
}
#mocha ul, #mocha li {
margin: 0;
padding: 0;
}
#mocha ul {
list-style: none;
}
#mocha h1, #mocha h2 {
margin: 0;
}
#mocha h1 {
margin-top: 15px;
font-size: 1em;
font-weight: 200;
}
#mocha h1 a {
text-decoration: none;
color: inherit;
}
#mocha h1 a:hover {
text-decoration: underline;
}
#mocha .suite .suite h1 {
margin-top: 0;
font-size: .8em;
}
.hidden {
display: none;
}
#mocha h2 {
font-size: 12px;
font-weight: normal;
cursor: pointer;
}
#mocha .suite {
margin-left: 15px;
}
#mocha .test {
margin-left: 15px;
overflow: hidden;
}
#mocha .test.pending:hover h2::after {
content: '(pending)';
font-family: arial;
}
#mocha .test.pass.medium .duration {
background: #C09853;
}
#mocha .test.pass.slow .duration {
background: #B94A48;
}
#mocha .test.pass::before {
content: '✓';
font-size: 12px;
display: block;
float: left;
margin-right: 5px;
color: #00d6b2;
}
#mocha .test.pass .duration {
font-size: 9px;
margin-left: 5px;
padding: 2px 5px;
color: white;
-webkit-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
-moz-box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
box-shadow: inset 0 1px 1px rgba(0,0,0,.2);
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
-ms-border-radius: 5px;
-o-border-radius: 5px;
border-radius: 5px;
}
#mocha .test.pass.fast .duration {
display: none;
}
#mocha .test.pending {
color: #0b97c4;
}
#mocha .test.pending::before {
content: '◦';
color: #0b97c4;
}
#mocha .test.fail {
color: #c00;
}
#mocha .test.fail pre {
color: black;
}
#mocha .test.fail::before {
content: '✖';
font-size: 12px;
display: block;
float: left;
margin-right: 5px;
color: #c00;
}
#mocha .test pre.error {
color: #c00;
max-height: 300px;
overflow: auto;
}
#mocha .test pre {
display: block;
float: left;
clear: left;
font: 12px/1.5 monaco, monospace;
margin: 5px;
padding: 15px;
border: 1px solid #eee;
border-bottom-color: #ddd;
-webkit-border-radius: 3px;
-webkit-box-shadow: 0 1px 3px #eee;
-moz-border-radius: 3px;
-moz-box-shadow: 0 1px 3px #eee;
}
#mocha .test h2 {
position: relative;
}
#mocha .test a.replay {
position: absolute;
top: 3px;
right: 0;
text-decoration: none;
vertical-align: middle;
display: block;
width: 15px;
height: 15px;
line-height: 15px;
text-align: center;
background: #eee;
font-size: 15px;
-moz-border-radius: 15px;
border-radius: 15px;
-webkit-transition: opacity 200ms;
-moz-transition: opacity 200ms;
transition: opacity 200ms;
opacity: 0.3;
color: #888;
}
#mocha .test:hover a.replay {
opacity: 1;
}
#mocha-report.pass .test.fail {
display: none;
}
#mocha-report.fail .test.pass {
display: none;
}
#mocha-error {
color: #c00;
font-size: 1.5 em;
font-weight: 100;
letter-spacing: 1px;
}
#mocha-stats {
position: fixed;
top: 15px;
right: 10px;
font-size: 12px;
margin: 0;
color: #888;
}
#mocha-stats .progress {
float: right;
padding-top: 0;
}
#mocha-stats em {
color: black;
}
#mocha-stats a {
text-decoration: none;
color: inherit;
}
#mocha-stats a:hover {
border-bottom: 1px solid #eee;
}
#mocha-stats li {
display: inline-block;
margin: 0 5px;
list-style: none;
padding-top: 11px;
}
code .comment { color: #ddd }
code .init { color: #2F6FAD }
code .string { color: #5890AD }
code .keyword { color: #8A6343 }
code .number { color: #2F6FAD }
@media screen and (max-device-width: 480px) {
body {
padding: 60px 0px;
}
#stats {
position: absolute;
}
}

5341
lib/client/console/test/vendor/mocha.js vendored Normal file

File diff suppressed because it is too large Load diff

75
lib/client/contact.js Normal file
View file

@ -0,0 +1,75 @@
var CloudCmd, Util, DOM, olark;
(function(CloudCmd, Util, DOM) {
'use strict';
CloudCmd.Contact = ContactProto;
function ContactProto(pCallBack) {
var Contact = this,
Images = DOM.Images,
Key = CloudCmd.Key,
Inited = false,
DIR = CloudCmd.LIBDIRCLIENT;
function init(pCallBack) {
if (!Inited) {
Contact.show = show;
Contact.hide = hide;
load(function() {
Inited = true;
olark('api.box.onExpand', Contact.show);
olark('api.box.onShow', Contact.show);
olark('api.box.onHide', Contact.hide);
olark('api.box.onShrink', Contact.hide);
DOM.Events.addKey(listener);
Util.exec(pCallBack);
});
}
}
function load(callback) {
var path = DIR + 'contact/olark.js';
Images.showLoad({top: true});
DOM.jsload(path, function() {
Util.exec(callback);
});
}
function show() {
Key.unsetBind();
Images.hideLoad();
if (Inited)
olark('api.box.expand');
else
init(Contact.show);
}
function hide() {
Key.setBind();
if (Inited)
olark('api.box.hide');
else
init(Contact.hide);
}
function listener(pEvent) {
var ESC = Key.ESC,
isBind = Key.isBind(),
key = pEvent.keyCode;
if (!isBind && key === ESC)
Contact.hide();
}
init(pCallBack);
}
})(CloudCmd, Util, DOM);

View file

@ -0,0 +1,87 @@
window.olark || (function (c) {
var f = window,
d = document,
l = f.location.protocol == "https:" ? "https:" : "http:",
z = c.name,
r = "load";
var nt = function () {
f[z] = function () {
(a.s = a.s || []).push(arguments);
};
var a = f[z]._ = {}, q = c.methods.length;
while (q--) {
(function (n) {
f[z][n] = function () {
f[z]("call", n, arguments);
};
})(c.methods[q]);
}
a.l = c.loader;
a.i = nt;
a.p = {
0: +new Date()
};
a.P = function (u) {
a.p[u] = new Date() - a.p[0];
};
function s() {
a.P(r);
f[z](r);
}
f.addEventListener ? f.addEventListener(r, s, false) : f.attachEvent("on" + r, s);
var ld = function () {
function p(hd) {
hd = "head";
return ["<", hd, "></", hd, "><", i, ' onl' + 'oad="var d=', g, ";d.getElementsByTagName('head')[0].", j, "(d.", h, "('script')).", k, "='", l, "//", a.l, "'", '"', "></", i, ">"].join("");
}
var i = "body",
m = d[i];
if (!m) {
return setTimeout(ld, 100);
}
a.P(1);
var j = "appendChild",
h = "createElement",
k = "src",
n = d[h]("div"),
v = n[j](d[h](z)),
b = d[h]("iframe"),
g = "document",
e = "domain",
o;
n.style.display = "none";
m.insertBefore(n, m.firstChild).id = z;
b.frameBorder = "0";
b.id = z + "-loader";
if (/MSIE[ ]+6/.test(navigator.userAgent)) {
b.src = "javascript:false";
}
b.allowTransparency = "true";
v[j](b);
try {
b.contentWindow[g].open();
} catch (w) {
c[e] = d[e];
o = "javascript:var d=" + g + ".open();d.domain='" + d.domain + "';";
b[k] = o + "void(0);";
}
try {
var t = b.contentWindow[g];
t.write(p());
t.close();
} catch (x) {
b[k] = o + 'd.write("' + p().replace(/"/g, String.fromCharCode(92) + '"') + '");d.close();';
}
a.P(2);
};
ld();
};
nt();
})({
loader: "static.olark.com/jsclient/loader0.js",
name: "olark",
methods: ["configure", "extend", "declare", "identify"]
});
/* custom configuration goes here (www.olark.com/documentation) */
olark.identify('6216-545-10-4223');

File diff suppressed because it is too large Load diff

View file

@ -9,6 +9,7 @@ var CloudCmd, Util, DOM, CloudFunc, ace, DiffProto, diff_match_patch;
Loading = false,
DIR = CloudCmd.LIBDIRCLIENT + 'edit/',
LIBDIR = CloudCmd.LIBDIR,
Info = DOM.CurrentInfo,
Value,
Edit = this,
Diff,
@ -22,29 +23,19 @@ var CloudCmd, Util, DOM, CloudFunc, ace, DiffProto, diff_match_patch;
Element;
function init() {
var lFunc, lIsFunc = Util.isFunction(CloudCmd.View);
Loading = true;
if (lIsFunc)
lFunc = CloudCmd.View;
else
lFunc = Util.exec;
Util.loadOnLoad([
Edit.show,
CloudCmd.View,
load,
lFunc
Edit.show
]);
DOM.Events.addKey(listener);
DOM.setButtonKey('f4', Edit.show);
}
this.show = function(pValue) {
var lMode,
lName = DOM.getCurrentName(),
isDir = DOM.isCurrentIsDir(),
lExt = Util.getExtension(lName);
var lMode, htmlMode, cssMode,
lName = Info.name,
isDir = Info.isDir,
lExt = Info.ext;
if (!Loading) {
Images.showLoad();
@ -63,33 +54,34 @@ var CloudCmd, Util, DOM, CloudFunc, ace, DiffProto, diff_match_patch;
initAce();
}
if (!Modelist)
Modelist = ace.require('ace/ext/modelist');
if (isDir)
lMode = Modelist.modesByName.json.mode;
else
lMode = Modelist.getModeForPath(lName).mode;
htmlMode = Modelist.modesByName.html.mode;
cssMode = Modelist.modesByName.css.mode;
Session.setMode(lMode);
if (lMode === htmlMode || lMode === cssMode)
DOM.jsload(DIR + 'emmet.js', function() {
var Emmet = ace.require("ace/ext/emmet");
Emmet.setCore(window.emmet);
Ace.setOption("enableEmmet", true);
});
if (Util.isString(pValue)) {
Ace.setValue(pValue);
CloudCmd.View.show(Element, focus);
Key.unsetBind();
} else {
DOM.getCurrentData({
success : function(pData) {
var lValue = '';
if (pData)
lValue = pData.data;
Value = lValue;
Ace.setValue(lValue);
CloudCmd.View.show(Element, focus);
}
} else
Info.getData(function(data) {
Value = data;
Ace.setValue(data);
CloudCmd.View.show(Element, focus);
});
}
}
};
@ -146,10 +138,18 @@ var CloudCmd, Util, DOM, CloudFunc, ace, DiffProto, diff_match_patch;
bindKey : { win: 'Ctrl-S', mac: 'Command-S' },
exec : save
});
ace.require('ace/ext/language_tools');
Modelist = ace.require('ace/ext/modelist');
Ace.setOptions({
enableBasicAutocompletion : true,
enableSnippets : true
});
}
function save () {
var lPath = DOM.getCurrentPath(),
var lPath = Info.path,
lValue = Ace.getValue();
CloudCmd.getConfig(function(config) {
@ -187,10 +187,10 @@ var CloudCmd, Util, DOM, CloudFunc, ace, DiffProto, diff_match_patch;
function diff(pNewValue, pCallBack) {
var libs = [
LIBDIR + 'diff.js',
LIBDIR + 'diff/diff-match-patch.js'
LIBDIR + 'diff/diff-match-patch.js',
LIBDIR + 'diff.js'
],
url = CloudFunc.getCombineURL(libs);
url = CloudFunc.getJoinURL(libs);
DOM.jsload(url, function() {
var patch,
@ -203,7 +203,7 @@ var CloudCmd, Util, DOM, CloudFunc, ace, DiffProto, diff_match_patch;
patch = Diff.createPatch(Value, pNewValue);
Util.exec(pCallBack, patch);
}, function(callback) {
var path = DOM.getCurrentPath();
var path = Info.path;
DOM.getDataFromStorage(path, function(data) {
if (data)
@ -215,42 +215,31 @@ var CloudCmd, Util, DOM, CloudFunc, ace, DiffProto, diff_match_patch;
});
}
function load(pCallBack) {
function load(callback) {
var files = [
DIR + 'theme-tomorrow_night_blue.js',
DIR + 'ext-language_tools.js',
DIR + 'ext-searchbox.js',
DIR + 'ext-modelist.js',
DIR + 'ext-emmet.js'
],
ace = DIR + 'ace.js',
url = CloudFunc.getJoinURL(files);
Util.time(Name + ' load');
var lFiles = [
DIR + 'theme-tomorrow_night_blue.js',
DIR + 'ext-language_tools.js',
DIR + 'ext-searchbox.js',
DIR + 'ext-modelist.js',
],
lAce = DIR + 'ace.js',
lURL = CloudFunc.getCombineURL(lFiles);
DOM.anyLoadOnLoad([lURL, lAce], function() {
DOM.anyLoadOnLoad([ace, url], function() {
Util.timeEnd(Name + ' load');
Loading = false;
Util.exec(pCallBack);
Util.exec(callback);
});
}
function listener(pEvent) {
var lF4, lKey, lIsBind = Key.isBind();
if (lIsBind) {
lF4 = Key.F4,
lKey = pEvent.keyCode;
if(lKey === lF4)
Edit.show();
}
}
function onSave(text) {
var ret,
isError = Util.isContainStrAtBegin(text, 'error'),
path = DOM.getCurrentPath(),
path = Info.path,
msg = '\nShould I save file anyway?';
if (!isError) {

13790
lib/client/edit/emmet.js Normal file

File diff suppressed because it is too large Load diff

1096
lib/client/edit/ext-emmet.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -11,12 +11,9 @@ var CloudCmd, Util, DOM;
function init() {
Util.loadOnLoad([
Help.show,
CloudCmd.View,
Help.show,
]);
DOM.Events.addKey(listener);
DOM.setButtonKey('f1', Help.show);
}
this.show = function() {
@ -35,30 +32,30 @@ var CloudCmd, Util, DOM;
});
DOM.ajax({
url: '/HELP.md',
success: function (pData) {
url : '/HELP.md',
success : function (pData) {
var lData = {text: pData};
DOM.ajax({
method : 'post',
url : 'https://api.github.com/markdown',
data : Util.stringifyJSON(lData),
success:function(pResult){
var lDiv = DOM.anyload({
name : 'div',
id : 'help',
inner : pResult.toString()
});
success : function(pResult){
var div = DOM.anyload({
name : 'div',
id : 'help',
inner : pResult.toString()
});
Images.hideLoad();
CloudCmd.View.show(lDiv);
CloudCmd.View.show(div);
},
error: Images.showError
});
},
error:Images.showError
error : Images.showError
});
};
@ -66,16 +63,6 @@ var CloudCmd, Util, DOM;
CloudCmd.View.hide();
};
function listener(pEvent) {
var lF1 = Key.F1,
lIsBind = Key.isBind(),
lKey = pEvent.keyCode;
/* если клавиши можно обрабатывать */
if (lIsBind && lKey === lF1)
Help.show();
}
init();
}

View file

@ -2,50 +2,58 @@ var CloudCmd, Util, DOM;
(function(CloudCmd, Util, DOM) {
'use strict';
var Chars = [],
KEY = {
TAB : 9,
ENTER : 13,
ESC : 27,
SPACE : 32,
PAGE_UP : 33,
PAGE_DOWN : 34,
END : 35,
HOME : 36,
UP : 38,
DOWN : 40,
INSERT : 45,
DELETE : 46,
A : 65,
D : 68,
G : 71,
O : 79,
Q : 81,
R : 82,
S : 83,
T : 84,
Z : 90,
F1 : 112,
F2 : 113,
F3 : 114,
F4 : 115,
F5 : 116,
F6 : 117,
F7 : 118,
F8 : 119,
F9 : 120,
F10 : 121,
TRA : 192 /* Typewritten Reverse Apostrophe (`) */
};
var Info = DOM.CurrentInfo,
Chars = [],
KEY = {
BACKSPACE : 8,
TAB : 9,
ENTER : 13,
ESC : 27,
SPACE : 32,
PAGE_UP : 33,
PAGE_DOWN : 34,
END : 35,
HOME : 36,
UP : 38,
DOWN : 40,
INSERT : 45,
DELETE : 46,
A : 65,
D : 68,
G : 71,
O : 79,
Q : 81,
R : 82,
S : 83,
T : 84,
Z : 90,
ASTERISK : 106,
PLUS : 107,
MINUS : 109,
F1 : 112,
F2 : 113,
F3 : 114,
F4 : 115,
F5 : 116,
F6 : 117,
F7 : 118,
F8 : 119,
F9 : 120,
F10 : 121,
SLASH : 191,
TRA : 192, /* Typewritten Reverse Apostrophe (`) */
BACKSLASH : 220
};
KeyProto.prototype = KEY;
CloudCmd.Key = KeyProto;
@ -53,9 +61,9 @@ var CloudCmd, Util, DOM;
function KeyProto() {
var Key = this,
Binded,
lTabPanel = {
left : 0,
right : 0
TabPanel = {
'js-left' : null,
'js-right' : null
};
this.isBind = function() {return Binded;};
@ -74,13 +82,12 @@ var CloudCmd, Util, DOM;
function listener(pEvent) {
/* получаем выдленный файл*/
var i, n,
lCurrent = DOM.getCurrentFile(),
lKeyCode = pEvent.keyCode,
lAlt = pEvent.altKey,
lCtrl = pEvent.ctrlKey;
ctrl = pEvent.ctrlKey;
/* если клавиши можно обрабатывать*/
if (Binded) {
if (!lAlt && !lCtrl && lKeyCode >= KEY.A && lKeyCode <= KEY.Z)
if (!lAlt && !ctrl && lKeyCode >= KEY.A && lKeyCode <= KEY.Z)
setCurrentByLetter(lKeyCode);
else {
Chars = [];
@ -103,15 +110,15 @@ var CloudCmd, Util, DOM;
}
function setCurrentByLetter(pKeyCode) {
var i, n, name, isCurrent, isContain, byName, firstByName,
var i, n, name, isCurrent, isMatch, byName, firstByName,
skipCount = 0,
skipN = 0,
setted = false,
lCurrent = DOM.getCurrentFile(),
files = DOM.getFiles(),
SMALL = 32,
current = Info.element,
panel = Info.panel,
files = Info.files,
char = String.fromCharCode(pKeyCode),
charSmall = String.fromCharCode(pKeyCode + SMALL);
regExp = new RegExp('^' + char + '.*$', 'i');
n = Chars.length;
for (i = 0; i < n; i++)
@ -126,11 +133,11 @@ var CloudCmd, Util, DOM;
n = files.length;
for (i = 0; i < n; i++) {
lCurrent = files[i];
name = DOM.getCurrentName(lCurrent);
isContain = Util.isContainStrAtBegin(name, [char, charSmall]);
current = files[i];
name = DOM.getCurrentName(current);
isMatch = name.match(regExp);
if (isContain) {
if (isMatch) {
byName = DOM.getCurrentFileByName(name);
if (!skipCount) {
@ -153,91 +160,114 @@ var CloudCmd, Util, DOM;
}
function switchKey(pEvent) {
var i, n, lCurrent = DOM.getCurrentFile(),
var i, n, id, obj,
current = Info.element,
panel = Info.panel,
path = Info.path,
dirPath = Info.dirPath,
filesPassive = Info.filesPassive,
prev = current.previousSibling,
next = current.nextSibling,
lKeyCode = pEvent.keyCode,
lShift = pEvent.shiftKey,
shift = pEvent.shiftKey,
lAlt = pEvent.altKey,
lCtrl = pEvent.ctrlKey;
ctrl = pEvent.ctrlKey;
switch (lKeyCode) {
case Key.TAB:
/* changing parent panel of curent-file */
var lFirstFileOnList,
lPanel = DOM.getPanel(),
lId = lPanel.id;
id = panel.id;
TabPanel[id] = current;
lTabPanel[lId] = lCurrent;
panel = Info.panelPassive;
id = panel.id;
lPanel = DOM.getPanel({active:false});
lId = lPanel.id;
current = TabPanel[id];
lCurrent = lTabPanel[lId];
if (lCurrent && lCurrent.parentElement)
DOM.setCurrentFile(lCurrent);
if (current && current.parentElement)
DOM.setCurrentFile(current);
else {
lFirstFileOnList = DOM.getByTag('li', lPanel)[2];
DOM.setCurrentFile(lFirstFileOnList);
current = filesPassive[0];
DOM.setCurrentFile(current);
}
DOM.preventDefault(pEvent);//запрет на дальнейшее действие
break;
case Key.INSERT:
DOM .toggleSelectedFile(lCurrent)
.setCurrentFile(lCurrent.nextSibling);
DOM .toggleSelectedFile(current)
.setCurrentFile(next);
break;
case Key.DELETE:
if (lShift) {
var lUrl = DOM.getCurrentPath(lCurrent);
if (shift) {
if (Info.isDir)
path += '?dir';
if ( DOM.isCurrentIsDir(lCurrent) )
lUrl += '?dir';
DOM.RESTful.delete(lUrl, function() {
DOM.deleteCurrent(lCurrent);
DOM.RESTful.delete(path, function() {
DOM.deleteCurrent(current);
});
}
else
DOM.promptDeleteSelected(lCurrent);
DOM.promptDeleteSelected(current);
break;
case Key.ASTERISK:
DOM.toggleAllSelectedFiles(current);
break;
case Key.PLUS:
DOM.expandSelection();
break;
case Key.MINUS:
DOM.shrinkSelection();
break;
case Key.SLASH:
if (shift) {
Util.exec(CloudCmd.Help.show);
DOM.preventDefault(pEvent);
}
break;
case Key.F1:
Util.exec(CloudCmd.Help);
Util.exec(CloudCmd.Help.show);
DOM.preventDefault(pEvent);
break;
case Key.F2:
DOM.renameCurrent(lCurrent);
DOM.renameCurrent(current);
break;
case Key.F3:
Util.exec(CloudCmd.View);
Util.exec(CloudCmd.View.show);
DOM.preventDefault(pEvent);
break;
case Key.F4:
Util.exec(CloudCmd.Edit);
Util.exec(CloudCmd.Edit.show);
DOM.preventDefault(pEvent);
break;
case Key.F5:
DOM.copyCurrent(lCurrent);
DOM.copyCurrent(current);
DOM.preventDefault(pEvent);
break;
case Key.F6:
DOM.moveCurrent(lCurrent);
DOM.moveCurrent(current);
DOM.preventDefault(pEvent);
break;
case Key.F7:
DOM.promptNewDir();
if (shift)
DOM.promptNewFile();
else
DOM.promptNewDir();
break;
case Key.F8:
DOM.promptDeleteSelected(lCurrent);
DOM.promptDeleteSelected(current);
break;
case Key.F9:
@ -247,29 +277,38 @@ var CloudCmd, Util, DOM;
break;
case Key.F10:
Util.exec(CloudCmd.Config);
Util.exec(CloudCmd.Config.show);
DOM.preventDefault(pEvent);
break;
case Key.TRA:
DOM.Images.showLoad({top: true});
Util.exec(CloudCmd.Console);
if (shift)
obj = CloudCmd.Terminal;
else
obj = CloudCmd.Console;
Util.exec(obj.show);
DOM.preventDefault(pEvent);
break;
case Key.SPACE:
var lSelected = DOM.isSelected(lCurrent),
lDir = DOM.isCurrentIsDir(lCurrent),
lName = DOM.getCurrentName(lCurrent);
var lSelected,
isDir = Info.isDir,
lName = Info.name;
if (!lDir || lName === '..')
lSelected = true;
if (!isDir || lName === '..')
lSelected = true;
else
lSelected = Info.isSelected;
Util.ifExec(lSelected, function() {
DOM.toggleSelectedFile(lCurrent);
DOM.toggleSelectedFile(current);
}, function(pCallBack) {
DOM.loadCurrentSize(pCallBack, lCurrent);
DOM.loadCurrentSize(pCallBack, current);
});
@ -280,57 +319,56 @@ var CloudCmd, Util, DOM;
* если нажали клавишу вверх *
* выделяем предыдущую строку */
case Key.UP:
if (lShift)
DOM.toggleSelectedFile(lCurrent);
if (shift)
DOM.toggleSelectedFile(current);
DOM.setCurrentFile( lCurrent.previousSibling );
DOM.preventDefault( pEvent );
DOM.setCurrentFile(prev);
DOM.preventDefault(pEvent);
break;
/* если нажали клавишу в низ - выделяем следующую строку */
case Key.DOWN:
if (lShift)
DOM.toggleSelectedFile(lCurrent);
if (shift)
DOM.toggleSelectedFile(current);
DOM.setCurrentFile( lCurrent.nextSibling );
DOM.preventDefault( pEvent );
DOM.setCurrentFile(next);
DOM.preventDefault(pEvent);
break;
/* если нажали клавишу Home *
* переходим к самому верхнему *
* элементу */
case Key.HOME:
DOM.setCurrentFile( lCurrent.parentElement.firstChild );
DOM.setCurrentFile(Info.first);
DOM.preventDefault(pEvent);
break;
/* если нажали клавишу End выделяем последний элемент */
case Key.END:
DOM.setCurrentFile( lCurrent.parentElement.lastElementChild );
DOM.preventDefault( pEvent );
DOM.setCurrentFile(Info.last);
DOM.preventDefault(pEvent);
break;
/* если нажали клавишу page down проматываем экран */
case Key.PAGE_DOWN:
DOM.scrollByPages( DOM.getPanel(), 1 );
DOM.scrollByPages(panel, 1);
for (i = 0; i < 30; i++) {
if (!lCurrent.nextSibling)
if (!current.nextSibling)
break;
lCurrent = lCurrent.nextSibling;
current = current.nextSibling;
}
DOM.setCurrentFile(lCurrent);
DOM.setCurrentFile(current);
DOM.preventDefault(pEvent);
break;
/* если нажали клавишу page up проматываем экран */
case Key.PAGE_UP:
DOM.scrollByPages(DOM.getPanel(), -1);
DOM.scrollByPages(panel, -1);
var lC = lCurrent,
tryCatch = function(pCurrentFile) {
var tryCatch = function(pCurrentFile) {
Util.tryCatch(function() {
return pCurrentFile
.previousSibling
@ -341,26 +379,39 @@ var CloudCmd, Util, DOM;
};
for (i = 0; i < 30; i++) {
if (!lC.previousSibling || tryCatch(lC) ) break;
if (!current.previousSibling || tryCatch(current) )
break;
lC = lC.previousSibling;
current = current.previousSibling;
}
DOM.setCurrentFile(lC);
DOM.setCurrentFile(current);
DOM.preventDefault(pEvent);
break;
/* открываем папку*/
case Key.ENTER:
if (DOM.isCurrentIsDir())
Util.exec( CloudCmd.loadDir() );
if (Info.isDir)
Util.exec(CloudCmd.loadDir());
break;
case Key.BACKSPACE:
CloudCmd.goToParentDir();
DOM.preventDefault(pEvent);
break;
case Key.BACKSLASH:
if (ctrl) {
path = '/';
Util.exec(CloudCmd.loadDir(path));
}
break;
case Key.A:
if (pEvent.ctrlKey) {
DOM .toggleAllSelectedFiles(lCurrent)
if (ctrl)
DOM .toggleAllSelectedFiles()
.preventDefault(pEvent);
}
break;
/*
@ -371,7 +422,7 @@ var CloudCmd, Util, DOM;
* (обновляем кэш)
*/
case Key.R:
if (lCtrl) {
if (ctrl) {
Util.log('<ctrl>+r pressed\n' +
'reloading page...\n' +
'press <alt>+q to remove all key-handlers');
@ -383,7 +434,7 @@ var CloudCmd, Util, DOM;
/* чистим кэш */
case Key.D:
if (lCtrl) {
if (ctrl) {
Util.log('<ctrl>+d pressed\n' +
'clearing Storage...\n' +
'press <alt>+q to remove all key-handlers');

View file

@ -5,8 +5,9 @@ var Util, DOM, CloudCmd;
CloudCmd.Listeners = new ListenersProto(CloudCmd, Util, DOM);
function ListenersProto(CloudCmd, Util, DOM){
var Storage = DOM.Storage,
function ListenersProto(CloudCmd, Util, DOM) {
var Info = DOM.CurrentInfo,
Storage = DOM.Storage,
Events = DOM.Events,
getConfig = CloudCmd.getConfig;
@ -16,12 +17,12 @@ var Util, DOM, CloudCmd;
online = config.online;
if (analytics && online) {
Events.addOnce('mousemove', function(){
Events.addOnce('mousemove', function() {
var FIVE_SECONDS = 5000,
lUrl = CloudCmd.LIBDIRCLIENT + 'analytics.js';
url = CloudCmd.LIBDIRCLIENT + 'analytics.js';
setTimeout(function(){
DOM.jsload(lUrl);
setTimeout(function() {
DOM.jsload(url);
}, FIVE_SECONDS);
});
}
@ -38,105 +39,113 @@ var Util, DOM, CloudCmd;
};
this.initKeysPanel = function() {
var i, lButton, lEl,
lKeysPanel = {},
var button, id, func,
keysElement = DOM.getByClass('keyspanel');
lFuncs =[
null,
CloudCmd.Help, /* f1 */
DOM.renameCurrent, /* f2 */
CloudCmd.View, /* f3 */
CloudCmd.Edit, /* f4 */
DOM.copyCurrent, /* f5 */
DOM.moveCurrent, /* f6 */
DOM.promptNewDir, /* f7 */
DOM.promptDeleteSelected, /* f8 */
CloudCmd.Menu, /* f9 */
CloudCmd.Config, /* f10 */
];
for (i = 1; i <= 10; i++) {
lButton = 'f' + i,
lEl = DOM.getById('f' + i);
lKeysPanel[lButton] = lEl;
if (i === 1 || i === 3 || i === 4 || i === 9 || i === 10)
Events.addOnce('click', lFuncs[i], lEl);
else
Events.addClick(lFuncs[i], lEl);
}
lButton = '~',
lEl = DOM.getById('~');
lKeysPanel[lButton] = lEl;
Events.addOnce('click', CloudCmd.Console, lEl);
return lKeysPanel;
if (keysElement)
Events.addClick(function(event) {
var element = event.target,
id = element.id,
clickFuncs = {
'f1' : CloudCmd.Help.show,
'f3' : CloudCmd.View.show,
'f4' : CloudCmd.Edit.show,
'f9' : CloudCmd.Menu.show,
'f10' : CloudCmd.Config.show,
'~' : CloudCmd.Console.show,
'contact' : CloudCmd.Contact.show,
'f2' : DOM.renameCurrent,
'f5' : DOM.copyCurrent,
'f6' : DOM.moveCurrent,
'f7' : DOM.promptNewDir,
'f8' : DOM.promptDeleteSelected
},
func = clickFuncs[id];
Util.exec(func);
}, keysElement);
};
/**
* функция меняет ссыки на ajax-овые
* @param pPanelID
* @param panelId
*/
this.changeLinks = function(pPanelID) {
/* назначаем кнопку очистить кэш и показываем её */
var lClearStorage = DOM.getById('clear-storage');
Events.addClick(Storage.clear, lClearStorage);
this.changeLinks = function(panelId) {
var i, n, a, ai, current, link, loadDir, events, id, isDir,
/* меняем ссылки на ajax-запросы */
var lPanel = DOM.getById(pPanelID),
a = DOM.getByTag('a', lPanel),
url = CloudCmd.HOST,
loadDirOnce = CloudCmd.loadDir(),
panel = DOM.getById(panelId),
pathElement = DOM.getByClass('js-path', panel),
filesElement = DOM.getByClass('files', panel),
files = filesElement.children,
pathLinks = DOM.getByClass('links', pathElement).children,
clearStorage = DOM.getByClass('clear-storage', pathElement),
refresh = DOM.getByClass('refresh-icon', pathElement),
fileClick = function (event) {
var ctrl = event.ctrlKey;
if (!ctrl)
DOM.preventDefault(event);
},
/* right mouse click function varible */
lOnContextMenu_f = function(pEvent){
var lReturn_b = true,
onContextMenu = function(pEvent) {
var target,
isFunc = Util.isFunction(CloudCmd.Menu),
ret = true,
Key = CloudCmd.Key;
Key && Key.unsetBind();
/* getting html element
* currentTarget - DOM event
* target - jquery event
*/
var lTarget = pEvent.currentTarget || pEvent.target;
DOM.setCurrentFile(lTarget);
target = pEvent.currentTarget || pEvent.target;
DOM.setCurrentFile(target);
if(Util.isFunction(CloudCmd.Menu)) {
if (isFunc) {
CloudCmd.Menu({
x: pEvent.clientX,
y: pEvent.clientY
});
/* disabling browsers menu*/
lReturn_b = false;
DOM.Images.showLoad();
}
ret = false;
}
return lReturn_b;
return ret;
},
/* drag and drop function varible
* download file from browser to descktop
* in Chrome (HTML5)
*/
lOnDragStart_f = function(pEvent) {
/* drag and drop function varible
* download file from browser to descktop
* in Chrome (HTML5)
*/
onDragStart = function(pEvent) {
var lElement = pEvent.target,
EXT = 'json',
isDir = Info.isDir,
lLink = lElement.href,
lName = lElement.textContent;
/* if it's directory - adding json extension */
if( DOM.isCurrentIsDir() ){
lName += '.json';
lLink += '?json';
if (isDir) {
lName += '.' + EXT;
lLink += '?' + EXT;
}
pEvent.dataTransfer.setData("DownloadURL",
pEvent.dataTransfer.setData('DownloadURL',
'application/octet-stream' + ':' +
lName + ':' +
lLink);
},
lSetCurrentFile_f = function(pEvent){
setCurrentFile = function(pEvent) {
var pElement = pEvent.target,
lTag = pElement.tagName;
@ -147,47 +156,56 @@ var Util, DOM, CloudCmd;
} while(lTag !== 'LI');
DOM.setCurrentFile(pElement);
},
lUrl = CloudCmd.HOST,
lLoadDirOnce = CloudCmd.loadDir();
};
/* ставим загрузку гифа на клик*/
Events.addClick( CloudCmd.refresh, a[0].parentElement );
Events.addClick(CloudCmd.refresh, refresh);
Events.addClick(Storage.clear, clearStorage);
/* start from 1 cous 0 is a refresh and it's setted up */
for(var i = 1, n = a.length; i < n ; i++){
/* убираем адрес хоста*/
var ai = a[i],
lLink = Util.removeStr(ai.href, lUrl),
lLoadDir = CloudCmd.loadDir(lLink),
/* устанавливаем обработчики на строку
* на двойное нажатие на левую кнопку мышки */
lLi = ai.parentElement.parentElement;
n = pathLinks.length;
for (i = 0; i < n; i++) {
ai = pathLinks[i];
link = Util.removeStr(ai.href, url),
loadDir = CloudCmd.loadDir(link),
/* if we in path - set click event */
if (lLi.className === 'path')
Events.addClick( lLoadDir, ai );
else {
Events.add({
'click' : DOM.preventDefault,
'mousedown' : lSetCurrentFile_f,
'contextmenu' : lOnContextMenu_f
}, lLi);
Events.add('dragstart', lOnDragStart_f, ai);
/* если ссылка на папку, а не файл */
if(ai.target !== '_blank'){
Events.add({
'dblclick' : lLoadDirOnce,
'touchend' : lLoadDirOnce
}, lLi);
}
lLi.id = (ai.title ? ai.title : ai.textContent) +
'(' + pPanelID + ')';
}
Events.addClick(loadDir, ai);
}
a = DOM.getByTag('a', filesElement);
n = a.length;
for (i = 0; i < n ; i++) {
current = files[i];
isDir = DOM.isCurrentIsDir(current);
ai = a[i];
if (ai.title)
id = ai.title;
else
id = ai.textContent;
id += '(' + panelId + ')';
current.id = id;
if (!isDir)
events = {
'click' : fileClick
};
else
events = {
'dblclick' : loadDirOnce,
'touchend' : loadDirOnce,
'click' : DOM.preventDefault,
};
Util.copyObj(events, {
'mousedown' : setCurrentFile,
'contextmenu' : onContextMenu,
'dragstart' : onDragStart
});
Events.add(events, current);
}
};
@ -208,13 +226,13 @@ var Util, DOM, CloudCmd;
}
function contextMenu() {
Events.addContextMenu(function(pEvent){
Events.addContextMenu(function(pEvent) {
CloudCmd.Menu.ENABLED || DOM.preventDefault(pEvent);
}, document);
}
function dragndrop() {
var panels = DOM.getByClass('panel'),
var panels = DOM.getByClassAll('panel'),
i = 0,
n = panels.length,
preventDefault = function (event) {
@ -227,8 +245,8 @@ var Util, DOM, CloudCmd;
},
onDrop = function (event) {
var reader, file, files,
dir = DOM.getCurrentDirPath(),
load = function(file){
dir = Info.dirPath,
load = function(file) {
return function(event) {
var path = dir + file.name,
data = event.target.result;
@ -268,7 +286,7 @@ var Util, DOM, CloudCmd;
Key = CloudCmd.Key,
lIsBind = Key && Key.isBind();
if(!lIsBind) {
if (!lIsBind) {
DOM.preventDefault(pEvent);
lRet = 'Please make sure that you saved all work.';
}
@ -292,7 +310,7 @@ var Util, DOM, CloudCmd;
}
function online() {
var cssSet = Util.retFunc(DOM.cssSet, {
var cssSet = DOM.cssSet.bind(null, {
id :'local-droids-font',
element : document.head,
inner : '@font-face {font-family: "Droid Sans Mono";' +

409
lib/client/loader.js Normal file
View file

@ -0,0 +1,409 @@
var Util, DOM;
(function (Util, DOMTree) {
'use strict';
var Loader = Util.extendProto(LoaderProto),
DOMProto = Object.getPrototypeOf(DOMTree);
Util.extend(DOMProto, Loader);
function LoaderProto() {
var Images = DOM.Images,
Events = DOM.Events,
Loader = this;
/**
* Function gets id by src
* @param pSrc
*
* Example: http://domain.com/1.js -> 1_js
*/
this.getIdBySrc = function(pSrc) {
var lRet = Util.isString(pSrc);
if (lRet) {
var lNum = pSrc.lastIndexOf('/') + 1,
lSub = pSrc.substr(pSrc, lNum),
lID = Util.removeStrOneTime(pSrc, lSub );
/* убираем точки */
while (lID.indexOf('.') > 0)
lID = lID.replace('.', '_');
lRet = lID;
}
return lRet;
};
/**
* load file countent via ajax
*
* @param pParams
*/
this.ajax = function(params) {
var p = params,
type = p.type || p.method || 'GET',
xhr = new XMLHttpRequest();
xhr.open(type, p.url, true);
if (p.responseType)
xhr.responseType = p.responseType;
Events.add('progress', function(event) {
var percent, count, msg;
if (event.lengthComputable) {
percent = (event.loaded / event.total) * 100,
count = Math.round(percent),
msg = type + ' ' + p.url + ': ' + count + '%';
Util.log(msg);
}
}, xhr.upload);
Events.add('readystatechange', function(event) {
var TYPE_JSON, type, data, isContain,
xhr = event.target;
if (xhr.readyState === 4 /* Complete */) {
TYPE_JSON = 'application/json';
type = xhr.getResponseHeader('content-type');
if (xhr.status === 200 /* OK */) {
data = xhr.response;
isContain = Util.isContainStr(type, TYPE_JSON);
if (p.dataType !== 'text')
/* If it's json - parse it as json */
if (type && isContain)
data = Util.parseJSON(xhr.response) || xhr.response;
Util.exec(p.success, data, xhr.statusText, xhr);
}
/* file not found or connection lost */
else {
/* if html given or something like thet
* getBack just status of result
*/
if (type && type.indexOf('text/plain') !== 0)
xhr.responseText = xhr.statusText;
Util.exec(p.error, xhr);
}
}
}, xhr);
xhr.send(p.data);
};
/**
* create elements and load them to DOM-tree
* one-by-one
*
* @param params
* @param callback - onload function
*/
this.anyLoadOnLoad = function(params, callback) {
if (Util.isArray(params)) {
var param = params.shift(),
func = function() {
Loader.anyLoadOnLoad(params, callback);
};
if (!param)
Util.exec(callback);
else if (Util.isArray(param))
Loader.anyLoadInParallel(param, callback);
else {
if (Util.isString(param))
param = {
src : param
};
if (!param.func)
param.func = func;
Loader.anyload(param);
}
}
return Loader;
};
/**
* improve callback of funcs so
* we pop number of function and
* if it's last we call pCallBack
*
* @param params
* @param callback - onload function
*/
this.anyLoadInParallel = function(params, callback) {
var i, n, param, func,
done = [],
doneFunc = function (func) {
Util.exec(func);
if (!done.pop())
Util.exec(callback);
};
if (!Util.isArray(params))
params = [params];
n = params.length;
for (i = 0; i < n; i++) {
param = params.pop();
if (param) {
done.push(i);
if (Util.isString(param))
param = { src : param };
else
func = param.func;
param.func = Util.retExec(doneFunc, func);
Loader.anyload(param);
}
}
return Loader;
};
/**
* Функция создаёт элемент и загружает файл с src.
*
* @param pParams_o = {
* name, - название тэга
* src', - путь к файлу
* func, - обьект, содержаий одну из функций
* или сразу две onload и onerror
* {onload: function() {}, onerror: function();}
* style,
* id,
* element,
* async, - true by default
* inner: 'id{color:red, },
* class,
* not_append - false by default
* }
*/
this.anyload = function(pParams_o) {
var i, n, lElements_a;
if (!pParams_o ) return;
/* if a couple of params was
* processing every of params
* and quit
*/
if (Util.isArray(pParams_o)) {
lElements_a = [];
for(i = 0, n = pParams_o.length; i < n ; i++)
lElements_a[i] = this.anyload(pParams_o[i]);
return lElements_a;
}
var lName = pParams_o.name,
lAttr = pParams_o.attribute,
lID = pParams_o.id,
lClass = pParams_o.className,
lSrc = pParams_o.src,
lFunc = pParams_o.func,
lOnError,
lAsync = pParams_o.async,
lParent = pParams_o.parent || document.body,
lInner = pParams_o.inner,
lStyle = pParams_o.style,
lNotAppend = pParams_o.not_append;
if (Util.isObject(lFunc)) {
lOnError = lFunc.onerror;
lFunc = lFunc.onload;
}
/* убираем путь к файлу, оставляя только название файла */
if (!lID && lSrc)
lID = DOM.getIdBySrc(lSrc);
var lElement = DOMTree.getById(lID);
/* если скрипт еще не загружен */
if (!lElement) {
if (!lName && lSrc) {
var lDot = lSrc.lastIndexOf('.'),
lExt = lSrc.substr(lDot);
switch(lExt) {
case '.js':
lName = 'script';
break;
case '.css':
lName = 'link';
lParent = document.head;
break;
default:
return {code: -1, text: 'name can not be empty'};
}
}
lElement = document.createElement(lName);
if (lID)
lElement.id = lID;
if (lClass)
lElement.className = lClass;
if (lSrc) {
/* if work with css use href */
if (lName === 'link') {
lElement.href = lSrc;
lElement.rel = 'stylesheet';
} else
lElement.src = lSrc;
/*
* if passed arguments function
* then it's onload by default
*
* if object - then onload and onerror
*/
var lLoad = function(pEvent) {
Events.remove('load', lLoad, lElement);
Events.remove('error', lError, lElement);
Util.exec(lFunc, pEvent);
},
lError = function() {
lParent.removeChild(lElement);
Images.showError({
responseText: 'file ' +
lSrc +
' could not be loaded',
status : 404
});
Util.exec(lOnError);
};
Events.add('load', lLoad, lElement);
Events.addError(lError, lElement);
}
if (lAttr)
for(i in lAttr)
lElement.setAttribute(i, lAttr[i]);
if (lStyle)
lElement.style.cssText = lStyle;
if (lAsync || lAsync === undefined)
lElement.async = true;
if (!lNotAppend)
lParent.appendChild(lElement);
if (lInner)
lElement.innerHTML = lInner;
}
/* если js-файл уже загружен
* запускаем функцию onload
*/
else
Util.exec(lFunc);
return lElement;
},
/**
* Функция загружает js-файл
*
* @param pSrc
* @param pFunc
*/
this.jsload = function(pSrc, pFunc) {
var lRet = Loader.anyload({
name : 'script',
src : pSrc,
func : pFunc
});
return lRet;
},
/**
* returns jsload functions
*/
this.retJSLoad = function(pSrc, pFunc) {
var lRet = function() {
return Loader.jsload(pSrc, pFunc);
};
return lRet;
},
/**
* Функция создаёт елемент style и записывает туда стили
* @param pParams_o - структура параметров, заполняеться таким
* образом: {src: ' ',func: '', id: '', element: '', inner: ''}
* все параметры опциональны
*/
this.cssSet = function(pParams_o) {
pParams_o.name = 'style';
pParams_o.parent = pParams_o.parent || document.head;
return Loader.anyload(pParams_o);
},
/**
* Function loads external css files
* @pParams_o - структура параметров, заполняеться таким
* образом: {src: ' ',func: '', id: '', element: '', inner: ''}
* все параметры опциональны
*/
this.cssLoad = function(pParams_o) {
if (Util.isArray(pParams_o)) {
for(var i = 0, n = pParams_o.length; i < n; i++) {
pParams_o[i].name = 'link';
pParams_o[i].parent = pParams_o.parent || document.head;
}
return Loader.anyload(pParams_o);
}
else if (Util.isString(pParams_o))
pParams_o = { src: pParams_o };
pParams_o.name = 'link';
pParams_o.parent = pParams_o.parent || document.head;
return Loader.anyload(pParams_o);
};
/**
* load jquery from google cdn or local copy
* @param pParams
*/
this.jquery = function(pParams) {
if (!pParams)
pParams = {};
/* загружаем jquery: */
Loader.jsload('//ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js',{
onload : pParams.onload,
onerror : pParams.onerror
});
};
}
})(Util, DOM);

View file

@ -3,13 +3,14 @@
*/
var CloudCmd, Util, DOM, CloudFunc, $;
(function(CloudCmd, Util, DOM, CloudFunc){
(function(CloudCmd, Util, DOM, CloudFunc) {
'use strict';
CloudCmd.Menu = MenuProto;
function MenuProto(pPosition) {
var Name = 'Menu',
Info = DOM.CurrentInfo,
Loading = false,
Key = CloudCmd.Key,
Events = DOM.Events,
@ -26,27 +27,23 @@ var CloudCmd, Util, DOM, CloudFunc, $;
Position = pPosition;
Util.loadOnLoad([
Menu.show,
setUploadToItemNames,
DOM.jqueryLoad,
load,
DOM.jqueryLoad
setUploadToItemNames,
Menu.show
]);
Events.addKey( lListener );
DOM.setButtonKey('f9', function() {
var lCurrent = DOM.getCurrentFile();
Events.dispatch('contextmenu', lCurrent);
});
Events.addKey(listener);
}
this.show = function() {
Key.unsetBind();
if (!Loading) {
set();
DOM.Images.hideLoad();
if(Position && !Position.x )
if (Position && !Position.x )
Position = undefined;
$('li').contextMenu(Position);
@ -70,20 +67,24 @@ var CloudCmd, Util, DOM, CloudFunc, $;
/**
* function get menu item object for Upload To
*/
function getUploadToItems(pObjectName){
function getUploadToItems(pObjectName) {
var i, n, lStr, lObj = {};
if (Util.isArray(pObjectName)) {
n = pObjectName.length;
for(i = 0; i < n; i++){
for (i = 0; i < n; i++) {
lStr = pObjectName[i];
lObj[lStr] = getUploadToItems( lStr );
}
}
else if (Util.isString(pObjectName)) {
lObj = function(key, opt){
DOM.getCurrentData(function(pParams) {
CloudCmd.execFromModule(pObjectName, 'uploadFile', pParams);
lObj = function(key, opt) {
Info.getData(function(data) {
var name = Info.name;
CloudCmd.execFromModule(pObjectName, 'uploadFile', {
name: name,
data: data
});
});
Util.log('Uploading to ' + pObjectName+ '...');
@ -96,20 +97,27 @@ var CloudCmd, Util, DOM, CloudFunc, $;
/**
* get menu item
*/
function getItem(pName, pCallBack){
var lRet = {
name : pName
function getItem(pName, pCallBack) {
var lRet,
name = pName,
icon = name && name.toLowerCase();
icon = Util.removeStr(icon, ['(', ')']);
icon = Util.replaceStr(icon, ' ', '-');
lRet = {
name : pName,
icon : icon
};
if( Util.isFunction(pCallBack) )
if (Util.isFunction(pCallBack))
lRet.callback = pCallBack;
else if ( Util.isObject(pCallBack) ){
if(pCallBack.name)
else if (Util.isObject(pCallBack))
if (pCallBack.name)
lRet.items = pCallBack;
else
lRet.items = getAllItems(pCallBack);
}
return lRet;
}
@ -118,13 +126,13 @@ var CloudCmd, Util, DOM, CloudFunc, $;
* get all menu items
* pItems = [{pName, pFunc}]
*/
function getAllItems(pItems){
function getAllItems(pItems) {
var lRet = {},
lName,
lFunc;
if(pItems)
for(lName in pItems){
if (pItems)
for (lName in pItems) {
lFunc = pItems[lName];
lRet[lName] = getItem(lName, lFunc);
}
@ -135,30 +143,31 @@ var CloudCmd, Util, DOM, CloudFunc, $;
/**
* download menu item callback
*/
function downloadFromMenu(key, opt){
function downloadFromMenu(key, opt) {
DOM.Images.showLoad();
var lPath = DOM.getCurrentPath(),
var TIME = 1000,
lPath = Info.path,
lId = DOM.getIdBySrc(lPath),
lDir = DOM.isCurrentIsDir() ? '&&json' : '';
lDir = Info.isDir ? '&&json' : '';
Util.log('downloading file ' + lPath +'...');
lPath = CloudFunc.FS + lPath + '?download' + lDir;
if (!DOM.getById(lId)){
if (!DOM.getById(lId)) {
var lDownload = DOM.anyload({
name : 'iframe',
async : false,
className : 'hidden',
src : lPath,
func : Util.retFunc(DOM.Images.hideLoad)
func : DOM.Images.hideLoad
});
DOM.Images.hideLoad();
setTimeout(function() {
document.body.removeChild(lDownload);
}, 10000);
}, TIME);
}
else
DOM.Images.showError({
@ -169,30 +178,30 @@ var CloudCmd, Util, DOM, CloudFunc, $;
/**
* function return configureation for menu
*/
function getConfig (){
function getConfig () {
var lRet,
lMenuItems = {
'View' : Util.retFunc(show, 'View'),
'Edit' : Util.retFunc(show, 'Edit'),
'Rename' : function(){
setTimeout( Util.retFunc(DOM.renameCurrent), 100);
'View' : show.bind(null, 'View'),
'Edit' : show.bind(null, 'Edit'),
'Rename' : function() {
setTimeout(DOM.renameCurrent, 100);
},
'Delete' : Util.retFunc(DOM.promptDeleteSelected),
'Delete' : DOM.promptDeleteSelected,
'(Un)Select All': DOM.toggleAllSelectedFiles,
'Zip file' : DOM.zipFile
'Zip file' : DOM.zipFile
};
if (UploadToItemNames.length)
lMenuItems['Upload to'] = getUploadToItems(UploadToItemNames);
lMenuItems.Download = Util.retFunc(downloadFromMenu);
lMenuItems.Download = downloadFromMenu;
lMenuItems.New = {
'File' : DOM.promptNewFile,
'Dir' : DOM.promptNewDir,
'Directory' : DOM.promptNewDir,
'From Filepicker' : function(){
CloudCmd.execFromModule('FilePicker', 'saveFile', function(pName, pData){
'From Cloud' : function() {
CloudCmd.execFromModule('FilePicker', 'saveFile', function(pName, pData) {
var lPath = DOM.getCurrentDirPath() + pName;
DOM.RESTful.save(lPath, pData, CloudCmd.refresh);
@ -204,23 +213,23 @@ var CloudCmd, Util, DOM, CloudFunc, $;
// define which elements trigger this menu
selector: 'li',
callback: function(key, options) {
var m = "clicked: " + key;
Util.log(m, options);
Key.setBind();
},
// define the elements of the menu
items : getAllItems(lMenuItems),
events :{
hide: clickProcessing
hide: function() {
var event = window.event;
if (!event || !event.keyCode)
clickProcessing();
else if (event.keyCode)
listener(event);
}
}
};
return lRet;
function show(pName){
function show(pName) {
var lEditor = CloudCmd[pName],
lResult = Util.exec(lEditor);
@ -232,22 +241,35 @@ var CloudCmd, Util, DOM, CloudFunc, $;
/** function loads css and js of Menu
* @param pCallBack
*/
function load(pCallBack){
function load(pCallBack) {
Util.time(Name + ' load');
var lDir = '/lib/client/menu/',
lFiles = [
lDir + 'contextMenu.js',
lDir + 'contextMenu.css'
var dir = CloudCmd.LIBDIRCLIENT + 'menu/',
files = [
dir + 'contextMenu.js',
dir + 'contextMenu.css'
];
DOM.anyLoadInParallel(lFiles, function(){
DOM.anyLoadInParallel(files, function() {
setCSS();
Util.timeEnd(Name + ' load');
Loading = false;
Util.exec(pCallBack);
});
}
function setCSS() {
DOM.cssSet({
id : 'menu-css',
inner: '.context-menu-item.icon-edit {' +
'background-image: none;' +
'}' +
'.context-menu-item.icon-delete {' +
'background-image: none;' +
'}'
});
}
/*
* Menu works in some crazy way so need a
* little hack to get every thing work out.
@ -260,9 +282,10 @@ var CloudCmd, Util, DOM, CloudFunc, $;
* is not going on. All magic happening in
* DOM tree.
*/
function clickProcessing(){
function clickProcessing() {
var lLayer = DOM.getById('context-menu-layer');
if(lLayer){
if (lLayer && Position) {
DOM.hide(lLayer);
var lElement = document.elementFromPoint(Position.x, Position.y),
@ -289,11 +312,11 @@ var CloudCmd, Util, DOM, CloudFunc, $;
}
function set(){
if(!MenuSeted){
function set() {
if (!MenuSeted) {
$.contextMenu(getConfig());
MenuSeted = true;
DOM.Events.add('mousemove', function(pEvent){
DOM.Events.add('mousemove', function(pEvent) {
Position = {
x : pEvent.clientX,
y : pEvent.clientY
@ -302,22 +325,21 @@ var CloudCmd, Util, DOM, CloudFunc, $;
}
}
function lListener(pEvent){
var lCurrent,
lF9 = Key.F9,
lESC = Key.ESC,
lKey = pEvent.keyCode,
lIsBind = Key.isBind();
function listener(event) {
var current,
F9 = Key.F9,
ESC = Key.ESC,
key = event.keyCode,
isBind = Key.isBind();
if (isBind && key === F9) {
current = DOM.getCurrentFile();
$(current).contextmenu();
if (lIsBind && lKey === lF9) {
lCurrent = DOM.getCurrentFile();
$(lCurrent).contextmenu();
DOM.preventDefault(pEvent);
}
else if (lKey === lESC)
DOM.preventDefault(event);
} else if (key === ESC)
Key.setBind();
}
}
init();
}

63
lib/client/notify.js Normal file
View file

@ -0,0 +1,63 @@
var Util, DOM, CloudCmd;
(function(Util, DOM) {
'use strict';
var Notify = Util.extendProto(NotifyProto),
DOMProto = Object.getPrototypeOf(DOM);
Util.extend(DOMProto, {
Notify: Notify
});
function NotifyProto() {
var Events = DOM.Events,
Show, Allow,
Notify = this,
Notification = window.Notification;
Events.add({
'blur' :function() {
Show = true;
},
'focus': function() {
Show = false;
}
}, window);
this.send = function(msg) {
CloudCmd.getConfig(function(config) {
var notify,
notifications = config.notifications,
focus = window.focus.bind(window),
granted = Notify.check();
if (notifications && granted && Show) {
notify = new Notification(msg, {
icon: '/img/favicon/favicon-notify.png',
});
Events.addClick(focus, notify);
}
});
};
this.check = function () {
var ret,
Not = Notification,
perm = Not && Not.permission;
if (perm === 'granted')
ret = true;
return ret;
};
this.request = function () {
var Not = Notification;
if (Not)
Not.requestPermission();
};
}
})(Util, DOM);

View file

@ -1,20 +1,20 @@
var Util, DOM, jQuery;
(function(Util, DOM, $){
(function(Util, DOM, $) {
'use strict';
if(!window.XMLHttpRequest || !document.head)
if (!window.XMLHttpRequest || !document.head)
DOM.ajax = $.ajax;
/* setting head ie6 - ie8 */
if(!document.head){
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){
DOM.cssSet = function(pParams_o) {
var lElement = '<style ';
if (pParams_o.id) lElement += 'id=' + pParams_o.id + ' ';
@ -28,15 +28,24 @@ var Util, DOM, jQuery;
.appendTo(pParams_o.parent || document.head);
};
}
if (!Function.bind)
Function.prototype.bind = function (context) {
var aArgs = Util.slice(arguments, 1),
fToBind = this,
NOP = function () {},
fBound = function () {
var arr = Util.slice(arguments),
args = aArgs.concat(arr);
return fToBind.apply(context, args);
};
NOP.prototype = this.prototype;
fBound.prototype = new NOP();
/* setting function context (this) */
Util.bind = function(pFunction, pContext){
var lRet;
lRet = $.proxy(pFunction, pContext);
return lRet;
};
return fBound;
};
/*
* typeof callback === "function" should not be used,
@ -51,10 +60,10 @@ var Util, DOM, jQuery;
* @param pType
* @param pListener
*/
DOM.addListener = function(pType, pListener, pCapture, pElement){
DOM.addListener = function(pType, pListener, pCapture, pElement) {
var lRet;
if(!pElement)
if (!pElement)
pElement = window;
lRet = $(pElement).bind(pType, null, pListener);
@ -62,11 +71,11 @@ var Util, DOM, jQuery;
return lRet;
};
if(!document.removeEventListener){
DOM.removeListener = function(pType, pListener, pCapture, pElement){
if (!document.removeEventListener) {
DOM.removeListener = function(pType, pListener, pCapture, pElement) {
var lRet;
if(!pElement)
if (!pElement)
pElement = window;
$(pElement).unbind(pType, pListener);
@ -75,59 +84,72 @@ var Util, DOM, jQuery;
};
}
if(!document.getElementsByClassName){
DOM.getByClass = function(pClass, pElement){
if (!document.getElementsByClassName) {
DOM.getByClassAll = function(pClass, pElement) {
var lClass = '.' + pClass,
lResult;
if(pElement)
if (pElement)
lResult = $(pElement).find(lClass);
else lResult = $.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;
DOM.scrollByPages = Util.retFalse;
/* function polyfill webkit standart function
* https://gist.github.com/2581101
*/
DOM.scrollIntoViewIfNeeded = function(pElement, centerIfNeeded) {
var parent,
topWidth,
leftWidth,
parentComputedStyle,
parentBorderTopWidth,
parentBorderLeftWidth,
overTop,
overBottom,
overLeft,
overRight,
alignWithTop;
if (window.getComputedStyle) {
if (arguments.length === 1)
centerIfNeeded = false;
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),
parent = pElement.parentNode;
parentComputedStyle = window.getComputedStyle(parent, null);
topWidth = parentComputedStyle.getPropertyValue('border-top-width');
leftWidth = parentComputedStyle.getPropertyValue('border-left-width');
parentBorderTopWidth = parseInt(topWidth, 10);
parentBorderLeftWidth = parseInt(leftWidth, 10);
alignWithTop = overTop && !overBottom;
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 =
parent.scrollTop =
pElement.offsetTop -
parent.offsetTop -
parent.clientHeight / 2 -
@ -135,55 +157,55 @@ var Util, DOM, jQuery;
pElement.clientHeight / 2;
if ((overLeft || overRight) && centerIfNeeded)
parent.scrollLeft =
parent.scrollLeft =
pElement.offsetLeft -
parent.offsetLeft -
parent.clientWidth / 2 -
parentBorderLeftWidth +
pElement.clientWidth / 2;
if ( (overTop || overBottom || overLeft || overRight) &&
!centerIfNeeded)
pElement.scrollIntoView(alignWithTop);
};
if ((overTop || overBottom || overLeft || overRight) && !centerIfNeeded)
pElement.scrollIntoView(alignWithTop);
}
};
if(!document.body.classList){
if (!document.body.classList) {
DOM.isContainClass = function(pElement, pClass){
DOM.isContainClass = function(pElement, pClass) {
var lRet,
lClassName = pElement && pElement.className;
if(lClassName)
if (lClassName)
lRet = lClassName.indexOf(pClass) > 0;
return lRet;
};
DOM.addClass = function(pElement, pClass){
DOM.addClass = function(pElement, pClass) {
var lRet,
lClassName = pElement && pElement.className,
lSpaceChar = lClassName ? ' ' : '';
lRet = !DOM.isContainClass(pElement, pClass);
if( lRet )
if ( lRet )
pElement.className += lSpaceChar + pClass;
return lRet;
};
DOM.removeClass = function(pElement, pClass){
DOM.removeClass = function(pElement, pClass) {
var lClassName = pElement.className;
if(lClassName.length > pClass.length)
if (lClassName.length > pClass.length)
pElement.className = lClassName.replace(pClass, '');
};
}
if(!window.JSON){
if (!window.JSON) {
Util.parseJSON = $.parseJSON;
/* https://gist.github.com/754454 */
Util.stringifyJSON = function(pObj){
Util.stringifyJSON = function(pObj) {
var lRet;
if (!Util.isObject(pObj) || pObj === null) {
@ -213,26 +235,26 @@ var Util, DOM, jQuery;
};
}
if(!window.localStorage){
var Storage = function(){
if (!window.localStorage) {
var Storage = function() {
/* приватный переключатель возможности работы с кэшем */
var StorageAllowed,
Data = {};
/* функция проверяет возможно ли работать с кэшем каким-либо образом */
this.isAllowed = function(){
this.isAllowed = function() {
return StorageAllowed;
};
this.setAllowed = function(pAllowed){
this.setAllowed = function(pAllowed) {
StorageAllowed = pAllowed;
return pAllowed;
};
/** remove element */
this.remove = function(pItem){
this.remove = function(pItem) {
var lRet = this;
if(StorageAllowed)
if (StorageAllowed)
delete Data[pItem];
return lRet;
@ -242,40 +264,40 @@ var Util, DOM, jQuery;
* в нём есть нужная нам директория -
* записываем данные в него
*/
this.set = function(pName, pData){
this.set = function(pName, pData) {
var lRet = this;
if(StorageAllowed && pName && pData)
if (StorageAllowed && pName && pData)
Data[pName] = pData;
return lRet;
},
/** Если доступен Storage принимаем из него данные*/
this.get = function(pName){
this.get = function(pName) {
var lRet = false;
if(StorageAllowed)
if (StorageAllowed)
lRet = Data[pName];
return lRet;
},
/* get all Storage from local storage */
this.getAll = function(){
this.getAll = function() {
var lRet = null;
if(StorageAllowed)
if (StorageAllowed)
lRet = Data;
return lRet;
};
/** функция чистит весь кэш для всех каталогов*/
this.clear = function(){
this.clear = function() {
var lRet = this;
if(StorageAllowed)
if (StorageAllowed)
Data = {};
return lRet;

View file

@ -1,92 +1,104 @@
/* module make possible connectoin thrue socket.io on a client */
var CloudCmd, Util, DOM, io;
(function(CloudCmd, Util, DOM) {
var CloudCmd, Util, DOM, CloudFunc, io;
(function(CloudCmd, Util, DOM, CloudFunc) {
'use strict';
var Messages = [],
socket,
Terminal,
CloudCmd.Socket = SocketProto;
ERROR_MSG = 'could not connect to socket.io\n'+
'npm i socket.io';
DOM.jsload('/socket.io/lib/socket.io.js', {
onerror : Util.retFunc(Util.log, ERROR_MSG),
onload : connect
});
function connect() {
var FIVE_SECONDS = 5000;
socket = io.connect(CloudCmd.HOST, {
'max reconnection attempts' : Math.pow(2, 32),
'reconnection limit' : FIVE_SECONDS
});
CloudCmd.Socket = socket;
socket.on('connect', function () {
outToTerminal({stdout: 'socket connected\n'});
});
socket.on('message', function (msg) {
var lMsg = Util.parseJSON(msg);
function SocketProto(callback) {
var Socket = Util.exec.bind(Util),
AllListeners = {},
socket,
outToTerminal(lMsg);
});
CONNECTED = 'socket connected\n',
DISCONNECTED = 'socket disconnected\n',
ERROR_MSG = 'could not connect to socket.io\n'+
'npm i socket.io';
socket.on('disconnect', function () {
outToTerminal({stderr: 'socket disconected\n'});
});
Socket.on = addListener;
Socket.addListener = addListener;
Socket.removeListener = removeListener;
Socket.send = send;
Socket.emit = emit;
socket.on('reconnect_failed', function () {
Util.log('Could not reconnect. Reload page.');
});
}
Socket.CONNECTED = CONNECTED;
Socket.DISCONNECTED = DISCONNECTED;
function outToTerminal(pMsg) {
var i, n, lResult, lStdout, lStderr,
lConsole = CloudCmd.Console;
DOM.Images.hideLoad();
if (Util.isObject(lConsole)) {
if (Messages.length) {
/* show oll msg from buffer */
for (i = 0, n = Messages.length; i < n; i++) {
lStdout = Messages[i].stdout;
lStderr = Messages[i].stderr;
function init(callback) {
DOM.jsload('/socket.io/lib/socket.io.js', {
onerror : Util.log.bind(Util, ERROR_MSG),
onload : function() {
Util.exec(callback);
if (lStdout)
lConsole.log(lStdout);
if (lStderr) {
/* if it's object - convert is to string' */
if (Util.isObject(lStderr))
lStderr = Util.stringifyJSON(lStderr);
lConsole.error(lStderr);
}
if (!socket)
connect();
}
});
}
function addListener(name, func) {
CloudFunc.addListener(name, func, AllListeners, socket);
}
function removeListener(name, func) {
CloudFunc.removeListener(name, func, AllListeners, socket);
}
function send(data) {
if (socket)
socket.send(data);
}
function emit(channel, data) {
if (socket)
socket.emit(channel, data);
}
function setListeners(all, socket) {
var i, n, name, func, listeners;
for (name in all) {
listeners = all[name];
n = listeners.length;
for (i = 0; i < n; i++) {
func = listeners[i];
if (func && socket)
socket.on(name, func);
}
Messages = [];
}
lStdout = pMsg.stdout;
lStderr = pMsg.stderr;
if (lStdout)
lResult = lConsole.log(lStdout);
if (lStderr)
lResult = lConsole.error(lStderr);
}
else
/* if term not accesable save msg to buffer */
Messages.push(pMsg);
Util.log(pMsg);
function connect() {
var FIVE_SECONDS = 5000;
socket = io.connect(CloudCmd.HOST, {
'max reconnection attempts' : Math.pow(2, 32),
'reconnection limit' : FIVE_SECONDS
});
socket.on('connect', function () {
Util.log(CONNECTED);
});
setListeners(AllListeners, socket);
socket.on('disconnect', function () {
Util.log(DISCONNECTED);
});
socket.on('reconnect_failed', function () {
Util.log('Could not reconnect. Reload page.');
});
}
return lResult;
init(callback);
return Socket;
}
})(CloudCmd, Util, DOM);
})(CloudCmd, Util, DOM, CloudFunc);

View file

@ -1,6 +1,6 @@
var CloudCmd, Util, DOM, Dropbox, cb, Client;
var CloudCmd, Util, DOM, CloudFunc, Dropbox, cb, Client;
(function(CloudCmd, Util, DOM){
(function(CloudCmd, Util, DOM, CloudFunc){
'use strict';
CloudCmd.DropBox = DropBoxProto;
@ -10,10 +10,10 @@ var CloudCmd, Util, DOM, Dropbox, cb, Client;
function init(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
getUserData,
load,
DropBoxStore.login,
load
getUserData,
Util.retExec(pCallBack)
]);
}
@ -213,4 +213,4 @@ var CloudCmd, Util, DOM, Dropbox, cb, Client;
init(pCallBack);
}
})(CloudCmd, Util, DOM);
})(CloudCmd, Util, DOM, CloudFunc);

View file

@ -1,66 +1,68 @@
var CloudCmd, Util, DOM, $, filepicker;
var CloudCmd, Util, DOM, filepicker;
(function(CloudCmd, Util, DOM) {
'use strict';
var FilePicker = function(pCallBack) {
function init(pCallBack) {
CloudCmd.FilePicker = FilePickerProto;
function FilePickerProto(callback) {
function init(callback) {
Util.loadOnLoad([
Util.retExec(pCallBack),
load
load,
callback,
]);
}
this.uploadFile = function(pParams, pCallBack) {
var lContent = pParams.data,
lName = pParams.name;
this.uploadFile = function(params) {
var content = params.data,
name = params.name,
log = Util.log.bind(Util);
filepicker.store(lContent, {
filepicker.store(content, {
mimetype: '',
filename: lName
filename: name
},
function(pFPFile) {
Util.log(pFPFile);
function(fpFile) {
log(fpFile);
filepicker.exportFile(pFPFile, Util.log, Util.log);
filepicker.exportFile(fpFile, log, log);
});
};
this.saveFile = function(pCallBack) {
filepicker.pick(function(FPFile) {
Util.log(FPFile);
this.saveFile = function(callback) {
filepicker.pick(function(fpFile) {
Util.log(fpFile);
DOM.ajax({
url : FPFile.url,
url : fpFile.url,
responseType :'arraybuffer',
success : function(pData) {
Util.exec(pCallBack, FPFile.filename, pData);
success : function(data) {
Util.exec(callback, fpFile.filename, data);
}
});
});
};
function load(pCallBack) {
function load(callback) {
Util.time('filepicker load');
var lHTTP = document.location.protocol;
DOM.jsload(lHTTP + '//api.filepicker.io/v1/filepicker.js', function() {
CloudCmd.getModules(function(pModules) {
var lStorage = Util.findObjByNameInArr(pModules, 'storage'),
lFilePicker = Util.findObjByNameInArr(lStorage, 'FilePicker'),
lKey = lFilePicker && lFilePicker.key;
DOM.jsload('//api.filepicker.io/v1/filepicker.js', function() {
CloudCmd.getModules(function(modules) {
var storage = Util.findObjByNameInArr(modules, 'storage'),
picker = Util.findObjByNameInArr(storage, 'FilePicker'),
key = picker && picker.key;
filepicker.setKey(key);
filepicker.setKey(lKey);
DOM.Images.hideLoad();
Util.timeEnd('filepicker loaded');
Util.exec(pCallBack);
Util.exec(callback);
});
});
}
init(pCallBack);
};
CloudCmd.FilePicker = new FilePicker();
init(callback);
}
})(CloudCmd, Util, DOM);

View file

@ -99,9 +99,9 @@ var CloudCmd, Util, DOM, gapi;
GDrive.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
load
Util.loadOnLoad([
load,
Util.retExec(pCallBack)
]);
};

View file

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

View file

@ -66,10 +66,10 @@ var CloudCmd, Util, DOM, WL;
}
SkyDrive.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
Util.loadOnLoad([
load,
auth,
load
Util.retExec(pCallBack)
]);
};

View file

@ -86,10 +86,10 @@ var CloudCmd, Util, DOM, VK;
VKStorage.init = function(pCallBack){
Util.loadOnLoad([
Util.retExec(pCallBack),
Util.loadOnLoad([
load,
auth,
load
Util.retExec(pCallBack)
]);
};

View file

@ -78,5 +78,3 @@ var Base64 = (function () {
return obj;
})();
window.Base64 = Base64

191
lib/client/terminal.js Normal file
View file

@ -0,0 +1,191 @@
var CloudCmd, Util, DOM, CloudFunc, Terminal;
(function(CloudCmd, Util, DOM, CloudFunc) {
'use strict';
CloudCmd.Terminal = TerminalProto;
function TerminalProto(CallBack) {
var Name = 'Terminal',
Loading,
Element,
MouseBinded,
Term,
Cell,
Socket = CloudCmd.Socket,
Key = CloudCmd.Key,
ESC = Key.ESC,
Images = DOM.Images,
Notify = DOM.Notify,
Size = {
cols: 0,
rows: 0
},
CHANNEL = CloudFunc.CHANNEL_TERMINAL,
CHANNEL_RESIZE = CloudFunc.CHANNEL_TERMINAL_RESIZE,
CloudTerm = this;
function init() {
Loading = true;
Util.loadOnLoad([
DOM.jqueryLoad,
CloudCmd.View,
load,
CloudCmd.Socket,
/* rm view keys, it ruin terminal */
function(callback) {
CloudCmd.View.rmKeys(),
Socket = CloudCmd.Socket;
Util.exec(callback);
},
CloudTerm.show,
addListeners
]);
}
CloudTerm.show = show;
CloudTerm.write = write;
function show(callback) {
var options = {
onUpdate: onResize,
};
if (!Loading) {
Images.showLoad({top:true});
if (!Element) {
Element = DOM.anyload({
name : 'div',
id : 'terminal',
style : 'height :100%'
});
/* hack to determine console size
* inspired with
*
* https://github.com/petethepig/devtools-terminal
*/
Cell = DOM.anyload({
name : 'div',
inner : '&nbsp',
parent : Element,
style : 'position: absolute;' +
'top : -1000px;'
});
DOM.cssSet({
id : 'terminal-css',
inner : '#terminal, .terminal, #view {' +
'height' + ': 100%;' +
'}' +
'.terminal-cursor {' +
'background' + ': gray' +
'}'
});
Term = new Terminal({
screenKeys: true,
cursorBlink: false,
});
Term.open(Element);
}
CloudCmd.View.show(Element, function() {
Element.focus();
Terminal.brokenBold = true;
Util.exec(callback);
}, options);
}
}
function write(data) {
Term.write(data);
}
function addListeners(callback) {
var options = {
'connect': function() {
write(Socket.CONNECTED + '\r');
},
'disconnect': function() {
write(Socket.DISCONNECTED +'\r');
},
};
options[CHANNEL] = write;
options[CHANNEL_RESIZE] = function(size) {
Term.resize(size.cols, size.rows);
};
Socket.on(options);
Term.on('data', function(data) {
Socket.emit(CHANNEL, data);
});
Term.on('resize', function(size) {
Socket.emit(CHANNEL_RESIZE, size);
});
Util.exec(callback);
}
function getSize() {
var wSubs = Element.offsetWidth - Element.clientWidth,
w = Element.clientWidth - wSubs,
hSubs = Element.offsetHeight - Element.clientHeight,
h = Element.clientHeight - hSubs,
x = Cell.clientWidth,
y = Cell.clientHeight,
cols = Math.max(Math.floor(w / x), 10),
rows = Math.max(Math.floor(h / y), 10),
size = {
cols: cols,
rows: rows
};
return size;
}
function onResize() {
var size = getSize(),
cols = size.cols,
rows = size.rows;
if (Size.cols !== cols || Size.rows !== rows) {
Size = size;
//Term.resize(size.cols, size.rows);
Term.emit('resize', size);
}
}
function load(pCallBack) {
var dir = CloudCmd.LIBDIRCLIENT + 'terminal/',
path = dir + 'term.js';
Util.time(Name + ' load');
DOM.jsload(path, function() {
Util.timeEnd(Name + ' load');
Loading = false;
Util.exec(pCallBack);
});
}
init();
}
})(CloudCmd, Util, DOM, CloudFunc);

5866
lib/client/terminal/term.js Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,6 +1,6 @@
var CloudCmd, Util, DOM, CloudFunc, $;
(function(CloudCmd, Util, DOM, CloudFunc){
(function(CloudCmd, Util, DOM, CloudFunc) {
'use strict';
CloudCmd.View = ViewProto;
@ -8,133 +8,126 @@ var CloudCmd, Util, DOM, CloudFunc, $;
function ViewProto(CallBack) {
var Name = 'View',
Loading = false,
Events = DOM.Events,
Info = DOM.CurrentInfo,
Key = CloudCmd.Key,
Images = DOM.Images,
View = this,
View = Util.exec.bind(Util),
Element,
Config = {
beforeShow : function(){
beforeShow : function() {
Images.hideLoad();
Key.unsetBind();
},
afterShow : function(){
Element.focus();
},
beforeClose : Key.setBind,
loop : false,
openEffect : 'none',
closeEffect : 'none',
autoSize : false,
height : window.innerHeight,
width : window.innerWidth/0.75,
height : '100%',
width : '100%',
minWidth : 0,
minHeight : 0,
helpers : {
overlay : {
css : {
'background' : 'rgba(255, 255, 255, 0.1)'
}
}
},
padding : 0
};
View.show = show;
View.hide = hide;
View.rmKeys = rmKeys;
function rmKeys() {
/* remove default key binding
* which is ruin terminal
*/
$.fancybox.defaults.keys = null;
}
function init() {
var lFunc, lIsFunc, lIsCallBack;
var func = CallBack || Util.bind(show, null);
Loading = true;
if (CallBack){
lIsFunc = Util.isFunction(CallBack);
lIsCallBack = Util.isFunction(CallBack.callback);
}
if (lIsFunc)
lFunc = CallBack;
else if (lIsCallBack)
lFunc = CallBack.callback;
Util.loadOnLoad([
lFunc || Util.retExec(View.show, null),
DOM.jqueryLoad,
load,
DOM.jqueryLoad
func
]);
DOM.Events.addKey(listener);
DOM.setButtonKey('f3', view);
Events.addKey(listener);
}
/**
* function shows FancyBox
*/
this.show = function(pData, pCallBack, pConfig) {
var lPath, lElement, lAfterFunc, lFunc, name,
function show(data, callback, newConfig) {
var path, element, func, name, isImage,
config = {};
if (!Loading) {
Element = $('<div id=view tabindex=0>');
if (pData) {
lElement = $(Element).append(pData);
lAfterFunc = Config.afterShow,
lFunc = function(){
Util.exec(lAfterFunc);
Util.exec(pCallBack);
};
if (data) {
element = $(Element).append(data);
func = Util.retExec(callback);
Config.afterShow = lFunc;
Config.afterShow = func;
Util.copyObj(Config, config);
Util.copyObj(config, Config);
for (name in pConfig)
config[name] = pConfig[name];
for (name in newConfig)
config[name] = newConfig[name];
$.fancybox(lElement, config);
$.fancybox(element, config);
} else {
lPath = CloudFunc.FS + DOM.getCurrentPath();
if( Util.checkExtension(lPath, ['png','jpg', 'gif','ico']) ) {
$.fancybox.open({ href : lPath }, Config);
}
else
DOM.getCurrentData(function(pParams){
var data = document.createTextNode(pParams.data);
$.fancybox( Element.append(data), Config );
path = CloudFunc.FS + Info.path;
isImage = $.fancybox.isImage(path);
if (isImage) {
$.fancybox.open({ href : path }, Config);
} else
Info.getData(function(data) {
var element = document.createTextNode(data);
/* add margin only for view text documents */
Element.css('margin', '2%');
$.fancybox(Element.append(element), Config);
});
}
}
};
}
this.hide = function() {
function hide() {
$.fancybox.close();
};
}
/**
* function loads css and js of FancyBox
* @pParent - this
* @pCallBack - executes, when everything loaded
* @callback - executes, when everything loaded
*/
function load(pCallBack) {
Util.time(Name + ' load');
var lDir = CloudCmd.LIBDIRCLIENT + 'view/fancyBox/source/',
lFiles = [ lDir + 'jquery.fancybox.css',
lDir + 'jquery.fancybox.js' ];
function load(callback) {
var dir = CloudCmd.LIBDIRCLIENT + 'view/fancyBox/source/',
files = [
dir + 'jquery.fancybox.css',
dir + 'jquery.fancybox.js'
];
DOM.anyLoadOnLoad([lFiles], function(){
Util.time(Name + ' load');
DOM.anyLoadOnLoad([files], function() {
Util.timeEnd(Name + ' load');
Loading = false;
Util.exec( pCallBack );
Util.exec(callback);
Images.hideLoad();
})
.cssSet({id:'view-css',
inner : '#view{' +
inner : '#view {' +
'font-size: 16px;' +
'white-space :pre;' +
'outline: 0;' +
'}' +
'#view::selection{' +
'#view::selection {' +
/*
'background: #fe57a1;'
'color: #fff;'
@ -146,6 +139,9 @@ var CloudCmd, Util, DOM, CloudFunc, $;
'background: none;' +
'width: 0;' +
'height: 0' +
'}' +
'.fancybox-overlay {' +
'background: rgba(255, 255, 255, 0.1)' +
'}'
});
@ -156,19 +152,18 @@ var CloudCmd, Util, DOM, CloudFunc, $;
View.show();
}
function listener(pEvent) {
var lF3 = Key.F3,
lIsBind = Key.isBind(),
lKey = pEvent.keyCode;
function listener(event) {
var keyCode = event.keyCode,
ESC = Key.ESC;
/* если клавиши можно обрабатывать */
if (lIsBind && lKey === lF3) {
view();
DOM.preventDefault(pEvent);
}
if (keyCode === ESC)
hide();
}
init();
return View;
}
})(CloudCmd, Util, DOM, CloudFunc);

View file

@ -1,6 +1,6 @@
var Util;
(function(scope) {
(function(scope, Util) {
'use strict';
/**
@ -11,7 +11,7 @@ var Util;
if (scope.window) {
scope.CloudFunc = new CloudFuncProto(Util);
} else {
if(!global.cloudcmd)
if (!global.cloudcmd)
return console.log(
'# cloudfunc.js' + '\n' +
'# -----------' + '\n' +
@ -26,53 +26,96 @@ var Util;
}
function CloudFuncProto(Util) {
function CloudFuncProto(Util) {
var CloudFunc = this,
FS,
COMBINE = '/combine/';
/* Путь с которым мы сейчас работаем */
this.Path = '';
JOIN = '/join/';
/* КОНСТАНТЫ (общие для клиента и сервера)*/
/* название программы */
this.NAME = 'Cloud Commander';
this.NAME = 'Cloud Commander';
/* если в ссылке будет эта строка - в браузере js отключен */
this.FS = FS = '/fs';
this.FS = FS = '/fs';
/* название css-класа кнопки обновления файловой структуры*/
this.REFRESHICON = 'refresh-icon';
this.REFRESHICON = 'refresh-icon';
/* id панелей с файлами */
this.LEFTPANEL = 'left';
this.RIGHTPANEL = 'right';
this.PANEL_LEFT = 'js-left';
this.PANEL_RIGHT = 'js-right';
this.CHANNEL_CONSOLE = 'console-data';
this.CHANNEL_TERMINAL = 'terminal-data';
this.CHANNEL_TERMINAL_RESIZE= 'terminal-resize';
this.getCombineURL = function(names) {
var url,
nameStr = names + '';
this.addListener = function(name, func, allListeners, socket) {
var listeners, obj;
nameStr = Util.replaceStr(nameStr, ',', ':');
url = COMBINE + nameStr;
if (Util.isString(name)) {
listeners = allListeners[name];
if (!listeners)
listeners = allListeners[name] = [];
listeners.push(func);
if (func && socket)
socket.on(name, func);
} else if (Util.isObject(name)) {
obj = name;
for (name in obj) {
func = obj[name];
CloudFunc.addListener(name, func, allListeners, socket);
}
}
};
this.removeListener = function(name, func, allListeners, socket) {
var i, n, listeners;
if (socket)
socket.removeListener(name, func);
listeners = allListeners[name];
if (listeners) {
n = listeners.length;
for (i = 0; i < n; i++)
if (listeners[i] === func)
listeners[i] = null;
}
};
this.getJoinURL = function(names) {
var url, isContain,
regExp = new RegExp(',', 'g'),
nameStr = names + '';
nameStr = nameStr.replace(regExp, ':');
nameStr = this.rmFirstSlash(nameStr);
url = JOIN + nameStr;
return url;
};
this.getCombineArray = function(url) {
var str = Util.removeStrOneTime(url, COMBINE),
this.getJoinArray = function(url) {
var str = Util.removeStrOneTime(url, JOIN),
names = str.split(':');
return names;
};
this.isCombineURL = function(url) {
var ret = Util.isContainStrAtBegin(url, COMBINE);
this.isJoinURL = function(url) {
var ret = Util.isContainStrAtBegin(url, JOIN);
return ret;
};
this.formatMsg = function(pMsg, pName, pStatus) {
this.formatMsg = function(pMsg, pName, pStatus) {
var status = pStatus || 'ok',
name = !pName ? '': '("' + pName + '")',
msg = pMsg + ': ' + status + name;
@ -83,23 +126,33 @@ var Util;
* Функция убирает последний слеш,
* если он - последний символ строки
*/
this.removeLastSlash = function(pPath) {
this.rmLastSlash = function(pPath) {
var lRet = pPath,
lIsStr = typeof pPath==='string',
lIsStr = Util.isString(pPath),
lLengh = pPath.length-1,
lLastSlash = pPath.lastIndexOf('/');
if(lIsStr && lLastSlash === lLengh)
if (lIsStr && lLastSlash === lLengh)
lRet = pPath.substr(pPath, lLengh);
return lRet;
};
};
this.rmFirstSlash = function(str) {
var ret = str,
isContain = Util.isContainStrAtBegin(str, '/');
if (isContain)
ret = Util.removeStrOneTime(str, '/');
return ret;
};
/** Функция возвращает заголовок веб страницы
* @pPath
*/
this.getTitle = function(pPath) {
if(!CloudFunc.Path)
this.getTitle = function(pPath) {
if (!CloudFunc.Path)
CloudFunc.Path = '/';
return CloudFunc.NAME + ' - ' + (pPath || CloudFunc.Path);
@ -110,7 +163,7 @@ var Util;
* @param pPerm_s - строка с правами доступа
* к файлу в 8-миричной системе
*/
this.getSymbolicPermissions = function(pPerm_s) {
this.getSymbolicPermissions = function(pPerm_s) {
var lType, lOwner, lGroup, lAll,
perms = pPerm_s && pPerm_s.toString(),
lPermissions = perms;
@ -160,23 +213,23 @@ var Util;
of using an application-specific shorthand).
*/
/* Переводим в двоичную систему */
lOwner = ( perms[0] - 0 ).toString(2),
lGroup = ( perms[1] - 0 ).toString(2),
lAll = ( perms[2] - 0 ).toString(2),
lOwner = (perms[0] - 0).toString(2),
lGroup = (perms[1] - 0).toString(2),
lAll = (perms[2] - 0).toString(2),
/* переводим в символьную систему*/
lPermissions =
( lOwner[0] - 0 > 0 ? 'r' : '-' ) +
( lOwner[1] - 0 > 0 ? 'w' : '-' ) +
( lOwner[2] - 0 > 0 ? 'x' : '-' ) +
(lOwner[0] - 0 > 0 ? 'r' : '-') +
(lOwner[1] - 0 > 0 ? 'w' : '-') +
(lOwner[2] - 0 > 0 ? 'x' : '-') +
' ' +
( lGroup[0] - 0 > 0 ? 'r' : '-' ) +
( lGroup[1] - 0 > 0 ? 'w' : '-' ) +
( lGroup[2] - 0 > 0 ? 'x' : '-' ) +
(lGroup[0] - 0 > 0 ? 'r' : '-') +
(lGroup[1] - 0 > 0 ? 'w' : '-') +
(lGroup[2] - 0 > 0 ? 'x' : '-') +
' ' +
( lAll[0]- 0 > 0 ? 'r' : '-' ) +
( lAll[1]- 0 > 0 ? 'w' : '-' ) +
( lAll[2]- 0 > 0 ? 'x' : '-' );
(lAll[0]- 0 > 0 ? 'r' : '-') +
(lAll[1]- 0 > 0 ? 'w' : '-') +
(lAll[2]- 0 > 0 ? 'x' : '-');
}
return lPermissions;
@ -216,7 +269,7 @@ var Util;
* гигайбайты и терабайты
* @pSize - размер в байтах
*/
this.getShortSize = function(pSize) {
this.getShortSize = function(pSize) {
if (pSize === pSize-0) {
/* Константы размеров, что используются внутри функции */
var l1KB = 1024,
@ -235,47 +288,12 @@ var Util;
return pSize;
};
/** Функция парсит uid и имена пользователей
* из переданного в строке вычитаного файла /etc/passwd
* и возвращает массив обьектов имён и uid пользователей
* @pPasswd_s - строка, в которой находиться файл /etc/passwd
*/
this.getUserUIDsAndNames = function(pPasswd_s) {
var lUsers = {name:'', uid:''},
lUsersData = [],
i = 0;
do {
/* получаем первую строку */
var lLine = pPasswd_s.substr(pPasswd_s, pPasswd_s.indexOf('\n') + 1);
if(lLine) {
/* удаляем первую строку из /etc/passwd*/
pPasswd_s = Util.removeStr(pPasswd_s, lLine);
/* получаем первое слово строки */
var lName = lLine.substr(lLine,lLine.indexOf(':'));
lLine = Util.removeStr(lLine, lName + ':x:');
/* получаем uid*/
var lUID = lLine.substr(lLine,lLine.indexOf(':'));
if((lUID - 0).toString()!=='NaN') {
lUsers.name = lName;
lUsers.uid = lUID;
lUsersData[i++] = lUsers;
console.log('uid='+lUID+' name='+lName);
}
}
} while(pPasswd_s !== '');
return lUsersData;
};
/** Функция получает адреса каждого каталога в пути
* возвращаеться массив каталогов
* @param url - адрес каталога
*/
this._getDirPath = function(url) {
function getDirPath(url) {
var lShortName,
folders = [],
i;
@ -283,10 +301,10 @@ var Util;
do {
folders.push(url);
url = url.substr(url, url.lastIndexOf('/'));
} while(url !== '');
} while (url !== '');
/* Формируем ссылки на каждый каталог в пути */
var lHref = '<a class=links href="',
var lHref = '<a href="',
lTitle = '" title="',
_l = '">',
lHrefEnd ='</a>',
@ -296,13 +314,13 @@ var Util;
'/' + _l + '/' +
lHrefEnd;
for(i = folders.length - 1; i > 0; i--) {
for (i = folders.length - 1; i > 0; i--) {
var lUrl = folders[i],
lSlashIndex = lUrl.lastIndexOf('/') + 1;
lShortName = Util.removeStr(lUrl, lUrl.substr(lUrl, lSlashIndex) );
lShortName = Util.removeStr(lUrl, lUrl.substr(lUrl, lSlashIndex));
if(i !== 1)
if (i !== 1)
lHtmlPath += lHref + FS + lUrl +
lTitle + lUrl + _l +
lShortName + lHrefEnd + '/';
@ -311,40 +329,42 @@ var Util;
}
return lHtmlPath;
};
}
/**
* Функция строит таблицу файлв из JSON-информации о файлах
* @param pJSON - информация о файлах
* @param pKeyBinded - если клавиши назначены, выделяем верхний файл
* [{path:'путь',size:'dir'},
* @param json - информация о файлах
*
* {name:'имя',size:'размер',mode:'права доступа'}]
*/
this.buildFromJSON = function(pJSON, pTemplate, pPathTemplate, pLinkTemplate) {
var lFile, i, n, type, link, target, size, owner, mode,
this.buildFromJSON = function(json, template, pathTemplate, linkTemplate) {
var file, i, n, type, target, size, owner, mode,
/* ссылка на верхний каталог*/
dotDot, link,
linkResult,
files = pJSON.files,
files = json.files,
/* сохраняем путь каталога в котором мы сейчас находимся*/
lPath = pJSON.path,
path = json.path,
/*
* Строим путь каталога в котором мы находимся
* со всеми подкаталогами
*/
lHtmlPath = CloudFunc._getDirPath(lPath),
htmlPath = getDirPath(path),
/* Убираем последний слэш
* с пути для кнопки обновить страницу
* если он есть
*/
lRefreshPath = CloudFunc.removeLastSlash(lPath),
refreshPath = CloudFunc.rmLastSlash(path),
lFileTable = Util.render(pPathTemplate, {
link: FS + lRefreshPath,
path: lHtmlPath
fileTable = Util.render(pathTemplate, {
link : FS + refreshPath,
fullPath : path,
path : htmlPath
}),
lHeader = Util.render(pTemplate, {
header = Util.render(template, {
className : 'fm-header',
type : '',
name : 'name',
@ -353,34 +373,34 @@ var Util;
mode : 'mode'
});
lHeader = Util.replaceStr(lHeader, 'li', 'div');
lFileTable += lHeader;
header = Util.replaceStr(header, 'li', 'div');
header = Util.removeStrOneTime(header, 'draggable ');
fileTable += header;
/* сохраняем путь */
CloudFunc.Path = lPath;
CloudFunc.Path = path;
lFileTable += '<ul class="files">';
fileTable += '<ul class="files">';
/* Если мы не в корне */
if (lPath !== '/') {
/* ссылка на верхний каталог*/
var lDotDot, lLink;
if (path !== '/') {
/* убираем последний слеш и каталог в котором мы сейчас находимся*/
lDotDot = lPath.substr(lPath, lPath.lastIndexOf('/'));
lDotDot = lDotDot.substr(lDotDot, lDotDot.lastIndexOf('/'));
dotDot = path.substr(path, path.lastIndexOf('/'));
dotDot = dotDot.substr(dotDot, dotDot.lastIndexOf('/'));
/* Если предыдущий каталог корневой */
if(lDotDot === '')
lDotDot = '/';
if (dotDot === '')
dotDot = '/';
lLink = FS + lDotDot;
link = FS + dotDot;
linkResult = Util.render(pLinkTemplate, {
link : lLink,
linkResult = Util.render(linkTemplate, {
link : link,
name : '..',
target : ''
});
/* Сохраняем путь к каталогу верхнего уровня*/
lFileTable += Util.render(pTemplate,{
fileTable += Util.render(template,{
className : '',
type : 'directory',
name : linkResult,
@ -392,23 +412,30 @@ var Util;
n = files.length;
for (i = 0; i < n; i++) {
lFile = files[i];
type = lFile.size === 'dir' ? 'directory' : 'text-file';
link = FS + lPath + lFile.name;
target = lFile.size === 'dir' ? '' : "_blank";
size = lFile.size === 'dir' ? '&lt;dir&gt;' : CloudFunc.getShortSize( lFile.size );
owner = !lFile.uid ? 'root' : lFile.uid;
mode = CloudFunc.getSymbolicPermissions(lFile.mode);
file = files[i];
link = FS + path + file.name;
if (file.size === 'dir') {
type = 'directory';
target = '';
size = '&lt;dir&gt;';
} else {
type = 'text-file';
target = '_blank';
size = CloudFunc.getShortSize(file.size);
}
linkResult = Util.render(pLinkTemplate, {
owner = file.owner || 'root';
mode = CloudFunc.getSymbolicPermissions(file.mode);
linkResult = Util.render(linkTemplate, {
link : link,
name : lFile.name,
name : file.name,
target : target
});
lFileTable += Util.render(pTemplate,{
fileTable += Util.render(template,{
className : '',
/* Если папка - выводим пиктограмму папки *
* В противоположном случае - файла */
@ -420,9 +447,9 @@ var Util;
});
}
lFileTable += '</ul>';
fileTable += '</ul>';
return lFileTable;
return fileTable;
};
}
})(this);
})(this, Util);

View file

@ -16,9 +16,8 @@
var patches = dmp.patch_fromText(patch),
result = dmp.patch_apply(patches, oldText),
newText = result[0];
return newText;
};
};
})(this);

View file

@ -23,13 +23,15 @@
CloudFunc = main.cloudfunc,
AppCache = main.appcache,
Socket = main.socket,
Console = main.console,
Terminal = main.terminal,
zlib = main.zlib,
http = main.http,
https = main.https,
Util = main.util,
express = main.express,
expressApp = express.getApp(controller),
expressApp,
files = main.files,
Server, Rest, Route;
@ -49,80 +51,97 @@
* @param pConfig
* @param pProcessing {index, appcache, rest}
*/
function start(pProcessing) {
var lConfig = main.config;
function start(options) {
var redirectServer,
config = main.config;
if (!pProcessing)
pProcessing = {};
if (!options)
options = {};
Rest = pProcessing.rest;
Route = pProcessing.route;
Rest = options.rest;
Route = options.route;
init(pProcessing.appcache);
init(options.appcache);
var lPort = process.env.PORT || /* c9 */
process.env.app_port || /* nodester */
process.env.VCAP_APP_PORT || /* cloudfoundry */
lConfig.port,
config.port,
lIP = process.env.IP || /* c9 */
lConfig.ip ||
config.ip ||
(main.WIN32 ? '127.0.0.1' : '0.0.0.0'),
lSSL = pProcessing.ssl,
lSSLPort = lConfig.sslPort,
lSSL = options.ssl,
lSSLPort = config.sslPort,
lHTTP = 'http://',
lHTTPS = 'https://',
lSockets = function(pServer) {
var lListen, msg, status;
var listen, msg,
status = 'off';
if (lConfig.socket && Socket)
lListen = Socket.listen(pServer);
if (config.socket && Socket) {
listen = Socket.listen(pServer);
if (listen) {
status = 'on';
Console.init();
Terminal.init();
}
}
status = lListen ? 'on' : 'off';
msg = CloudFunc.formatMsg('sockets', '', status);
Util.log(msg);
},
lHTTPServer = function() {
Server = http.createServer(expressApp || controller);
Server.on('error', Util.log);
expressApp = express.getApp([
Rest,
Route,
join,
controller
]);
Server = http.createServer(expressApp || respond);
Server.on('error', Util.log.bind(Util));
Server.listen(lPort, lIP);
lServerLog(lHTTP, lPort);
lSockets(Server);
},
lServerLog = function(pHTTP, pPort) {
Util.log('* Server running at ' + pHTTP + lIP + ':' + pPort);
lServerLog = function(http, port) {
Util.log('* Server running at ' + http + lIP + ':' + port);
};
/* server mode or testing mode */
if (lConfig.server) {
if (config.server)
if (lSSL) {
Util.log('* Redirection http -> https is setted up');
lServerLog(lHTTP, lPort);
var lRedirectServer = http.createServer( function(pReq, pRes) {
var lURL,
lHost = pReq.headers.host,
lParsed = URL.parse(lHost),
lHostName = lParsed.protocol;
redirectServer = http.createServer(function(req, res) {
var url,
host = req.headers.host,
parsed = url.parse(host),
hostName = parsed.protocol;
lURL = lHTTPS + lHostName + lSSLPort + pReq.url;
url = lHTTPS + hostName + lSSLPort + req.url;
main.redirect({
response: pRes,
url: lURL
response: res,
url: url
});
});
lRedirectServer.listen(lPort, lIP);
redirectServer.listen(lPort, lIP);
Server = https.createServer(lSSL, controller);
Server.on('error', function (pError) {
Server = https.createServer(lSSL, respond);
Server.on('error', function (error) {
Util.log('Could not use https port: ' + lSSLPort);
Util.log(pError);
lRedirectServer.close();
Util.log(error);
redirectServer.close();
Util.log('* Redirection http -> https removed');
lHTTPServer();
});
@ -133,10 +152,28 @@
lServerLog(lHTTPS, lSSLPort);
} else
lHTTPServer();
} else
else
Util.log('Cloud Commander testing mode');
}
function respond(req, res) {
var i, n, func,
funcs = ([
Rest,
Route,
join,
controller
]);
n = funcs.length;
for (i = 0; i < n; i++) {
func = funcs[i];
funcs[i] = func.bind(null, req, res);
}
Util.loadOnLoad(funcs);
}
/**
* Главная функция, через которую проихсодит
@ -144,96 +181,72 @@
* @param req - запрос клиента (Request)
* @param res - ответ сервера (Response)
*/
function controller(pReq, pRes) {
/* Читаем содержимое папки, переданное в url */
var lRet, lName, lMin, lCheck, lResult,
lConfig = main.config,
lParsedUrl = URL.parse(pReq.url),
lQuery = lParsedUrl.search || '',
lPath = lParsedUrl.pathname;
/* added supporting of Russian language in directory names */
lPath = Querystring.unescape(lPath);
function controller(req, res) {
var check, result,
config = main.config,
isMin = config.minify,
parsedUrl = URL.parse(req.url),
query = parsedUrl.search || '',
path = main.getPathName(req),
name = path;
if (!expressApp)
Util.log(pReq.method + ' ' + lPath + lQuery);
Util.log(req.method + ' ' + path + query);
var lData = {
name : lPath,
request : pReq,
response : pRes
};
/* watching is file changed */
if (config.appcache)
AppCache.watch(name);
if (lConfig.rest )
lRet = Util.exec(Rest, lData);
name = Path.join(DIR, name);
check = checkExtension(name);
result = isMin && check;
if (!lRet && Route)
lRet = Util.exec(Route, lData);
if (!lRet)
lRet = combine(lData);
if (!lRet) {
lName = lData.name;
/* watching is file changed */
if (lConfig.appcache)
AppCache.watch(lName);
lName = Path.join(DIR, lName);
lMin = lConfig.minify,
lCheck = checkExtension(lName);
lResult = lMin && lCheck;
Util.ifExec(!lResult,
function(pParams) {
var lSendName = pParams && pParams.name || lName;
main.sendFile({
name : lSendName,
cache : lConfig.cache,
gzip : true,
request : pReq,
response : pRes
});
}, function(pCallBack) {
Minify.optimize(lName, {
callback : pCallBack,
returnName : true
Util.ifExec(!result,
function(params) {
var sendName = params && params.name || name;
main.sendFile({
name : sendName,
cache : config.cache,
gzip : true,
request : req,
response : res
});
});
}
}
function minify(name) {
return function(callback) {
}, function(callback) {
Minify.optimize(name, {
callback : callback
callback : callback,
returnName : true
});
};
});
}
function combine(params) {
function minify(name, callback) {
Minify.optimize(name, {
callback : callback
});
}
function join(request, response, callback) {
var names, i, n, name, minName, stream, check,
funcs = [],
config = main.config,
dir = DIR,
gzip = zlib.createGzip(),
p = params,
isGzip = main.isGZIP(p.request),
path = params.name,
isCombine = CloudFunc.isCombineURL(path),
isGzip = main.isGZIP(request),
path = main.getPathName(request),
isJoin = CloudFunc.isJoinURL(path),
readPipe = function() {
main.mainSetHeader({
name : names[0],
cache : config.cache,
gzip : isGzip,
request : p.request,
response : p.response
request : request,
response : response
});
if (!isGzip)
stream = p.response;
stream = response;
else
stream = gzip;
@ -244,15 +257,18 @@
callback : function(error) {
var errorStr;
if (error) {
if (!p.response.headersSent)
main.sendError(params, error);
if (error)
if (!response.headersSent)
main.sendError({
request : request,
response : response,
name : path
}, error);
else {
Util.log(error);
errorStr = error.toString();
stream.end(errorStr);
}
}
}
});
@ -261,11 +277,11 @@
* readPipe called with stream param
*/
if (isGzip)
gzip.pipe(p.response);
gzip.pipe(response);
};
if (isCombine) {
names = CloudFunc.getCombineArray(path);
if (isJoin) {
names = CloudFunc.getJoinArray(path);
n = names.length;
if (!config.minify)
@ -284,13 +300,14 @@
}
}
funcs.push(minify(name));
funcs.push(minify.bind(null, name));
}
Util.asyncCall(funcs, readPipe);
}
}
} else
Util.exec(callback);
return isCombine;
return isJoin;
}
function checkExtension(name) {

View file

@ -13,20 +13,24 @@
var main = global.cloudcmd.main,
fs = main.fs,
Util = main.util,
users = main.users,
WIN32 = main.WIN32,
checkParams = main.checkCallBackParams;
exports.getDirContent = function(pPath, pCallBack) {
var lRet = Util.isString(pPath);
exports.getDirContent = function(path, callback) {
var ret = Util.isString(path);
if (lRet)
fs.readdir(pPath, Util.call(readDir, {
callback: pCallBack,
path : pPath
}));
if (!ret)
Util.exec(callback, "First parameter should be a string");
else
Util.exec(pCallBack, "First parameter should be a string");
fs.readdir(path, readDir.bind(null, {
callback : callback,
path : path
}));
return lRet;
return ret;
};
@ -35,84 +39,78 @@
* @param pError
* @param pFiles
*/
function readDir(pParams) {
var lRet = checkParams(pParams);
lRet = lRet && Util.checkObj(pParams.params, ['path']);
function readDir(params, error, files) {
var i, n, stats, filesData, fill, name, fileParams,
p = params,
dirPath = getDirPath(p.path);
if (lRet) {
var p = pParams,
c = pParams.params,
lFiles = p.data,
lDirPath = getDirPath(c.path);
if (error)
Util.exec(p.callback, error.toString());
else {
/* Получаем информацию о файлах */
n = files.length,
stats = {},
if (!p.error && lFiles) {
lFiles.data = lFiles.sort();
/* Получаем информацию о файлах */
var n = lFiles.length,
lStats = {},
lFilesData = {
files : lFiles,
stats : lStats,
callback : c.callback,
path : c.path
},
filesData = {
files : files,
stats : stats,
callback : p.callback,
path : p.path
},
fill = fillJSON.bind(null, filesData);
if (n)
for (i = 0; i < n; i++) {
name = dirPath + files[i],
lFill = Util.retFunc(fillJSON, lFilesData);
if (n)
for (var i = 0; i < n; i++) {
var lName = lDirPath + lFiles[i],
lParams = {
callback : lFill,
count : n,
name : lFiles[i],
stats : lStats,
};
fs.stat(lName, Util.call(getFilesStat, lParams));
}
else
fillJSON(lFilesData);
} else
Util.exec(c.callback, p.error.toString());
fileParams = {
callback : fill,
count : n,
name : files[i],
stats : stats,
};
fs.stat(name, onStat.bind(null, fileParams));
}
else
fillJSON(filesData);
}
}
/**
* async getting file states
* and putting it to lStats object
* and putting it to stats object
*/
function getFilesStat(pParams) {
var lRet = checkParams(pParams);
lRet = lRet && Util.checkObjTrue(pParams.params,
['callback', 'stats', 'name', 'count']);
function onStat(params, error, stat) {
var n, keys, p = params;
if (lRet) {
var p = pParams,
c = p.params;
c.stats[c.name] = !p.error ? p.data : {
if (!error)
p.stats[p.name] = stat;
else
p.stats[p.name] = {
'mode' : 0,
'size' : 0,
'isDirectory' : Util.retFalse
};
if (c.count === Object.keys(c.stats).length)
Util.exec(c.callback);
}
keys = Object.keys(p.stats);
n = keys.length;
if (p.count === n)
Util.exec(p.callback);
}
/**
* Function fill JSON by file stats
*
* @param pStats - object, contain file stats.
* @param stats - object, contain file stats.
* example {'1.txt': stat}
*
* @param pFiles - array of files of current directory
* @param files - array of files of current directory
*/
function fillJSON(pParams) {
var name, stat, mode, isDir, size, uid, modeStr,
var name, stat, mode, isDir, size, owner, modeStr,
p, i, n, file, path, json, files,
ret = Util.checkObjTrue(pParams, ['files', 'stats', 'path']);
@ -132,7 +130,7 @@
for (i = 0; i < n; i++ ) {
name = p.files[i];
stat = p.stats[name];
uid = stat.uid;
owner = stat.uid;
if (stat) {
/* Переводим права доступа в 8-ричную систему */
@ -145,7 +143,7 @@
file = {
'name' : name,
'size' : size,
'uid' : uid,
'owner' : owner,
'mode' : mode
};
@ -154,10 +152,37 @@
json.files = changeOrder(files);
Util.exec(p.callback, null, json);
changeUIDToName(json, function(json) {
Util.exec(p.callback, null, json);
});
}
}
function changeUIDToName(json, callback) {
Util.ifExec(WIN32, callback, function() {
users.getNames(function(error, names) {
var i, n, current, owner,
files = json.files;
Util.log(error);
n = files.length;
for (i = 0; i < n; i++) {
current = files[i];
owner = current.owner;
owner = names[owner];
if (owner)
current.owner = owner;
}
Util.exec(callback, json);
});
});
}
function changeOrder(json) {
var file, i, n,
files = [],

321
lib/server/console.js Normal file
View file

@ -0,0 +1,321 @@
(function() {
'use strict';
var main = global.cloudcmd.main,
DIR = main.DIR,
SRVDIR = main.SRVDIR,
socket = main.socket,
update = main.srvrequire('update'),
exec = main.child_process.exec,
spawn = main.child_process.spawn,
Util = main.util,
path = main.path,
CloudFunc = main.cloudfunc,
mainpackage = main.mainpackage,
equalPart = Util.isContainStrAtBegin,
CLOUDCMD = mainpackage.name,
ClientFuncs = [],
ClientDirs = [],
Clients = [],
WIN32 = main.WIN32,
ConNum = 0,
CHANNEL = CloudFunc.CHANNEL_CONSOLE,
HELP = {
stdout : CLOUDCMD + ' exit \n' +
CLOUDCMD + ' update \n',
},
/* windows commands thet require
* unicode charset on locales
* different then English
*/
Win32Commands = ['ASSOC', 'AT', 'ATTRIB', 'BREAK', 'CACLS', 'CALL',
'CD', 'CHCP', 'CHDIR', 'CHKDSK', 'CHKNTFS', 'CLS',
'CMD', 'COLOR', 'COMP', 'COMPACT', 'CONVERT', 'COPY',
'DATE', 'DEL', 'DIR', 'DISKCOMP', 'DISKCOPY', 'DOSKEY',
'ECHO', 'ENDLOCAL', 'ERASE', 'EXIT', 'FC', 'FIND',
'FINDSTR', 'FOR', 'FORMAT', 'FTYPE', 'GOTO', 'GRAFTABL',
'HELP', 'IF', 'LABEL', 'MD', 'MKDIR', 'MODE', 'MORE',
'MOVE', 'PATH', 'PAUSE', 'POPD', 'PRINT', 'PROMPT',
'PUSHD', 'RD', 'RECOVER', 'REM', 'REN', 'RENAME',
'REPLACE', 'RMDIR', 'SET', 'SETLOCAL', 'SHIFT', 'SORT',
'START', 'SUBST', 'TIME', 'TITLE', 'TREE', 'TYPE',
'VER', 'VERIFY', 'VOL', 'XCOPY'];
/**
* function listen on servers port
* @pServer {Object} started server object
*/
exports.init = function() {
var ret;
ret = socket.on('connection', function(clientSocket) {
onConnection(clientSocket, function(json, all) {
socket.emit(CHANNEL, json, clientSocket, all);
});
});
return ret;
};
function onConnection(clientSocket, callback) {
var msg, onDisconnect, onMessage;
++ConNum;
if (!Clients[ConNum]) {
msg = log(ConNum, 'console connected\n');
Util.exec(callback, {
stdout : msg
});
Clients[ConNum] = true;
onMessage = getOnMessage(ConNum, callback);
onDisconnect = function(conNum) {
Clients[conNum] =
ClientFuncs[conNum] = null;
log(conNum, 'console disconnected');
socket.removeListener(CHANNEL, onMessage, clientSocket);
socket.removeListener('disconnect', onDisconnect, clientSocket);
}.bind(null, ConNum);
socket.on(CHANNEL, onMessage, clientSocket);
socket.on('disconnect', onDisconnect, clientSocket);
} else {
msg = log(ConNum, ' in use. Reconnecting...\n');
Util.exec(callback, {
stdout: msg
});
socket.disconnect();
}
}
/**
* function gets onMessage function
* that execute needed command
*
* @param pConnNum
* @param callback
*/
function getOnMessage(pConnNum, callback) {
return function(pCommand) {
var lWinCommand, lExec_func, firstChar,
connName,
lError, lRet, lExecSymbols, isContain,
dir, options = {};
dir = ClientDirs[pConnNum];
if (!dir)
dir = ClientDirs[pConnNum] = DIR;
connName = '#' + pConnNum + ': ';
Util.log(connName + pCommand);
if (equalPart(pCommand, CLOUDCMD))
lRet = onCloudCmd(pCommand, callback);
else if (equalPart(pCommand, 'cd ')) {
lRet = true;
onCD(pCommand, dir, function(json) {
var error = json.stderr,
stdout = json.stdout;
if (error)
Util.exec(callback, json);
else
ClientDirs[pConnNum] = stdout;
});
}
if (!lRet) {
/* if we on windows and command is build in
* change code page to unicode becouse
* windows use unicode on non English versions
*/
if (WIN32) {
lWinCommand = pCommand.toUpperCase();
if (Win32Commands.indexOf(lWinCommand) >= 0)
pCommand = 'chcp 65001 |' + pCommand;
}
if (!ClientFuncs[pConnNum])
ClientFuncs[pConnNum] = getExec(function(json, pError, pStderr) {
log(pConnNum, pError, 'error');
log(pConnNum, pStderr, 'stderror');
Util.exec(callback, json);
});
lExec_func = ClientFuncs[pConnNum];
lExecSymbols = ['*', '&', '{', '}', '|', '\'', '"'];
isContain = Util.isContainStr(pCommand, lExecSymbols);
firstChar = pCommand[0];
options.cwd = dir;
if (firstChar === '#') {
pCommand = pCommand.slice(1);
pCommand = connName + pCommand;
pCommand = Util.addNewLine(pCommand);
Util.exec(callback, {
stdout: pCommand
}, true);
} else if (WIN32 || firstChar === ' ' || isContain)
exec(pCommand, options, lExec_func);
else
getSpawn(pCommand, options, callback);
}
};
}
/**
* function send result of command to client
* @param callback
*/
function getExec(callback) {
return function(pError, pStdout, pStderr) {
var lErrorStr, lExecStr, lExec,
lError = pStderr || pError;
if (lError) {
if (Util.isString(lError))
lErrorStr = lError;
else
lErrorStr = lError.toString();
lErrorStr = Util.addNewLine(lErrorStr);
}
lExec = {
stdout : pStdout,
stderr : lErrorStr || lError
};
Util.exec(callback, lExec, pError, pStderr);
};
}
function getSpawn(pCommand, options, callback) {
var send, cmd, error,
args = pCommand.split(' ');
pCommand = args.shift();
error = Util.tryCatchLog(function() {
cmd = spawn(pCommand, args, options);
});
if (!cmd)
send(error + '', null);
else {
send = function(error, data) {
var exec = {
stderr: error,
stdout: data
};
Util.exec(callback, exec);
};
cmd.stdout.on('data', function(data) {
send(null, data + '');
});
cmd.stderr.on('data', function(error) {
send(error + '', null);
});
cmd.on('error', Util.retFalse);
cmd.on('close', function (code) {
cmd = null;
});
}
}
function onCloudCmd(pCommand, callback) {
var lRet;
pCommand = Util.removeStr(pCommand, CLOUDCMD);
if (!equalPart(pCommand, ' ')) {
lRet = true;
Util.exec(callback, HELP);
}
else {
pCommand = Util.removeStr(pCommand, ' ');
if (equalPart(pCommand, 'update') && update) {
lRet = true;
update.get();
Util.exec(callback, {
stdout: Util.addNewLine('update: ok')
});
}
if (Util.strCmp(pCommand, 'exit'))
process.exit();
}
return lRet;
}
function onCD(pCommand, currDir, callback) {
var dir,
getDir = WIN32 ? 'chdir' : 'pwd',
paramDir = Util.removeStr(pCommand, 'cd ');
if (equalPart(paramDir, ['/', '~']))
dir = paramDir;
else
dir = path.join(currDir, paramDir);
exec('cd ' + dir + ' && ' + getDir, function (error, stdout, stderr) {
var lRet,
lMsg = '',
lError = error || stderr;
if (lError) {
lError = Util.stringifyJSON(lError);
lMsg = lError;
}
Util.exec(callback, {
stderr : lMsg,
stdout : Util.rmNewLine(stdout)
});
});
}
function log(pConnNum, pStr, pType) {
var lRet,
lType = ' ';
if (pStr) {
if (pType)
lType += pType + ':';
lRet = 'client #' + pConnNum + lType + pStr;
Util.log(lRet);
}
return lRet;
}
})();

View file

@ -19,85 +19,74 @@
Util = main.util,
path = main.path;
exports.getSize = function(pDir, pCallBack) {
var lTotal = 0;
exports.getSize = function(dir, callback) {
var total = 0;
function calcSize(pParams){
var lStat = pParams.stat,
lSize = lStat && lStat.size || 0;
function calcSize(stat) {
var size = stat && stat.size || 0;
lTotal += lSize;
total += size;
}
processDir(pDir, calcSize, function() {
Util.exec(pCallBack, null, lTotal);
processDir(dir, calcSize, function() {
Util.exec(callback, null, total);
});
};
function processDir(pDir, pFunc, pCallBack){
var lAsyncRunning = 0,
lFileCounter = 1;
function processDir(dir, func, callback) {
var asyncRunning = 0,
fileCounter = 1;
function getDirInfo(pDir) {
function getDirInfo(dir) {
/* The lstat() function shall be equivalent to stat(),
except when path refers to a symbolic link. In that case lstat()
shall return information about the link, while stat() shall return
information about the file the link references. */
information about the file the link references.
*/
fs.lstat(pDir, Util.call(getStat, {
name: pDir
}));
fs.lstat(dir, getStat.bind(null, dir));
}
function getStat(pParams) {
var lRet = Util.checkObj(pParams, ['params']);
if(lRet){
var p = pParams,
d = p.params,
lStat = p.data,
lPath = d.name;
--lFileCounter;
if (!p.error) {
if ( lStat.isFile() )
Util.exec(pFunc, {
name: d.name,
stat: lStat
});
else if ( lStat.isDirectory() ) {
++lAsyncRunning;
function getStat(dir, error, stat) {
--fileCounter;
if (!error) {
if (stat.isFile())
Util.exec(func, stat);
else if (stat.isDirectory()) {
++asyncRunning;
fs.readdir(dir, function(error, files) {
var dirPath, file, n, i;
fs.readdir(lPath, function(pError, pFiles) {
lAsyncRunning--;
asyncRunning--;
if (!error) {
n = files.length;
fileCounter += n;
var lDirPath, n;
if (!pError){
n = pFiles.length;
lFileCounter += n;
for (i = 0; i < n; i++) {
file = files[i];
dirPath = path.join(dir, file);
for (var i = 0; i < n; i++) {
lDirPath = path.join(lPath, pFiles[i]);
process.nextTick(Util.retFunc(getDirInfo, lDirPath));
}
process.nextTick(getDirInfo.bind(null, dirPath));
}
if(!n)
execCallBack();
});
}
}
if(!n)
execCallBack();
});
}
}
execCallBack();
}
function execCallBack(){
if (!lFileCounter && !lAsyncRunning)
Util.exec(pCallBack);
function execCallBack() {
if (!fileCounter && !asyncRunning)
Util.exec(callback);
}
getDirInfo(pDir);
getDirInfo(dir);
}
})();
})();

View file

@ -11,13 +11,72 @@
var main = global.cloudcmd.main,
express = main.require('express'),
httpAuth = main.require('http-auth'),
crypto = main.crypto,
basic,
oldPass,
oldName,
app = express && express();
exports.getApp = function(controller) {
if (app)
app.use(express.logger('dev'))
.all('*', controller);
exports.getApp = function(middleware) {
var i, n, middle,
config = main.config,
auth = config.auth;
if (app) {
app.use(express.logger('dev'));
if (auth && httpAuth) {
initAuth();
app.use(httpAuth.connect(basic));
}
if (middleware) {
n = middleware.length;
for (i = 0; i < n; i++) {
middle = middleware[i];
app.use(middle);
}
}
}
return app;
};
function initAuth() {
basic = httpAuth.basic({
realm: "Cloud Commander"
}, function (username, password, callback) { // Custom authentication method.
var hash,
config = main.config,
name = config.username,
passwd = config.password,
equal = username === name,
sha = crypto.createHash('sha1');
if (!oldPass)
oldPass = passwd;
if (!oldName)
oldName = name;
if (!equal)
username === oldName;
sha.update(password);
hash = sha.digest('hex');
equal = passwd === hash && equal;
if (!equal) {
sha = crypto.createHash('sha1');
sha.update(oldPass);
hash = sha.digest('hex');
equal = passwd === hash && equal;
}
callback(equal);
});
}
})();

View file

@ -16,60 +16,50 @@
CloudFunc = main.cloudfunc,
Util = main.util;
object.read = function(pFiles, pOptions, pCallBack) {
var lDone = [],
lFiles,
lErrors,
object.read = function(files, options, callback) {
var done = [],
errors,
i, n,
lName,
lReadedFiles = {},
lDoneFunc = function (pParams) {
var msg, status, p, lName,
lRet = Util.checkObj(pParams, ['error', 'data', 'params']);
name,
readFiles = {},
doneFunc = function (name, error, data) {
var msg, status;
if (lRet) {
lDone.pop();
p = pParams,
lName = p.params;
done.pop();
if (error) {
status = 'error';
if (p.error) {
status = 'error';
if (!lErrors)
lErrors = {};
lErrors[lName] = p.error;
}
else {
status = 'ok';
lReadedFiles[lName] = p.data;
}
if (!errors)
errors = {};
lName = path.basename(lName);
msg = CloudFunc.formatMsg('read', lName, status);
Util.log(msg);
if (!lDone.length)
Util.exec(pCallBack, lErrors, lReadedFiles);
errors[name] = error;
}
else {
status = 'ok';
readFiles[name] = data;
}
name = path.basename(name);
msg = CloudFunc.formatMsg('read', name, status);
Util.log(msg);
if (!done.length)
Util.exec(callback, errors, readFiles);
};
if (Util.isFunction(pOptions)) {
pCallBack = pOptions;
pOptions = null;
if (Util.isFunction(options)) {
callback = options;
options = null;
}
if (Util.isArray(pFiles))
lFiles = pFiles;
else
lFiles = [pFiles];
for(i = 0, n = lFiles.length; i < n; i++) {
lName = lFiles.pop();
lDone.push(lName);
n = files && files.length;
for (i = 0; i < n; i++) {
name = files.pop();
done.push(name);
fs.readFile(lName, pOptions, Util.call(lDoneFunc, lName));
fs.readFile(name, options, doneFunc.bind(null, name));
}
};
@ -84,7 +74,7 @@
p.write.end();
Util.exec(p.callback);
} else {
name = p.dir + p.names.pop();
name = p.dir + p.names.shift();
pipe.create({
from : name,

View file

@ -11,7 +11,7 @@
SLASH,
ISWIN32,
ext,
path, fs, zlib, url, pipe, CloudFunc, diffPatch,
path, fs, zlib, url, pipe, CloudFunc, diffPatch, querystring,
OK, FILE_NOT_FOUND, MOVED_PERMANENTLY,
REQUEST, RESPONSE,
@ -39,7 +39,7 @@
exports.https = require('https'),
exports.path = path = require('path'),
exports.url = url = require('url'),
exports.querystring = require('querystring'),
exports.querystring = querystring = require('querystring'),
/* Constants */
/* current dir + 2 levels up */
@ -61,6 +61,7 @@
exports.generateHeaders = generateHeaders,
exports.getQuery = getQuery,
exports.getPathName = getPathName,
exports.isGZIP = isGZIP,
exports.mainSetHeader = mainSetHeader,
@ -104,6 +105,8 @@
exports.cloudfunc = CloudFunc = librequire('cloudfunc'),
exports.pipe = pipe = srvrequire('pipe'),
exports.socket = srvrequire('socket'),
exports.console = srvrequire('console'),
exports.terminal = srvrequire('terminal'),
exports.express = srvrequire('express'),
exports.auth = srvrequire('auth').auth,
exports.appcache = srvrequire('appcache'),
@ -112,6 +115,7 @@
diffPatch = librequire('diff/diff-match-patch').diff_match_patch,
exports.diff = new (librequire('diff').DiffProto)(diffPatch),
exports.time = srvrequire('time');
exports.users = srvrequire('users');
exports.rest = srvrequire('rest').api,
exports.update = srvrequire('update'),
exports.ischanged = srvrequire('ischanged');
@ -126,39 +130,42 @@
/**
* function do safe require of needed module
* @param {Strin} pSrc
* @param {Strin} src
*/
function mrequire(pSrc) {
var lModule, msg,
lError = Util.tryCatch(function() {
lModule = require(pSrc);
function mrequire(src) {
var module, msg,
error = Util.tryCatch(function() {
module = require(src);
});
if (lError)
msg = CloudFunc.formatMsg('require', pSrc, 'no');
if (error)
if (error.code === 'MODULE_NOT_FOUND')
msg = CloudFunc.formatMsg('require', src, 'no');
else
Util.log(error);
Util.log(msg);
return lModule;
return module;
}
function quietrequire(pSrc) {
var lModule;
function quietrequire(src) {
var module;
Util.tryCatch(function() {
lModule = require(pSrc);
module = require(src);
});
return lModule;
return module;
}
function rootrequire(pSrc) { return mrequire(DIR + pSrc); }
function rootrequire(src) { return mrequire(DIR + src); }
function librequire(pSrc) { return mrequire(LIBDIR + pSrc); }
function librequire(src) { return mrequire(LIBDIR + src); }
function srvrequire(pSrc) { return mrequire(SRVDIR + pSrc); }
function srvrequire(src) { return mrequire(SRVDIR + src); }
function jsonrequire(pSrc) { return mrequire(JSONDIR + pSrc);}
function jsonrequire(src) { return mrequire(JSONDIR + src);}
/**
* function check is current platform is win32
@ -280,45 +287,52 @@
* @param Data - данные
* @param pName - имя отсылаемого файла
*/
function sendResponse(pParams, pData, pNotLog) {
var p, lQuery, lGzip, lHead, data,
lRet = checkParams(pParams);
function sendResponse(params, data, notLog) {
var p, query, isGzip, head,
ret = checkParams(params);
if (lRet) {
p = pParams;
data = p.data || pData;
lGzip = isGZIP(p.request);
if (ret) {
p = params;
data = p.data || data;
isGzip = isGZIP(p.request);
lHead = generateHeaders({
head = generateHeaders({
name : p.name,
cache : p.cache,
gzip : lGzip,
query : lQuery
gzip : isGzip,
query : query
});
setHeader(lHead, p.response);
setHeader(head, p.response);
if (!pNotLog)
if (!notLog)
Util.log(data);
/* если браузер поддерживает gzip-сжатие - сжимаем данные*/
Util.ifExec(!lGzip,
function(pParams) {
var lRet = Util.checkObj(pParams, ['data']);
Util.ifExec(!isGzip,
function(params) {
var ret = Util.checkObj(params, ['data']);
if (lRet) {
p.status = pParams.status || p.status;
p.data = pParams.data;
if (ret) {
p.status = params.status || p.status;
p.data = params.data;
}
p.response.statusCode = p.status || OK;
p.response.end(p.data);
},
function(pCallBack) {
zlib.gzip (data, Util.call(gzipData, {
callback : pCallBack
}));
function(callback) {
zlib.gzip (data, function(error, data) {
if (!error)
p.data = data;
else {
p.status = FILE_NOT_FOUND;
p.data = error.toString();
}
Util.exec(callback, p);
});
});
}
}
@ -348,54 +362,26 @@
/**
* send error response
*/
function sendError(pParams, pError) {
var p, lRet = checkParams(pParams);
function sendError(params, error) {
var p, ret = checkParams(params);
if (lRet) {
p = pParams;
if (ret) {
p = params;
p.status = FILE_NOT_FOUND;
if (!p.data && pError)
p.data = pError.toString();
if (!p.data && error)
p.data = error.toString();
sendResponse(p);
}
}
/**
* Функция получает сжатые данные
* @param pHeader - заголовок файла
* @pName
*/
function gzipData(pParams) {
var lRet = checkCallBackParams(pParams),
p = pParams;
if (lRet)
lRet = Util.checkObj(pParams.params, ['callback']);
if (lRet) {
var lCallBack = p.params.callback,
lParams = {};
if (!p.error)
lParams.data = p.data;
else {
lParams.status = FILE_NOT_FOUND;
lParams.data = p.error.toString();
}
Util.exec(lCallBack, lParams);
}
}
function checkCallBackParams(pParams) {
return Util.checkObj(pParams, ['error', 'data', 'params']);
}
function checkParams(pParams, pAdditional) {
var lRet = Util.checkObjTrue( pParams, ['name', REQUEST, RESPONSE] );
var lRet = Util.checkObjTrue(pParams, ['name', REQUEST, RESPONSE]);
if (lRet && pAdditional)
lRet = Util.checkObjTrue( pParams, pAdditional);
@ -403,15 +389,28 @@
return lRet;
}
function getQuery(pReq) {
var lQuery, lParsedUrl;
function getQuery(req) {
var query, parsed;
if (pReq) {
lParsedUrl = url.parse(pReq.url);
lQuery = lParsedUrl.query;
if (req) {
parsed = url.parse(req.url);
query = parsed.query;
}
return lQuery;
return query;
}
function getPathName(req) {
var pathname, parsed;
if (req) {
parsed = url.parse(req.url);
pathname = parsed.pathname;
/* supporting of Russian language in directory names */
pathname = querystring.unescape(pathname);
}
return pathname;
}
function isGZIP(pReq) {

View file

@ -10,13 +10,14 @@
'# Module is part of Cloud Commander,' + '\n' +
'# used for work with REST API.' + '\n' +
'# If you wont to see at work set rest: true' + '\n' +
'# and api_url in config.json' + '\n' +
'# and apiURL in config.json' + '\n' +
'# http://cloudcmd.io' + '\n');
var main = global.cloudcmd.main,
fs = main.fs,
path = main.path,
Hash = main.hash,
crypto = main.crypto,
Util = main.util,
pipe = main.pipe,
CloudFunc = main.cloudfunc,
@ -29,26 +30,45 @@
sendResponse= main.sendResponse,
Header = main.generateHeaders({
name:'api.json'
});
}),
fse = main.require('fs-extra') || {
remove : fs.rmdir.bind(fs),
mkdirs : fs.mkdir.bind(fs),
copy : function(from, to, callback) {
pipe.create({
from : from,
to : to,
callback : callback
});
}
};
/**
* rest interface
* @pParams {request, responce}
*/
exports.api = function(pParams) {
var lRet = main.checkParams(pParams);
exports.api = function(request, response, callback) {
var apiURL, name, ret;
if (lRet) {
var lAPIURL = main.config.apiURL,
p = pParams;
if (request && response) {
apiURL = main.config.apiURL;
name = main.getPathName(request);
ret = Util.isContainStr(name, apiURL);
lRet = Util.isContainStr(p.name, lAPIURL);
if (lRet) {
p.name = Util.removeStrOneTime(p.name, lAPIURL) || '/';
sendData(pParams);
if (ret) {
name = Util.removeStrOneTime(name, apiURL) || '/';
sendData({
request : request,
response : response,
name : name
});
}
}
return lRet;
if (!ret)
Util.exec(callback);
return ret;
};
/**
@ -71,24 +91,24 @@
* @param pParams {command, method, body, requrest, response}
*/
function sendData(pParams) {
var p, lRet = main.checkParams(pParams);
if(lRet){
var p, ret = main.checkParams(pParams);
if (ret) {
p = pParams;
lRet = Util.isContainStrAtBegin(p.name, CloudFunc.FS);
ret = Util.isContainStrAtBegin(p.name, CloudFunc.FS);
if (lRet)
if (ret)
onFS(pParams);
else {
if(p.name[0] === '/')
if (p.name[0] === '/')
p.command = Util.removeStrOneTime(p.name, '/');
switch(p.request.method){
switch(p.request.method) {
case 'GET':
lRet = onGET(pParams);
ret = onGET(pParams);
break;
case 'PUT':
getBody(p.request, function(pBody){
getBody(p.request, function(pBody) {
p.body = pBody;
onPUT(p);
});
@ -96,22 +116,22 @@
}
}
}
return lRet;
return ret;
}
function onFS(pParams) {
function onFS(params) {
var p, lQuery, isGet,
lRet = main.checkParams(pParams);
ret = main.checkParams(params);
if (lRet){
p = pParams;
if (ret) {
p = params;
lQuery = main.getQuery(p.request);
p.name = Util.removeStrOneTime(p.name, CloudFunc.FS) || '/';
switch (p.request.method) {
case 'GET':
isGet = onFSGet(lQuery, p.name, function(error, result) {
checkSendError(error, pParams, function() {
checkSendError(error, params, function() {
sendResponse(p, result);
});
});
@ -139,53 +159,37 @@
case 'PUT':
if (lQuery === 'dir')
fs.mkdir(p.name, function(pError) {
checkSendError(pError, pParams, function() {
sendMsg(pParams, 'make dir', p.name);
fse.mkdirs(p.name, function(pError) {
checkSendError(pError, params, function() {
sendMsg(params, 'make dir', p.name);
});
});
else if(lQuery === 'patch')
getBody(p.request, function(pPatch) {
fs.readFile(p.name, Util.call(read, pParams));
else if (lQuery === 'patch')
getBody(p.request, function(patch) {
fs.readFile(p.name, 'utf8', read.bind(null, p.name));
function read(pParams) {
var lDiff, lStr, p, lData, lName,
lRet = main.checkCallBackParams(pParams) &&
main.checkParams(pParams.params);
if (lRet) {
p = pParams;
lName = p.params.name;
function read(name, error, data) {
checkSendError(error, p.params, function() {
var diffResult;
checkSendError(p.error, p.params, function() {
lStr = p.data.toString();
lRet = Util.tryCatchLog(function() {
lDiff = diff.applyPatch(lStr, pPatch);
});
if (lDiff && !lRet)
fs.writeFile(lName, lDiff, Util.call(write, p.params));
else {
lName = path.basename(lName);
sendMsg(p.params, 'patch', lName, 'fail');
}
ret = Util.tryCatchLog(function() {
diffResult = diff.applyPatch(data, patch);
});
}
if (diffResult && !ret)
fs.writeFile(name, diffResult, write.bind(null, name));
else {
name = path.basename(name);
sendMsg(p.params, 'patch', name, 'fail');
}
});
}
function write(pParams) {
var p, lName,
lRet = main.checkCallBackParams(pParams) &&
main.checkParams(pParams.params);
if (lRet) {
p = pParams;
checkSendError(p.error, p.params, function() {
lName = path.basename(p.params.name);
sendMsg(p.params, 'patch', lName);
});
}
function write(name, error) {
checkSendError(error, params, function() {
name = path.basename(name);
sendMsg(params, 'patch', name);
});
}
});
else
@ -193,73 +197,77 @@
read : p.request,
to : p.name,
callback : function(pError) {
checkSendError(pError, pParams, function() {
checkSendError(pError, params, function() {
var lName = path.basename(p.name);
sendMsg(pParams, 'save', lName);
sendMsg(params, 'save', lName);
});
}
});
break;
case 'DELETE':
if (lQuery === 'dir')
fs.rmdir(p.name, function(pError){
checkSendError(pError, pParams, function() {
sendMsg(pParams, 'delete', p.name);
});
onDelete(params, lQuery, function(error, msg, callback) {
checkSendError(error, params, function() {
if (callback)
Util.exec(callback);
else
sendMsg(params, 'delete', msg);
});
else if (lQuery === 'files') {
getBody(p.request, function(pBody) {
var lFiles = Util.parseJSON(pBody),
n = lFiles.length,
lDir = p.name,
log = Util.log,
lAssync = 0;
function stat(pStat) {
var lRet = Util.checkObjTrue(pStat, 'params') &&
Util.checkObjTrue(pStat.params, 'name');
if (lRet) {
var p = pStat,
d = p.params;
++lAssync;
checkSendError(p.error, pParams, function() {
if (p.data.isDirectory())
fs.rmdir(d.name, log);
else if (p.data.isFile())
fs.unlink(d.name, log);
});
if (lAssync === n && !p.error)
sendMsg(pParams, 'delete', pBody);
}
}
for(var i = 0; i < n; i ++) {
var lName = lDir + lFiles[i];
Util.log(lName);
fs.stat(lName, Util.call(stat, {
name: lName
}));
}
});
}else
fs.unlink(p.name, function(pError) {
checkSendError(pError, pParams, function() {
sendMsg(pParams, 'delete', p.name);
});
});
});
break;
}
}
return lRet;
return ret;
}
function onDelete(params, query, callback) {
var rmFile = fs.unlink.bind(fs),
rmDir = fse.remove.bind(fse),
p = params;
if (query === 'dir')
rmDir(p.name, function(error) {
Util.exec(callback, error, p.name);
});
else if (query === 'files')
getBody(p.request, function(body) {
var i, name,
files = Util.parseJSON(body),
n = files.length,
dir = p.name,
log = Util.log.bind(Util),
assync = 0;
function onStat(name, error, stat) {
++assync;
if (error)
Util.exec(callback, error);
else {
if (stat.isDirectory())
rmDir(name, log);
else if (stat.isFile())
rmFile(name, log);
if (assync === n)
Util.exec(callback, null, body);
}
}
for (i = 0; i < n; i ++) {
name = dir + files[i];
Util.log(name);
fs.stat(name, onStat.bind(null, name));
}
});
else
rmFile(p.name, function(error) {
Util.exec(callback, error, p.name);
});
}
function onFSGet(query, name, callback) {
@ -314,8 +322,8 @@
* @param pParams {command, method, body, requrest, response}
*/
function onGET(pParams) {
var lRet = main.checkParams(pParams);
if (lRet) {
var ret = main.checkParams(pParams);
if (ret) {
var p = pParams,
lCmd = p.command;
@ -336,7 +344,7 @@
}
}
return lRet;
return ret;
}
/**
@ -345,17 +353,19 @@
* @param pParams {command, method, body, requrest, response}
*/
function onPUT(pParams) {
var name, data, json, config,
lRet = main.checkParams(pParams, ['body']);
var name, data, json, config, callback,
ret = main.checkParams(pParams, ['body']);
if (lRet) {
if (ret) {
var p = pParams,
lCmd = p.command,
lFiles = Util.parseJSON(p.body);
console.log(lFiles);
switch(lCmd) {
case 'auth':
main.auth(p.body, function(pTocken){
main.auth(p.body, function(pTocken) {
send({
response: p.response,
data: pTocken
@ -364,7 +374,7 @@
break;
case 'mv':
if(!Util.checkObjTrue(lFiles, ['from', 'to']) )
if (!Util.checkObjTrue(lFiles, ['from', 'to']) )
sendError(pParams, p.data);
else
fs.rename(lFiles.from, lFiles.to, function(pError) {
@ -376,19 +386,16 @@
break;
case 'cp':
if (!Util.checkObjTrue(lFiles, ['from', 'to']))
callback = function(error) {
checkSendError(error, pParams, function() {
sendMsg(pParams, 'copy', lFiles.to);
});
};
if (!Util.checkObjTrue(lFiles, ['from', 'to']))
sendError(pParams, p.data);
else
pipe.create({
from : lFiles.from,
to : lFiles.to,
callback : function(pError) {
if (pError)
sendError(pParams, pError);
else
sendMsg(pParams, 'copy', lFiles.to);
}
});
fse.copy(lFiles.from, lFiles.to, callback);
break;
@ -411,7 +418,16 @@
break;
case 'config':
config = main.config;
var hash,
passwd = lFiles && lFiles.password,
sha = crypto.createHash('sha1');
config = main.config;
if (passwd) {
sha.update(passwd);
passwd = sha.digest('hex');
lFiles.password = passwd;
}
for (name in lFiles)
config[name] = lFiles[name];
@ -432,7 +448,7 @@
}
}
return lRet;
return ret;
}
/**
@ -441,14 +457,14 @@
* @param pReq
* @param pCallBack
*/
function getBody(pReq, pCallBack) {
function getBody(req, pCallBack) {
var lBody = '';
pReq.on('data', function(chunk) {
req.on('data', function(chunk) {
lBody += chunk.toString();
});
pReq.on('end', function() {
req.on('end', function() {
Util.exec(pCallBack, lBody);
});
}

View file

@ -1,58 +1,57 @@
/* module make possible connectoin thru socket.io on a server */
(function() {
'use strict';
var main = global.cloudcmd.main,
DIR = main.DIR,
SRVDIR = main.SRVDIR,
io = main.require('socket.io'),
update = main.srvrequire('update'),
exec = main.child_process.exec,
Util = main.util,
path = main.path,
mainpackage = main.mainpackage,
equalPart = Util.isContainStrAtBegin,
CLOUDCMD = mainpackage.name,
ClientFuncs = [],
ClientDirs = [],
OnMessageFuncs = [],
INFO_LOG_LEVEL = 2,
ENV = process.env,
WIN32 = main.WIN32,
HELP = {
stdout : CLOUDCMD + ' exit \n' +
CLOUDCMD + ' update \n',
},
CloudFunc = main.cloudfunc,
/* windows commands thet require
* unicode charset on locales
* different then English
*/
Win32Commands = ['ASSOC', 'AT', 'ATTRIB', 'BREAK', 'CACLS', 'CALL',
'CD', 'CHCP', 'CHDIR', 'CHKDSK', 'CHKNTFS', 'CLS',
'CMD', 'COLOR', 'COMP', 'COMPACT', 'CONVERT', 'COPY',
'DATE', 'DEL', 'DIR', 'DISKCOMP', 'DISKCOPY', 'DOSKEY',
'ECHO', 'ENDLOCAL', 'ERASE', 'EXIT', 'FC', 'FIND',
'FINDSTR', 'FOR', 'FORMAT', 'FTYPE', 'GOTO', 'GRAFTABL',
'HELP', 'IF', 'LABEL', 'MD', 'MKDIR', 'MODE', 'MORE',
'MOVE', 'PATH', 'PAUSE', 'POPD', 'PRINT', 'PROMPT',
'PUSHD', 'RD', 'RECOVER', 'REM', 'REN', 'RENAME',
'REPLACE', 'RMDIR', 'SET', 'SETLOCAL', 'SHIFT', 'SORT',
'START', 'SUBST', 'TIME', 'TITLE', 'TREE', 'TYPE',
'VER', 'VERIFY', 'VOL', 'XCOPY'];
WIN32 = main.WIN32,
INFO_LOG_LEVEL = 2,
AllListeners = [];
exports.on = addListener;
exports.addListener = addListener;
exports.removeListener = removeListener;
exports.send = send;
exports.emit = emit;
exports.listen = listen;
function addListener(name, func, socket) {
if (!socket)
socket = io.sockets;
CloudFunc.addListener(name, func, AllListeners, socket);
}
function removeListener(name, func, socket) {
CloudFunc.removeListener(name, func, AllListeners, socket);
}
function send(msg, socket) {
if (socket)
socket.send(msg);
}
function emit(channel, message, socket, all) {
var obj;
if (socket) {
if (all)
obj = socket.broadcast;
else
obj = socket;
obj.emit(channel, message);
}
}
/**
* function listen on servers port
* @pServer {Object} started server object
*/
exports.listen = function(pServer) {
var lRet, lConnNum, lMsg, lConn_func;
function listen(pServer) {
if (io) {
io = io.listen(pServer);
lConnNum = 0;
io.set('log level', INFO_LOG_LEVEL);
@ -75,268 +74,8 @@
'xhr-polling',
'jsonp-polling'
]);
lRet = io.sockets.on('connection', function (socket) {
++lConnNum;
if(!OnMessageFuncs[lConnNum]) {
lMsg = log(lConnNum, 'connected\n');
jsonSend(socket, {
stdout : lMsg
});
OnMessageFuncs[lConnNum] = onMessage(lConnNum, socket);
lConn_func = OnMessageFuncs[lConnNum];
socket.on('message', lConn_func);
socket.on('disconnect', Util.call(disconnect, lConnNum));
} else {
lMsg = log(lConnNum, ' in use. Reconnecting...');
jsonSend(socket, {
stdout: lMsg
});
socket.disconnect();
}
});
}
return lRet;
};
function disconnect(pParams) {
var lConnNum, lRet = Util.checkObj(pParams, ['params']);
if(lRet) {
lConnNum = pParams.params;
OnMessageFuncs [lConnNum] =
ClientFuncs [lConnNum] = null;
log(lConnNum, 'disconnected');
}
}
/**
* function gets onMessage function
* that execute needed command
*
* @param pConnNum, pSocket
*/
function onMessage(pConnNum, pSocket) {
return function(pCommand) {
var lMsg, lWinCommand, lExec_func, firstChar,
connName,
lError, lRet, lExecSymbols, isContain,
dir, options = {};
dir = ClientDirs[pConnNum];
if (!dir)
dir = ClientDirs[pConnNum] = DIR;
connName = '#' + pConnNum + ': ';
Util.log(connName + pCommand);
if (equalPart(pCommand, CLOUDCMD))
lRet = onCloudCmd(pCommand, function(json) {
jsonSend(pSocket, json);
});
else if (equalPart(pCommand, 'cd ')) {
lRet = true;
onCD(pCommand, dir, function(json) {
var error = json.stderr,
stdout = json.stdout;
if (error)
jsonSend(pSocket, json);
else
ClientDirs[pConnNum] = stdout;
});
}
if (!lRet) {
/* if we on windows and command is build in
* change code page to unicode becouse
* windows use unicode on non English versions
*/
if(WIN32) {
lWinCommand = pCommand.toUpperCase();
if (Win32Commands.indexOf(lWinCommand) >= 0)
pCommand = 'chcp 65001 |' + pCommand;
}
if(!ClientFuncs[pConnNum])
ClientFuncs[pConnNum] = getExec(function(json, pError, pStderr) {
log(pConnNum, pError, 'error');
log(pConnNum, pStderr, 'stderror');
jsonSend(pSocket, json);
});
lExec_func = ClientFuncs[pConnNum];
lExecSymbols = ['*', '&', '{', '}', '|', '\'', '"'];
isContain = Util.isContainStr(pCommand, lExecSymbols);
firstChar = pCommand[0];
options.cwd = dir;
if (firstChar === '#') {
pCommand = pCommand.slice(1);
pCommand = connName + pCommand;
pCommand = Util.addNewLine(pCommand);
lMsg = Util.stringifyJSON({
stdout: pCommand
});
io.sockets.emit('message', lMsg);
} else if (WIN32 || firstChar === ' ' || isContain)
exec(pCommand, options, lExec_func);
else
getSpawn(pCommand, options, function(json) {
jsonSend(pSocket, json);
});
}
};
}
/**
* function send result of command to client
* @param pSocket
*/
function getExec(callback) {
return function(pError, pStdout, pStderr) {
var lErrorStr, lExecStr, lExec,
lError = pStderr || pError;
if (lError) {
if (Util.isString(lError))
lErrorStr = lError;
else
lErrorStr = lError.toString();
lErrorStr = Util.addNewLine(lErrorStr);
}
lExec = {
stdout : pStdout,
stderr : lErrorStr || lError
};
Util.exec(callback, lExec, pError, pStderr);
};
}
function getSpawn(pCommand, options, callback) {
var send, cmd, spawn,
args = pCommand.split(' ');
pCommand = args.shift();
spawn = main.child_process.spawn;
cmd = spawn(pCommand, args, options);
send = function(data, isError) {
var lExec = {},
msg = data.toString();
if (isError)
lExec.stderr = msg;
else
lExec.stdout = msg;
Util.exec(callback, lExec);
};
cmd.stdout.on('data', send);
cmd.stderr.on('data', function(data) {
send(data, true);
});
cmd.on('error', Util.retFalse);
}
function onCloudCmd(pCommand, callback) {
var lRet;
pCommand = Util.removeStr(pCommand, CLOUDCMD);
if (!equalPart(pCommand, ' ')) {
lRet = true;
Util.exec(callback, HELP);
}
else {
pCommand = Util.removeStr(pCommand, ' ');
if (equalPart(pCommand, 'update') && update) {
lRet = true;
update.get();
Util.exec(callback, {
stdout: Util.addNewLine('update: ok')
});
}
if (Util.strCmp(pCommand, 'exit'))
process.exit();
}
return lRet;
}
function onCD(pCommand, currDir, callback) {
var dir,
getDir = WIN32 ? 'chdir' : 'pwd',
paramDir = Util.removeStr(pCommand, 'cd ');
if (equalPart(paramDir, ['/', '~']))
dir = paramDir;
else
dir = path.join(currDir, paramDir);
exec('cd ' + dir + ' && ' + getDir, function (error, stdout, stderr) {
var lRet,
lMsg = '',
lError = error || stderr;
if (lError) {
lError = Util.stringifyJSON(lError);
lMsg = lError;
}
Util.exec(callback, {
stderr : lMsg,
stdout : Util.rmNewLine(stdout)
});
});
}
function log(pConnNum, pStr, pType) {
var lRet,
lType = ' ';
if (pStr) {
if (pType)
lType += pType + ':';
lRet = 'client #' + pConnNum + lType + pStr;
Util.log(lRet);
}
return lRet;
}
function jsonSend(socket, json) {
var msg = Util.stringifyJSON(json);
socket.send(msg);
return io;
}
})();

128
lib/server/terminal.js Normal file
View file

@ -0,0 +1,128 @@
(function() {
'use strict';
var main = global.cloudcmd.main,
DIR = main.DIR,
socket = main.socket,
spawn = main.child_process.spawn,
pty = main.require('pty.js'),
Util = main.util,
path = main.path,
CloudFunc = main.cloudfunc,
mainpackage = main.mainpackage,
CLOUDCMD = mainpackage.name,
ClientDirs = [],
Clients = [],
WIN32 = main.WIN32,
CHANNEL = CloudFunc.CHANNEL_TERMINAL,
CHANNEL_RESIZE = CloudFunc.CHANNEL_TERMINAL_RESIZE,
ConNum = 0,
INFO = 'to use terminal install pty.js: npm i pty.js';
/**
* function listen on servers port
* @pServer {Object} started server object
*/
exports.init = function() {
var ret, func,
makePty = function(clientSocket) {
onConnection(clientSocket, function(channel, data) {
socket.emit(channel, data, clientSocket);
});
},
sendInfo = function(clientSocket) {
Util.log(INFO);
socket.emit(CHANNEL, INFO, clientSocket);
};
if (pty)
func = makePty;
else
func = sendInfo;
ret = socket.on('connection', func);
return ret;
};
function onConnection(clientSocket, callback) {
var msg, onDisconnect, resizeFunc, dataFunc, term;
++ConNum;
if (!Clients[ConNum]) {
log(ConNum, 'terminal connected');
term = getTerm(callback);
dataFunc = onData.bind(null, term);
resizeFunc = onResize.bind(null, term, callback);
onDisconnect = function(conNum, term) {
Clients[conNum] = null;
log(conNum, 'terminal disconnected');
socket.removeListener(CHANNEL, dataFunc, clientSocket);
socket.removeListener(CHANNEL_RESIZE, resizeFunc, clientSocket);
socket.removeListener('disconnect', onDisconnect, clientSocket);
term.destroy();
}.bind(null, ConNum, term);
socket.on(CHANNEL, dataFunc, clientSocket);
socket.on(CHANNEL_RESIZE, resizeFunc, clientSocket);
socket.on('disconnect', onDisconnect, clientSocket);
} else {
log(ConNum, ' in use. Reconnecting...\n');
socket.disconnect();
}
}
function onResize(term, callback, size) {
term.resize(size.cols, size.rows);
Util.exec(callback, CHANNEL_RESIZE, size);
}
function onData(term, data) {
term.write(data);
}
function getTerm(callback) {
var onData = Util.exec.bind(Util, callback, CHANNEL),
term = pty.spawn('bash', [], {
name: 'xterm-color',
cols: 80,
rows: 25,
cwd : DIR,
env : process.env
});
term.on('data', onData);
return term;
}
function log(pConnNum, pStr, pType) {
var lRet,
lType = ' ';
if (pStr) {
if (pType)
lType += pType + ':';
lRet = 'client #' + pConnNum + lType + pStr;
Util.log(lRet);
}
return lRet;
}
})();

82
lib/server/users.js Normal file
View file

@ -0,0 +1,82 @@
(function(object) {
'use strict';
if(!global.cloudcmd)
return console.log(
'# time.js' + '\n' +
'# -----------' + '\n' +
'# Module is part of Cloud Commander,' + '\n' +
'# used for getting file change time.' + '\n' +
'# http://cloudcmd.io' + '\n');
var main = global.cloudcmd.main,
fs = main.fs,
time = main.time,
Util = main.util,
FILE = '/etc/passwd',
FileTime,
Names;
object.getNames = function(callback) {
getTime(function(error, names) {
Util.exec(callback, error, names);
});
};
function getTime(callback) {
time.get(FILE, function(error, time) {
if (error)
callback(error);
else if (FileTime === time) {
if (!Names)
error = 'user: parse error of ' + FILE;
callback(error, Names);
} else {
FileTime = time;
read(callback);
}
});
}
function read(callback) {
fs.readFile(FILE, 'utf8', function(error, passwd) {
if (!error)
Names = get(passwd);
callback(error, Names);
});
}
/** Функция парсит uid и имена пользователей
* из переданного в строке вычитаного файла /etc/passwd
* и возвращает массив обьектов имён и uid пользователей
* @pPasswd_s - строка, в которой находиться файл /etc/passwd
*/
function get(passwd) {
var uid, name, line,
users = {};
if (passwd)
do {
line = passwd.substr(passwd, passwd.indexOf('\n') + 1);
if (line) {
passwd = Util.removeStr(passwd, line);
/* получаем первое слово строки */
name = line.substr(line, line.indexOf(':'));
line = Util.removeStr(line, name + ':x:');
/* получаем uid */
uid = line.substr(line, line.indexOf(':'));
if (uid)
users[uid] = name;
}
} while (passwd);
return users;
}
})(this);

View file

@ -19,25 +19,25 @@
function UtilProto() {
var Util = this;
this.asyncCall = function(pFuncs, pOnLoad, pContext) {
this.asyncCall = function(funcs, callback) {
var i, element, name, func,
funcsCount = pFuncs.length,
funcsCount = funcs.length,
count = 0,
data = [];
for (i = 0; i < funcsCount; i++) {
func = pFuncs[i];
func = funcs[i];
callCheckFunc(i, func);
}
function checkFunc(pNum, pData) {
var i, m = pData.length,
var i, n = pData.length,
params = [];
++count;
if (m >= 2) {
for (i = 0; i < m; i++)
if (n >= 2) {
for (i = 0; i < n; i++)
params[i] = pData[i];
data[pNum] = params;
@ -45,21 +45,36 @@
data[pNum] = pData[0];
if (count === funcsCount)
pOnLoad.apply(pContext, data);
Util.retExec(callback).apply(null, data);
}
function callCheckFunc(pNum, pFunc) {
Util.exec(pFunc, function() {
checkFunc(pNum, arguments);
function callCheckFunc(num, func) {
Util.exec(func, function() {
checkFunc(num, arguments);
});
}
},
/*
* bind function to arguments without context
*/
this.bind = function(callback) {
var result,
args = Util.slice(arguments, 1),
bind = Function.prototype.bind;
args.unshift(null);
result = bind.apply(callback, args);
return result;
};
/**
* ad new line (if it's not)
* @param {string} pText
*/
this.addNewLine = function(pText){
this.addNewLine = function(pText) {
var lNewLine = '',
n = pText && pText.length;
@ -73,45 +88,13 @@
* rm new line (if it's)
* @param {string} pText
*/
this.rmNewLine = function(pText){
this.rmNewLine = function(pText) {
var strs = ['\n', '\r'],
text = Util.removeStr(pText, strs);
return text;
};
/** setting function context
* @param {function} pFunction
* @param {object} pContext
*/
this.bind = function(pFunction, pContext) {
var lRet;
if (Util.isFunction(pFunction))
lRet = pFunction.bind(pContext);
return lRet;
};
/**
* callback for functions(pError, pData)
* thet moves on our parameters.
*
* @param pFunc
* @param pParams
*/
this.call = function(pFunc, pParams) {
function lFunc(pError, pData) {
Util.exec(pFunc, {
error : pError,
data : pData,
params : pParams
});
}
return lFunc;
};
/**
* Функция ищет в имени файла расширение
* и если находит возвращает true
@ -210,28 +193,22 @@
};
/**
* Copy properties array pProps from pFromObj to pToObj
* Copy properties from from to to
*
* @param pFromObj
* @param pToObj
* @param pProps
* @param from
* @param to
*/
this.copyObj = function(pFromObj, pToObj, pProps) {
var toObj = pToObj || pProps || {},
forIn = function(obj) {
Util.forIn(obj, function(name) {
toObj[name] = obj[name];
});
};
if (Util.isObject(pFromObj)) {
if (pProps)
forIn(pProps);
forIn(pFromObj);
this.copyObj = function(to, from) {
if (!from) {
from = to;
to = {};
}
return toObj;
Util.forIn(from, function(name) {
to[name] = from[name];
});
return to;
};
this.convertArrToObj = function(pArrKeys, pArrVal) {
@ -335,13 +312,13 @@
* @param pObj
*/
this.stringifyJSON = function(pObj) {
var lRet;
var ret;
Util.tryCatchLog(function() {
lRet = JSON.stringify(pObj, null, 4);
ret = JSON.stringify(pObj, null, 4);
});
return lRet;
return ret;
};
/**
@ -350,20 +327,20 @@
* @param pStr2
*/
this.strCmp = function(pStr1, pStr2) {
var lRet = Util.isString(pStr1);
var i, n,
lRet = Util.isString(pStr1);
if (lRet) {
if ( Util.isArray(pStr2) )
for(var i = 0, n = pStr2.length; i < n; i++) {
lRet = Util.strCmp( pStr1, pStr2[i] );
if (lRet)
if (Util.isArray(pStr2))
for (i = 0, n = pStr2.length; i < n; i++) {
lRet = Util.strCmp(pStr1, pStr2[i]);
if (lRet)
break;
}
else if ( Util.isString(pStr2) )
else if (Util.isString(pStr2))
lRet = Util.isContainStr(pStr1, pStr2) &&
pStr1.length === pStr2.length;
}
return lRet;
@ -388,21 +365,24 @@
*/
this.isContainStr = function(pStr1, pStr2) {
var lRet = Util.isString(pStr1);
var i, n, regExp, str,
ret = Util.isString(pStr1);
if ( lRet ) {
if ( Util.isArray(pStr2) )
for(var i = 0, n = pStr2.length; i < n; i++) {
lRet = Util.isContainStr( pStr1, pStr2[i] );
if (ret)
if (Util.isArray(pStr2)) {
n = pStr2.length;
for(i = 0; i < n; i++) {
str = pStr2[i];
ret = Util.isContainStr(pStr1, str);
if (lRet)
if (ret)
break;
}
else if ( Util.isString(pStr2) )
lRet = pStr1.indexOf(pStr2) >= 0;
}
} else if (Util.isString(pStr2))
ret = pStr1.indexOf(pStr2) >= 0;
return lRet;
return ret;
};
/**
@ -438,21 +418,19 @@
* @param pArg
*/
this.log = function() {
var lArg = arguments,
lConsole = Scope.console,
lDate = '[' + Util.getDate() + '] ',
var args = this.slice(arguments),
console = Scope.console,
lDate = '[' + Util.getDate() + '] ';
lUnShift = Util.bind([].unshift, lArg),
lShift = Util.bind([].shift, lArg),
lJoin = Util.bind([].join, lArg);
if (lConsole && lArg.length && lArg[0]) {
lUnShift(lDate);
lConsole.log.apply(lConsole, lArg);
lShift();
if (console && args.length && args[0]) {
args.unshift(lDate);
console.log.apply(console, args);
args.shift();
}
return lJoin(' ');
return args.join(' ');
};
/**
@ -490,24 +468,19 @@
/**
* load functions thrue callbacks one-by-one
* @param pFunc_a {Array} - array of functions
* @param pData - not necessarily
* @param funcs {Array} - array of functions
*/
this.loadOnLoad = function(pFunc_a, pData) {
if ( Util.isArray(pFunc_a) && pFunc_a.length) {
var lFunc_a = pFunc_a.slice(),
lFunc = lFunc_a.pop(),
lCallBack = function(pData) {
return Util.loadOnLoad(lFunc_a, pData);
};
this.loadOnLoad = function(funcs) {
var func, callback;
if (Util.isArray(funcs)) {
func = funcs.shift();
if ( !Util.isUndefined(pData) )
pData = {
data : pData,
callback : lCallBack
};
callback = function(pData) {
return Util.loadOnLoad(funcs);
};
Util.exec(lFunc , pData || lCallBack);
Util.exec(func, callback);
}
};
@ -581,9 +554,10 @@
this.escapeRegExp = function(pStr) {
var lRet = pStr;
var lRet = pStr,
isStr = Util.isString(pStr);
if ( Util.isString(pStr) )
if (isStr)
lRet = pStr.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
return lRet;
@ -749,38 +723,17 @@
/**
* return save exec function
* @param pCallBack
* @param pArg
* @param callback
*/
this.retExec = function() {
var args = arguments;
var result,
exec = Util.exec.bind(Util),
args = Util.slice(arguments);
return function() {
var argsLocal, callback,
n = args.length;
if (n > 1)
argsLocal = args;
else {
callback = args[0];
argsLocal = arguments;
[].unshift.call(argsLocal, callback);
}
Util.exec.apply(null, argsLocal);
};
};
/**
* return function wich exec function in params
* @param arguments: callback, args
*/
this.retFunc = function() {
var args = arguments;
args.unshift(exec);
result = Util.bind.apply(null, args);
return function() {
return Util.exec.apply(null, args);
};
return result;
};
/**
@ -811,52 +764,19 @@
};
/**
* set value to property of object, if object exist
* @param pArgs {object, property, value}
* function makes new array based on first
*
* @param array
*/
this.setValue = function(pArgs) {
var lRet = false;
this.slice = function(array, count) {
var ret;
if ( Util.isObject(pArgs) ) {
var lObj = pArgs.object,
lProp = pArgs.property,
lVal = pArgs.lVal;
if (lObj) {
lObj[lProp] = lVal;
lRet = true;
}
}
if (array)
ret = [].slice.call(array, count);
return lRet;
return ret;
};
/**
* set timout before callback would be called
* @param pArgs {func, callback, time}
*/
this.setTimeout = function(pArgs) {
var lDone,
lFunc = pArgs.func,
lTime = pArgs.time || 1000,
lCallBack = function(pArgument) {
if (!lDone) {
lDone = Util.exec(pArgs.callback, pArgument);
}
};
var lTimeoutFunc = function() {
setTimeout(function() {
Util.exec(lFunc, lCallBack);
if (!lDone)
lTimeoutFunc();
}, lTime);
};
lTimeoutFunc();
};
/**
* function execute param function in
* try...catch block
@ -928,23 +848,14 @@
* ...
* @param pArgN
*/
this.exec = function(pCallBack) {
var lRet, lCallBack;
this.exec = function(callback) {
var ret,
args = Util.slice(arguments, 1);
if (Util.isFunction(callback))
ret = callback.apply(null, args);
/* drop first element */
[].shift.call(arguments);
if (pCallBack) {
if (Util.isFunction(pCallBack))
lRet = pCallBack.apply(null, arguments);
else {
lCallBack = pCallBack.callback || pCallBack.success;
/* add first element */
[].unshift.call(arguments, lCallBack);
lRet = Util.exec.apply(null, arguments);
}
}
return lRet;
return ret;
};
/**
@ -952,14 +863,15 @@
* @pArg
*/
this.execIfExist = function(pObj, pName, pArg) {
var lRet;
var ret, bind,
func = pObj && pObj[pName];
if (pObj) {
var lFunc = Util.bind(pObj[pName], pObj);
lRet = Util.exec(lFunc, pArg);
if (func) {
func = func.bind(pObj);
ret = Util.exec(func, pArg);
}
return lRet;
return ret;
};
/**

View file

@ -1,6 +1,6 @@
{
"name": "cloudcmd",
"version": "0.7.0",
"version": "0.8.0",
"author": "coderaiser <mnemonic.enemy@gmail.com> (https://github.com/coderaiser)",
"description": "Cloud Commander - file manager with console and editor",
"homepage": "http://cloudcmd.io",
@ -18,9 +18,11 @@
"subdomain": "cloudcmd",
"dependencies": {
"dropbox": "0.10.2",
"minify": "0.2.3",
"minify": "0.2.5",
"socket.io": "0.9.16",
"express": "3.4.x"
"express": "3.4.x",
"http-auth": "2.1.x",
"fs-extra": "0.8.x"
},
"license": "MIT",
"engines": {

View file

@ -3,7 +3,6 @@
"browser" : true,
"devel" : true,
"eqeqeq" : true,
"globalstrict" : true,
"jquery" : false,
"newcap" : true,
"noarg" : true,

View file

@ -1,22 +1,25 @@
<li draggable class>
<div class="fm-header">
<span class="mini-icon "></span>
<span class="name reduce-text">name</span>
<span class="size reduce-text">size</span>
<span class="owner reduce-text">owner</span>
<span class="mode reduce-text">mode</span>
</div><ul class="files"><li draggable class="">
<span class="mini-icon directory"></span>
<span class=name>
<a href="/fs/etc" title=".." target="" draggable=true>..</a>
</span>
<span class=size>&lt;dir&gt;</span><span class=owner>.</span>
<span class=mode></span>
</li><li draggable class>
<span class="name reduce-text"><a href="/fs/etc" title=".." target="" draggable=true>..</a></span>
<span class="size reduce-text">&lt;dir&gt;</span>
<span class="owner reduce-text">.</span>
<span class="mode reduce-text">--- --- ---</span>
</li><li draggable class="">
<span class="mini-icon directory"></span>
<span class=name>
<a href="/fs/etc/X11/applnk" title="applnk" target="" draggable=true>applnk</a>
</span>
<span class=size>&lt;dir&gt;</span><span class=owner>root</span>
<span class=mode>rwx r-x r-x</span>
</li><li draggable class>
<span class="name reduce-text"><a href="/fs/etc/X11/applnk" title="applnk" target="" draggable=true>applnk</a></span>
<span class="size reduce-text">&lt;dir&gt;</span>
<span class="owner reduce-text">root</span>
<span class="mode reduce-text">rwx r-x r-x</span>
</li><li draggable class="">
<span class="mini-icon text-file"></span>
<span class=name>
<a href="/fs/etc/X11/prefdm" title="prefdm" target="_blank" draggable=true>prefdm</a>
</span>
<span class=size>1.30kb</span><span class=owner>root</span>
<span class=mode>rwx r-x r-x</span>
</li>
<span class="name reduce-text"><a href="/fs/etc/X11/prefdm" title="prefdm" target="_blank" draggable=true>prefdm</a></span>
<span class="size reduce-text">1.30kb</span>
<span class="owner reduce-text">root</span>
<span class="mode reduce-text">rwx r-x r-x</span>
</li></ul>

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