diff --git a/README.md b/README.md new file mode 100644 index 00000000..62e574e6 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +Cloud Commander [![Build Status](https://secure.travis-ci.org/coderaiser/cloudcmd.png?branch=master)](http://travis-ci.org/coderaiser/cloudcmd) +=============== +**Cloud Commander** - two-panels file manager, totally writed on js. +View [demo](http://demo-cloudcmd.cloudfoundry.com/ "demo"). + +Google PageSpeed Score : [100](https://developers.google.com/speed/pagespeed/insights#url=http_3A_2F_2Fdemo-cloudcmd.cloudfoundry.com_2F&mobile=false "score") (out of 100). + +Benefits +--------------- +- full browser compatibility *(ie6+,chrome,safari,opera,firefox)*; +- responsible design +- one full page loading, *and then just one-time json-dir-listings loading +(with refresh opportunity).* +- caching readed directories *to localStorage (for now) +(so if network will disconnected or something heppen with a signal, we +definitely will can work cached copy of directory listings)*; +- key binding +- disabled js support *(working in limited mode)*. +- automated minification *client js-files and onstart-reading Cloud manager files on server starting.* + +**Cloud Commander** uses all benefits of js, so if js is disabled, +we moves to *limited mode*. + +Limited-mode features: +--------------- +- only 1 panel available +- no keybinding +- no local caching +- full loading of all web page(with styles, js-scripts, html-page etc). + +Hot keys: +--------------- +In all modern web browsers (but not in IE, becouse he special) hot keys works. +There is a short list: +- 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 +- up, down, enter - filesystem navigation + + +Additional modules: +--------------- +**Cloud Commander** not using additional modules for main functionality. +But for minification and optimization tricks optional can be +assingned (and installed) modules: +- [UglifyJS] (https://github.com/mishoo/UglifyJS) +- [clean-css] (https://github.com/GoalSmashers/clean-css) +- [html-minifier] (https://github.com/kangax/html-minifier) +- [css-b64-images] (https://github.com/Filirom1/css-base64-images) \ No newline at end of file diff --git a/client.js b/client.js new file mode 100644 index 00000000..633eae40 --- /dev/null +++ b/client.js @@ -0,0 +1,716 @@ +/* Функция которая возвратит обьект CloudCommander + * @window - обьект window + * @document - обьект document + * @CloudFunc - обьект содержащий общий функционал + * клиентский и серверный + */ + +var CloudCommander=(function(){ +"use strict"; + +/* Клиентский обьект, содержащий функциональную часть*/ +var CloudClient={ + /* Конструктор CloudClient, который + * выполняет весь функционал по + * инициализации + */ + init :function(){}, + + keyBinding :function(){},/* функция нажатий обработки клавишь */ + keyBinded :false,/* оброботка нажатий клавишь установлена*/ + _loadDir :function(){}, + /* + * Функция привязываеться ко всем ссылкам и + * загружает содержимое каталогов + */ + /* Обьект для работы с кэшем */ + Cashe :{}, + + /* ПРИВАТНЫЕ ФУНКЦИИ */ + /* функция загружает json-данные о файловой системе */ + _ajaxLoad :function(){}, + /* Функция генерирует JSON из html-таблицы файлов */ + _getJSONfromFileTable :function(){}, + /* функция меняет ссыки на ajax-овые */ + _changeLinks :function(){}, + /* ОБЬЕКТЫ */ + /* обьект, который содержит функции для отображения картинок*/ + _images :{}, + /* КОНСТАНТЫ*/ + /* название css-класа текущего файла*/ + CURRENT_FILE :'current-file', + LIBDIR :'/lib', + LIBDIRCLIENT :'/lib/client' +}; + +/* + * Обьект для работы с кэшем + * в него будут включены функции для + * работы с LocalStorage, webdb, + * idexed db etc. + */ +CloudClient.Cache={ + _allowed :true, /* приватный переключатель возможности работы с кэшем */ + /* функция проверяет возможно ли работать с кэшем каким-либо образом */ + isAllowed :function(){}, + /* Тип кэша, который доступен*/ + type :{}, + /* Функция устанавливает кэш, если выбранный вид поддерживаеться браузером*/ + set :function(){}, + /* Функция достаёт кэш, если выбранный вид поддерживаеться браузером*/ + get :function(){}, + /* функция чистит весь кэш для всех каталогов*/ + clear :function(){} +}; + +/* Обьект, который содержит + * функции для отображения + * картинок + */ +CloudClient._images={ + /* Функция создаёт картинку загрузки*/ + loading :function(){ + var e=document.createElement('span'); + e.className='icon loading'; + e.id='loading-image'; + return e; +}, + + /* Функция создаёт картинку ошибки загрузки*/ + error :function(){ + var e=document.createElement('span'); + e.className='icon error'; + e.id='error-image'; + return e; + } +}; + +/* функция проверяет поддерживаеться ли localStorage */ +CloudClient.Cache.isAllowed=(function(){ + if(window.localStorage && + localStorage.setItem && + localStorage.getItem){ + CloudClient.Cache._allowed=true; + }else + { + CloudClient.Cache._allowed=false; + /* загружаем PolyFill для localStorage, + * если он не поддерживаеться браузером + * https://gist.github.com/350433 + */ + /* + CloudClient.jsload('https://raw.github.com/gist/350433/c9d3834ace63e5f5d7c8e1f6e3e2874d477cb9c1/gistfile1.js', + function(){CloudClient.Cache._allowed=true; + }); + */ + } +}); + /* если доступен localStorage и + * в нём есть нужная нам директория - + * записываем данные в него + */ +CloudClient.Cache.set=(function(pName, pData){ + if(CloudClient.Cache._allowed && pName && pData){ + localStorage.setItem(pName,pData); + } +}); +/* Если доступен Cache принимаем из него данные*/ +CloudClient.Cache.get=(function(pName){ + if(CloudClient.Cache._allowed && pName){ + return localStorage.getItem(pName); + } + else return null; +}); +/* Функция очищает кэш*/ +CloudClient.Cache.clear=(function(){ + if(CloudClient.Cache._allowed){ + localStorage.clear(); + } +}); + + +/* функция обработки нажатий клавишь */ +CloudClient.keyBinding=(function(){ + /* loading keyBinding module and start it */ + CloudClient.jsload(CloudClient.LIBDIRCLIENT+'/keyBinding.js',function(){ + CloudCommander.keyBinding(); + }); +}); + + +/* + * Функция привязываеться ко всем ссылкам и + * загружает содержимое каталогов + */ +CloudClient._loadDir=(function(pLink,pNeedRefresh){ + /* @pElem - элемент, + * для которого нужно + * выполнить загрузку + */ + return function(){ + /* показываем гиф загрузки возле пути папки сверху*/ + LoadingImage.className='icon loading';/* показываем загрузку*/ + ErrorImage.className='icon error hidden';/* прячем ошибку */ + /* если элемент задан - + * работаем с ним + */ + /* если мы попали сюда с таблицы файлов*/ + try{ + this.firstChild.nextSibling.appendChild(LoadingImage); + }catch(error){ + /* если + + * кнопка обновления + */ + try{this.firstChild.parentElement.appendChild(LoadingImage);} + catch(error){console.log(error);} + } + + var lCurrentFile=document.getElementsByClassName(CloudClient.CURRENT_FILE); + /* получаем имя каталога в котором находимся*/ + var lHref; + try{ + lHref=lCurrentFile[0].parentElement.getElementsByClassName('path')[0].innerText; + }catch(error){console.log('error');} + + lHref=CloudFunc.removeLastSlash(lHref); + var lSubstr=lHref.substr(lHref,lHref.lastIndexOf('/')); + lHref=lHref.replace(lSubstr+'/',''); + + /* загружаем содержимое каталога*/ + CloudClient._ajaxLoad(pLink, pNeedRefresh); + + /* получаем все элементы выделенной папки*/ + /* при этом, если мы нажали обновить + * или +R - ссылок мы ненайдём + * и заходить не будем + */ + var lA=this.getElementsByTagName('a'); + /* если нажали на ссылку на верхний каталог*/ + if(lA.length>0 && lA[0].innerText==='..' && + lHref!=='/'){ + /* функция устанавливает курсор на каталог + * с которого мы пришли, если мы поднялись + * в верх по файловой структуре + */ + CloudClient._currentToParent(lHref); + } + + /* что бы не переходить по ссылкам + * а грузить всё ajax'ом, + * возвращаем false на событие + * onclick + */ + return false; + }; + }); + +/* Функция устанавливает текущим файлом, тот + * на который кликнули единожды + */ +CloudClient._setCurrent=(function(){ + /* + * @pFromEnter - если мы сюда попали + * из события нажатия на энтер - + * вызоветься _loadDir + */ + return function(pFromEnter){ + var lCurrentFile=document.getElementsByClassName(CloudClient.CURRENT_FILE); + if(lCurrentFile && lCurrentFile.length > 0){ + /* если мы находимся не на + * пути и не на заголовках + */ + if(this.className!=='path' && + this.className!=='fm_header'){ + + lCurrentFile[0].className=''; + /* устанавливаем курсор на файл, + * на который нажали */ + this.className=CloudClient.CURRENT_FILE; + } + } + /* если мы попали сюда с энтера*/ + if(pFromEnter===true){ + this.ondblclick(this); + }/* если мы попали сюда от клика мышки */ + else{pFromEnter.returnValue=false;} + + /* что бы не переходить по ссылкам + * а грузить всё ajax'ом, + * возвращаем false на событие + * onclick + */ + return false; + }; + }); + +/* функция устанавливает курсор на каталог + * с которого мы пришли, если мы поднялись + * в верх по файловой структуре + * @pDirName - имя каталога с которого мы пришли + */ +CloudClient._currentToParent = (function(pDirName){ + /* опредиляем в какой мы панели: + * правой или левой + */ + var lCurrentFile=document.getElementsByClassName(CloudClient.CURRENT_FILE); + var lPanel; + try{ + lPanel=lCurrentFile[0].parentElement.id; + }catch(error){console.log("Current file not found\n"+error);} + /* убираем слэш с имени каталога*/ + pDirName=pDirName.replace('/',''); + /* ищем файл с таким именем*/ + lPanel=document.getElementById(lPanel); + if(!lPanel)return; + + var lLi=lPanel.getElementsByTagName('li'); + for(var i=0;i0)lCurrentFile[0].className=''; + + lLi[i].className=CloudClient.CURRENT_FILE; + } + } +}); + +/* глобальные переменные */ +var LoadingImage; +var ErrorImage; + +var $; +var CloudFunc; +/* Конструктор CloudClient, который + * выполняет весь функционал по + * инициализации + */ +CloudClient.init=(function() +{ + /* меняем title + * если js включен - имена папок отображать необязательно... + * а может и обязательно при переходе, можно будет это сделать + */ + var lTitle=document.getElementsByTagName('title'); + if(lTitle.length>0)lTitle[0].innerText='Cloud Commander'; + + /* загружаем jquery: */ + CloudClient.jsload('//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js',function(){ + /* сохраняем переменную jQuery себе в область видимости */ + $=window.jQuery; + if(!window.jQuery)CloudClient.jsload('jquery.min.js', + function(){ + $=window.jQuery; + }); + }); + + /* загружаем общие функции для клиента и сервера*/ + CloudClient.jsload(CloudClient.LIBDIR+'/cloudfunc.js',function(){ + /* берём из обьекта window общий с сервером функционал */ + CloudFunc=window.CloudFunc; + + /* меняем ссылки на ajax'овые*/ + CloudClient._changeLinks(CloudFunc.LEFTPANEL); + CloudClient._changeLinks(CloudFunc.RIGHTPANEL); + + /* устанавливаем переменную доступности кэша*/ + CloudClient.Cache.isAllowed(); + /* Устанавливаем кэш корневого каталога */ + if(!CloudClient.Cache.get('/'))CloudClient.Cache.set('/',CloudClient._getJSONfromFileTable()); + } + ); + + LoadingImage=CloudClient._images.loading(); + /* загружаем иконку загрузки возле кнопки обновления дерева каталогов*/ + try{ + document.getElementsByClassName('path')[0].getElementsByTagName('a')[0].appendChild(LoadingImage); + LoadingImage.className+=' hidden'; /* прячем её */ + }catch(error){console.log(error);} + ErrorImage=CloudClient._images.error(); + + /* устанавливаем размер высоты таблицы файлов + * исходя из размеров разрешения экрана + */ + + /* формируем и округляем высоту экрана + * при разрешениии 1024x1280: + * 658 -> 700 + */ + + var lHeight=window.screen.height - (window.screen.height/3).toFixed(); + lHeight=(lHeight/100).toFixed()*100; + + var lFm=document.getElementById('fm'); + if(lFm)lFm.style.cssText='height:' + + lHeight + + 'px'; + + /* выделяем строку с первым файлом */ + var lFmHeader=document.getElementsByClassName('fm_header'); + if(lFmHeader && lFmHeader[0].nextSibling) + lFmHeader[0].nextSibling.className=CloudClient.CURRENT_FILE; + + /* показываем элементы, которые будут работать только, если есть js */ + var lFM=document.getElementById('fm'); + if(lFM)lFm.className='localstorage'; + + /* если есть js - показываем правую панель*/ + var lRight=document.getElementById('right'); + if(lRight)lRight.className=lRight.className.replace('hidden',''); + + CloudClient.cssSet({id:'show_2panels', + element:document.head, + inner:'#left{width:45%;}' + }); +}); + +/* функция меняет ссыки на ajax-овые */ +CloudClient._changeLinks = function(pPanelID) +{ + /* назначаем кнопку очистить кэш и показываем её*/ + var lClearcache=document.getElementById('clear-cache'); + if(lClearcache)lClearcache.onclick=CloudClient.Cache.clear; + + /* меняем ссылки на ajax-запросы */ + var lPanel=document.getElementById(pPanelID); + var a=lPanel.getElementsByTagName('a'); + + /* Если нажмут на кнопку перезагрузить страниц - её нужно будет обязательно + * перезагрузить + */ + /* номер ссылки очистки кэша*/ + //var lCLEARICON=0; + /* номер ссылки иконки обновления страницы */ + var lREFRESHICON=0; + + /* путь в ссылке, который говорит + * что js отключен + */ + var lNoJS_s = CloudFunc.NOJS; + var lFS_s = CloudFunc.FS; + + for(var i=0;i=3){ + element.onload=pFunc; + if(arguments.length>=4){ + element.style.cssText=pStyle; + } + } + //document.body + pElement.appendChild(element); + return element;//'elem '+src+' loaded'; + } + /* если js-файл уже загружен + * запускаем функцию onload + */ + else if(pFunc){ + try{ + pFunc(); + }catch(error){console.log(error);} + } +}; + +/* Функция загружает js-файл */ +CloudClient.jsload = function(pSrc,pFunc,pStyle,pId) +{ + CloudClient._anyload('script',pSrc,pFunc,pStyle,pId,document.body); +}; +/* Функция создаёт елемент style и записывает туда стили + * @pParams_o - структура параметров, заполняеться таким + * образом: {src: ' ',func: '', id: '', element: '', inner: ''} + * все параметры опциональны + */ +CloudClient.cssSet = function(pParams_o){ + var lElem=CloudClient._anyload('style', + pParams_o.src, + pParams_o.func, + pParams_o.style, + pParams_o.id, + pParams_o.element?pParams_o.element:document.body); + lElem.innerText=pParams_o.inner; +}; + +/* + * Функция генерирует JSON из html-таблицы файлов + */ +/* + * Используеться при первом заходе в корень + */ +CloudClient._getJSONfromFileTable=function() +{ + var lLeft=document.getElementById('left'); + + + //var lPath=document.getElementById('path').innerText; + var lPath=document.getElementsByClassName('path')[0].innerText; + var lFileTable=[{path:lPath,size:'dir'}]; + var lLI=lLeft.getElementsByTagName('li'); + + var j=1;/* счётчик реальных файлов */ + var i=1;/* счётчик элементов файлов в DOM */ + /* Если путь отличный от корневного + * второй элемент li - это ссылка на верхний + * каталог '..' + */ + i=2;/* пропускам Path и Header*/ + + + for(;i<\/script>'); +*/ + +/* Клиентский обьект, содержащий функциональную часть*/ +var CloudClient={ + /* Конструктор CloudClient, который + * выполняет весь функционал по + * инициализации + */ + init :function(){}, + + keyBinding :function(){},/* функция нажатий обработки клавишь */ + keyBinded :false,/* оброботка нажатий клавишь установлена*/ + _loadDir :function(){}, + /* + * Функция привязываеться ко всем ссылкам и + * загружает содержимое каталогов + */ + /* Обьект для работы с кэшем */ + Cashe :{}, + + /* ПРИВАТНЫЕ ФУНКЦИИ */ + /* функция загружает json-данные о файловой системе */ + _ajaxLoad :function(){}, + /* Функция генерирует JSON из html-таблицы файлов */ + _getJSONfromFileTable :function(){}, + /* функция меняет ссыки на ajax-овые */ + _changeLinks :function(){}, + /* ОБЬЕКТЫ */ + /* обьект, который содержит функции для отображения картинок*/ + _images :{}, + /* КОНСТАНТЫ*/ + /* название css-класа текущего файла*/ + CURRENT_FILE :'current-file', + LIBDIR :'/lib', + LIBDIRCLIENT :'/lib/client' +}; + +/* + * Обьект для работы с кэшем + * в него будут включены функции для + * работы с LocalStorage, webdb, + * idexed db etc. + */ +CloudClient.Cache={ + _allowed :true, /* приватный переключатель возможности работы с кэшем */ + /* функция проверяет возможно ли работать с кэшем каким-либо образом */ + isAllowed :function(){}, + /* Тип кэша, который доступен*/ + type :{}, + /* Функция устанавливает кэш, если выбранный вид поддерживаеться браузером*/ + set :function(){}, + /* Функция достаёт кэш, если выбранный вид поддерживаеться браузером*/ + get :function(){}, + /* функция чистит весь кэш для всех каталогов*/ + clear :function(){} +}; + +/* Обьект, который содержит + * функции для отображения + * картинок + */ +CloudClient._images={ + /* Функция создаёт картинку загрузки*/ + loading :function(){ + var e=document.createElement('span'); + e.className='icon loading'; + e.id='loading-image'; + return e; +}, + + /* Функция создаёт картинку ошибки загрузки*/ + error :function(){ + var e=document.createElement('span'); + e.className='icon error'; + e.id='error-image'; + return e; + } +}; + +/* функция проверяет поддерживаеться ли localStorage */ +CloudClient.Cache.isAllowed=(function(){ + if(window.localStorage && + localStorage.setItem && + localStorage.getItem){ + CloudClient.Cache._allowed=true; + }else + { + CloudClient.Cache._allowed=false; + /* загружаем PolyFill для localStorage, + * если он не поддерживаеться браузером + * https://gist.github.com/350433 + */ + /* + CloudClient.jsload('https://raw.github.com/gist/350433/c9d3834ace63e5f5d7c8e1f6e3e2874d477cb9c1/gistfile1.js', + function(){CloudClient.Cache._allowed=true; + }); + */ + } +}); + /* если доступен localStorage и + * в нём есть нужная нам директория - + * записываем данные в него + */ +CloudClient.Cache.set=(function(pName, pData){ + if(CloudClient.Cache._allowed && pName && pData){ + localStorage.setItem(pName,pData); + } +}); +/* Если доступен Cache принимаем из него данные*/ +CloudClient.Cache.get=(function(pName){ + if(CloudClient.Cache._allowed && pName){ + return localStorage.getItem(pName); + } + else return null; +}); +/* Функция очищает кэш*/ +CloudClient.Cache.clear=(function(){ + if(CloudClient.Cache._allowed){ + localStorage.clear(); + } +}); + + +/* функция обработки нажатий клавишь */ +CloudClient.keyBinding=(function(){ + /* loading keyBinding module and start it */ + CloudClient.jsload(CloudClient.LIBDIRCLIENT+'/keyBinding.js',function(){ + CloudCommander.keyBinding(); + }); +}); + + +/* + * Функция привязываеться ко всем ссылкам и + * загружает содержимое каталогов + */ +CloudClient._loadDir=(function(pLink,pNeedRefresh){ + /* @pElem - элемент, + * для которого нужно + * выполнить загрузку + */ + return function(){ + /* показываем гиф загрузки возле пути папки сверху*/ + LoadingImage.className='icon loading';/* показываем загрузку*/ + ErrorImage.className='icon error hidden';/* прячем ошибку */ + /* если элемент задан - + * работаем с ним + */ + /* если мы попали сюда с таблицы файлов*/ + try{ + this.firstChild.nextSibling.appendChild(LoadingImage); + }catch(error){ + /* если + + * кнопка обновления + */ + try{this.firstChild.parentElement.appendChild(LoadingImage);} + catch(error){console.log(error);} + } + + var lCurrentFile=document.getElementsByClassName(CloudClient.CURRENT_FILE); + /* получаем имя каталога в котором находимся*/ + var lHref; + try{ + lHref=lCurrentFile[0].parentElement.getElementsByClassName('path')[0].innerText; + }catch(error){console.log('error');} + + lHref=CloudFunc.removeLastSlash(lHref); + var lSubstr=lHref.substr(lHref,lHref.lastIndexOf('/')); + lHref=lHref.replace(lSubstr+'/',''); + + /* загружаем содержимое каталога*/ + CloudClient._ajaxLoad(pLink, pNeedRefresh); + + /* получаем все элементы выделенной папки*/ + /* при этом, если мы нажали обновить + * или +R - ссылок мы ненайдём + * и заходить не будем + */ + var lA=this.getElementsByTagName('a'); + /* если нажали на ссылку на верхний каталог*/ + if(lA.length>0 && lA[0].innerText==='..' && + lHref!=='/'){ + /* функция устанавливает курсор на каталог + * с которого мы пришли, если мы поднялись + * в верх по файловой структуре + */ + CloudClient._currentToParent(lHref); + } + + /* что бы не переходить по ссылкам + * а грузить всё ajax'ом, + * возвращаем false на событие + * onclick + */ + return false; + }; + }); + +/* Функция устанавливает текущим файлом, тот + * на который кликнули единожды + */ +CloudClient._setCurrent=(function(){ + /* + * @pFromEnter - если мы сюда попали + * из события нажатия на энтер - + * вызоветься _loadDir + */ + return function(pFromEnter){ + var lCurrentFile=document.getElementsByClassName(CloudClient.CURRENT_FILE); + if(lCurrentFile && lCurrentFile.length > 0){ + /* если мы находимся не на + * пути и не на заголовках + */ + if(this.className!=='path' && + this.className!=='fm_header'){ + + lCurrentFile[0].className=''; + /* устанавливаем курсор на файл, + * на который нажали */ + this.className=CloudClient.CURRENT_FILE; + } + } + /* если мы попали сюда с энтера*/ + if(pFromEnter===true){ + this.ondblclick(this); + }/* если мы попали сюда от клика мышки */ + else{pFromEnter.returnValue=false;} + + /* что бы не переходить по ссылкам + * а грузить всё ajax'ом, + * возвращаем false на событие + * onclick + */ + return false; + }; + }); + +/* функция устанавливает курсор на каталог + * с которого мы пришли, если мы поднялись + * в верх по файловой структуре + * @pDirName - имя каталога с которого мы пришли + */ +CloudClient._currentToParent = (function(pDirName){ + /* опредиляем в какой мы панели: + * правой или левой + */ + var lCurrentFile=document.getElementsByClassName(CloudClient.CURRENT_FILE); + var lPanel; + try{ + lPanel=lCurrentFile[0].parentElement.id; + }catch(error){console.log("Current file not found\n"+error);} + /* убираем слэш с имени каталога*/ + pDirName=pDirName.replace('/',''); + /* ищем файл с таким именем*/ + lPanel=document.getElementById(lPanel); + if(!lPanel)return; + + var lLi=lPanel.getElementsByTagName('li'); + for(var i=0;i0)lCurrentFile[0].className=''; + + lLi[i].className=CloudClient.CURRENT_FILE; + } + } +}); + +/* глобальные переменные */ +var LoadingImage; +var ErrorImage; + +var $; +var CloudFunc; +/* Конструктор CloudClient, который + * выполняет весь функционал по + * инициализации + */ +CloudClient.init=(function() +{ + /* меняем title + * если js включен - имена папок отображать необязательно... + * а может и обязательно при переходе, можно будет это сделать + */ + var lTitle=document.getElementsByTagName('title'); + if(lTitle.length>0)lTitle[0].innerText='Cloud Commander'; + + /* загружаем jquery: */ + CloudClient.jsload('//ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js',function(){ + /* сохраняем переменную jQuery себе в область видимости */ + $=window.jQuery; + if(!window.jQuery)CloudClient.jsload('jquery.min.js', + function(){ + $=window.jQuery; + }); + }); + + /* загружаем общие функции для клиента и сервера*/ + CloudClient.jsload(CloudClient.LIBDIR+'/cloudfunc.js',function(){ + /* берём из обьекта window общий с сервером функционал */ + CloudFunc=window.CloudFunc; + + /* меняем ссылки на ajax'овые*/ + CloudClient._changeLinks(CloudFunc.LEFTPANEL); + CloudClient._changeLinks(CloudFunc.RIGHTPANEL); + + /* устанавливаем переменную доступности кэша*/ + CloudClient.Cache.isAllowed(); + /* Устанавливаем кэш корневого каталога */ + if(!CloudClient.Cache.get('/'))CloudClient.Cache.set('/',CloudClient._getJSONfromFileTable()); + } + ); + + LoadingImage=CloudClient._images.loading(); + /* загружаем иконку загрузки возле кнопки обновления дерева каталогов*/ + try{ + document.getElementsByClassName('path')[0].getElementsByTagName('a')[0].appendChild(LoadingImage); + LoadingImage.className+=' hidden'; /* прячем её */ + }catch(error){console.log(error);} + ErrorImage=CloudClient._images.error(); + + /* устанавливаем размер высоты таблицы файлов + * исходя из размеров разрешения экрана + */ + + /* формируем и округляем высоту экрана + * при разрешениии 1024x1280: + * 658 -> 700 + */ + + var lHeight=window.screen.height - (window.screen.height/3).toFixed(); + lHeight=(lHeight/100).toFixed()*100; + + var lFm=document.getElementById('fm'); + if(lFm)lFm.style.cssText='height:' + + lHeight + + 'px'; + + /* выделяем строку с первым файлом */ + var lFmHeader=document.getElementsByClassName('fm_header'); + if(lFmHeader && lFmHeader[0].nextSibling) + lFmHeader[0].nextSibling.className=CloudClient.CURRENT_FILE; + + /* показываем элементы, которые будут работать только, если есть js */ + var lFM=document.getElementById('fm'); + if(lFM)lFm.className='localstorage'; + + /* если есть js - показываем правую панель*/ + var lRight=document.getElementById('right'); + if(lRight)lRight.className=lRight.className.replace('hidden',''); + + CloudClient.cssSet({id:'show_2panels', + element:document.head, + inner:'#left{width:45%;}' + }); +}); + +/* функция меняет ссыки на ajax-овые */ +CloudClient._changeLinks = function(pPanelID) +{ + /* назначаем кнопку очистить кэш и показываем её*/ + var lClearcache=document.getElementById('clear-cache'); + if(lClearcache)lClearcache.onclick=CloudClient.Cache.clear; + + /* меняем ссылки на ajax-запросы */ + var lPanel=document.getElementById(pPanelID); + var a=lPanel.getElementsByTagName('a'); + + /* Если нажмут на кнопку перезагрузить страниц - её нужно будет обязательно + * перезагрузить + */ + /* номер ссылки очистки кэша*/ + //var lCLEARICON=0; + /* номер ссылки иконки обновления страницы */ + var lREFRESHICON=0; + + /* путь в ссылке, который говорит + * что js отключен + */ + var lNoJS_s = CloudFunc.NOJS; + var lFS_s = CloudFunc.FS; + + for(var i=0;i=3){ + element.onload=pFunc; + if(arguments.length>=4){ + element.style.cssText=pStyle; + } + } + //document.body + pElement.appendChild(element); + return element;//'elem '+src+' loaded'; + } + /* если js-файл уже загружен + * запускаем функцию onload + */ + else if(pFunc){ + try{ + pFunc(); + }catch(error){console.log(error);} + } +}; + +/* Функция загружает js-файл */ +CloudClient.jsload = function(pSrc,pFunc,pStyle,pId) +{ + CloudClient._anyload('script',pSrc,pFunc,pStyle,pId,document.body); +}; +/* Функция создаёт елемент style и записывает туда стили + * @pParams_o - структура параметров, заполняеться таким + * образом: {src: ' ',func: '', id: '', element: '', inner: ''} + * все параметры опциональны + */ +CloudClient.cssSet = function(pParams_o){ + var lElem=CloudClient._anyload('style', + pParams_o.src, + pParams_o.func, + pParams_o.style, + pParams_o.id, + pParams_o.element?pParams_o.element:document.body); + lElem.innerText=pParams_o.inner; +}; + +/* + * Функция генерирует JSON из html-таблицы файлов + */ +/* + * Используеться при первом заходе в корень + */ +CloudClient._getJSONfromFileTable=function() +{ + var lLeft=document.getElementById('left'); + + + //var lPath=document.getElementById('path').innerText; + var lPath=document.getElementsByClassName('path')[0].innerText; + var lFileTable=[{path:lPath,size:'dir'}]; + var lLI=lLeft.getElementsByTagName('li'); + + var j=1;/* счётчик реальных файлов */ + var i=1;/* счётчик элементов файлов в DOM */ + /* Если путь отличный от корневного + * второй элемент li - это ссылка на верхний + * каталог '..' + */ + i=2;/* пропускам Path и Header*/ + + + for(;i + + + + + + Cloud Commander + + + + + + + + + + +
+
+ + + + \ No newline at end of file diff --git a/cloudcmd1/1/package.json b/cloudcmd1/1/package.json new file mode 100644 index 00000000..5c67dd25 --- /dev/null +++ b/cloudcmd1/1/package.json @@ -0,0 +1,11 @@ +{ + "name": "cloudcmd", + "version": "0.0.1-9", + "scripts": { + "start": "server.js" + }, + "engines": { + "node": "0.6.x" + }, + "subdomain": "cloudcmd" +} \ No newline at end of file diff --git a/cloudcmd1/1/package2.json b/cloudcmd1/1/package2.json new file mode 100644 index 00000000..f2d59387 --- /dev/null +++ b/cloudcmd1/1/package2.json @@ -0,0 +1,12 @@ +{ + "name": "cloudcmd", + "version": "0.0.1-3", + "scripts": { + + "start": "server.js" + }, + "engines": { + "node": "0.6.x" + }, + "subdomain": "cloudcmd" +} diff --git a/cloudcmd1/1/reset.css b/cloudcmd1/1/reset.css new file mode 100644 index 00000000..a24c865b --- /dev/null +++ b/cloudcmd1/1/reset.css @@ -0,0 +1,100 @@ +/* ============================================================================= + HTML5 display definitions + ========================================================================== */ + +/* +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } +audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } +audio:not([controls]) { display: none; } +[hidden] { display: none; } +*/ + +/* ============================================================================= + Base + ========================================================================== */ + +/* + * 1. Correct text resizing oddly in IE6/7 when body font-size is set using em units + * 2. Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g + */ + +html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: sans-serif; color: #222;} +body { margin: 0; font-size: 1em; line-height: 1.4; } + +/* + * Remove text-shadow in selection highlight: h5bp.com/i + * These selection declarations have to be separate + * Also: hot pink! (or customize the background color to match your design) + */ +/* +::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; } + ::selection { background: #fe57a1; color: #fff; text-shadow: none; } +*/ +::selection { text-shadow: none; opacity: 0;} + + +/* ============================================================================= + Links + ========================================================================== */ + +a {text-decoration:none; color: #00e; } +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; } + +ul{ margin: 1em 0; padding: 0 0 0 40px; } + +/* + * 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) + */ + + +/* ============================================================================= + Chrome Frame Prompt + ========================================================================== */ + +.chromeframe { margin: 0.2em 0; background: #ccc; color: black; padding: 0.2em 0; } + + +/* ==|== non-semantic helper classes ======================================== + Please define your styles before this section. + ========================================================================== */ + +/* For image replacement */ +.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; *line-height: 0; } +.ir br { display: none; } + +/* Hide from both screenreaders and browsers: h5bp.com/u */ +.hidden { display: none !important; visibility: hidden; } + +/* Hide only visually, but have it available for screenreaders: h5bp.com/v */ +.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } + +/* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: h5bp.com/p */ +.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } + +/* Hide visually and from screenreaders, but maintain layout */ +.invisible { visibility: hidden; } + +/* Contain floats: h5bp.com/q */ +.clearfix:before, .clearfix:after { content: ""; display: table; } +.clearfix:after { clear: both; } +.clearfix { *zoom: 1; } + +/* ==|== 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; } +} \ No newline at end of file diff --git a/cloudcmd1/1/server.js b/cloudcmd1/1/server.js new file mode 100644 index 00000000..2839b1ef --- /dev/null +++ b/cloudcmd1/1/server.js @@ -0,0 +1,635 @@ +"use strict"; + +/* Обьект содержащий все функции и переменные + * серверной части Cloud Commander'а + */ +var CloudServer={ + /* функция, которая генерирует заголовки + * файлов, отправляемые сервером клиенту + */ + generateHeaders :function(){}, + /* функция высылает + * данные клиенту + */ + sendResponse :function(){}, + /* Структура содержащая функции, + * и переменные, в которых + * говориться о поддерживаемых + * браузером технологиях + */ + BrowserSuport :{}, + /* Обьект для работы с кэшем */ + Cashe :{}, + /* Обьект через который + * выполняеться сжатие + * скриптов и стилей + */ + Minify :{}, + /* Асоциативный масив обьектов для + * работы с ответами сервера + * высылаемыми на запрос о файле и + * хранащий информацию в виде + * Responces[name]=responce; + */ + Responses :{}, + + /* ПЕРЕМЕННЫЕ */ + /* Поддержка браузером JS*/ + NoJS :true, + /* Поддержка gzip-сжатия + * браузером + */ + Gzip :undefined, + + /* КОНСТАНТЫ */ + /* index.html */ + INDEX :'index.html', + /* name of direcotory with libs */ + LIBDIR :'./lib', + LIBDIRSERVER :'./lib/server' +}; + +/* + * Обьект для работы с кэшем + * аналог клиентского обьекта + * с тем отличием, что в нём + * будут храниться серверные + * данные, такие как файлы + * отдаваемые клиенту + * (файлы проэкта по большому + * счёту, для ускорения + * первичной загрузки) + */ +CloudServer.Cache={ + _allowed :true, /* приватный переключатель возможности работы с кэшем */ + /* данные в которых храняться файлы + * в формате <поле> : <значение> + * _data[name]=pData; + * одному имени соответствуют + * одни данные + */ + _data :{}, + + /* функция говорит можно ли работать с кэшем */ + isAllowed :(function(){ + return CloudServer.Cache._allowed; + }), + /* функция устанавливает возможность работать с кэшем */ + setAllowed :(function(pAllowed){ + CloudServer.Cache._allowed=pAllowed; + }), + /* Если доступен кэш + * сохраняем в него данные + */ + set :(function(pName, pData){ + if(CloudServer.Cache._allowed && pName && pData){ + CloudServer.Cache._data[pName]=pData; + } + }), + /* Если доступен Cache принимаем из него данные*/ + get :(function(pName){ + if(CloudServer.Cache._allowed && pName){ + return CloudServer.Cache._data[pName]; + } + else return null; + }), + + /* Функция очищает кэш*/ + clear :(function(){ + if(CloudServer.Cache._allowed){ + CloudServer.Cache._data={}; + } + }) +}; + +/* Обьект для сжатия скриптов и стилей + * по умолчанию - сжимаються + */ +CloudServer.Minify={ + /* приватный переключатель минимизации */ + _allowed :{css:true,js:true,html:true, img: true}, + + /* функция разрешает или + * запрещает минимизировать + * css/js/html + * @pAllowed: - структура, в которой + * передаються параметры + * минификации, вида + * {js:true,css:true,html:false; img:true} + * img отвечает за перевод картинок в base64 + * и сохранение их в css-файл + */ + setAllowed :(function(pAllowed){ + if(pAllowed){ + this._allowed.css=pAllowed.css; + this._allowed.js=pAllowed.js; + this._allowed.html=pAllowed.html; + this._allowed.img=pAllowed.img; + } + }), + + /* + * Функция минимизирует css/js/html + * если установлены параметры минимизации + */ + doit :(function(){ + if(this._allowed.css || + this._allowed.js || + this._allowed.html){ + var lMinify = require(CloudServer.LIBDIRSERVER+'/minify'); + + this.done.js=this._allowed.js?lMinify.jsScripts():false; + this.done.html=this._allowed.html?lMinify.html():false; + this.done.css=this._allowed.css?lMinify.cssStyles(this._allowed.img):false; + } + }), + /* свойство показывающее случилась ли ошибка*/ + done:{js: false,css: false, html:false} +}; + + +var LeftDir='/'; +var RightDir=LeftDir; +/* + var Path = require('path'); +*/ /* модуль для работы с путями*/ + +var Fs = require('fs'); /* модуль для работы с файловой системой*/ + +var Zlib; +/* node v0.4 not contains zlib + */ +try{ + Zlib = require('zlib'); /* модуль для сжатия данных gzip-ом*/ +}catch(error){ + Zlib=undefined; + console.log('to use gzip-commpression' + + 'you should install zlib module\n' + + 'npm install zlib'); +} +var CloudFunc = require(CloudServer.LIBDIR + + (CloudServer.Minify.done.js?/* если стоит минификация*/ + '/cloudfunc.min':/* добавляем сжатый - иначе обычный */ + '/cloudfunc')); /* модуль с функциями */ +/* конструктор*/ +CloudServer.init=(function(){ + /* Переменная в которой храниться кэш*/ + CloudServer.Cache.setAllowed(true); + /* Change default parameters of + * js/css/html minification + */ + CloudServer.Minify.setAllowed({ + js:true, + css:true, + html:true, + img:true + }); + /* Если нужно минимизируем скрипты */ + CloudServer.Minify.doit(); +}); + + +/* создаём сервер на порту 31337*/ +CloudServer.start=function() +{ + CloudServer.init(); + + var http = require('http'); + http.createServer(CloudServer._controller).listen(process.env.PORT || + process.env.VCAP_APP_PORT /* cloudfoundry */ || + 31337, + '0.0.0.0' || '127.0.0.1'); + console.log('Cloud Commander server running at http://127.0.0.1:'+ + (process.env.PORT===undefined?31337:process.env.PORT)); +}; + + +/* Функция создаёт заголовки файлов + * в зависимости от расширения файла + * перед отправкой их клиенту + * @pName - имя файла + * @pGzip - данные сжаты gzip'ом + */ +CloudServer.generateHeaders = function(pName, pGzip){ + var lType=''; + /* высылаем заголовок в зависимости от типа файла */ + /* если расширение у файла css - + * загружаем стили + */ + if(CloudFunc.checkExtension(pName,'css')) + lType='text/css'; + /* загружаем js */ + else if(CloudFunc.checkExtension(pName,'js')) + lType='text/javascript'; + /* загружаем картинки*/ + else if(CloudFunc.checkExtension(pName,'png')) + lType='image/png'; + /* загружаем json*/ + else if(CloudFunc.checkExtension(pName,'json')) + lType='application/json'; + else if(CloudFunc.checkExtension(pName,'html')) + lType='text/html'; + else if(CloudFunc.checkExtension(pName,'appcache')) + lType='text/cache-manifest'; + /* если это неизвестный тип файла - + * высылаем его просто как текст + */ + else lType='text/plain'; + + return { + /* if type of file any, but img - + * then we shoud specify charset + */ + 'Content-Type': lType + (lType.indexOf('img')<0?'; charset=UTF-8':''), + 'cache-control': 'max-age='+(31337*21), + 'last-modified': new Date().toString(), + 'content-encoding': pGzip?'gzip':'', + /* https://developers.google.com/speed/docs/best-practices/caching?hl=ru#LeverageProxyCaching */ + 'Vary': 'Accept-Encoding' + }; +}; + +/* + * Главная функция, через которую проихсодит + * взаимодействие, обмен данными с клиентом + * @req - запрос клиента (Request) + * @res - ответ сервера (Response) + */ +CloudServer._controller=function(pReq, pRes) +{ + /* Читаем содержимое папки, + переданное в url + */ + var url = require("url"); + var pathname = url.parse(pReq.url).pathname; + console.log('pathname: '+pathname); + + /* получаем поддерживаемые браузером кодировки*/ + var lAcceptEncoding = pReq.headers['accept-encoding']; + /* запоминаем поддерживает ли браузер + * gzip-сжатие при каждом обращении к серверу + * и доступен ли нам модуль zlib + */ + if (lAcceptEncoding && + lAcceptEncoding.match(/\bgzip\b/) && + Zlib){ + CloudServer.Gzip=true; + }else + CloudServer.Gzip=false; + /* путь в ссылке, который говорит + * что js отключен + */ + var lNoJS_s=CloudFunc.NOJS; + var lFS_s=CloudFunc.FS; + + if(pathname!=='/favicon.ico') + { + console.log("request for " + pathname + " received..."); + var lName; + + /* если в пути нет информации ни о ФС, + * ни об отсутствии js, + * ни о том, что это корневой + * каталог - загружаем файлы проэкта + */ + if(pathname.indexOf(lFS_s)<0 && + pathname.indexOf(lNoJS_s)<0 && + pathname!=='/'){ + /* если имена файлов проекта - загружаем их*/ + /* убираем слеш и читаем файл с текущец директории*/ + //lName=Path.basename(pathname); + + /* добавляем текующий каталог к пути */ + lName='.'+pathname; + console.log('reading '+lName); + /* сохраняем указатель на responce и имя */ + CloudServer.Responses[lName]=pRes; + + /* Берём значение из кэша + * сжатый файл - если gzip-поддерживаеться браузером + * не сжатый - в обратном случае + */ + var lFileData=CloudServer.Cache.get(CloudServer.Gzip?(lName+'_gzip'):lName); + + var lReadFileFunc_f=CloudServer.getReadFileFunc(lName); + /* если там что-то есть передаём данные в функцию + * readFile + */ + if(lFileData){ + console.log('readed from cache'); + /* передаём данные с кэша, + * если gzip включен - сжатые + * в обратном случае - несжатые + */ + lReadFileFunc_f(undefined,lFileData,true); + } + else Fs.readFile(lName,lReadFileFunc_f); + + }else{/* если мы имеем дело с файловой системой*/ + /* если путь не начинаеться с no-js - значит + * js включен + */ + /* убираем пометку cloud, без которой c9.io + * не работает поскольку путь из двух слешей + * (/fs/no-js/) - очень короткий, нужно + * длиннее + */ + + if(pathname.indexOf(lNoJS_s)!==lFS_s.length && pathname!=='/'){ + CloudServer.NoJS=false; + }else pathname=pathname.replace(lNoJS_s,''); + + /* убираем индекс файловой системы */ + if(pathname.indexOf(lFS_s)===0){ + pathname=pathname.replace(lFS_s,''); + /* если посетитель только зашел на сайт + * no-js будет пустым, как и fs + */ + /* если в пути нету fs - посетитель только зашел на сайт + * загружаем его полностью. + */ + }else CloudServer.NoJS=true; + /* если в итоге путь пустой + * делаем его корневым + */ + if(pathname==='')pathname='/'; + + RightDir=pathname; + LeftDir=pathname; + + /* если встретиться пробел - + * меня код символа пробела на пробел + */ + + LeftDir=CloudFunc.replaceSpaces(LeftDir); + RightDir=CloudFunc.replaceSpaces(RightDir); + + /* Проверяем с папкой ли мы имеем дело */ + + /* читаем сновные данные о файле */ + var lStat; + try{ + lStat=Fs.statSync(LeftDir); + }catch(error){ + console.log(error); + CloudServer.Responses[LeftDir]=pRes; + CloudServer.sendResponse('OK',error.toString(),LeftDir); + } + /* если это каталог - + * читаем его содержимое + */ + try{ + /* если установлено сжатие + * меняем название html-файла и + * загружаем сжатый html-файл в дальнейшем + */ + CloudServer.INDEX=(CloudServer.Minify.done.html?'index.min.html':CloudServer.INDEX); + /* + * сохраним указатель на response + */ + CloudServer.Responses[CloudServer.INDEX]=pRes; + if(lStat.isDirectory()) + Fs.readdir(LeftDir,CloudServer._readDir); + /* отдаём файл */ + else if(lStat.isFile()){ + CloudServer.Responses[LeftDir]=pRes; + Fs.readFile(LeftDir,CloudServer.getReadFileFunc(LeftDir)); + console.log('reading file: '+LeftDir); + } + }catch(error){console.log(error);} + } + } +}; + +/* Функция читает ссылку или выводит информацию об ошибке*/ +CloudServer._readDir=function (pError, pFiles) +{ + if(!pError) + { + /* данные о файлах в формате JSON*/ + var lJSON=[]; + var lJSONFile={}; + /* Если мы не в корне добавляем слеш к будующим ссылкам */ + if(LeftDir!=='/') + { + RightDir+='/'; + LeftDir+='/'; + } + + pFiles=pFiles.sort(); + + lJSON[0]={path:LeftDir,size:'dir'}; + var fReturnFalse=function returnFalse(){return false;}; + for(var i=0;i','') + .replace('style.css','all.min.css') + :lIndex; + + lIndex = CloudServer.Minify.done.js?lIndex.replace('client.js','client.min.js'):lIndex; + + lIndex=lIndex.toString().replace('
','
'+lList); + /* меняем title */ + lIndex=lIndex.replace('Cloud Commander', + ''+CloudFunc.setTitle()+''); + /* отображаем панель быстрых клавишь */ + lList=lIndex; + /* если браузер поддерживает gzip-сжатие*/ + lHeader=CloudServer.generateHeaders('text/html',CloudServer.Gzip); + }catch(error){console.log(error);} + }else{ + /* в обычном режиме(когда js включен + * высылаем json-структуру файлов + * с соответствующими заголовками + */ + lList=JSON.stringify(lJSON); + lHeader=CloudServer.generateHeaders('application/json',CloudServer.Gzip); + } + /* если браузер поддерживает gzip-сжатие - сжимаем данные*/ + if(CloudServer.Gzip){ + Zlib.gzip(lList,CloudServer.getGzipDataFunc(lHeader,CloudServer.INDEX)); + } + /* если не поддерживаеться - отсылаем данные без сжатия*/ + else + CloudServer.sendResponse(lHeader,lList,CloudServer.INDEX); + } + else + { + console.log(pError); + CloudServer.sendResponse('OK',pError.toString()); + } +}; + +/* Функция генерирует функция считывания файла + * таким образом, что бы у нас было + * имя считываемого файла + * @pName - полное имя файла + */ +CloudServer.getReadFileFunc = function(pName){ +/* + * @pError - ошибка + * @pData - данные + * @pFromFile - прочитано с файла bool + */ + var lReadFile=function(pError,pData,pFromCache_b){ + if (!pError){ + console.log('file ' + pName + ' readed'); + + /* берём из кэша данные файла + * если их нет в кэше - + * сохраняем + */ + if(!pFromCache_b && CloudServer.Cache.isAllowed) + CloudServer.Cache.set(pName,pData); + /* если кэш есть + * сохраняем его в переменную + * которая до этого будет пустая + * по скольку мы будем вызывать этот метод + * сами, ведь файл уже вычитан + */ + + var lHeader=CloudServer.generateHeaders(pName,CloudServer.Gzip); + /* если браузер поддерживает gzip-сжатие - сжимаем данные*/ + if(CloudServer.Gzip &&!pFromCache_b){ + /* сжимаем содержимое */ + Zlib.gzip(pData,CloudServer.getGzipDataFunc(lHeader,pName)); + } + else{ + /* высылаем несжатые данные */ + CloudServer.sendResponse(lHeader,pData,pName); + } + } + else + { + console.log(pError.path); + if(pError.path!=='passwd.json') + { + console.log(pError); + CloudServer.sendResponse('OK',pError.toString()); + }else{ + CloudServer.sendResponse('OK','passwd.json'); + } + } + }; + return lReadFile; +}; + +/* Функция получает сжатые данные + * @pHeader - заголовок файла + */ +CloudServer.getGzipDataFunc=function(pHeader,pName){ + return function(error,pResult){ + if(!error){ + /* отправляем сжатые данные + * вместе с заголовком + */ + /* если установлена работа с кэшем + * сохраняем сжатые данные + */ + if(CloudServer.Cache.isAllowed){ + /* устанавливаем кєш */ + console.log(pName+' gziped'); + CloudServer.Cache.set(pName+'_gzip',pResult); + } + CloudServer.sendResponse(pHeader,pResult,pName); + } + else{ + console.log(error); + CloudServer.sendResponse(pHeader,error); + } + }; +}; +/* Функция высылает ответ серверу + * @pHead - заголовок + * @pData - данные + * @pName - имя отсылаемого файла + */ +CloudServer.sendResponse = function(pHead, pData,pName){ + /* если у нас есть указатель на responce + * для соответствующего файла - + * высылаем его + */ + var lResponse=CloudServer.Responses[pName]; + if(lResponse){ + lResponse.writeHead(200,pHead); + lResponse.end(pData); + console.log(pName+' sended'); + } +}; + +CloudServer.start(); \ No newline at end of file diff --git a/cloudcmd1/1/style.css b/cloudcmd1/1/style.css new file mode 100644 index 00000000..57136168 --- /dev/null +++ b/cloudcmd1/1/style.css @@ -0,0 +1,317 @@ +/* +@import url(//fonts.googleapis.com/css?family=Droid+Sans+Mono); +*/ + +/* Foundation Icons General Enclosed */ +@font-face { + font-family: 'FoundationIconsGeneralEnclosed'; + src: url('//dl.dropbox.com/u/78163899/mnemonia/fonts/foundation-icons-general-enclosed.woff') format('woff'); + font-weight: normal; + font-style: normal; + +} +/* символьный шрифт от гитхаба*/ +@font-face { + font-family: 'Octicons Regular'; + font-style: normal; + font-weight: normal; + src: local('Octicons Regular'), url('//dl.dropbox.com/u/78163899/mnemonia/fonts/octicons-regular-webfont.woff') format('woff'); +} + +@font-face { + font-family: 'Droid Sans Mono'; + font-style: normal; + font-weight: normal; + src: local('Droid Sans Mono'), local('DroidSansMono'), url('http://themes.googleusercontent.com/static/fonts/droidsansmono/v4/ns-m2xQYezAtqh7ai59hJUYuTAAIFFn5GTWtryCmBQ4.woff') format('woff'); +} + +body{ + font:16px "Droid Sans Mono"; +} + +.menu{ + font: 16px 'Octicons Regular'; + margin-bottom: 0; +} + +.path_icon{ + font-family:'FoundationIconsGeneralEnclosed'; + font-size:30px; + color: #46A4C3;/*#55BF3F; green*/ + text-shadow:black 0 2px 1px; + /* размер иконки и позиция на png-файле*/ + width: 15px; + height: 15px; + display: inline-block; + position: relative; + top: 3px; + left: -4px; +} +.path_icon:hover{ + /* + color:#45D827; + */ + cursor:pointer; +} +.path_icon:active{ + text-shadow:black 0 0 1px; + position: relative; + top: 4px; +} +.icon{ + font-family: 'Octicons Regular'; + font-size:16px; + width:16px; + height:16px; + display:inline-block; + margin-left:0.5%; +} +.error::before{ + content:'\f026'; + cursor:default; + color:rgb(222, 41, 41); + position: relative; + top: -3px; +} +.loading{ + background:url(/img/spinner.gif); + position:relative; + top:1px; +} +.error:hover{ + color:rgba(222, 41, 41, 0.81); +} +.refresh-icon{ + background:url(/img/panel_refresh.png) no-repeat; +} +.refresh-icon:active{ + /*background-position-y: -15px;*/ +background:url(/img/panel_refresh.png) 0 -15px no-repeat; +} +.clear-cache{ + background:url(/img/console_clear.png) -4px -4px no-repeat; + margin-right: 6px; + margin-left: 7px; +} +.clear-cache:active{ + /* + background-position-y: -25px; + */ + top:5px; +} + +.settings:before{ + content:'k'; +} +.links{ + color:red; + } + +.mini-icon { + float: left; + height: 16px; + left: -5px; + margin-left: 6px; + position: relative; + top: 2px; + width: 16px; + /* отступ перед картинкой + * для нормального отображения + * рамки + */ +} +/* freeupex */ +.directory{ + /*list-style-image*/ + background-image:url('/img/dir.png'); + background-repeat: no-repeat; + background-position: 0 0; +} +.text-file{ + /*list-style-image*/ + background-image:url('/img/txt.png'); + background-repeat: no-repeat; + background-position: 0 0; +} +#fm{ + height:90%; + overflow-y: scroll; +} +.fm_header{ + font-weight: bold; +} +#path{ + margin-left:1.5%; +} +#left{ + float:left; + width:90%; +} +/* фон файла, на котором курсор*/ +.current-file{ + border: 2px solid rgba(49, 123, 249, .40); +} +.selected-file{ + background-color: rgba(49, 123, 249, .40); + color:white; +} +#right{ + float:right; +} +.panel{ + display: table; + width:45%; +} +#keyspanel{ + text-align: center; +} +/* информация о файлах и папках*/ +.name{ + float: left; + width: 37%; +} +.size{ + float:left; + width:16%; + /* Ставим выравнивание, + * что бы размер был в + * одной ровной колонке + */ + text-align: right; + /* Ставим отступ, что бы + * size не налазил на uid + * (owner) + */ + margin-right: 27px; +} +.owner{ +} +.mode{ + float: right; + width: 25%; +} + +ul,li{list-style-type:none;} +button{ + width:10%; +} +a{ + text-decoration:none; +} +a:hover, a:active { cursor:pointer;outline: 0; color: #06e; } +a:focus { outline: thin dotted; } + +/* Если размер окна очень маленький + * располагаем имя и атрибуты файла + * друг-под-другом +*/ +/* responsive design */ +@media only screen and (max-width: 600px){ + #left{ + width:90% !important; + } + .panel >li{ + margin:10px; + } + /* если правая панель не помещаеться - прячем её */ + #right{ + display:none; + } + /* текущий файл под курсором */ +.current-file{ + background-color: rgba(49, 123, 249, .40); + color:white; + } + /* делаем иконки под курсом белыми*/ +.current-file > .mini-icon{ + color:white; + } +.current-file > .text-file::before{ + color:white; + } + +/* меняем иконки на шрифтовые*/ + .mini-icon { + font: 60px 'Octicons Regular'; + width: 40%; + height: 0; + margin-left: 0; + float: right; + position: relative; + top: -17px; + + color: rgba(246, 224, 124, 0.56); + } + .directory::before{ + content: '\f216'; + } + .text-file::before{ + color: rgba(26, 224, 124, 0.56); + content: '\f211'; + } + .text-file{ + background-image:none; + } + + + + /* убираем заголовок*/ + .fm_header{ + display:none; + } + .mode,.size,.owner{ + /* располагаем элементы + * один под другим + */ + display: table; + float: none; + width: 0; + + text-align: left; + } + /* выводим заголовки рядом с полями */ + .name::before{ + content: 'name:'; + font-weight: bold; + font-size: 13px; + } + .mode::before{ + content: 'mode:'; + font-weight: bold; + font-size: 13px; + } + .size::before{ + content: 'size:'; + font-weight: bold; + font-size: 13px; + } + .owner::before{ + content: 'owner:'; + font-weight: bold; + font-size: 13px; + } + + .name{ + float: none; + width:100%; + font-size: 18px; + } +} +@media only screen and (min-width: 601px) and (max-width: 767px){ + #left{ + width:90% !important; + } + #right{ + display:none; + } +} + +@media only screen and (min-width:767px) and (max-width: 1060px){ + #left{ + width:90% !important; + } + /* если правая панель не помещаеться - прячем её */ + #right{ + display:none; + } +} \ No newline at end of file diff --git a/cloudcmd1/1/test.sh b/cloudcmd1/1/test.sh new file mode 100644 index 00000000..6b68a51f --- /dev/null +++ b/cloudcmd1/1/test.sh @@ -0,0 +1,4 @@ +#!/bin/sh +npm i -g jshint +echo "jshint server.js client.js lib/cloudfunc.js" +jshint --config ./.jshintrc ./server.js ./client.js ./lib/cloudfunc.js \ No newline at end of file diff --git a/cloudcmd1/README.md b/cloudcmd1/README.md new file mode 100644 index 00000000..49cf0cba --- /dev/null +++ b/cloudcmd1/README.md @@ -0,0 +1,49 @@ +Cloud Commander [![Build Status](https://secure.travis-ci.org/coderaiser/cloudcmd.png?branch=master)](http://travis-ci.org/coderaiser/cloudcmd) +=============== +**Cloud Commander** - two-panels file manager, totally writed on js. +View [demo](http://demo-cloudcmd.cloudfoundry.com/ "demo"). + +Google PageSpeed Score : [100](https://developers.google.com/speed/pagespeed/insights#url=http_3A_2F_2Fdemo-cloudcmd.cloudfoundry.com_2F&mobile=false "score") (out of 100). + +Benefits +--------------- +- full browser compatibility *(ie6+,chrome,safari,opera,firefox)*; +- responsible design +- one full page loading, *and then just one-time json-dir-listings loading +(with refresh opportunity).* +- caching readed directories *to localStorage (for now) +(so if network will disconnected or something heppen with a signal, we +definitely will can work cached copy of directory listings)*; +- key binding +- disabled js support *(working in limited mode)*. +- automated minification *client js-files and onstart-reading Cloud manager files on server starting.* + +**Cloud Commander** uses all benefits of js, so if js is disabled, +we moves to *limited mode*. + +Limited-mode features: +--------------- +- only 1 panel available +- no keybinding +- no local caching +- full loading of all web page(with styles, js-scripts, html-page etc). + +Hot keys: +--------------- +In all modern web browsers (but not in IE, becouse he special) hot keys works. +There is a short list: +- Ctrl + r - reload dir content +- Alt + q - disable key bindings +- Alt + s - get all key bindings back +- up, down, enter - filesystem navigation + + +Additional modules: +--------------- +**Cloud Commander** not using additional modules for main functionality. +But for minification and optimization tricks optional can be +assingned (and installed) modules: +- [UglifyJS] (https://github.com/mishoo/UglifyJS) +- [clean-css] (https://github.com/GoalSmashers/clean-css) +- [html-minifier] (https://github.com/kangax/html-minifier) +- [css-b64-images] (https://github.com/Filirom1/css-base64-images) \ No newline at end of file diff --git a/cloudcmd1/client.js b/cloudcmd1/client.js new file mode 100644 index 00000000..6331b051 --- /dev/null +++ b/cloudcmd1/client.js @@ -0,0 +1,736 @@ +/* Функция которая возвратит обьект CloudCommander + * @window - обьект window + * @document - обьект document + * @CloudFunc - обьект содержащий общий функционал + * клиентский и серверный + */ +//var CloudCommander=(function(window,document){ + +//var document,window; + +var CloudCommander=(function(){ +"use strict"; + +/* если функции console.log нет - создаём заглушку */ + +/* +var console; +if(!window)window={console:{log:function(pParam){return pParam;}}}; +else if(window && !window.console){ + console={ + 'log':function(param){ + return param; + } + }; +}else console=window.console; +*/ +/* + window.jQuery || document.write(' + + \ No newline at end of file diff --git a/cloudcmd1/lib/README.md b/cloudcmd1/lib/README.md new file mode 100644 index 00000000..a741f80c --- /dev/null +++ b/cloudcmd1/lib/README.md @@ -0,0 +1,5 @@ +Cloud Commander Libraries +=============== +**Cloud Commander Libraries** - dir thet contains scripts, thet uses +on client and server side, and modules, wich is not necessary +for CloudCommander work, but they adds some sugor. \ No newline at end of file diff --git a/cloudcmd1/lib/client/keyBinding.js b/cloudcmd1/lib/client/keyBinding.js new file mode 100644 index 00000000..2416002e --- /dev/null +++ b/cloudcmd1/lib/client/keyBinding.js @@ -0,0 +1,248 @@ +var CloudCommander; +var CloudFunc; +CloudCommander.keyBinding=(function(){ + "use strict"; + var key_event=function(event){ + /* + * Делаем допущение что перезагружать Cloud Commander + * посетителям не придёться, в любом случае, параметр + * должен быть опциональным и должна быть возможность + * его отключить. В любом случае, кроме хакеров и + * разработчиков (при чём сомнительно, что хакерам + * это пригодиться), функция перезагрузки никому не + * нужна, поскольку загружать весь дополнительный + * контент снова (js,css) в готовой версии нет + * необходимости. + * + */ + //console.log(event.keyCode); + var lCurrentFile; + var lName; + /* если клавиши можно обрабатывать*/ + if(CloudCommander.keyBinded){ + /* если нажали таб: + * переносим курсор на + * правую панель, если + * мы были на левой и + * наоборот + */ + if(event.keyCode===9){ + console.log('Tab pressed'); + try{ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE)[0]; + }catch(error){console.log(error);} + } + /* навигация по таблице файлов*/ + /* если нажали клавишу вверх*/ + else if(event.keyCode===38){ + /* получаем выдленный файл*/ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE); + /* если ненайдены выделенные файлы - выходим*/ + if(lCurrentFile.length===0)return; + lCurrentFile=lCurrentFile[0]; + /* если это строка существет и + * если она не заголовок + * файловой таблицы + */ + if(lCurrentFile.previousSibling && + lCurrentFile.previousSibling.className!=='fm_header' ){ + /* убираем выделение с текущего элемента */ + lCurrentFile.className=''; + /* и выделяем предыдущую строку*/ + lCurrentFile.previousSibling.className=CloudCommander.CURRENT_FILE; + event.preventDefault(); + } + } + /* если нажали клавишу в низ*/ + else if(event.keyCode===40){ + /* получаем выдленный файл*/ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE); + /* если ненайдены выделенные файлы - выходим*/ + if(lCurrentFile.length===0)return; + lCurrentFile=lCurrentFile[0]; + /* если это не последняя строка */ + if(lCurrentFile.nextSibling){ + /* убираем с него выделение */ + lCurrentFile.className=''; + /* выделяем следующую строку*/ + lCurrentFile.nextSibling.className=CloudCommander.CURRENT_FILE; + event.preventDefault(); + } + } + /* если нажали клавишу page up или Home + * переходим к самому верхнему + * элементу + */ + else if(/*event.keyCode===33 ||*/ event.keyCode===36){ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE)[0]; + /* убираем выделение с текущего файла*/ + lCurrentFile.className=''; + /* получаем первый элемент*/ + lCurrentFile.parentElement.firstElementChild + /* пропускаем путь и заголовки столбиков*/ + .nextElementSibling.nextElementSibling + /* выделяем верхий файл */ + .className=CloudCommander.CURRENT_FILE; + } + /* если нажали клавишу page down или End + * выделяем последний элемент + */ + else if(/*event.keyCode===34 ||*/ event.keyCode===35){ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE)[0]; + /* снимаем выделение с текущего файла*/ + lCurrentFile.className=''; + /* выделяем самый нижний файл */ + lCurrentFile.parentElement.lastElementChild.className=CloudCommander.CURRENT_FILE; + } + /* если нажали Enter - открываем папку*/ + else if(event.keyCode===13){ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE); + /* если ненайдены выделенные файлы - выходим*/ + if(!lCurrentFile.length)return; + lCurrentFile=lCurrentFile[0]; + /* из него достаём спан с именем файла*/ + lName=lCurrentFile.getElementsByClassName('name'); + /* если нету (что вряд ли) - выходим*/ + if(!lName)return false; + /* достаём все ссылки*/ + var lATag=lName[0].getElementsByTagName('a'); + /* если нету - выходим */ + if(!lATag)return false; + /* получаем ссылку на каталог, + * что на уровень выше + */ + /* получаем имя каталога в котором находимся*/ + var lHref; + try{ + lHref=lCurrentFile.parentElement.getElementsByClassName('path')[0].innerText; + }catch(error){console.log('error');} + lHref=CloudFunc.removeLastSlash(lHref); + var lSubstr=lHref.substr(lHref,lHref.lastIndexOf('/')); + lHref=lHref.replace(lSubstr+'/',''); + + /* вызываем ajaxload привязанный через changelinks + * пробулем нажать на ссылку, если не получиться + * (opera, ie), вызываем событие onclick, + * которое пока не прописано у файлов + */ + + if(lCurrentFile.onclick)lCurrentFile.onclick(true); + else try{ + lATag[0].click(); + } + catch(error){ + console.log(error); + } + } + /* если нажали +r */ + else if(event.keyCode===82 && + event.ctrlKey){ + console.log('+r pressed'); + console.log('reloading page...'); + console.log('press +q to remove all key-handlers'); + + /* обновляем страницу, */ + /* Загружаем содержимое каталога + * при этом данные берём всегда из + * сервера, а не из кэша + * (обновляем кэш) + */ + /* Программно нажимаем на кнопку перезагрузки + * содержимого каталога + */ + var lRefreshIcon=document.getElementsByClassName(CloudFunc.REFRESHICON); + if(lRefreshIcon)lRefreshIcon=lRefreshIcon[0]; + if(lRefreshIcon){ + /* находим файл который сейчас выделен */ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE); + if(lCurrentFile.length>0)lCurrentFile=lCurrentFile[0]; + /* получаем название файла*/ + var lSelectedName=lCurrentFile.getElementsByTagName('a')[0].innerText; + /* если нашли элемент нажимаем него + * а если не можем - нажимаем на + * ссылку, на которую повешен eventHandler + * onclick + */ + if(lRefreshIcon.click)lRefreshIcon.parentElement.click(); + else lRefreshIcon.parentElement.onclick(); + + /* перебираем файлы левой панели + * в поисках подсвеченого файла + */ + var lLeft=document.getElementById('left'); + if(lLeft){ + /* перебираем все файлы в панели */ + var lLi=lLeft.getElementsByTagName('li'); + lCurrentFile.className=''; + /* начинаем с 2-ух, по скольку + * 0 - это путь + * 1 - это заголовок файловой таблицы + */ + for(var i=2;i+d чистим кэш */ + else if(event.keyCode===68 && + event.ctrlKey){ + console.log('+d pressed'); + console.log('clearing cache...'); + console.log('press +q to remove all key-handlers'); + + var lClearCache=document.getElementById('clear-cache'); + if(lClearCache && lClearCache.onclick)lClearCache.onclick(); + + event.preventDefault();//запрет на дальнейшее действие + } + /* если нажали +q + * убираем все обработчики + * нажатий клавиш + */ + else if(event.keyCode===81 && + event.altKey){ + //document.removeEventListener('keydown', key_event,false); + console.log('+q pressed'); + console.log('+r reload key-handerl - removed'); + console.log('+s clear cache key-handler - removed'); + console.log('press +s to to set them'); + + /* обработчик нажатий клавиш снят*/ + CloudCommander.keyBinded=false; + } + } + /* если нажали +s + * устанавливаем все обработчики + * нажатий клавиш + */ + else if(event.keyCode===83 && + event.altKey){ + /* + document.addEventListener('keydown', key_event,false); + */ + /* обрабатываем нажатия на клавиши*/ + CloudCommander.keyBinded=true; + + console.log('+s pressed'); + console.log('+r reload key-handerl - set'); + console.log('+s clear cache key-handler - set'); + console.log('press +q to remove them'); + } + + return false; + }; + /* добавляем обработчик клавишь */ + if(document.addEventListener) + document.addEventListener('keydown', key_event,false); + else document.onkeypress=key_event; + /* клавиши назначены*/ + CloudCommander.keyBinded=true; +}); \ No newline at end of file diff --git a/cloudcmd1/lib/cloudfunc.js b/cloudcmd1/lib/cloudfunc.js new file mode 100644 index 00000000..2c1c62b0 --- /dev/null +++ b/cloudcmd1/lib/cloudfunc.js @@ -0,0 +1,513 @@ +"use strict"; +/* Модуль, содержащий функции, которые будут работать + * и на клиенте и на сервере + * + * Правила названий: + * varName - имя функции + * lVarName - имя локальной переменной + * pVarName - имя параметра + * fVarName - имя функции созданной внутри функции + * VARNAME - имя константы + * + * Типы переменных: + * varNameS - строка + * varNameN - число + * varNameO - обьект + * varNameM - массив + */ + +var CloudFunc={ + /* Путь с которым мы сейчас работаем */ + Path :'', + /* КОНСТАНТЫ (общие для клиента и сервера)*/ + /* название программы */ + NAME :'Cloud Commander', + /* если в ссылке будет эта строка - + * в браузере js отключен + */ + NOJS : '/no-js', + FS : '/c/f/s', + /* название css-класа кнопки обновления файловой структуры*/ + REFRESHICON : 'refresh-icon', + /* id панелей с файлами */ + LEFTPANEL : 'left', + RIGHTPANEL : 'right' + /* name of direcotory with libs */ +}; + +/* + * Функция убирает последний слеш, + * если он - последний символ строки + */ +CloudFunc.removeLastSlash = function(pPath){ + if(typeof pPath==='string') + return (pPath.lastIndexOf('/')===pPath.length-1)? + pPath.substr(pPath, pPath.length-1):pPath; + else return pPath; +}; +/* + * Функция меняет код символа пробела на пробел + * в переданной строке + * @pPath - строка + */ +CloudFunc.replaceSpaces = function(pPath){ + if(pPath.indexOf('%20')>0){ + do{ + pPath=pPath.replace('%20',' '); + }while(pPath.indexOf('%20')>0); + } + return pPath; +}; + +/* Функция возвращает заголовок веб страницы */ +CloudFunc.setTitle = function(){ + + return CloudFunc.Path===''?CloudFunc.NAME: + CloudFunc.Path + + ' - ' + + CloudFunc.NAME; +}; +/* Функция переводит права из цыфрового вида в символьный + * @pPerm_s - строка с правами доступа + * к файлу в 8-миричной системе + */ +CloudFunc.convertPermissionsToSymbolic= function(pPerm_s){ + /* + S_IRUSR 0000400 protection: readable by owner + S_IWUSR 0000200 writable by owner + S_IXUSR 0000100 executable by owner + S_IRGRP 0000040 readable by group + S_IWGRP 0000020 writable by group + S_IXGRP 0000010 executable by group + S_IROTH 0000004 readable by all + S_IWOTH 0000002 writable by all + S_IXOTH 0000001 executable by all + */ + if(pPerm_s===undefined) return; + + /* тип файла */ + var lType=pPerm_s.charAt(0); + + switch (lType-0) { + case 1: /* обычный файл */ + lType='-'; + break; + case 2: /* байт-ориентированное (символьное) устройство*/ + lType='c'; + break; + case 4: /* каталог */ + lType='d'; + break; + default: + lType='-'; + } + + /* оставляем последние 3 символа*/ + pPerm_s=pPerm_s.length>5?pPerm_s.substr(3):pPerm_s.substr(2); + + /* Рекомендации гугла советуют вместо string[3] + * использовать string.charAt(3) + */ +/* + http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml?showone=Standards_features#Standards_features + + Always preferred over non-standards featuresFor + maximum portability and compatibility, always + prefer standards features over non-standards + features (e.g., string.charAt(3) over string[3] + and element access with DOM functions instead + of using an application-specific shorthand). +*/ + /* Переводим в двоичную систему */ + var lOwner=(pPerm_s[0]-0).toString(2); + var lGroup=(pPerm_s[1]-0).toString(2); + var lAll =(pPerm_s[2]-0).toString(2); + /* + console.log(lOwner+' '+lGroup+' '+lAll); + */ + /* переводим в символьную систему*/ + var lPermissions=//lType+' '+ + (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':'-')+ + ' ' + + (lAll[0]-0>0?'r':'-')+ + (lAll[1]-0>0?'w':'-')+ + (lAll[2]-0>0?'x':'-'); + /* + console.log(lPermissions); + */ + return lPermissions; +}; + +/* Функция конвертирует права доступа к файлам из символьного вида + * в цыфровой + */ +CloudFunc.convertPermissionsToNumberic= function(pPerm_s){ + /* если передана правильная строка, конвертированная + * функциец convertPermissionsToSymbolic + */ + if(!pPerm_s || pPerm_s.length!==11)return pPerm_s; + + var lOwner= (pPerm_s[0]==='r'?4:0) + + (pPerm_s[1]==='w'?2:0) + + (pPerm_s[2]==='x'?1:0); + var lGroup= (pPerm_s[4]==='r'?4:0) + + (pPerm_s[5]==='w'?2:0) + + (pPerm_s[6]==='x'?1:0); + var lAll = (pPerm_s[8]==='r'?4:0) + + (pPerm_s[9]==='w'?2:0) + + (pPerm_s[10]==='x'?1:0); + /* добавляем 2 цыфры до 5 */ + return '00'+lOwner+lGroup+lAll; +}; +/* Функция получает короткие размеры + * конвертируя байт в килобайты, мегабойты, + * гигайбайты и терабайты + * @pSize - размер в байтах + */ +CloudFunc.getShortedSize=function(pSize){ + /* Константі размеров, что используются + * внутри функции + */ + var l1BMAX=1024; + var l1KBMAX=1048576; + var l1MBMAX=1073741824; + var l1GBMAX=1099511627776; + var l1TBMAX=1125899906842624; + + var lShorted; + + if(pSize2){ + if(folders[0].lastIndexOf('/')===folders[0].length) + LPrevDir=folders[1]; + else LPrevDir=folders[2]; + }else LPrevDir='/'; + */ + /* ################################### */ + + /* Формируем ссылки на каждый каталог в пути */ + var lHref=''; + var lHrefEnd=''; + + var lHtmlPath; + /* путь в ссылке, который говорит + * что js отключен + */ + var lNoJS_s=CloudFunc.NOJS; + var lFS_s=CloudFunc.FS; + /* корневой каталог */ + lHtmlPath=lHref+lFS_s+lNoJS_s+lTitle+'"/"'+_l+'/'+lHrefEnd; + for(i=folders.length-1;i>0;i--) + { + var lUrl=folders[i]; + var lShortName=lUrl.replace(lUrl.substr(lUrl,lUrl.lastIndexOf('/')+1),''); + if(i!==1) + { + lHtmlPath+=lHref+lFS_s+lNoJS_s+lUrl+lTitle+lUrl+_l+lShortName+lHrefEnd+'/'; + } + else + lHtmlPath+=lShortName+'/'; + } + /* *** */ + return lHtmlPath; +}; + +/* + * Функция ищет в имени файла расширение + * и если находит возвращает true + * @pName - получает имя файла + * @pExt - расширение + */ +CloudFunc.checkExtension=function(pName,pExt) +{ + /* если длина имени больше + * длинны расширения - + * имеет смысл продолжать + */ + if(pName.length>pExt.length){ + var lLength=pName.length; /* длина имени*/ + var lExtNum=pName.lastIndexOf(pExt);/* последнее вхождение расширения*/ + var lExtSub=lLength-lExtNum; /* длина расширения*/ + /* если pExt - расширение pName */ + if(lExtSub===pExt.length) + return true; + else + return false; + } + else return false; +}; + +/* + * Функция формирует заголовки столбиков + * @pFileTableTitles - массив названий столбиков + */ +CloudFunc._getFileTableHeader=function(pFileTableTitles) +{ + var lHeader='
  • '; + lHeader+=''; + for(var i=0;i'+ + lStr+ + ''; + } + lHeader+='
  • '; + + return lHeader; +}; + +/* + * Функция строит таблицу файлв из JSON-информации о файлах + * @pJSON - информация о файлах + * @pKeyBinded - если клавиши назначены, выделяем верхний файл + * [{path:'путь',size:'dir'}, + * {name:'имя',size:'размер',mode:'права доступа'}] + */ +CloudFunc.buildFromJSON=function(pJSON,pKeyBinded) +{ + var files; + /* + * если пропарсить стандартными + * функциями нельзя - + * пробуем eval, + */ + /* + * Если мы на клиенте и нет JSON - + * через eval парсим. + * Если-же мы на сервере, + * или на клиенте всё есть + * парсим стандарным методом + */ + + /* По скольку мы прописали заголовок application/json + * нет необходимости его конвертировать, + * но она есть, если мы вытягиваем данные из + * localStorage + */ + /* + if(typeof pJSON==='string'){ + if(window && !window.JSON){ + try{ + files=eval('('+pJSON+')'); + }catch(err){ + console.log(err); + } + } + else files=JSON.parse(pJSON); + }else + */ + files=pJSON; + /* сохраняем путь каталога в котором мы сейчас находимся*/ + var lPath=files[0].path; + + /* сохраняем путь */ + CloudFunc.Path=lPath; + + /* + * Строим путь каталога в котором мы находимся + * со всеми подкаталогами + */ + var lHtmlPath=CloudFunc._getDirPath(lPath); + + /* Убираем последний слэш + * с пути для кнопки обновить страницу + * если он есть + */ + var lRefreshPath=CloudFunc.removeLastSlash(lPath); + + /* путь в ссылке, который говорит + * что js отключен + */ + var lNoJS_s=CloudFunc.NOJS; + var lFS_s=CloudFunc.FS; + + var lFileTable='
  • '+ + ''+ + ''+ + '' + + ''+ + ''+ + ''+lHtmlPath+''+ + '
  • '; + + var fileTableTitles=['name','size','owner','mode']; + lFileTable+=CloudFunc._getFileTableHeader(fileTableTitles); + /* Если мы не в корне */ + if(lPath!=='/'){ + /* ссылка на верхний каталог*/ + var lDotDot; + /* убираем последний слеш и каталог в котором мы сейчас находимся*/ + lDotDot=lPath.substr(lPath,lPath.lastIndexOf('/')); + lDotDot=lDotDot.substr(lDotDot,lDotDot.lastIndexOf('/')); + /* Если предыдущий каталог корневой */ + if(lDotDot==='')lDotDot='/'; + + /* Сохраняем путь к каталогу верхнего уровня*/ + lFileTable += '
  • '+ + '' + + '' + + '' + + ''+".." + + '' + + '<dir>'+ + '.' + + '' + + '
  • '; + } + var lLength=files.length; + + for(var i=1;i'; + lFileTable +=''; + lFileTable +='' + + '16? + ' title="'+files[i].name+'">' + + files[i].name.substr( + files[i].name,16)+ + '..':'>'+files[i].name) + + "" + + ''; + /* если папка - не выводим размер */ + lFileTable +='' + + (files[i].size==='dir'? + '<dir>': + /* если это файл - получаем + * короткий размер + */ + CloudFunc.getShortedSize( + files[i].size)); + lFileTable +='' + + '' + + (!files[i].uid?'root':files[i].uid) + + '' + + '' + + /* конвертируем названия разрешений + * из числового формата в буквенный + * при этом корневой каталог не трогаем + * по скольку в нём и так всё уже + * установлено еще на сервере + */ + (//lPath==='/'?files[i].mode: + CloudFunc.convertPermissionsToSymbolic(files[i].mode)) + + ''; + lFileTable +=''; + } + + /* если клавиши назначены и + * мы в корневом каталоге и + * верхний файл еще не выделен - + * выделяем верхний файл + */ + if(pKeyBinded && lPath==='/'&& + lFileTable.indexOf('
  • ')<0){ + lFileTable=lFileTable.replace('
  • ','
  • '); + } + + + + return lFileTable; +}; + +/* + * Если мы на стороне сервера - + * прописываем экспортируемые функции + */ +try{ + if(exports){ + /* экспортируемые функции */ + exports.checkExtension = CloudFunc.checkExtension; + exports.buildFromJSON = CloudFunc.buildFromJSON; + exports.replaceSpaces = CloudFunc.replaceSpaces; + exports.setTitle = CloudFunc.setTitle; + exports.convertPermissions = CloudFunc.convertPermissions; + exports.getUserUIDsAndNames = CloudFunc.getUserUIDsAndNames; + + /* константы*/ + exports.Name = CloudFunc.NAME; + exports.NOJS = CloudFunc.NOJS; + exports.FS =CloudFunc.FS; + + console.log('cloudfunc.js loaded...'); + } +}catch(err){ + /* если мы на клиенте */ +} \ No newline at end of file diff --git a/cloudcmd1/lib/server/minify.js b/cloudcmd1/lib/server/minify.js new file mode 100644 index 00000000..556fd119 --- /dev/null +++ b/cloudcmd1/lib/server/minify.js @@ -0,0 +1,274 @@ +/* Модуль сжатия js-скриптов и css*/ + +/* функция сжимает js-скрипты + * и сохраняет их с именем .min.js + */ + +var fs = require('fs'); + +exports.jsScripts=function jsScripts(){ + 'use strict'; + + /* подключаем модуль uglify-js + * если его нет - дальнейшая + * работа функции не имеет смысла + */ + try{ + var jsp = require("uglify-js").parser; + var pro = require("uglify-js").uglify; + }catch(error){ + console.log('can\'n load uglify-js\n' + + 'to use js-minification you need to install uglify-js\n' + + 'npm install uglify-js\n' + + 'https://github.com/mishoo/UglifyJS'); + return false; + } + /* Константы */ + var CLIENT_JS='client.js'; + var CLOUDFUNC_JS='lib/cloudfunc.js'; + var CLIENT_KEYBINDING_JS='lib/client/keyBinding.js'; + + var dataReaded_f=function(pFileName, pData){ + console.log('file ' + pFileName + ' readed'); + + /*********************************/ + /* сжимаем код через uglify-js */ + var uglify_js=function(pDdata){ + var orig_code = pDdata.toString(); + var ast = jsp.parse(orig_code); // parse code and get the initial AST + ast = pro.ast_mangle(ast); // get a new AST with mangled names + ast = pro.ast_squeeze(ast); // get an AST with compression optimizations + var result_code = pro.gen_code(ast); // compressed code here + return result_code; + }; + /*********************************/ + var final_code=uglify_js(pData); + + var minFileName=pFileName.replace('.js','.min.js'); + /* если мы сжимаем client.js - + * меняем строку cloudfunc.js на + * cloudfunc.min.js и выводим сообщение + * + * меняем строку keyBinding.js на + * keyBinding.min.js + * если другой файл - ничего не деалем + */ + if(pFileName===CLIENT_JS) + console.log('file name of ' + + CLOUDFUNC_JS + + ' in ' + + CLIENT_JS + + ' changed. size:', + (final_code=final_code + .replace('cloudfunc.js','cloudfunc.min.js') + .replace('keyBinding.js','keyBinding.min.js')).length); + + /* записываем сжатый js-скрипт*/ + fs.writeFile(minFileName, final_code, fileWrited(minFileName)); + }; + console.log('reading file ' + CLIENT_JS+'...'); + fs.readFile(CLIENT_JS,fileReaded(CLIENT_JS,dataReaded_f)); + + console.log('reading file ' + CLOUDFUNC_JS+'...'); + fs.readFile(CLOUDFUNC_JS,fileReaded(CLOUDFUNC_JS,dataReaded_f)); + + console.log('reading file ' + CLIENT_KEYBINDING_JS+'...'); + fs.readFile(CLIENT_KEYBINDING_JS, fileReaded(CLIENT_KEYBINDING_JS,dataReaded_f)); + + + return true; +}; + +/* функция сжимает css-стили + * и сохраняет их с именем .min.css + * @pImgConvertToBase64_b - булевый признак, + * который отвечает за то, что быконвертировать + * картинки в base64 и поместить в выходной css файл + */ +exports.cssStyles=function cssStyles(pImgConvertToBase64_b){ + 'use strict'; + + /* connecting cleanCSS, + * if we can't find it - + * return false + */ + var cleanCSS; + try{ + cleanCSS = require('clean-css'); + }catch(error){ + console.log('can\'n load clean-css \n' + + 'to use css-minification you need to install clean-css \n' + + 'npm install clean-css\n' + + 'https://github.com/GoalSmashers/clean-css'); + return false; + } + + /* Константы */ + var STYLE_CSS='style.css'; + var RESET_CSS='reset.css'; + + var lAllStyle=''; + var lResetCssDone=false; + var lStyleCssDone=false; + var dataReaded_f=function(pFileName, pData){ + console.log('file ' + pFileName + ' readed'); + /*********************************/ + /* сжимаем код через clean-css */ + var clean_css=function(pData){ + /* Сохраняем весь стиль в одну переменную*/ + return cleanCSS.process(pData); + }; + /*********************************/ + var final_code=clean_css(pData); + + lAllStyle+=final_code; + + var minFileName=pFileName.replace('.css','.min.css'); + + if(pFileName===STYLE_CSS)lStyleCssDone=true; + if(pFileName===RESET_CSS)lResetCssDone=true; + /* if all files writed we + * save all minimized css + * to one file all.min.css + */ + /* если включена конвертация картинок в base64 + * вызываем её + */ + if(lStyleCssDone && lResetCssDone) + if(pImgConvertToBase64_b) + base64_images(lAllStyle); + else + fs.writeFile('all.min.css', lAllStyle, fileWrited('all.min.css')); + + /* записываем сжатый css файл*/ + else fs.writeFile(minFileName, final_code, fileWrited(minFileName)); + }; + + console.log('reading file ' + STYLE_CSS+'...'); + fs.readFile(STYLE_CSS,fileReaded(STYLE_CSS,dataReaded_f)); + + console.log('reading file ' + RESET_CSS+'...'); + fs.readFile(RESET_CSS,fileReaded(RESET_CSS,dataReaded_f)); + + return true; +}; + +/* функция сжимает css-стили + * и сохраняет их с именем .min.css + */ +exports.html=function(){ + 'use strict'; + + /* connecting cleanCSS, + * if we can't find it - + * return false + */ + var htmlMinifier; + try{ + htmlMinifier = require('html-minifier'); + }catch(error){ + console.log('can\'n load html-minifier \n' + + 'to use html-minification you need to install html-minifier\n' + + 'npm install html-minifier\n' + + 'https://github.com/kangax/html-minifier'); + return false; + } + + /* Константы */ + var INDEX_HTML='index.html'; + + var dataReaded_f=function(pFileName, pData){ + console.log('file ' + pFileName + ' readed'); + /*********************************/ + /* сжимаем код через clean-css */ + var html_minify=function(pData){ + /* Сохраняем весь стиль в одну переменную*/ + + var lOptions={ + removeComments: true, + removeCommentsFromCDATA: true, + removeCDATASectionsFromCDATA: true, + collapseWhitespace: true, + collapseBooleanAttributes: true, + removeAttributeQuotes: true, + removeRedundantAttributes: true, + useShortDoctype: true, + removeEmptyAttributes: true, + /* оставляем, поскольку у нас + * в элемент fm генерируеться + * таблица файлов + */ + removeEmptyElements: false, + removeOptionalTags: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true + }; + + + return htmlMinifier.minify(pData,lOptions); + }; + /*********************************/ + var final_code=html_minify(pData); + + var minFileName=pFileName.replace('.html','.min.html'); + + /* записываем сжатый html файл*/ + fs.writeFile(minFileName, final_code, fileWrited(minFileName)); + }; + + console.log('reading file ' + INDEX_HTML+'...'); + fs.readFile(INDEX_HTML,fileReaded(INDEX_HTML,dataReaded_f)); + + return true; +}; + +/* функция переводит картинки в base64 и записывает в css-файл*/ +function base64_images(pFileContent_s){ + 'use strict'; + var b64img; + try{ + b64img = require('css-b64-images'); + }catch(error){ + console.log('can\'n load clean-css \n' + + 'to use images to base64 convertation you need to install css-base64-images \n' + + 'npm install -g css-b64-images\n' + + 'https://github.com/Filirom1/css-base64-images'); + return false; + } + b64img.fromString(pFileContent_s, '.','', function(err, css){ + fs.writeFile('all.min.css', css, fileWrited('all.min.css')); + }); +} + +/* Функция создаёт асинхроную версию + * для чтения файла + * @pFileName - имя считываемого файла + */ +function fileReaded(pFileName,pCompressFunc){ + "use strict"; + return function(pError,pData){ + /* функция в которую мы попадаем, + * если данные считались + * + * если ошибка - показываем её + * иначе если переданная функция - + * функция запускаем её + */ + if(!pError) + if (pCompressFunc && typeof pCompressFunc==="function") + pCompressFunc(pFileName,pData.toString()); + else console.log(pError); + }; +} + +/* + * Функция вызываеться после записи файла + * и выводит ошибку или сообщает, + * что файл успешно записан + */ +function fileWrited(pFileName){ + "use strict"; + return function(error){ + console.log(error?error:('file '+pFileName+' writed...')); + }; +} \ No newline at end of file diff --git a/cloudcmd1/node_modules/.bin/jitsu b/cloudcmd1/node_modules/.bin/jitsu new file mode 120000 index 00000000..ab860a6f --- /dev/null +++ b/cloudcmd1/node_modules/.bin/jitsu @@ -0,0 +1 @@ +../jitsu/bin/jitsu \ No newline at end of file diff --git a/cloudcmd1/node_modules/.bin/nodester b/cloudcmd1/node_modules/.bin/nodester new file mode 120000 index 00000000..aa4a5eb1 --- /dev/null +++ b/cloudcmd1/node_modules/.bin/nodester @@ -0,0 +1 @@ +../nodester-cli/bin/nodester.js \ No newline at end of file diff --git a/cloudcmd1/node_modules/jitsu b/cloudcmd1/node_modules/jitsu new file mode 120000 index 00000000..d5540629 --- /dev/null +++ b/cloudcmd1/node_modules/jitsu @@ -0,0 +1 @@ +../../local/lib/node_modules/jitsu \ No newline at end of file diff --git a/cloudcmd1/node_modules/nodester-cli b/cloudcmd1/node_modules/nodester-cli new file mode 120000 index 00000000..da254801 --- /dev/null +++ b/cloudcmd1/node_modules/nodester-cli @@ -0,0 +1 @@ +../../local/lib/node_modules/nodester-cli \ No newline at end of file diff --git a/cloudcmd1/package.json b/cloudcmd1/package.json new file mode 100644 index 00000000..4bcf5c47 --- /dev/null +++ b/cloudcmd1/package.json @@ -0,0 +1,16 @@ +{ + "name": "cloudcmd1", + "version": "0.0.1-10", + "scripts": { + "start": "server.js" + }, + "engines": { + "node": "0.6.x" + }, + "subdomain": "cloudcmd1", + "dependencies": { + "uglify-js": "*", + "html-minifier": "*", + "clean-css": "*" + } +} diff --git a/cloudcmd1/package2.json b/cloudcmd1/package2.json new file mode 100644 index 00000000..f2d59387 --- /dev/null +++ b/cloudcmd1/package2.json @@ -0,0 +1,12 @@ +{ + "name": "cloudcmd", + "version": "0.0.1-3", + "scripts": { + + "start": "server.js" + }, + "engines": { + "node": "0.6.x" + }, + "subdomain": "cloudcmd" +} diff --git a/cloudcmd1/reset.css b/cloudcmd1/reset.css new file mode 100644 index 00000000..a24c865b --- /dev/null +++ b/cloudcmd1/reset.css @@ -0,0 +1,100 @@ +/* ============================================================================= + HTML5 display definitions + ========================================================================== */ + +/* +article, aside, details, figcaption, figure, footer, header, hgroup, nav, section { display: block; } +audio, canvas, video { display: inline-block; *display: inline; *zoom: 1; } +audio:not([controls]) { display: none; } +[hidden] { display: none; } +*/ + +/* ============================================================================= + Base + ========================================================================== */ + +/* + * 1. Correct text resizing oddly in IE6/7 when body font-size is set using em units + * 2. Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g + */ + +html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: sans-serif; color: #222;} +body { margin: 0; font-size: 1em; line-height: 1.4; } + +/* + * Remove text-shadow in selection highlight: h5bp.com/i + * These selection declarations have to be separate + * Also: hot pink! (or customize the background color to match your design) + */ +/* +::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; } + ::selection { background: #fe57a1; color: #fff; text-shadow: none; } +*/ +::selection { text-shadow: none; opacity: 0;} + + +/* ============================================================================= + Links + ========================================================================== */ + +a {text-decoration:none; color: #00e; } +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; } + +ul{ margin: 1em 0; padding: 0 0 0 40px; } + +/* + * 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) + */ + + +/* ============================================================================= + Chrome Frame Prompt + ========================================================================== */ + +.chromeframe { margin: 0.2em 0; background: #ccc; color: black; padding: 0.2em 0; } + + +/* ==|== non-semantic helper classes ======================================== + Please define your styles before this section. + ========================================================================== */ + +/* For image replacement */ +.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; *line-height: 0; } +.ir br { display: none; } + +/* Hide from both screenreaders and browsers: h5bp.com/u */ +.hidden { display: none !important; visibility: hidden; } + +/* Hide only visually, but have it available for screenreaders: h5bp.com/v */ +.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } + +/* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: h5bp.com/p */ +.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } + +/* Hide visually and from screenreaders, but maintain layout */ +.invisible { visibility: hidden; } + +/* Contain floats: h5bp.com/q */ +.clearfix:before, .clearfix:after { content: ""; display: table; } +.clearfix:after { clear: both; } +.clearfix { *zoom: 1; } + +/* ==|== 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; } +} \ No newline at end of file diff --git a/cloudcmd1/server.js b/cloudcmd1/server.js new file mode 100644 index 00000000..2839b1ef --- /dev/null +++ b/cloudcmd1/server.js @@ -0,0 +1,635 @@ +"use strict"; + +/* Обьект содержащий все функции и переменные + * серверной части Cloud Commander'а + */ +var CloudServer={ + /* функция, которая генерирует заголовки + * файлов, отправляемые сервером клиенту + */ + generateHeaders :function(){}, + /* функция высылает + * данные клиенту + */ + sendResponse :function(){}, + /* Структура содержащая функции, + * и переменные, в которых + * говориться о поддерживаемых + * браузером технологиях + */ + BrowserSuport :{}, + /* Обьект для работы с кэшем */ + Cashe :{}, + /* Обьект через который + * выполняеться сжатие + * скриптов и стилей + */ + Minify :{}, + /* Асоциативный масив обьектов для + * работы с ответами сервера + * высылаемыми на запрос о файле и + * хранащий информацию в виде + * Responces[name]=responce; + */ + Responses :{}, + + /* ПЕРЕМЕННЫЕ */ + /* Поддержка браузером JS*/ + NoJS :true, + /* Поддержка gzip-сжатия + * браузером + */ + Gzip :undefined, + + /* КОНСТАНТЫ */ + /* index.html */ + INDEX :'index.html', + /* name of direcotory with libs */ + LIBDIR :'./lib', + LIBDIRSERVER :'./lib/server' +}; + +/* + * Обьект для работы с кэшем + * аналог клиентского обьекта + * с тем отличием, что в нём + * будут храниться серверные + * данные, такие как файлы + * отдаваемые клиенту + * (файлы проэкта по большому + * счёту, для ускорения + * первичной загрузки) + */ +CloudServer.Cache={ + _allowed :true, /* приватный переключатель возможности работы с кэшем */ + /* данные в которых храняться файлы + * в формате <поле> : <значение> + * _data[name]=pData; + * одному имени соответствуют + * одни данные + */ + _data :{}, + + /* функция говорит можно ли работать с кэшем */ + isAllowed :(function(){ + return CloudServer.Cache._allowed; + }), + /* функция устанавливает возможность работать с кэшем */ + setAllowed :(function(pAllowed){ + CloudServer.Cache._allowed=pAllowed; + }), + /* Если доступен кэш + * сохраняем в него данные + */ + set :(function(pName, pData){ + if(CloudServer.Cache._allowed && pName && pData){ + CloudServer.Cache._data[pName]=pData; + } + }), + /* Если доступен Cache принимаем из него данные*/ + get :(function(pName){ + if(CloudServer.Cache._allowed && pName){ + return CloudServer.Cache._data[pName]; + } + else return null; + }), + + /* Функция очищает кэш*/ + clear :(function(){ + if(CloudServer.Cache._allowed){ + CloudServer.Cache._data={}; + } + }) +}; + +/* Обьект для сжатия скриптов и стилей + * по умолчанию - сжимаються + */ +CloudServer.Minify={ + /* приватный переключатель минимизации */ + _allowed :{css:true,js:true,html:true, img: true}, + + /* функция разрешает или + * запрещает минимизировать + * css/js/html + * @pAllowed: - структура, в которой + * передаються параметры + * минификации, вида + * {js:true,css:true,html:false; img:true} + * img отвечает за перевод картинок в base64 + * и сохранение их в css-файл + */ + setAllowed :(function(pAllowed){ + if(pAllowed){ + this._allowed.css=pAllowed.css; + this._allowed.js=pAllowed.js; + this._allowed.html=pAllowed.html; + this._allowed.img=pAllowed.img; + } + }), + + /* + * Функция минимизирует css/js/html + * если установлены параметры минимизации + */ + doit :(function(){ + if(this._allowed.css || + this._allowed.js || + this._allowed.html){ + var lMinify = require(CloudServer.LIBDIRSERVER+'/minify'); + + this.done.js=this._allowed.js?lMinify.jsScripts():false; + this.done.html=this._allowed.html?lMinify.html():false; + this.done.css=this._allowed.css?lMinify.cssStyles(this._allowed.img):false; + } + }), + /* свойство показывающее случилась ли ошибка*/ + done:{js: false,css: false, html:false} +}; + + +var LeftDir='/'; +var RightDir=LeftDir; +/* + var Path = require('path'); +*/ /* модуль для работы с путями*/ + +var Fs = require('fs'); /* модуль для работы с файловой системой*/ + +var Zlib; +/* node v0.4 not contains zlib + */ +try{ + Zlib = require('zlib'); /* модуль для сжатия данных gzip-ом*/ +}catch(error){ + Zlib=undefined; + console.log('to use gzip-commpression' + + 'you should install zlib module\n' + + 'npm install zlib'); +} +var CloudFunc = require(CloudServer.LIBDIR + + (CloudServer.Minify.done.js?/* если стоит минификация*/ + '/cloudfunc.min':/* добавляем сжатый - иначе обычный */ + '/cloudfunc')); /* модуль с функциями */ +/* конструктор*/ +CloudServer.init=(function(){ + /* Переменная в которой храниться кэш*/ + CloudServer.Cache.setAllowed(true); + /* Change default parameters of + * js/css/html minification + */ + CloudServer.Minify.setAllowed({ + js:true, + css:true, + html:true, + img:true + }); + /* Если нужно минимизируем скрипты */ + CloudServer.Minify.doit(); +}); + + +/* создаём сервер на порту 31337*/ +CloudServer.start=function() +{ + CloudServer.init(); + + var http = require('http'); + http.createServer(CloudServer._controller).listen(process.env.PORT || + process.env.VCAP_APP_PORT /* cloudfoundry */ || + 31337, + '0.0.0.0' || '127.0.0.1'); + console.log('Cloud Commander server running at http://127.0.0.1:'+ + (process.env.PORT===undefined?31337:process.env.PORT)); +}; + + +/* Функция создаёт заголовки файлов + * в зависимости от расширения файла + * перед отправкой их клиенту + * @pName - имя файла + * @pGzip - данные сжаты gzip'ом + */ +CloudServer.generateHeaders = function(pName, pGzip){ + var lType=''; + /* высылаем заголовок в зависимости от типа файла */ + /* если расширение у файла css - + * загружаем стили + */ + if(CloudFunc.checkExtension(pName,'css')) + lType='text/css'; + /* загружаем js */ + else if(CloudFunc.checkExtension(pName,'js')) + lType='text/javascript'; + /* загружаем картинки*/ + else if(CloudFunc.checkExtension(pName,'png')) + lType='image/png'; + /* загружаем json*/ + else if(CloudFunc.checkExtension(pName,'json')) + lType='application/json'; + else if(CloudFunc.checkExtension(pName,'html')) + lType='text/html'; + else if(CloudFunc.checkExtension(pName,'appcache')) + lType='text/cache-manifest'; + /* если это неизвестный тип файла - + * высылаем его просто как текст + */ + else lType='text/plain'; + + return { + /* if type of file any, but img - + * then we shoud specify charset + */ + 'Content-Type': lType + (lType.indexOf('img')<0?'; charset=UTF-8':''), + 'cache-control': 'max-age='+(31337*21), + 'last-modified': new Date().toString(), + 'content-encoding': pGzip?'gzip':'', + /* https://developers.google.com/speed/docs/best-practices/caching?hl=ru#LeverageProxyCaching */ + 'Vary': 'Accept-Encoding' + }; +}; + +/* + * Главная функция, через которую проихсодит + * взаимодействие, обмен данными с клиентом + * @req - запрос клиента (Request) + * @res - ответ сервера (Response) + */ +CloudServer._controller=function(pReq, pRes) +{ + /* Читаем содержимое папки, + переданное в url + */ + var url = require("url"); + var pathname = url.parse(pReq.url).pathname; + console.log('pathname: '+pathname); + + /* получаем поддерживаемые браузером кодировки*/ + var lAcceptEncoding = pReq.headers['accept-encoding']; + /* запоминаем поддерживает ли браузер + * gzip-сжатие при каждом обращении к серверу + * и доступен ли нам модуль zlib + */ + if (lAcceptEncoding && + lAcceptEncoding.match(/\bgzip\b/) && + Zlib){ + CloudServer.Gzip=true; + }else + CloudServer.Gzip=false; + /* путь в ссылке, который говорит + * что js отключен + */ + var lNoJS_s=CloudFunc.NOJS; + var lFS_s=CloudFunc.FS; + + if(pathname!=='/favicon.ico') + { + console.log("request for " + pathname + " received..."); + var lName; + + /* если в пути нет информации ни о ФС, + * ни об отсутствии js, + * ни о том, что это корневой + * каталог - загружаем файлы проэкта + */ + if(pathname.indexOf(lFS_s)<0 && + pathname.indexOf(lNoJS_s)<0 && + pathname!=='/'){ + /* если имена файлов проекта - загружаем их*/ + /* убираем слеш и читаем файл с текущец директории*/ + //lName=Path.basename(pathname); + + /* добавляем текующий каталог к пути */ + lName='.'+pathname; + console.log('reading '+lName); + /* сохраняем указатель на responce и имя */ + CloudServer.Responses[lName]=pRes; + + /* Берём значение из кэша + * сжатый файл - если gzip-поддерживаеться браузером + * не сжатый - в обратном случае + */ + var lFileData=CloudServer.Cache.get(CloudServer.Gzip?(lName+'_gzip'):lName); + + var lReadFileFunc_f=CloudServer.getReadFileFunc(lName); + /* если там что-то есть передаём данные в функцию + * readFile + */ + if(lFileData){ + console.log('readed from cache'); + /* передаём данные с кэша, + * если gzip включен - сжатые + * в обратном случае - несжатые + */ + lReadFileFunc_f(undefined,lFileData,true); + } + else Fs.readFile(lName,lReadFileFunc_f); + + }else{/* если мы имеем дело с файловой системой*/ + /* если путь не начинаеться с no-js - значит + * js включен + */ + /* убираем пометку cloud, без которой c9.io + * не работает поскольку путь из двух слешей + * (/fs/no-js/) - очень короткий, нужно + * длиннее + */ + + if(pathname.indexOf(lNoJS_s)!==lFS_s.length && pathname!=='/'){ + CloudServer.NoJS=false; + }else pathname=pathname.replace(lNoJS_s,''); + + /* убираем индекс файловой системы */ + if(pathname.indexOf(lFS_s)===0){ + pathname=pathname.replace(lFS_s,''); + /* если посетитель только зашел на сайт + * no-js будет пустым, как и fs + */ + /* если в пути нету fs - посетитель только зашел на сайт + * загружаем его полностью. + */ + }else CloudServer.NoJS=true; + /* если в итоге путь пустой + * делаем его корневым + */ + if(pathname==='')pathname='/'; + + RightDir=pathname; + LeftDir=pathname; + + /* если встретиться пробел - + * меня код символа пробела на пробел + */ + + LeftDir=CloudFunc.replaceSpaces(LeftDir); + RightDir=CloudFunc.replaceSpaces(RightDir); + + /* Проверяем с папкой ли мы имеем дело */ + + /* читаем сновные данные о файле */ + var lStat; + try{ + lStat=Fs.statSync(LeftDir); + }catch(error){ + console.log(error); + CloudServer.Responses[LeftDir]=pRes; + CloudServer.sendResponse('OK',error.toString(),LeftDir); + } + /* если это каталог - + * читаем его содержимое + */ + try{ + /* если установлено сжатие + * меняем название html-файла и + * загружаем сжатый html-файл в дальнейшем + */ + CloudServer.INDEX=(CloudServer.Minify.done.html?'index.min.html':CloudServer.INDEX); + /* + * сохраним указатель на response + */ + CloudServer.Responses[CloudServer.INDEX]=pRes; + if(lStat.isDirectory()) + Fs.readdir(LeftDir,CloudServer._readDir); + /* отдаём файл */ + else if(lStat.isFile()){ + CloudServer.Responses[LeftDir]=pRes; + Fs.readFile(LeftDir,CloudServer.getReadFileFunc(LeftDir)); + console.log('reading file: '+LeftDir); + } + }catch(error){console.log(error);} + } + } +}; + +/* Функция читает ссылку или выводит информацию об ошибке*/ +CloudServer._readDir=function (pError, pFiles) +{ + if(!pError) + { + /* данные о файлах в формате JSON*/ + var lJSON=[]; + var lJSONFile={}; + /* Если мы не в корне добавляем слеш к будующим ссылкам */ + if(LeftDir!=='/') + { + RightDir+='/'; + LeftDir+='/'; + } + + pFiles=pFiles.sort(); + + lJSON[0]={path:LeftDir,size:'dir'}; + var fReturnFalse=function returnFalse(){return false;}; + for(var i=0;i','') + .replace('style.css','all.min.css') + :lIndex; + + lIndex = CloudServer.Minify.done.js?lIndex.replace('client.js','client.min.js'):lIndex; + + lIndex=lIndex.toString().replace('
    ','
    '+lList); + /* меняем title */ + lIndex=lIndex.replace('Cloud Commander', + ''+CloudFunc.setTitle()+''); + /* отображаем панель быстрых клавишь */ + lList=lIndex; + /* если браузер поддерживает gzip-сжатие*/ + lHeader=CloudServer.generateHeaders('text/html',CloudServer.Gzip); + }catch(error){console.log(error);} + }else{ + /* в обычном режиме(когда js включен + * высылаем json-структуру файлов + * с соответствующими заголовками + */ + lList=JSON.stringify(lJSON); + lHeader=CloudServer.generateHeaders('application/json',CloudServer.Gzip); + } + /* если браузер поддерживает gzip-сжатие - сжимаем данные*/ + if(CloudServer.Gzip){ + Zlib.gzip(lList,CloudServer.getGzipDataFunc(lHeader,CloudServer.INDEX)); + } + /* если не поддерживаеться - отсылаем данные без сжатия*/ + else + CloudServer.sendResponse(lHeader,lList,CloudServer.INDEX); + } + else + { + console.log(pError); + CloudServer.sendResponse('OK',pError.toString()); + } +}; + +/* Функция генерирует функция считывания файла + * таким образом, что бы у нас было + * имя считываемого файла + * @pName - полное имя файла + */ +CloudServer.getReadFileFunc = function(pName){ +/* + * @pError - ошибка + * @pData - данные + * @pFromFile - прочитано с файла bool + */ + var lReadFile=function(pError,pData,pFromCache_b){ + if (!pError){ + console.log('file ' + pName + ' readed'); + + /* берём из кэша данные файла + * если их нет в кэше - + * сохраняем + */ + if(!pFromCache_b && CloudServer.Cache.isAllowed) + CloudServer.Cache.set(pName,pData); + /* если кэш есть + * сохраняем его в переменную + * которая до этого будет пустая + * по скольку мы будем вызывать этот метод + * сами, ведь файл уже вычитан + */ + + var lHeader=CloudServer.generateHeaders(pName,CloudServer.Gzip); + /* если браузер поддерживает gzip-сжатие - сжимаем данные*/ + if(CloudServer.Gzip &&!pFromCache_b){ + /* сжимаем содержимое */ + Zlib.gzip(pData,CloudServer.getGzipDataFunc(lHeader,pName)); + } + else{ + /* высылаем несжатые данные */ + CloudServer.sendResponse(lHeader,pData,pName); + } + } + else + { + console.log(pError.path); + if(pError.path!=='passwd.json') + { + console.log(pError); + CloudServer.sendResponse('OK',pError.toString()); + }else{ + CloudServer.sendResponse('OK','passwd.json'); + } + } + }; + return lReadFile; +}; + +/* Функция получает сжатые данные + * @pHeader - заголовок файла + */ +CloudServer.getGzipDataFunc=function(pHeader,pName){ + return function(error,pResult){ + if(!error){ + /* отправляем сжатые данные + * вместе с заголовком + */ + /* если установлена работа с кэшем + * сохраняем сжатые данные + */ + if(CloudServer.Cache.isAllowed){ + /* устанавливаем кєш */ + console.log(pName+' gziped'); + CloudServer.Cache.set(pName+'_gzip',pResult); + } + CloudServer.sendResponse(pHeader,pResult,pName); + } + else{ + console.log(error); + CloudServer.sendResponse(pHeader,error); + } + }; +}; +/* Функция высылает ответ серверу + * @pHead - заголовок + * @pData - данные + * @pName - имя отсылаемого файла + */ +CloudServer.sendResponse = function(pHead, pData,pName){ + /* если у нас есть указатель на responce + * для соответствующего файла - + * высылаем его + */ + var lResponse=CloudServer.Responses[pName]; + if(lResponse){ + lResponse.writeHead(200,pHead); + lResponse.end(pData); + console.log(pName+' sended'); + } +}; + +CloudServer.start(); \ No newline at end of file diff --git a/cloudcmd1/style.css b/cloudcmd1/style.css new file mode 100644 index 00000000..57136168 --- /dev/null +++ b/cloudcmd1/style.css @@ -0,0 +1,317 @@ +/* +@import url(//fonts.googleapis.com/css?family=Droid+Sans+Mono); +*/ + +/* Foundation Icons General Enclosed */ +@font-face { + font-family: 'FoundationIconsGeneralEnclosed'; + src: url('//dl.dropbox.com/u/78163899/mnemonia/fonts/foundation-icons-general-enclosed.woff') format('woff'); + font-weight: normal; + font-style: normal; + +} +/* символьный шрифт от гитхаба*/ +@font-face { + font-family: 'Octicons Regular'; + font-style: normal; + font-weight: normal; + src: local('Octicons Regular'), url('//dl.dropbox.com/u/78163899/mnemonia/fonts/octicons-regular-webfont.woff') format('woff'); +} + +@font-face { + font-family: 'Droid Sans Mono'; + font-style: normal; + font-weight: normal; + src: local('Droid Sans Mono'), local('DroidSansMono'), url('http://themes.googleusercontent.com/static/fonts/droidsansmono/v4/ns-m2xQYezAtqh7ai59hJUYuTAAIFFn5GTWtryCmBQ4.woff') format('woff'); +} + +body{ + font:16px "Droid Sans Mono"; +} + +.menu{ + font: 16px 'Octicons Regular'; + margin-bottom: 0; +} + +.path_icon{ + font-family:'FoundationIconsGeneralEnclosed'; + font-size:30px; + color: #46A4C3;/*#55BF3F; green*/ + text-shadow:black 0 2px 1px; + /* размер иконки и позиция на png-файле*/ + width: 15px; + height: 15px; + display: inline-block; + position: relative; + top: 3px; + left: -4px; +} +.path_icon:hover{ + /* + color:#45D827; + */ + cursor:pointer; +} +.path_icon:active{ + text-shadow:black 0 0 1px; + position: relative; + top: 4px; +} +.icon{ + font-family: 'Octicons Regular'; + font-size:16px; + width:16px; + height:16px; + display:inline-block; + margin-left:0.5%; +} +.error::before{ + content:'\f026'; + cursor:default; + color:rgb(222, 41, 41); + position: relative; + top: -3px; +} +.loading{ + background:url(/img/spinner.gif); + position:relative; + top:1px; +} +.error:hover{ + color:rgba(222, 41, 41, 0.81); +} +.refresh-icon{ + background:url(/img/panel_refresh.png) no-repeat; +} +.refresh-icon:active{ + /*background-position-y: -15px;*/ +background:url(/img/panel_refresh.png) 0 -15px no-repeat; +} +.clear-cache{ + background:url(/img/console_clear.png) -4px -4px no-repeat; + margin-right: 6px; + margin-left: 7px; +} +.clear-cache:active{ + /* + background-position-y: -25px; + */ + top:5px; +} + +.settings:before{ + content:'k'; +} +.links{ + color:red; + } + +.mini-icon { + float: left; + height: 16px; + left: -5px; + margin-left: 6px; + position: relative; + top: 2px; + width: 16px; + /* отступ перед картинкой + * для нормального отображения + * рамки + */ +} +/* freeupex */ +.directory{ + /*list-style-image*/ + background-image:url('/img/dir.png'); + background-repeat: no-repeat; + background-position: 0 0; +} +.text-file{ + /*list-style-image*/ + background-image:url('/img/txt.png'); + background-repeat: no-repeat; + background-position: 0 0; +} +#fm{ + height:90%; + overflow-y: scroll; +} +.fm_header{ + font-weight: bold; +} +#path{ + margin-left:1.5%; +} +#left{ + float:left; + width:90%; +} +/* фон файла, на котором курсор*/ +.current-file{ + border: 2px solid rgba(49, 123, 249, .40); +} +.selected-file{ + background-color: rgba(49, 123, 249, .40); + color:white; +} +#right{ + float:right; +} +.panel{ + display: table; + width:45%; +} +#keyspanel{ + text-align: center; +} +/* информация о файлах и папках*/ +.name{ + float: left; + width: 37%; +} +.size{ + float:left; + width:16%; + /* Ставим выравнивание, + * что бы размер был в + * одной ровной колонке + */ + text-align: right; + /* Ставим отступ, что бы + * size не налазил на uid + * (owner) + */ + margin-right: 27px; +} +.owner{ +} +.mode{ + float: right; + width: 25%; +} + +ul,li{list-style-type:none;} +button{ + width:10%; +} +a{ + text-decoration:none; +} +a:hover, a:active { cursor:pointer;outline: 0; color: #06e; } +a:focus { outline: thin dotted; } + +/* Если размер окна очень маленький + * располагаем имя и атрибуты файла + * друг-под-другом +*/ +/* responsive design */ +@media only screen and (max-width: 600px){ + #left{ + width:90% !important; + } + .panel >li{ + margin:10px; + } + /* если правая панель не помещаеться - прячем её */ + #right{ + display:none; + } + /* текущий файл под курсором */ +.current-file{ + background-color: rgba(49, 123, 249, .40); + color:white; + } + /* делаем иконки под курсом белыми*/ +.current-file > .mini-icon{ + color:white; + } +.current-file > .text-file::before{ + color:white; + } + +/* меняем иконки на шрифтовые*/ + .mini-icon { + font: 60px 'Octicons Regular'; + width: 40%; + height: 0; + margin-left: 0; + float: right; + position: relative; + top: -17px; + + color: rgba(246, 224, 124, 0.56); + } + .directory::before{ + content: '\f216'; + } + .text-file::before{ + color: rgba(26, 224, 124, 0.56); + content: '\f211'; + } + .text-file{ + background-image:none; + } + + + + /* убираем заголовок*/ + .fm_header{ + display:none; + } + .mode,.size,.owner{ + /* располагаем элементы + * один под другим + */ + display: table; + float: none; + width: 0; + + text-align: left; + } + /* выводим заголовки рядом с полями */ + .name::before{ + content: 'name:'; + font-weight: bold; + font-size: 13px; + } + .mode::before{ + content: 'mode:'; + font-weight: bold; + font-size: 13px; + } + .size::before{ + content: 'size:'; + font-weight: bold; + font-size: 13px; + } + .owner::before{ + content: 'owner:'; + font-weight: bold; + font-size: 13px; + } + + .name{ + float: none; + width:100%; + font-size: 18px; + } +} +@media only screen and (min-width: 601px) and (max-width: 767px){ + #left{ + width:90% !important; + } + #right{ + display:none; + } +} + +@media only screen and (min-width:767px) and (max-width: 1060px){ + #left{ + width:90% !important; + } + /* если правая панель не помещаеться - прячем её */ + #right{ + display:none; + } +} \ No newline at end of file diff --git a/cloudcmd1/test.sh b/cloudcmd1/test.sh new file mode 100644 index 00000000..6b68a51f --- /dev/null +++ b/cloudcmd1/test.sh @@ -0,0 +1,4 @@ +#!/bin/sh +npm i -g jshint +echo "jshint server.js client.js lib/cloudfunc.js" +jshint --config ./.jshintrc ./server.js ./client.js ./lib/cloudfunc.js \ No newline at end of file diff --git a/css/reset.css b/css/reset.css new file mode 100644 index 00000000..23504a59 --- /dev/null +++ b/css/reset.css @@ -0,0 +1,90 @@ + +/* ============================================================================= + Base + ========================================================================== */ + +/* + * 1. Correct text resizing oddly in IE6/7 when body font-size is set using em units + * 2. Prevent iOS text size adjust on device orientation change, without disabling user zoom: h5bp.com/g + */ + +html { font-size: 100%; -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; font-family: sans-serif; color: #222;} +body { margin: 0; font-size: 1em; line-height: 1.4; } + +/* + * Remove text-shadow in selection highlight: h5bp.com/i + * These selection declarations have to be separate + * Also: hot pink! (or customize the background color to match your design) + */ +/* +::-moz-selection { background: #fe57a1; color: #fff; text-shadow: none; } + ::selection { background: #fe57a1; color: #fff; text-shadow: none; } +*/ +::selection { text-shadow: none; opacity: 0;} + + +/* ============================================================================= + Links + ========================================================================== */ + +a {text-decoration:none; color: #00e; } +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; } + +ul{ margin: 1em 0; padding: 0 0 0 40px; } + +/* + * 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) + */ + + +/* ============================================================================= + Chrome Frame Prompt + ========================================================================== */ + +.chromeframe { margin: 0.2em 0; background: #ccc; color: black; padding: 0.2em 0; } + + +/* ==|== non-semantic helper classes ======================================== + Please define your styles before this section. + ========================================================================== */ + +/* For image replacement */ +.ir { display: block; border: 0; text-indent: -999em; overflow: hidden; background-color: transparent; background-repeat: no-repeat; text-align: left; direction: ltr; *line-height: 0; } +.ir br { display: none; } + +/* Hide from both screenreaders and browsers: h5bp.com/u */ +.hidden { display: none !important; visibility: hidden; } + +/* Hide only visually, but have it available for screenreaders: h5bp.com/v */ +.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; } + +/* Extends the .visuallyhidden class to allow the element to be focusable when navigated to via the keyboard: h5bp.com/p */ +.visuallyhidden.focusable:active, .visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; } + +/* Hide visually and from screenreaders, but maintain layout */ +.invisible { visibility: hidden; } + +/* Contain floats: h5bp.com/q */ +.clearfix:before, .clearfix:after { content: ""; display: table; } +.clearfix:after { clear: both; } +.clearfix { *zoom: 1; } + +/* ==|== 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; } +} \ No newline at end of file diff --git a/css/style.css b/css/style.css new file mode 100644 index 00000000..57136168 --- /dev/null +++ b/css/style.css @@ -0,0 +1,317 @@ +/* +@import url(//fonts.googleapis.com/css?family=Droid+Sans+Mono); +*/ + +/* Foundation Icons General Enclosed */ +@font-face { + font-family: 'FoundationIconsGeneralEnclosed'; + src: url('//dl.dropbox.com/u/78163899/mnemonia/fonts/foundation-icons-general-enclosed.woff') format('woff'); + font-weight: normal; + font-style: normal; + +} +/* символьный шрифт от гитхаба*/ +@font-face { + font-family: 'Octicons Regular'; + font-style: normal; + font-weight: normal; + src: local('Octicons Regular'), url('//dl.dropbox.com/u/78163899/mnemonia/fonts/octicons-regular-webfont.woff') format('woff'); +} + +@font-face { + font-family: 'Droid Sans Mono'; + font-style: normal; + font-weight: normal; + src: local('Droid Sans Mono'), local('DroidSansMono'), url('http://themes.googleusercontent.com/static/fonts/droidsansmono/v4/ns-m2xQYezAtqh7ai59hJUYuTAAIFFn5GTWtryCmBQ4.woff') format('woff'); +} + +body{ + font:16px "Droid Sans Mono"; +} + +.menu{ + font: 16px 'Octicons Regular'; + margin-bottom: 0; +} + +.path_icon{ + font-family:'FoundationIconsGeneralEnclosed'; + font-size:30px; + color: #46A4C3;/*#55BF3F; green*/ + text-shadow:black 0 2px 1px; + /* размер иконки и позиция на png-файле*/ + width: 15px; + height: 15px; + display: inline-block; + position: relative; + top: 3px; + left: -4px; +} +.path_icon:hover{ + /* + color:#45D827; + */ + cursor:pointer; +} +.path_icon:active{ + text-shadow:black 0 0 1px; + position: relative; + top: 4px; +} +.icon{ + font-family: 'Octicons Regular'; + font-size:16px; + width:16px; + height:16px; + display:inline-block; + margin-left:0.5%; +} +.error::before{ + content:'\f026'; + cursor:default; + color:rgb(222, 41, 41); + position: relative; + top: -3px; +} +.loading{ + background:url(/img/spinner.gif); + position:relative; + top:1px; +} +.error:hover{ + color:rgba(222, 41, 41, 0.81); +} +.refresh-icon{ + background:url(/img/panel_refresh.png) no-repeat; +} +.refresh-icon:active{ + /*background-position-y: -15px;*/ +background:url(/img/panel_refresh.png) 0 -15px no-repeat; +} +.clear-cache{ + background:url(/img/console_clear.png) -4px -4px no-repeat; + margin-right: 6px; + margin-left: 7px; +} +.clear-cache:active{ + /* + background-position-y: -25px; + */ + top:5px; +} + +.settings:before{ + content:'k'; +} +.links{ + color:red; + } + +.mini-icon { + float: left; + height: 16px; + left: -5px; + margin-left: 6px; + position: relative; + top: 2px; + width: 16px; + /* отступ перед картинкой + * для нормального отображения + * рамки + */ +} +/* freeupex */ +.directory{ + /*list-style-image*/ + background-image:url('/img/dir.png'); + background-repeat: no-repeat; + background-position: 0 0; +} +.text-file{ + /*list-style-image*/ + background-image:url('/img/txt.png'); + background-repeat: no-repeat; + background-position: 0 0; +} +#fm{ + height:90%; + overflow-y: scroll; +} +.fm_header{ + font-weight: bold; +} +#path{ + margin-left:1.5%; +} +#left{ + float:left; + width:90%; +} +/* фон файла, на котором курсор*/ +.current-file{ + border: 2px solid rgba(49, 123, 249, .40); +} +.selected-file{ + background-color: rgba(49, 123, 249, .40); + color:white; +} +#right{ + float:right; +} +.panel{ + display: table; + width:45%; +} +#keyspanel{ + text-align: center; +} +/* информация о файлах и папках*/ +.name{ + float: left; + width: 37%; +} +.size{ + float:left; + width:16%; + /* Ставим выравнивание, + * что бы размер был в + * одной ровной колонке + */ + text-align: right; + /* Ставим отступ, что бы + * size не налазил на uid + * (owner) + */ + margin-right: 27px; +} +.owner{ +} +.mode{ + float: right; + width: 25%; +} + +ul,li{list-style-type:none;} +button{ + width:10%; +} +a{ + text-decoration:none; +} +a:hover, a:active { cursor:pointer;outline: 0; color: #06e; } +a:focus { outline: thin dotted; } + +/* Если размер окна очень маленький + * располагаем имя и атрибуты файла + * друг-под-другом +*/ +/* responsive design */ +@media only screen and (max-width: 600px){ + #left{ + width:90% !important; + } + .panel >li{ + margin:10px; + } + /* если правая панель не помещаеться - прячем её */ + #right{ + display:none; + } + /* текущий файл под курсором */ +.current-file{ + background-color: rgba(49, 123, 249, .40); + color:white; + } + /* делаем иконки под курсом белыми*/ +.current-file > .mini-icon{ + color:white; + } +.current-file > .text-file::before{ + color:white; + } + +/* меняем иконки на шрифтовые*/ + .mini-icon { + font: 60px 'Octicons Regular'; + width: 40%; + height: 0; + margin-left: 0; + float: right; + position: relative; + top: -17px; + + color: rgba(246, 224, 124, 0.56); + } + .directory::before{ + content: '\f216'; + } + .text-file::before{ + color: rgba(26, 224, 124, 0.56); + content: '\f211'; + } + .text-file{ + background-image:none; + } + + + + /* убираем заголовок*/ + .fm_header{ + display:none; + } + .mode,.size,.owner{ + /* располагаем элементы + * один под другим + */ + display: table; + float: none; + width: 0; + + text-align: left; + } + /* выводим заголовки рядом с полями */ + .name::before{ + content: 'name:'; + font-weight: bold; + font-size: 13px; + } + .mode::before{ + content: 'mode:'; + font-weight: bold; + font-size: 13px; + } + .size::before{ + content: 'size:'; + font-weight: bold; + font-size: 13px; + } + .owner::before{ + content: 'owner:'; + font-weight: bold; + font-size: 13px; + } + + .name{ + float: none; + width:100%; + font-size: 18px; + } +} +@media only screen and (min-width: 601px) and (max-width: 767px){ + #left{ + width:90% !important; + } + #right{ + display:none; + } +} + +@media only screen and (min-width:767px) and (max-width: 1060px){ + #left{ + width:90% !important; + } + /* если правая панель не помещаеться - прячем её */ + #right{ + display:none; + } +} \ No newline at end of file diff --git a/img/console_clear.png b/img/console_clear.png new file mode 100644 index 00000000..f5739eb1 Binary files /dev/null and b/img/console_clear.png differ diff --git a/img/dir.png b/img/dir.png new file mode 100644 index 00000000..784e8fa4 Binary files /dev/null and b/img/dir.png differ diff --git a/img/panel_refresh.png b/img/panel_refresh.png new file mode 100644 index 00000000..e250c871 Binary files /dev/null and b/img/panel_refresh.png differ diff --git a/img/spinner.gif b/img/spinner.gif new file mode 100644 index 00000000..88fac73d Binary files /dev/null and b/img/spinner.gif differ diff --git a/img/txt.png b/img/txt.png new file mode 100644 index 00000000..8b8b1ca0 Binary files /dev/null and b/img/txt.png differ diff --git a/index.html b/index.html new file mode 100644 index 00000000..6524a912 --- /dev/null +++ b/index.html @@ -0,0 +1,54 @@ + + + + + + + Cloud Commander + + + + + + + + + + +
    +
    + + + + \ No newline at end of file diff --git a/lib/README.md b/lib/README.md new file mode 100644 index 00000000..a741f80c --- /dev/null +++ b/lib/README.md @@ -0,0 +1,5 @@ +Cloud Commander Libraries +=============== +**Cloud Commander Libraries** - dir thet contains scripts, thet uses +on client and server side, and modules, wich is not necessary +for CloudCommander work, but they adds some sugor. \ No newline at end of file diff --git a/lib/client/keyBinding.js b/lib/client/keyBinding.js new file mode 100644 index 00000000..2416002e --- /dev/null +++ b/lib/client/keyBinding.js @@ -0,0 +1,248 @@ +var CloudCommander; +var CloudFunc; +CloudCommander.keyBinding=(function(){ + "use strict"; + var key_event=function(event){ + /* + * Делаем допущение что перезагружать Cloud Commander + * посетителям не придёться, в любом случае, параметр + * должен быть опциональным и должна быть возможность + * его отключить. В любом случае, кроме хакеров и + * разработчиков (при чём сомнительно, что хакерам + * это пригодиться), функция перезагрузки никому не + * нужна, поскольку загружать весь дополнительный + * контент снова (js,css) в готовой версии нет + * необходимости. + * + */ + //console.log(event.keyCode); + var lCurrentFile; + var lName; + /* если клавиши можно обрабатывать*/ + if(CloudCommander.keyBinded){ + /* если нажали таб: + * переносим курсор на + * правую панель, если + * мы были на левой и + * наоборот + */ + if(event.keyCode===9){ + console.log('Tab pressed'); + try{ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE)[0]; + }catch(error){console.log(error);} + } + /* навигация по таблице файлов*/ + /* если нажали клавишу вверх*/ + else if(event.keyCode===38){ + /* получаем выдленный файл*/ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE); + /* если ненайдены выделенные файлы - выходим*/ + if(lCurrentFile.length===0)return; + lCurrentFile=lCurrentFile[0]; + /* если это строка существет и + * если она не заголовок + * файловой таблицы + */ + if(lCurrentFile.previousSibling && + lCurrentFile.previousSibling.className!=='fm_header' ){ + /* убираем выделение с текущего элемента */ + lCurrentFile.className=''; + /* и выделяем предыдущую строку*/ + lCurrentFile.previousSibling.className=CloudCommander.CURRENT_FILE; + event.preventDefault(); + } + } + /* если нажали клавишу в низ*/ + else if(event.keyCode===40){ + /* получаем выдленный файл*/ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE); + /* если ненайдены выделенные файлы - выходим*/ + if(lCurrentFile.length===0)return; + lCurrentFile=lCurrentFile[0]; + /* если это не последняя строка */ + if(lCurrentFile.nextSibling){ + /* убираем с него выделение */ + lCurrentFile.className=''; + /* выделяем следующую строку*/ + lCurrentFile.nextSibling.className=CloudCommander.CURRENT_FILE; + event.preventDefault(); + } + } + /* если нажали клавишу page up или Home + * переходим к самому верхнему + * элементу + */ + else if(/*event.keyCode===33 ||*/ event.keyCode===36){ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE)[0]; + /* убираем выделение с текущего файла*/ + lCurrentFile.className=''; + /* получаем первый элемент*/ + lCurrentFile.parentElement.firstElementChild + /* пропускаем путь и заголовки столбиков*/ + .nextElementSibling.nextElementSibling + /* выделяем верхий файл */ + .className=CloudCommander.CURRENT_FILE; + } + /* если нажали клавишу page down или End + * выделяем последний элемент + */ + else if(/*event.keyCode===34 ||*/ event.keyCode===35){ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE)[0]; + /* снимаем выделение с текущего файла*/ + lCurrentFile.className=''; + /* выделяем самый нижний файл */ + lCurrentFile.parentElement.lastElementChild.className=CloudCommander.CURRENT_FILE; + } + /* если нажали Enter - открываем папку*/ + else if(event.keyCode===13){ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE); + /* если ненайдены выделенные файлы - выходим*/ + if(!lCurrentFile.length)return; + lCurrentFile=lCurrentFile[0]; + /* из него достаём спан с именем файла*/ + lName=lCurrentFile.getElementsByClassName('name'); + /* если нету (что вряд ли) - выходим*/ + if(!lName)return false; + /* достаём все ссылки*/ + var lATag=lName[0].getElementsByTagName('a'); + /* если нету - выходим */ + if(!lATag)return false; + /* получаем ссылку на каталог, + * что на уровень выше + */ + /* получаем имя каталога в котором находимся*/ + var lHref; + try{ + lHref=lCurrentFile.parentElement.getElementsByClassName('path')[0].innerText; + }catch(error){console.log('error');} + lHref=CloudFunc.removeLastSlash(lHref); + var lSubstr=lHref.substr(lHref,lHref.lastIndexOf('/')); + lHref=lHref.replace(lSubstr+'/',''); + + /* вызываем ajaxload привязанный через changelinks + * пробулем нажать на ссылку, если не получиться + * (opera, ie), вызываем событие onclick, + * которое пока не прописано у файлов + */ + + if(lCurrentFile.onclick)lCurrentFile.onclick(true); + else try{ + lATag[0].click(); + } + catch(error){ + console.log(error); + } + } + /* если нажали +r */ + else if(event.keyCode===82 && + event.ctrlKey){ + console.log('+r pressed'); + console.log('reloading page...'); + console.log('press +q to remove all key-handlers'); + + /* обновляем страницу, */ + /* Загружаем содержимое каталога + * при этом данные берём всегда из + * сервера, а не из кэша + * (обновляем кэш) + */ + /* Программно нажимаем на кнопку перезагрузки + * содержимого каталога + */ + var lRefreshIcon=document.getElementsByClassName(CloudFunc.REFRESHICON); + if(lRefreshIcon)lRefreshIcon=lRefreshIcon[0]; + if(lRefreshIcon){ + /* находим файл который сейчас выделен */ + lCurrentFile=document.getElementsByClassName(CloudCommander.CURRENT_FILE); + if(lCurrentFile.length>0)lCurrentFile=lCurrentFile[0]; + /* получаем название файла*/ + var lSelectedName=lCurrentFile.getElementsByTagName('a')[0].innerText; + /* если нашли элемент нажимаем него + * а если не можем - нажимаем на + * ссылку, на которую повешен eventHandler + * onclick + */ + if(lRefreshIcon.click)lRefreshIcon.parentElement.click(); + else lRefreshIcon.parentElement.onclick(); + + /* перебираем файлы левой панели + * в поисках подсвеченого файла + */ + var lLeft=document.getElementById('left'); + if(lLeft){ + /* перебираем все файлы в панели */ + var lLi=lLeft.getElementsByTagName('li'); + lCurrentFile.className=''; + /* начинаем с 2-ух, по скольку + * 0 - это путь + * 1 - это заголовок файловой таблицы + */ + for(var i=2;i+d чистим кэш */ + else if(event.keyCode===68 && + event.ctrlKey){ + console.log('+d pressed'); + console.log('clearing cache...'); + console.log('press +q to remove all key-handlers'); + + var lClearCache=document.getElementById('clear-cache'); + if(lClearCache && lClearCache.onclick)lClearCache.onclick(); + + event.preventDefault();//запрет на дальнейшее действие + } + /* если нажали +q + * убираем все обработчики + * нажатий клавиш + */ + else if(event.keyCode===81 && + event.altKey){ + //document.removeEventListener('keydown', key_event,false); + console.log('+q pressed'); + console.log('+r reload key-handerl - removed'); + console.log('+s clear cache key-handler - removed'); + console.log('press +s to to set them'); + + /* обработчик нажатий клавиш снят*/ + CloudCommander.keyBinded=false; + } + } + /* если нажали +s + * устанавливаем все обработчики + * нажатий клавиш + */ + else if(event.keyCode===83 && + event.altKey){ + /* + document.addEventListener('keydown', key_event,false); + */ + /* обрабатываем нажатия на клавиши*/ + CloudCommander.keyBinded=true; + + console.log('+s pressed'); + console.log('+r reload key-handerl - set'); + console.log('+s clear cache key-handler - set'); + console.log('press +q to remove them'); + } + + return false; + }; + /* добавляем обработчик клавишь */ + if(document.addEventListener) + document.addEventListener('keydown', key_event,false); + else document.onkeypress=key_event; + /* клавиши назначены*/ + CloudCommander.keyBinded=true; +}); \ No newline at end of file diff --git a/lib/cloudfunc.js b/lib/cloudfunc.js new file mode 100644 index 00000000..231f36dd --- /dev/null +++ b/lib/cloudfunc.js @@ -0,0 +1,504 @@ +"use strict"; +/* Модуль, содержащий функции, которые будут работать + * и на клиенте и на сервере + * + * Правила названий: + * varName - имя функции + * lVarName - имя локальной переменной + * pVarName - имя параметра + * fVarName - имя функции созданной внутри функции + * VARNAME - имя константы + * + * Типы переменных: + * varNameS - строка + * varNameN - число + * varNameO - обьект + * varNameM - массив + */ + +var CloudFunc={ + /* Путь с которым мы сейчас работаем */ + Path :'', + /* КОНСТАНТЫ (общие для клиента и сервера)*/ + /* название программы */ + NAME :'Cloud Commander', + /* если в ссылке будет эта строка - + * в браузере js отключен + */ + NOJS : '/no-js', + FS : '/c/f/s', + /* название css-класа кнопки обновления файловой структуры*/ + REFRESHICON : 'refresh-icon', + /* id панелей с файлами */ + LEFTPANEL : 'left', + RIGHTPANEL : 'right' + /* name of direcotory with libs */ +}; + +/* + * Функция убирает последний слеш, + * если он - последний символ строки + */ +CloudFunc.removeLastSlash = function(pPath){ + if(typeof pPath==='string') + return (pPath.lastIndexOf('/')===pPath.length-1)? + pPath.substr(pPath, pPath.length-1):pPath; + else return pPath; +}; +/* + * Функция меняет код символа пробела на пробел + * в переданной строке + * @pPath - строка + */ +CloudFunc.replaceSpaces = function(pPath){ + if(pPath.indexOf('%20')>0){ + do{ + pPath=pPath.replace('%20',' '); + }while(pPath.indexOf('%20')>0); + } + return pPath; +}; + +/* Функция возвращает заголовок веб страницы */ +CloudFunc.setTitle = function(){ + + return CloudFunc.Path===''?CloudFunc.NAME: + CloudFunc.Path + + ' - ' + + CloudFunc.NAME; +}; +/* Функция переводит права из цыфрового вида в символьный + * @pPerm_s - строка с правами доступа + * к файлу в 8-миричной системе + */ +CloudFunc.convertPermissionsToSymbolic= function(pPerm_s){ + /* + S_IRUSR 0000400 protection: readable by owner + S_IWUSR 0000200 writable by owner + S_IXUSR 0000100 executable by owner + S_IRGRP 0000040 readable by group + S_IWGRP 0000020 writable by group + S_IXGRP 0000010 executable by group + S_IROTH 0000004 readable by all + S_IWOTH 0000002 writable by all + S_IXOTH 0000001 executable by all + */ + if(pPerm_s===undefined) return; + + /* тип файла */ + var lType=pPerm_s.charAt(0); + + switch (lType-0) { + case 1: /* обычный файл */ + lType='-'; + break; + case 2: /* байт-ориентированное (символьное) устройство*/ + lType='c'; + break; + case 4: /* каталог */ + lType='d'; + break; + default: + lType='-'; + } + + /* оставляем последние 3 символа*/ + pPerm_s=pPerm_s.length>5?pPerm_s.substr(3):pPerm_s.substr(2); + + /* Рекомендации гугла советуют вместо string[3] + * использовать string.charAt(3) + */ +/* + http://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml?showone=Standards_features#Standards_features + + Always preferred over non-standards featuresFor + maximum portability and compatibility, always + prefer standards features over non-standards + features (e.g., string.charAt(3) over string[3] + and element access with DOM functions instead + of using an application-specific shorthand). +*/ + /* Переводим в двоичную систему */ + var lOwner=(pPerm_s[0]-0).toString(2); + var lGroup=(pPerm_s[1]-0).toString(2); + var lAll =(pPerm_s[2]-0).toString(2); + /* + console.log(lOwner+' '+lGroup+' '+lAll); + */ + /* переводим в символьную систему*/ + var lPermissions=//lType+' '+ + (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':'-')+ + ' ' + + (lAll[0]-0>0?'r':'-')+ + (lAll[1]-0>0?'w':'-')+ + (lAll[2]-0>0?'x':'-'); + /* + console.log(lPermissions); + */ + return lPermissions; +}; + +/* Функция конвертирует права доступа к файлам из символьного вида + * в цыфровой + */ +CloudFunc.convertPermissionsToNumberic= function(pPerm_s){ + /* если передана правильная строка, конвертированная + * функциец convertPermissionsToSymbolic + */ + if(!pPerm_s || pPerm_s.length!==11)return pPerm_s; + + var lOwner= (pPerm_s[0]==='r'?4:0) + + (pPerm_s[1]==='w'?2:0) + + (pPerm_s[2]==='x'?1:0); + var lGroup= (pPerm_s[4]==='r'?4:0) + + (pPerm_s[5]==='w'?2:0) + + (pPerm_s[6]==='x'?1:0); + var lAll = (pPerm_s[8]==='r'?4:0) + + (pPerm_s[9]==='w'?2:0) + + (pPerm_s[10]==='x'?1:0); + /* добавляем 2 цыфры до 5 */ + return '00'+lOwner+lGroup+lAll; +}; +/* Функция получает короткие размеры + * конвертируя байт в килобайты, мегабойты, + * гигайбайты и терабайты + * @pSize - размер в байтах + */ +CloudFunc.getShortedSize=function(pSize){ + /* if pSize=0 - return it */ + if(!pSize)return pSize; + + /* Константы размеров, что используются + * внутри функции + */ + var l1BMAX=1024; + var l1KBMAX=1048576; + var l1MBMAX=1073741824; + var l1GBMAX=1099511627776; + var l1TBMAX=1125899906842624; + + var lShorted; + + if(pSize2){ + if(folders[0].lastIndexOf('/')===folders[0].length) + LPrevDir=folders[1]; + else LPrevDir=folders[2]; + }else LPrevDir='/'; + */ + /* ################################### */ + + /* Формируем ссылки на каждый каталог в пути */ + var lHref=''; + var lHrefEnd=''; + + var lHtmlPath; + /* путь в ссылке, который говорит + * что js отключен + */ + var lNoJS_s=CloudFunc.NOJS; + var lFS_s=CloudFunc.FS; + /* корневой каталог */ + lHtmlPath=lHref+lFS_s+lNoJS_s+lTitle+'"/"'+_l+'/'+lHrefEnd; + for(i=folders.length-1;i>0;i--) + { + var lUrl=folders[i]; + var lShortName=lUrl.replace(lUrl.substr(lUrl,lUrl.lastIndexOf('/')+1),''); + if(i!==1) + { + lHtmlPath+=lHref+lFS_s+lNoJS_s+lUrl+lTitle+lUrl+_l+lShortName+lHrefEnd+'/'; + } + else + lHtmlPath+=lShortName+'/'; + } + /* *** */ + return lHtmlPath; +}; + +/* + * Функция ищет в имени файла расширение + * и если находит возвращает true + * @pName - получает имя файла + * @pExt - расширение + */ +CloudFunc.checkExtension=function(pName,pExt) +{ + /* если длина имени больше + * длинны расширения - + * имеет смысл продолжать + */ + if(pName.length>pExt.length){ + var lLength=pName.length; /* длина имени*/ + var lExtNum=pName.lastIndexOf(pExt);/* последнее вхождение расширения*/ + var lExtSub=lLength-lExtNum; /* длина расширения*/ + /* если pExt - расширение pName */ + if(lExtSub===pExt.length) + return true; + else + return false; + } + else return false; +}; + +/* + * Функция формирует заголовки столбиков + * @pFileTableTitles - массив названий столбиков + */ +CloudFunc._getFileTableHeader=function(pFileTableTitles) +{ + var lHeader='
  • '; + lHeader+=''; + for(var i=0;i'+ + lStr+ + ''; + } + lHeader+='
  • '; + + return lHeader; +}; + +/* + * Функция строит таблицу файлв из JSON-информации о файлах + * @pJSON - информация о файлах + * @pKeyBinded - если клавиши назначены, выделяем верхний файл + * [{path:'путь',size:'dir'}, + * {name:'имя',size:'размер',mode:'права доступа'}] + */ +CloudFunc.buildFromJSON=function(pJSON,pKeyBinded) +{ + var files; + /* + * если пропарсить стандартными + * функциями нельзя - + * пробуем eval, + */ + /* + * Если мы на клиенте и нет JSON - + * через eval парсим. + * Если-же мы на сервере, + * или на клиенте всё есть + * парсим стандарным методом + */ + + /* По скольку мы прописали заголовок application/json + * нет необходимости его конвертировать, + * но она есть, если мы вытягиваем данные из + * localStorage + */ + files=pJSON; + /* сохраняем путь каталога в котором мы сейчас находимся*/ + var lPath=files[0].path; + + /* сохраняем путь */ + CloudFunc.Path=lPath; + + /* + * Строим путь каталога в котором мы находимся + * со всеми подкаталогами + */ + var lHtmlPath=CloudFunc._getDirPath(lPath); + + /* Убираем последний слэш + * с пути для кнопки обновить страницу + * если он есть + */ + var lRefreshPath=CloudFunc.removeLastSlash(lPath); + + /* путь в ссылке, который говорит + * что js отключен + */ + var lNoJS_s=CloudFunc.NOJS; + var lFS_s=CloudFunc.FS; + + var lFileTable='
  • '+ + ''+ + ''+ + '' + + ''+ + ''+ + ''+lHtmlPath+''+ + '
  • '; + + var fileTableTitles=['name','size','owner','mode']; + lFileTable+=CloudFunc._getFileTableHeader(fileTableTitles); + /* Если мы не в корне */ + if(lPath!=='/'){ + /* ссылка на верхний каталог*/ + var lDotDot; + /* убираем последний слеш и каталог в котором мы сейчас находимся*/ + lDotDot=lPath.substr(lPath,lPath.lastIndexOf('/')); + lDotDot=lDotDot.substr(lDotDot,lDotDot.lastIndexOf('/')); + /* Если предыдущий каталог корневой */ + if(lDotDot==='')lDotDot='/'; + + /* Сохраняем путь к каталогу верхнего уровня*/ + lFileTable += '
  • '+ + '' + + '' + + '' + + ''+".." + + '' + + '<dir>'+ + '.' + + '' + + '
  • '; + } + var lLength=files.length; + + for(var i=1;i'; + lFileTable +=''; + lFileTable +='' + + '16? + ' title="'+files[i].name+'">' + + files[i].name.substr( + files[i].name,16)+ + '..':'>'+files[i].name) + + "" + + ''; + /* если папка - не выводим размер */ + lFileTable +='' + + (files[i].size==='dir'? + '<dir>': + /* если это файл - получаем + * короткий размер + */ + CloudFunc.getShortedSize( + files[i].size)); + lFileTable +='' + + '' + + (!files[i].uid?'root':files[i].uid) + + '' + + '' + + /* конвертируем названия разрешений + * из числового формата в буквенный + * при этом корневой каталог не трогаем + * по скольку в нём и так всё уже + * установлено еще на сервере + */ + (//lPath==='/'?files[i].mode: + CloudFunc.convertPermissionsToSymbolic(files[i].mode)) + + ''; + lFileTable +=''; + } + + /* если клавиши назначены и + * мы в корневом каталоге и + * верхний файл еще не выделен - + * выделяем верхний файл + */ + if(pKeyBinded && lPath==='/'&& + lFileTable.indexOf('
  • ')<0){ + lFileTable=lFileTable.replace('
  • ','
  • '); + } + + + + return lFileTable; +}; + +/* + * Если мы на стороне сервера - + * прописываем экспортируемые функции + */ +try{ + if(exports){ + /* экспортируемые функции */ + exports.checkExtension = CloudFunc.checkExtension; + exports.buildFromJSON = CloudFunc.buildFromJSON; + exports.replaceSpaces = CloudFunc.replaceSpaces; + exports.setTitle = CloudFunc.setTitle; + exports.convertPermissions = CloudFunc.convertPermissions; + exports.getUserUIDsAndNames = CloudFunc.getUserUIDsAndNames; + + /* константы*/ + exports.Name = CloudFunc.NAME; + exports.NOJS = CloudFunc.NOJS; + exports.FS =CloudFunc.FS; + + console.log('cloudfunc.js loaded...'); + } +}catch(err){ + /* если мы на клиенте */ +} \ No newline at end of file diff --git a/lib/server/minify.js b/lib/server/minify.js new file mode 100644 index 00000000..88e75813 --- /dev/null +++ b/lib/server/minify.js @@ -0,0 +1,276 @@ +/* Модуль сжатия js-скриптов и css*/ + +/* функция сжимает js-скрипты + * и сохраняет их с именем .min.js + */ + +var fs = require('fs'); + +/* CONSTANTS */ +/* dir contains css-files */ +var CSSDIR = 'css/'; + +exports.jsScripts=function jsScripts(){ + 'use strict'; + + /* подключаем модуль uglify-js + * если его нет - дальнейшая + * работа функции не имеет смысла + */ + try{ + var jsp = require("uglify-js").parser; + var pro = require("uglify-js").uglify; + }catch(error){ + console.log('can\'n load uglify-js\n' + + 'to use js-minification you need to install uglify-js\n' + + 'npm install uglify-js\n' + + 'https://github.com/mishoo/UglifyJS'); + return false; + } + /* Константы */ + var CLIENT_JS='client.js'; + var CLOUDFUNC_JS='lib/cloudfunc.js'; + var CLIENT_KEYBINDING_JS='lib/client/keyBinding.js'; + + var dataReaded_f=function(pFileName, pData){ + console.log('file ' + pFileName + ' readed'); + + /*********************************/ + /* сжимаем код через uglify-js */ + var uglify_js=function(pDdata){ + var orig_code = pDdata.toString(); + var ast = jsp.parse(orig_code); // parse code and get the initial AST + ast = pro.ast_mangle(ast); // get a new AST with mangled names + ast = pro.ast_squeeze(ast); // get an AST with compression optimizations + var result_code = pro.gen_code(ast); // compressed code here + return result_code; + }; + /*********************************/ + var final_code=uglify_js(pData); + + var minFileName=pFileName.replace('.js','.min.js'); + /* если мы сжимаем client.js - + * меняем строку cloudfunc.js на + * cloudfunc.min.js и выводим сообщение + * + * меняем строку keyBinding.js на + * keyBinding.min.js + * если другой файл - ничего не деалем + */ + if(pFileName===CLIENT_JS) + console.log('file name of ' + + CLOUDFUNC_JS + + ' in ' + + CLIENT_JS + + ' changed. size:', + (final_code=final_code + .replace('cloudfunc.js','cloudfunc.min.js') + .replace('keyBinding.js','keyBinding.min.js')).length); + + /* записываем сжатый js-скрипт*/ + fs.writeFile(minFileName, final_code, fileWrited(minFileName)); + }; + console.log('reading file ' + CLIENT_JS+'...'); + fs.readFile(CLIENT_JS,fileReaded(CLIENT_JS,dataReaded_f)); + + console.log('reading file ' + CLOUDFUNC_JS+'...'); + fs.readFile(CLOUDFUNC_JS,fileReaded(CLOUDFUNC_JS,dataReaded_f)); + + console.log('reading file ' + CLIENT_KEYBINDING_JS+'...'); + fs.readFile(CLIENT_KEYBINDING_JS, fileReaded(CLIENT_KEYBINDING_JS,dataReaded_f)); + + + return true; +}; + +/* функция сжимает css-стили + * и сохраняет их с именем .min.css + * @pImgConvertToBase64_b - булевый признак, + * который отвечает за то, что быконвертировать + * картинки в base64 и поместить в выходной css файл + */ +exports.cssStyles=function cssStyles(pImgConvertToBase64_b){ + 'use strict'; + + /* connecting cleanCSS, + * if we can't find it - + * return false + */ + var cleanCSS; + try{ + cleanCSS = require('clean-css'); + }catch(error){ + console.log('can\'n load clean-css \n' + + 'to use css-minification you need to install clean-css \n' + + 'npm install clean-css\n' + + 'https://github.com/GoalSmashers/clean-css'); + return false; + } + + /* Константы */ + var STYLE_CSS = CSSDIR+'style.css'; + var RESET_CSS = CSSDIR+'reset.css'; + + var lAllStyle=''; + var lResetCssDone=false; + var lStyleCssDone=false; + var dataReaded_f=function(pFileName, pData){ + console.log('file ' + pFileName + ' readed'); + /*********************************/ + /* сжимаем код через clean-css */ + var clean_css=function(pData){ + /* Сохраняем весь стиль в одну переменную*/ + return cleanCSS.process(pData); + }; + /*********************************/ + var final_code=clean_css(pData); + + lAllStyle+=final_code; + + var minFileName=pFileName.replace('.css','.min.css'); + + if(pFileName===STYLE_CSS)lStyleCssDone=true; + if(pFileName===RESET_CSS)lResetCssDone=true; + /* if all files writed we + * save all minimized css + * to one file all.min.css + */ + /* если включена конвертация картинок в base64 + * вызываем её + */ + if(pImgConvertToBase64_b) + base64_images(lAllStyle); + else if(lStyleCssDone && lResetCssDone) + fs.writeFile(CSSDIR+'all.min.css', lAllStyle, fileWrited('all.min.css')); + /* записываем сжатый css файл*/ + else fs.writeFile(minFileName, final_code, fileWrited(minFileName)); + }; + + console.log('reading file ' + STYLE_CSS+'...'); + fs.readFile(STYLE_CSS,fileReaded(STYLE_CSS,dataReaded_f)); + + console.log('reading file ' + RESET_CSS+'...'); + fs.readFile(RESET_CSS,fileReaded(RESET_CSS,dataReaded_f)); + + return true; +}; + +/* функция сжимает css-стили + * и сохраняет их с именем .min.css + */ +exports.html=function(){ + 'use strict'; + + /* connecting cleanCSS, + * if we can't find it - + * return false + */ + var htmlMinifier; + try{ + htmlMinifier = require('html-minifier'); + }catch(error){ + console.log('can\'n load html-minifier \n' + + 'to use html-minification you need to install html-minifier\n' + + 'npm install html-minifier\n' + + 'https://github.com/kangax/html-minifier'); + return false; + } + + /* Константы */ + var INDEX_HTML='index.html'; + + var dataReaded_f=function(pFileName, pData){ + console.log('file ' + pFileName + ' readed'); + /*********************************/ + /* сжимаем код через clean-css */ + var html_minify=function(pData){ + /* Сохраняем весь стиль в одну переменную*/ + + var lOptions={ + removeComments: true, + removeCommentsFromCDATA: true, + removeCDATASectionsFromCDATA: true, + collapseWhitespace: true, + collapseBooleanAttributes: true, + removeAttributeQuotes: true, + removeRedundantAttributes: true, + useShortDoctype: true, + removeEmptyAttributes: true, + /* оставляем, поскольку у нас + * в элемент fm генерируеться + * таблица файлов + */ + removeEmptyElements: false, + removeOptionalTags: true, + removeScriptTypeAttributes: true, + removeStyleLinkTypeAttributes: true + }; + + + return htmlMinifier.minify(pData,lOptions); + }; + /*********************************/ + var final_code=html_minify(pData); + + var minFileName=pFileName.replace('.html','.min.html'); + + /* записываем сжатый html файл*/ + fs.writeFile(minFileName, final_code, fileWrited(minFileName)); + }; + + console.log('reading file ' + INDEX_HTML+'...'); + fs.readFile(INDEX_HTML,fileReaded(INDEX_HTML,dataReaded_f)); + + return true; +}; + +/* функция переводит картинки в base64 и записывает в css-файл*/ +function base64_images(pFileContent_s){ + 'use strict'; + var b64img; + try{ + b64img = require('css-b64-images'); + }catch(error){ + console.log('can\'n load clean-css \n' + + 'to use images to base64 convertation you need to install css-base64-images \n' + + 'npm install -g css-b64-images\n' + + 'https://github.com/Filirom1/css-base64-images'); + return false; + } + b64img.fromString(pFileContent_s, '.','', function(err, css){ + fs.writeFile(CSSDIR+'all.min.css', css, fileWrited('all.min.css')); + }); +} + +/* Функция создаёт асинхроную версию + * для чтения файла + * @pFileName - имя считываемого файла + */ +function fileReaded(pFileName,pCompressFunc){ + "use strict"; + return function(pError,pData){ + /* функция в которую мы попадаем, + * если данные считались + * + * если ошибка - показываем её + * иначе если переданная функция - + * функция запускаем её + */ + if(!pError) + if (pCompressFunc && typeof pCompressFunc==="function") + pCompressFunc(pFileName,pData.toString()); + else console.log(pError); + }; +} + +/* + * Функция вызываеться после записи файла + * и выводит ошибку или сообщает, + * что файл успешно записан + */ +function fileWrited(pFileName){ + "use strict"; + return function(error){ + console.log(error?error:('file '+pFileName+' writed...')); + }; +} \ No newline at end of file diff --git a/node_modules/.bin/jitsu b/node_modules/.bin/jitsu new file mode 120000 index 00000000..ab860a6f --- /dev/null +++ b/node_modules/.bin/jitsu @@ -0,0 +1 @@ +../jitsu/bin/jitsu \ No newline at end of file diff --git a/node_modules/.bin/nodester b/node_modules/.bin/nodester new file mode 120000 index 00000000..aa4a5eb1 --- /dev/null +++ b/node_modules/.bin/nodester @@ -0,0 +1 @@ +../nodester-cli/bin/nodester.js \ No newline at end of file diff --git a/node_modules/jitsu b/node_modules/jitsu new file mode 120000 index 00000000..d5540629 --- /dev/null +++ b/node_modules/jitsu @@ -0,0 +1 @@ +../../local/lib/node_modules/jitsu \ No newline at end of file diff --git a/node_modules/nodester-cli b/node_modules/nodester-cli new file mode 120000 index 00000000..da254801 --- /dev/null +++ b/node_modules/nodester-cli @@ -0,0 +1 @@ +../../local/lib/node_modules/nodester-cli \ No newline at end of file diff --git a/package2.json b/package2.json new file mode 100644 index 00000000..f2d59387 --- /dev/null +++ b/package2.json @@ -0,0 +1,12 @@ +{ + "name": "cloudcmd", + "version": "0.0.1-3", + "scripts": { + + "start": "server.js" + }, + "engines": { + "node": "0.6.x" + }, + "subdomain": "cloudcmd" +} diff --git a/package_was.json b/package_was.json new file mode 100644 index 00000000..5c67dd25 --- /dev/null +++ b/package_was.json @@ -0,0 +1,11 @@ +{ + "name": "cloudcmd", + "version": "0.0.1-9", + "scripts": { + "start": "server.js" + }, + "engines": { + "node": "0.6.x" + }, + "subdomain": "cloudcmd" +} \ No newline at end of file diff --git a/test/test.sh b/test/test.sh new file mode 100644 index 00000000..9ad8c6e2 --- /dev/null +++ b/test/test.sh @@ -0,0 +1,6 @@ +#!/bin/sh +npm i jshint -g +echo "jshint server.js client.js lib/cloudfunc.js" +jshint --config ./.jshintrc ./server.js ./client.js +echo "jshint ./lib/cloudfunc.js ./lib/server/minify.js ./lib/client/keyBinding.js" +jshint --config ./.jshintrc ./lib/cloudfunc.js ./lib/server/minify.js ./lib/client/keyBinding.js \ No newline at end of file