mirror of
https://github.com/coderaiser/cloudcmd.git
synced 2026-01-23 02:35:49 +00:00
feature(user-menu) add (#221)
This commit is contained in:
parent
f60af287d3
commit
eb4f7c0d7c
34 changed files with 562 additions and 41 deletions
25
.cloudcmd.menu.js
Normal file
25
.cloudcmd.menu.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
'F2 - Rename file': async ({DOM}) => {
|
||||
await DOM.renameCurrent();
|
||||
},
|
||||
'D - Build Dev': async ({CloudCmd}) => {
|
||||
await CloudCmd.TerminalRun.show({
|
||||
command: 'npm run build:client:dev',
|
||||
autoClose: false,
|
||||
closeMessage: 'Press any button to close Terminal',
|
||||
});
|
||||
|
||||
CloudCmd.refresh();
|
||||
},
|
||||
'P - Build Prod': async ({CloudCmd}) => {
|
||||
await CloudCmd.TerminalRun.show({
|
||||
command: 'npm run build:client',
|
||||
autoClose: true,
|
||||
});
|
||||
|
||||
CloudCmd.refresh();
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -21,6 +21,7 @@ const cssNames = [
|
|||
'view',
|
||||
'config',
|
||||
'terminal',
|
||||
'user-menu',
|
||||
...getCSSList('columns'),
|
||||
];
|
||||
|
||||
|
|
@ -35,7 +36,7 @@ const plugins = clean([
|
|||
|
||||
const rules = [{
|
||||
test: /\.css$/,
|
||||
exclude: /css\/(nojs|view|config|terminal|columns.*)\.css/,
|
||||
exclude: /css\/(nojs|view|config|terminal|user-menu|columns.*)\.css/,
|
||||
use: extractMain.extract([
|
||||
'css-loader',
|
||||
]),
|
||||
|
|
|
|||
|
|
@ -32,7 +32,6 @@ const babelDev = {
|
|||
babelrc: false,
|
||||
plugins: [
|
||||
'module:babel-plugin-macros',
|
||||
'@babel/plugin-proposal-object-rest-spread',
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -43,7 +42,7 @@ const rules = clean([
|
|||
loader: 'babel-loader',
|
||||
},
|
||||
isDev && {
|
||||
test: /sw\.js$/,
|
||||
test: /\.js$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
options: babelDev,
|
||||
|
|
@ -90,7 +89,9 @@ module.exports = {
|
|||
[modules + '/operation']: `${dirModules}/operation/index.js`,
|
||||
[modules + '/konsole']: `${dirModules}/konsole.js`,
|
||||
[modules + '/terminal']: `${dirModules}/terminal.js`,
|
||||
[modules + '/terminal-run']: `${dirModules}/terminal-run.js`,
|
||||
[modules + '/cloud']: `${dirModules}/cloud.js`,
|
||||
[modules + '/user-menu']: `${dirModules}/user-menu/index.js`,
|
||||
[modules + '/polyfill']: `${dirModules}/polyfill.js`,
|
||||
},
|
||||
output: {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
".md"
|
||||
],
|
||||
"dictionary":[
|
||||
"CloudCmd",
|
||||
"Dev",
|
||||
"Dropbox",
|
||||
"Deepword",
|
||||
|
|
@ -27,12 +28,14 @@
|
|||
"Zalitok",
|
||||
"WebSocket",
|
||||
"auth",
|
||||
"binded",
|
||||
"cd",
|
||||
"cloudcmd",
|
||||
"coderaiser",
|
||||
"com",
|
||||
"dev",
|
||||
"deepword",
|
||||
"dev",
|
||||
"destructuring",
|
||||
"dropbox",
|
||||
"dword",
|
||||
"edward",
|
||||
|
|
|
|||
53
HELP.md
53
HELP.md
|
|
@ -109,6 +109,7 @@ Cloud Commander supports the following command-line parameters:
|
|||
| `--dropbox` | enable dropbox integration
|
||||
| `--dropbox-token` | set dropbox token
|
||||
| `--log` | enable logging
|
||||
| `--user-menu` | enable user menu
|
||||
| `--no-show-config` | do not show config values
|
||||
| `--no-server` | do not start server
|
||||
| `--no-auth` | disable authorization
|
||||
|
|
@ -137,6 +138,7 @@ Cloud Commander supports the following command-line parameters:
|
|||
| `--no-dropbox` | disable dropbox integration
|
||||
| `--no-dropbox-token` | unset dropbox token
|
||||
| `--no-log` | disable logging
|
||||
| `--no-user-menu` | disable user menu
|
||||
|
||||
For options not specified by command-line parameters, Cloud Commander then reads configuration data from `~/.cloudcmd.json`. It uses port `8000` by default.
|
||||
|
||||
|
|
@ -162,7 +164,7 @@ Hot keys
|
|||
|Key |Operation
|
||||
|:----------------------|:--------------------------------------------
|
||||
| `F1` | help
|
||||
| `F2` | rename
|
||||
| `F2` | rename or show `user menu`
|
||||
| `F3` | view, change directory
|
||||
| `Shift + F3` | view as markdown
|
||||
| `F4` | edit
|
||||
|
|
@ -421,7 +423,8 @@ Here's a description of all options:
|
|||
"importListen" : false, // listen on config updates
|
||||
"dropbox" : false, // disable dropbox integration
|
||||
"dropboxToken" : "", // unset dropbox token
|
||||
"log" : true // logging
|
||||
"log" : true, // logging
|
||||
"userMenu" : false // do not show user menu
|
||||
}
|
||||
```
|
||||
|
||||
|
|
@ -458,6 +461,52 @@ Some config options can be overridden with environment variables, such as:
|
|||
- `CLOUDCMD_IMPORT_TOKEN` - authorization token used to connect to export server
|
||||
- `CLOUDCMD_IMPORT_URL` - url of an import server
|
||||
- `CLOUDCMD_IMPORT_LISTEN`- enable listen on config updates from import server
|
||||
- `CLOUDCMD_USER_MENU`- enable `user menu`
|
||||
|
||||
### User Menu
|
||||
|
||||
You can enable `user menu` with help of a flag `--user-menu` (consider that file rename using `F2` will be disabled).
|
||||
When you press `F2` Cloud Commander will a file `.cloudcmd.menu.js` by walking up parent directories, if can't read it will try to read `~/.cloudcmd.menu.js`.
|
||||
Let's consider example `user menu` works file:
|
||||
|
||||
```js
|
||||
module.exports = {
|
||||
'F2 - Rename file': async ({DOM}) => {
|
||||
await DOM.renameCurrent();
|
||||
},
|
||||
'D - Build Dev': async ({CloudCmd}) => {
|
||||
await CloudCmd.TerminalRun.show({
|
||||
command: 'npm run build:client:dev',
|
||||
autoClose: false,
|
||||
closeMessage: 'Press any button to close Terminal',
|
||||
});
|
||||
|
||||
CloudCmd.refresh();
|
||||
},
|
||||
'P - Build Prod': async ({CloudCmd}) => {
|
||||
await CloudCmd.TerminalRun.show({
|
||||
command: 'npm run build:client',
|
||||
autoClose: true,
|
||||
});
|
||||
|
||||
CloudCmd.refresh();
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
You will have ability to run one of this 3 commands with help of double click, enter, or binded key (`F2`, `D` or `P` in this example). Also you can run commands in terminal, or execute any built-in function of `Cloud Commander` extended it's interface.
|
||||
|
||||
#### User Menu API
|
||||
|
||||
Here you can find `API` that can be used in **User Menu**. **DOM** and **CloudCmd** to main objects you receive in arguments list using destructuring.
|
||||
|
||||
**DOM** contains all base functions of `Cloud Commander` (rename, remove, download etc);
|
||||
|
||||
- `renameCurrent` - shows renames current file dialog, and does renaming.
|
||||
|
||||
**CloudCmd** contains all modules (`Terminal`, `View`, `Edit`, `Config`, `Console` etc);
|
||||
|
||||
- `TerminalRun` - module that shows `Terminal` with a `command` from options and closes terminal when everything is done.
|
||||
|
||||
### Distribute
|
||||
|
||||
|
|
|
|||
|
|
@ -73,6 +73,7 @@ const args = require('minimist')(argv.slice(2), {
|
|||
'import-listen',
|
||||
'log',
|
||||
'dropbox',
|
||||
'user-menu',
|
||||
],
|
||||
default: {
|
||||
server : true,
|
||||
|
|
@ -111,6 +112,7 @@ const args = require('minimist')(argv.slice(2), {
|
|||
'one-file-panel': choose(env.bool('one_file_panel'), config('oneFilePanel')),
|
||||
'confirm-copy': choose(env.bool('confirm_copy'), config('confirmCopy')),
|
||||
'confirm-move': choose(env.bool('confirm_move'), config('confirmMove')),
|
||||
'user-menu': choose(env.bool('user_menu'), config('userMenu')),
|
||||
'keys-panel': env.bool('keys_panel') || config('keysPanel'),
|
||||
'import-token': env('import_token') || config('importToken'),
|
||||
'export-token': env('export_token') || config('exportToken'),
|
||||
|
|
@ -180,6 +182,7 @@ function main() {
|
|||
config('importToken', args['import-token']);
|
||||
config('importListen', args['import-listen']);
|
||||
config('importUrl', args['import-url']);
|
||||
config('userMenu', args['user-menu']);
|
||||
|
||||
config('dropbox', args['dropbox']);
|
||||
config('dropboxToken', args['dropbox-token'] || '');
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@
|
|||
const itype = require('itype/legacy');
|
||||
const exec = require('execon');
|
||||
const jonny = require('jonny/legacy');
|
||||
const tryToCatch = require('try-to-catch/legacy');
|
||||
|
||||
const Util = require('../../common/util');
|
||||
|
||||
const Images = require('./images');
|
||||
|
|
@ -746,7 +748,7 @@ function CmdProto() {
|
|||
*
|
||||
* @currentFile
|
||||
*/
|
||||
this.renameCurrent = (current) => {
|
||||
this.renameCurrent = async (current) => {
|
||||
const {Dialog} = DOM;
|
||||
|
||||
if (!DOM.isCurrentFile(current))
|
||||
|
|
@ -757,30 +759,30 @@ function CmdProto() {
|
|||
if (from === '..')
|
||||
return Dialog.alert.noFiles();
|
||||
|
||||
const cancel = false;
|
||||
const [e, to] = await tryToCatch(Dialog.prompt, 'Rename', from);
|
||||
|
||||
Dialog.prompt('Rename', from, {cancel}).then((to) => {
|
||||
const isExist = !!DOM.getCurrentByName(to);
|
||||
const dirPath = DOM.getCurrentDirPath();
|
||||
|
||||
if (from === to)
|
||||
if (e)
|
||||
return;
|
||||
const isExist = !!DOM.getCurrentByName(to);
|
||||
const dirPath = DOM.getCurrentDirPath();
|
||||
|
||||
if (from === to)
|
||||
return;
|
||||
|
||||
const files = {
|
||||
from : dirPath + from,
|
||||
to : dirPath + to,
|
||||
};
|
||||
|
||||
RESTful.mv(files, (error) => {
|
||||
if (error)
|
||||
return;
|
||||
|
||||
const files = {
|
||||
from : dirPath + from,
|
||||
to : dirPath + to,
|
||||
};
|
||||
DOM.setCurrentName(to, current);
|
||||
Storage.remove(dirPath);
|
||||
|
||||
RESTful.mv(files, (error) => {
|
||||
if (error)
|
||||
return;
|
||||
|
||||
DOM.setCurrentName(to, current);
|
||||
Storage.remove(dirPath);
|
||||
|
||||
if (isExist)
|
||||
CloudCmd.refresh();
|
||||
});
|
||||
if (isExist)
|
||||
CloudCmd.refresh();
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -940,7 +942,7 @@ function CmdProto() {
|
|||
const info = DOM.CurrentInfo;
|
||||
const current = currentFile || DOM.getCurrentFile();
|
||||
const files = current.parentElement;
|
||||
const panel = files.parentElement;
|
||||
const panel = files.parentElement || DOM.getPanel();
|
||||
|
||||
const panelPassive = DOM.getPanel({
|
||||
active: false,
|
||||
|
|
|
|||
|
|
@ -186,6 +186,9 @@ function KeyProto() {
|
|||
break;
|
||||
|
||||
case Key.F2:
|
||||
if (CloudCmd.config('userMenu'))
|
||||
return CloudCmd.UserMenu.show();
|
||||
|
||||
DOM.renameCurrent(current);
|
||||
break;
|
||||
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ module.exports.initKeysPanel = () => {
|
|||
|
||||
const clickFuncs = {
|
||||
'f1' : CloudCmd.Help.show,
|
||||
'f2' : DOM.renameCurrent,
|
||||
'f2' : initF2,
|
||||
'f3' : CloudCmd.View.show,
|
||||
'f4' : CloudCmd.EditFile.show,
|
||||
'f5' : operation('copy'),
|
||||
|
|
@ -141,6 +141,13 @@ module.exports.initKeysPanel = () => {
|
|||
});
|
||||
};
|
||||
|
||||
function initF2() {
|
||||
if (CloudCmd.config('userMenu'))
|
||||
return CloudCmd.UserMenu.show();
|
||||
|
||||
return DOM.renameCurrent();
|
||||
}
|
||||
|
||||
const getPanel = (side) => {
|
||||
if (!itype.string(side))
|
||||
return side;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
'use strict';
|
||||
|
||||
/* global DOM */
|
||||
/* global CloudCmd */
|
||||
|
||||
const {
|
||||
Dialog,
|
||||
|
|
|
|||
146
client/modules/terminal-run.js
Normal file
146
client/modules/terminal-run.js
Normal file
|
|
@ -0,0 +1,146 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd, gritty */
|
||||
|
||||
const {promisify} = require('es6-promisify');
|
||||
const tryToCatch = require('try-to-catch/legacy');
|
||||
|
||||
require('../../css/terminal.css');
|
||||
|
||||
const exec = require('execon');
|
||||
const load = require('load.js');
|
||||
const DOM = require('../dom');
|
||||
const Images = require('../dom/images');
|
||||
|
||||
const loadParallel = promisify(load.parallel);
|
||||
|
||||
const {Dialog} = DOM;
|
||||
const {
|
||||
Key,
|
||||
config,
|
||||
} = CloudCmd;
|
||||
|
||||
CloudCmd.TerminalRun = exports;
|
||||
|
||||
let Loaded;
|
||||
let Terminal;
|
||||
let Socket;
|
||||
|
||||
const loadAll = async () => {
|
||||
const {prefix} = CloudCmd;
|
||||
|
||||
const prefixGritty = getPrefix();
|
||||
const js = `${prefixGritty}/gritty.js`;
|
||||
const css = `${prefix}/dist/terminal.css`;
|
||||
|
||||
const [e] = await tryToCatch(loadParallel, [js, css]);
|
||||
|
||||
if (e) {
|
||||
const src = e.target.src.replace(window.location.href, '');
|
||||
return Dialog.alert(`file ${src} could not be loaded`);
|
||||
}
|
||||
|
||||
Loaded = true;
|
||||
};
|
||||
|
||||
module.exports.init = async () => {
|
||||
if (!config('terminal'))
|
||||
return;
|
||||
|
||||
Images.show.load('top');
|
||||
|
||||
await CloudCmd.View();
|
||||
await loadAll();
|
||||
};
|
||||
|
||||
module.exports.show = show;
|
||||
module.exports.hide = hide;
|
||||
|
||||
function hide () {
|
||||
CloudCmd.View.hide();
|
||||
}
|
||||
|
||||
function getPrefix() {
|
||||
return CloudCmd.prefix + '/gritty';
|
||||
}
|
||||
|
||||
function getPrefixSocket() {
|
||||
return CloudCmd.prefixSocket + '/gritty';
|
||||
}
|
||||
|
||||
function getEnv() {
|
||||
return {
|
||||
ACTIVE_DIR: DOM.getCurrentDirPath,
|
||||
PASSIVE_DIR: DOM.getNotCurrentDirPath,
|
||||
CURRENT_NAME: DOM.getCurrentName,
|
||||
CURRENT_PATH: DOM.getCurrentPath,
|
||||
};
|
||||
}
|
||||
|
||||
function create(createOptions) {
|
||||
const {
|
||||
command,
|
||||
autoClose,
|
||||
closeMessage = 'Press any key to close Terminal...',
|
||||
} = createOptions;
|
||||
|
||||
const options = {
|
||||
env: getEnv(),
|
||||
prefix: getPrefixSocket(),
|
||||
socketPath: CloudCmd.prefix,
|
||||
fontFamily: 'Droid Sans Mono',
|
||||
command,
|
||||
autoRestart: false,
|
||||
};
|
||||
|
||||
let commandExit = false;
|
||||
|
||||
const {socket, terminal} = gritty(document.body, options);
|
||||
|
||||
Socket = socket;
|
||||
Terminal = terminal;
|
||||
|
||||
Terminal.on('key', (char, {keyCode, shiftKey}) => {
|
||||
if (commandExit)
|
||||
hide();
|
||||
|
||||
if (shiftKey && keyCode === Key.ESC) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
||||
Socket.on('exit', () => {
|
||||
if (autoClose)
|
||||
return hide();
|
||||
|
||||
terminal.write(`\n${closeMessage}`);
|
||||
commandExit = true;
|
||||
});
|
||||
|
||||
Socket.on('connect', exec.with(authCheck, socket));
|
||||
}
|
||||
|
||||
function authCheck(spawn) {
|
||||
spawn.emit('auth', config('username'), config('password'));
|
||||
|
||||
spawn.on('reject', () => {
|
||||
Dialog.alert('Wrong credentials!');
|
||||
});
|
||||
}
|
||||
|
||||
async function show(options = {}) {
|
||||
if (!Loaded)
|
||||
return;
|
||||
|
||||
if (!config('terminal'))
|
||||
return;
|
||||
|
||||
await create(options);
|
||||
|
||||
CloudCmd.View.show(Terminal.element, {
|
||||
afterShow: () => {
|
||||
Terminal.focus();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -24,6 +24,7 @@ CloudCmd.Terminal = exports;
|
|||
|
||||
let Loaded;
|
||||
let Terminal;
|
||||
let Socket;
|
||||
|
||||
const loadAll = async () => {
|
||||
const {prefix} = CloudCmd;
|
||||
|
|
@ -84,17 +85,19 @@ function create() {
|
|||
socketPath: CloudCmd.prefix,
|
||||
fontFamily: 'Droid Sans Mono',
|
||||
};
|
||||
|
||||
const {socket, terminal} = gritty(document.body, options);
|
||||
|
||||
Socket = socket;
|
||||
Terminal = terminal;
|
||||
|
||||
terminal.on('key', (char, {keyCode, shiftKey}) => {
|
||||
Terminal.on('key', (char, {keyCode, shiftKey}) => {
|
||||
if (shiftKey && keyCode === Key.ESC) {
|
||||
hide();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('connect', exec.with(authCheck, socket));
|
||||
Socket.on('connect', exec.with(authCheck, socket));
|
||||
}
|
||||
|
||||
function authCheck(spawn) {
|
||||
|
|
@ -105,7 +108,7 @@ function authCheck(spawn) {
|
|||
});
|
||||
}
|
||||
|
||||
function show(callback) {
|
||||
function show() {
|
||||
if (!Loaded)
|
||||
return;
|
||||
|
||||
|
|
@ -114,10 +117,7 @@ function show(callback) {
|
|||
|
||||
CloudCmd.View.show(Terminal.element, {
|
||||
afterShow: () => {
|
||||
if (Terminal)
|
||||
Terminal.focus();
|
||||
|
||||
exec(callback);
|
||||
Terminal.focus();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
|
|||
20
client/modules/user-menu/get-user-menu.js
Normal file
20
client/modules/user-menu/get-user-menu.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
'use strict';
|
||||
|
||||
const defaultUserMenu = {
|
||||
'F2 - Rename file': async ({DOM}) => {
|
||||
DOM.renameCurrent();
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = (menuFn) => {
|
||||
if (!menuFn)
|
||||
return defaultUserMenu;
|
||||
|
||||
const module = {};
|
||||
const fn = Function('module', menuFn);
|
||||
|
||||
fn(module);
|
||||
|
||||
return module.exports;
|
||||
};
|
||||
|
||||
29
client/modules/user-menu/get-user-menu.spec.js
Normal file
29
client/modules/user-menu/get-user-menu.spec.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
'use strict';
|
||||
|
||||
const test = require('supertape');
|
||||
const getUserMenu = require('./get-user-menu');
|
||||
|
||||
test('user-menu: getUserMenu', (t) => {
|
||||
const menu = `module.exports = {
|
||||
'F2 - Rename file': ({DOM}) => {
|
||||
const {element} = DOM.CurrentInfo;
|
||||
DOM.renameCurrent(element);
|
||||
}
|
||||
}`;
|
||||
|
||||
const result = getUserMenu(menu);
|
||||
|
||||
const [key] = Object.keys(result);
|
||||
|
||||
t.equal(key, 'F2 - Rename file', 'should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
test('user-menu: getUserMenu: no args', (t) => {
|
||||
const result = getUserMenu();
|
||||
const [key] = Object.keys(result);
|
||||
|
||||
t.equal(key, 'F2 - Rename file', 'should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
112
client/modules/user-menu/index.js
Normal file
112
client/modules/user-menu/index.js
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
'use strict';
|
||||
|
||||
/* global CloudCmd, DOM */
|
||||
|
||||
require('../../../css/user-menu.css');
|
||||
|
||||
const currify = require('currify/legacy');
|
||||
const {promisify} = require('es6-promisify');
|
||||
const load = require('load.js');
|
||||
const createElement = require('@cloudcmd/create-element');
|
||||
|
||||
const Images = require('../../dom/images');
|
||||
const getUserMenu = require('./get-user-menu');
|
||||
|
||||
const loadCSS = promisify(load.css);
|
||||
|
||||
const Name = 'UserMenu';
|
||||
CloudCmd[Name] = module.exports;
|
||||
|
||||
const {Key} = CloudCmd;
|
||||
|
||||
module.exports.init = async () => {
|
||||
await Promise.all([
|
||||
loadCSS(`${CloudCmd.prefix}/dist/user-menu.css`),
|
||||
CloudCmd.View(),
|
||||
]);
|
||||
};
|
||||
|
||||
module.exports.show = show;
|
||||
module.exports.hide = hide;
|
||||
|
||||
const getKey = (a) => a.split(' - ')[0];
|
||||
const beginWith = (a) => (b) => !b.indexOf(a);
|
||||
|
||||
const {CurrentInfo} = DOM;
|
||||
|
||||
async function show() {
|
||||
Images.show.load('top');
|
||||
|
||||
const {dirPath} = CurrentInfo;
|
||||
const res = await fetch(`/api/v1/user-menu?dir=${dirPath}`);
|
||||
const userMenu = getUserMenu(await res.text());
|
||||
const options = Object.keys(userMenu);
|
||||
|
||||
const el = createElement('select', {
|
||||
className: 'cloudcmd-user-menu',
|
||||
innerHTML: fillTemplate(options),
|
||||
size: 10,
|
||||
});
|
||||
|
||||
const keys = options.map(getKey);
|
||||
el.addEventListener('keydown', onKeyDown(keys, options, userMenu));
|
||||
el.addEventListener('dblclick', onDblClick(options, userMenu));
|
||||
|
||||
const afterShow = () => el.focus();
|
||||
const autoSize = true;
|
||||
|
||||
Images.hide();
|
||||
|
||||
CloudCmd.View.show(el, {
|
||||
autoSize,
|
||||
afterShow,
|
||||
});
|
||||
}
|
||||
|
||||
function fillTemplate(options) {
|
||||
const result = [];
|
||||
|
||||
for (const option of options) {
|
||||
result.push(`<option>${option}</option>`);
|
||||
}
|
||||
|
||||
return result.join('');
|
||||
}
|
||||
|
||||
function hide() {
|
||||
CloudCmd.View.hide();
|
||||
}
|
||||
|
||||
const onDblClick = currify(async (options, userMenu, e) => {
|
||||
const {value} = e.target;
|
||||
await runUserMenu(value, options, userMenu);
|
||||
});
|
||||
|
||||
const onKeyDown = currify(async (keys, options, userMenu, e) => {
|
||||
const {keyCode} = e;
|
||||
const key = e.key.toUpperCase();
|
||||
|
||||
let value;
|
||||
|
||||
if (keyCode === Key.ENTER)
|
||||
({value} = e.target);
|
||||
else if (keys.includes(key))
|
||||
value = options.find(beginWith(key));
|
||||
else
|
||||
return;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
await runUserMenu(value, options, userMenu);
|
||||
});
|
||||
|
||||
const runUserMenu = async (value, options, userMenu) => {
|
||||
hide();
|
||||
|
||||
await userMenu[value]({
|
||||
DOM,
|
||||
CloudCmd,
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -132,3 +132,8 @@
|
|||
font-family : 'Fontello';
|
||||
content : '\e81b ';
|
||||
}
|
||||
|
||||
.icon-user-menu::before {
|
||||
font-family : 'Fontello';
|
||||
content : '\e81c ';
|
||||
}
|
||||
|
|
|
|||
14
css/user-menu.css
Normal file
14
css/user-menu.css
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
.cloudcmd-user-menu {
|
||||
font-size: 16px;
|
||||
font-family: 'Droid Sans Mono', 'Ubuntu Mono', 'Consolas', monospace;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
.cloudcmd-user-menu:focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
.cloudcmd-user-menu > option:checked {
|
||||
box-shadow: 20px -20px 0 2px rgba(49, 123, 249) inset;
|
||||
}
|
||||
|
||||
Binary file not shown.
|
|
@ -174,6 +174,12 @@
|
|||
"code": 59413,
|
||||
"src": "entypo"
|
||||
},
|
||||
{
|
||||
"uid": "f805bb95d40c7ef2bc51b3d50d4f2e5c",
|
||||
"css": "th-list",
|
||||
"code": 59420,
|
||||
"src": "fontawesome"
|
||||
},
|
||||
{
|
||||
"uid": "60617c8adc1e7eb3c444a5491dd13f57",
|
||||
"css": "attention-circled-1",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg xmlns="http://www.w3.org/2000/svg">
|
||||
<metadata>Copyright (C) 2018 by original authors @ fontello.com</metadata>
|
||||
<metadata>Copyright (C) 2019 by original authors @ fontello.com</metadata>
|
||||
<defs>
|
||||
<font id="fontello" horiz-adv-x="1000" >
|
||||
<font-face font-family="fontello" font-weight="400" font-stretch="normal" units-per-em="1000" ascent="850" descent="-150" />
|
||||
|
|
@ -61,6 +61,8 @@
|
|||
<glyph glyph-name="logout" unicode="" d="M502 0l0 100 98 0 0-100q0-40-29-70t-71-30l-400 0q-40 0-70 30t-30 70l0 700q0 42 30 71t70 29l400 0q42 0 71-29t29-71l0-150-98 0 0 150-402 0 0-700 402 0z m398 326l-198-196 0 120-450 0 0 150 450 0 0 120z" horiz-adv-x="900" />
|
||||
|
||||
<glyph glyph-name="terminal-1" unicode="" d="M1360 849v-1000h-1360v1000h1360z m-838-600h318v77h-318v-77z m-362 77l317 135v96l-317 134v-99l209-84-209-83v-99z" horiz-adv-x="1360" />
|
||||
|
||||
<glyph glyph-name="th-list" unicode="" d="M286 154v-108q0-22-16-37t-38-16h-178q-23 0-38 16t-16 37v108q0 22 16 38t38 15h178q23 0 38-15t16-38z m0 285v-107q0-22-16-38t-38-15h-178q-23 0-38 15t-16 38v107q0 23 16 38t38 16h178q23 0 38-16t16-38z m714-285v-108q0-22-16-37t-38-16h-535q-23 0-38 16t-16 37v108q0 22 16 38t38 15h535q23 0 38-15t16-38z m-714 571v-107q0-22-16-38t-38-16h-178q-23 0-38 16t-16 38v107q0 22 16 38t38 16h178q23 0 38-16t16-38z m714-286v-107q0-22-16-38t-38-15h-535q-23 0-38 15t-16 38v107q0 23 16 38t38 16h535q23 0 38-16t16-38z m0 286v-107q0-22-16-38t-38-16h-535q-23 0-38 16t-16 38v107q0 22 16 38t38 16h535q23 0 38-16t16-38z" horiz-adv-x="1000" />
|
||||
</font>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 12 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -21,6 +21,7 @@
|
|||
<div id="js-keyspanel" class="keyspanel">
|
||||
<button id=f1 class="cmd-button reduce-text icon-help" title="Help" >F1</button>
|
||||
<button id=f2 class="cmd-button reduce-text icon-rename" title="Rename" >F2</button>
|
||||
<button id=f2 class="cmd-button reduce-text icon-user-menu" title="User Menu" >F2</button>
|
||||
<button id=f3 class="cmd-button reduce-text icon-view" title="View" >F3</button>
|
||||
<button id=f4 class="cmd-button reduce-text icon-edit " title="Edit" >F4</button>
|
||||
<button id=f5 class="cmd-button reduce-text icon-copy" title="Copy" >F5</button>
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@
|
|||
"importListen": false,
|
||||
"log": true,
|
||||
"dropbox": false,
|
||||
"dropboxToken": ""
|
||||
"dropboxToken": "",
|
||||
"userMenu": false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@
|
|||
"--dropbox ": "enable dropbox integration",
|
||||
"--dropbox-token ": "set dropbox token",
|
||||
"--log ": "enable logging",
|
||||
"--user-menu ": "enable user menu",
|
||||
"--no-show-config ": "do not show config values",
|
||||
"--no-server ": "do not start server",
|
||||
"--no-auth ": "disable authorization",
|
||||
|
|
@ -70,5 +71,6 @@
|
|||
"--no-show-file-name ": "do not show file name in view and edit",
|
||||
"--no-dropbox ": "disable dropbox integration",
|
||||
"--no-dropbox-token ": "unset dropbox token",
|
||||
"--no-log ": "disable logging"
|
||||
"--no-log ": "disable logging",
|
||||
"--no-user-menu ": "disable user menu"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,9 @@
|
|||
"operation",
|
||||
"konsole",
|
||||
"terminal",
|
||||
"cloud"
|
||||
"terminal-run",
|
||||
"cloud",
|
||||
"user-menu"
|
||||
],
|
||||
"remote": [{
|
||||
"name": "socket",
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ programs in browser from any computer, mobile or tablet device.
|
|||
--dropbox enable dropbox integration
|
||||
--dropbox-token set dropbox token
|
||||
--log enable logging
|
||||
--user-menu enable user menu
|
||||
--no-show-config do not show config values
|
||||
--no-server do not start server
|
||||
--no-auth disable authorization
|
||||
|
|
@ -94,6 +95,7 @@ programs in browser from any computer, mobile or tablet device.
|
|||
--no-dropbox disable dropbox integration
|
||||
--no-dropbox-token unset dropbox token
|
||||
--no-log disable logging
|
||||
--no-user-menu disable user menu
|
||||
|
||||
.SH RESOURCES AND DOCUMENTATION
|
||||
|
||||
|
|
|
|||
|
|
@ -102,6 +102,7 @@
|
|||
"execon": "^1.2.0",
|
||||
"express": "^4.13.0",
|
||||
"files-io": "^3.0.0",
|
||||
"find-up": "^4.0.0",
|
||||
"flop": "^6.0.0",
|
||||
"for-each-key": "^1.0.1",
|
||||
"format-io": "^1.0.0",
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ const cloudfunc = require(DIR_COMMON + 'cloudfunc');
|
|||
const authentication = require(DIR + 'auth');
|
||||
const config = require(DIR + 'config');
|
||||
const modulas = require(DIR + 'modulas');
|
||||
const userMenu = require(DIR + 'user-menu');
|
||||
const rest = require(DIR + 'rest');
|
||||
const route = require(DIR + 'route');
|
||||
const validate = require(DIR + 'validate');
|
||||
|
|
@ -223,6 +224,7 @@ function cloudcmd(prefix, plugins, modules) {
|
|||
root,
|
||||
}),
|
||||
|
||||
userMenu,
|
||||
rest,
|
||||
route({
|
||||
html: defaultHtml,
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ function indexProcessing(options) {
|
|||
const noConfig = !config('configDialog');
|
||||
const noConsole = !config('console');
|
||||
const noTerminal = !config('terminal');
|
||||
const noUserMenu = !config('userMenu');
|
||||
const {panel} = options;
|
||||
|
||||
let {data} = options;
|
||||
|
|
@ -139,6 +140,13 @@ function indexProcessing(options) {
|
|||
data = data
|
||||
.replace('icon-terminal', 'icon-terminal none');
|
||||
|
||||
if (noUserMenu)
|
||||
data = data
|
||||
.replace('icon-user-menu', 'icon-user-menu none');
|
||||
else
|
||||
data = data
|
||||
.replace('icon-rename', 'icon-rename none');
|
||||
|
||||
const left = rendy(Template.panel, {
|
||||
side : 'left',
|
||||
content : panel,
|
||||
|
|
|
|||
|
|
@ -264,6 +264,7 @@ test('cloudcmd: route: realpath: error', async (t) => {
|
|||
options,
|
||||
});
|
||||
|
||||
/*eslint require-atomic-updates:0*/
|
||||
fs.realpath = realpath;
|
||||
|
||||
t.ok(/^ENOENT/.test(body), 'should return error');
|
||||
|
|
|
|||
53
server/user-menu.js
Normal file
53
server/user-menu.js
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
'use strict';
|
||||
|
||||
const {homedir} = require('os');
|
||||
const fs = require('fs');
|
||||
const {join} = require('path');
|
||||
const {promisify} = require('util');
|
||||
|
||||
const tryToCatch = require('try-to-catch');
|
||||
const findUp = require('find-up');
|
||||
|
||||
const readFile = promisify(fs.readFile);
|
||||
const menuName = '.cloudcmd.menu.js';
|
||||
const homeMenuPath = join(homedir(), menuName);
|
||||
|
||||
module.exports = async (req, res, next) => {
|
||||
if (req.url.indexOf('/api/v1/user-menu'))
|
||||
return next();
|
||||
|
||||
const {method} = req;
|
||||
|
||||
if (method === 'GET')
|
||||
return onGET(req.query, res);
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
async function onGET({dir}, res) {
|
||||
const [errorFind, currentMenuPath] = await tryToCatch(findUp, [
|
||||
menuName,
|
||||
], {cwd: dir});
|
||||
|
||||
if (errorFind && errorFind.code !== 'ENOENT')
|
||||
return res
|
||||
.status(404)
|
||||
.send(e.message);
|
||||
|
||||
if (errorFind && errorFind.code === 'ENOENT')
|
||||
return res.send('');
|
||||
|
||||
const menuPath = currentMenuPath || homeMenuPath;
|
||||
const [e, data] = await tryToCatch(readFile, menuPath, 'utf8');
|
||||
|
||||
if (!e)
|
||||
return res.send(data);
|
||||
|
||||
if (e.code !== 'ENOENT')
|
||||
return res
|
||||
.status(404)
|
||||
.send(e.message);
|
||||
|
||||
return res.send('');
|
||||
}
|
||||
|
||||
21
server/user-menu.spec.js
Normal file
21
server/user-menu.spec.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const {join} = require('path');
|
||||
|
||||
const test = require('supertape');
|
||||
const serveOnce = require('serve-once');
|
||||
|
||||
const userMenu = require('./user-menu');
|
||||
const {request} = serveOnce(() => userMenu);
|
||||
|
||||
const userMenuPath = join(__dirname, '..', '.cloudcmd.menu.js');
|
||||
const userMenuFile = fs.readFileSync(userMenuPath, 'utf8');
|
||||
|
||||
test('cloudcmd: user menu', async (t) => {
|
||||
const {body} = await request.get(`/api/v1/user-menu?dir=${__dirname}`);
|
||||
|
||||
t.equal(userMenuFile, body, 'should equal');
|
||||
t.end();
|
||||
});
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue