mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-12 20:18:41 -09:00
[WIP] Use XO instead of Standard (#723)
* Bump `eslint-plugin-react`
* Add `eslint-config-xo-react`
* Add XO
* Remove eslint-related dependencies, add XO config and use XO as the linter
* Code style: Standard => XO ✨
* Use xo property to ignore files
* Fix remaining errors
This commit is contained in:
parent
32a665d7c0
commit
1866104d03
49 changed files with 910 additions and 784 deletions
|
|
@ -1,12 +0,0 @@
|
|||
# build output
|
||||
dist
|
||||
build
|
||||
app/dist
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
app/node_modules
|
||||
|
||||
# other
|
||||
app/static
|
||||
assets
|
||||
|
|
@ -1,17 +1,18 @@
|
|||
const { autoUpdater } = require('electron');
|
||||
const { version } = require('./package');
|
||||
const notify = require('./notify'); // eslint-disable-line no-unused-vars
|
||||
const {autoUpdater} = require('electron');
|
||||
const ms = require('ms');
|
||||
|
||||
const notify = require('./notify'); // eslint-disable-line no-unused-vars
|
||||
const {version} = require('./package');
|
||||
|
||||
// accepted values: `osx`, `win32`
|
||||
// https://nuts.gitbook.com/update-windows.html
|
||||
const platform = 'darwin' === process.platform
|
||||
? 'osx'
|
||||
: process.platform;
|
||||
const platform = process.platform === 'darwin' ?
|
||||
'osx' :
|
||||
process.platform;
|
||||
const FEED_URL = `https://hyperterm-updates.now.sh/update/${platform}`;
|
||||
let isInit = false;
|
||||
|
||||
function init () {
|
||||
function init() {
|
||||
autoUpdater.on('error', (err, msg) => {
|
||||
console.error('Error fetching updates', msg + ' (' + err.stack + ')');
|
||||
});
|
||||
|
|
@ -30,12 +31,14 @@ function init () {
|
|||
}
|
||||
|
||||
module.exports = function (win) {
|
||||
if (!isInit) init();
|
||||
if (!isInit) {
|
||||
init();
|
||||
}
|
||||
|
||||
const { rpc } = win;
|
||||
const {rpc} = win;
|
||||
|
||||
const onupdate = (ev, releaseNotes, releaseName) => {
|
||||
rpc.emit('update available', { releaseNotes, releaseName });
|
||||
rpc.emit('update available', {releaseNotes, releaseName});
|
||||
};
|
||||
|
||||
autoUpdater.on('update-downloaded', onupdate);
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
const { dialog } = require('electron');
|
||||
const { homedir } = require('os');
|
||||
const { resolve } = require('path');
|
||||
const { readFileSync, writeFileSync } = require('fs');
|
||||
const gaze = require('gaze');
|
||||
const {homedir} = require('os');
|
||||
const {readFileSync, writeFileSync} = require('fs');
|
||||
const {resolve} = require('path');
|
||||
const vm = require('vm');
|
||||
|
||||
const {dialog} = require('electron');
|
||||
const gaze = require('gaze');
|
||||
const notify = require('./notify');
|
||||
|
||||
const path = resolve(homedir(), '.hyperterm.js');
|
||||
|
|
@ -11,14 +12,16 @@ const watchers = [];
|
|||
|
||||
let cfg = {};
|
||||
|
||||
function watch () {
|
||||
function watch() {
|
||||
gaze(path, function (err) {
|
||||
if (err) throw err;
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
this.on('changed', () => {
|
||||
try {
|
||||
if (exec(readFileSync(path, 'utf8'))) {
|
||||
notify('HyperTerm configuration reloaded!');
|
||||
watchers.forEach((fn) => fn());
|
||||
watchers.forEach(fn => fn());
|
||||
}
|
||||
} catch (err) {
|
||||
dialog.showMessageBox({
|
||||
|
|
@ -31,12 +34,14 @@ function watch () {
|
|||
}
|
||||
|
||||
let _str; // last script
|
||||
function exec (str) {
|
||||
if (str === _str) return false;
|
||||
function exec(str) {
|
||||
if (str === _str) {
|
||||
return false;
|
||||
}
|
||||
_str = str;
|
||||
const script = new vm.Script(str);
|
||||
const module = {};
|
||||
script.runInNewContext({ module });
|
||||
script.runInNewContext({module});
|
||||
if (!module.exports) {
|
||||
throw new Error('Error reading configuration: `module.exports` not set');
|
||||
}
|
||||
|
|
|
|||
93
app/index.js
93
app/index.js
|
|
@ -1,21 +1,28 @@
|
|||
const { app, BrowserWindow, shell, Menu } = require('electron');
|
||||
const createRPC = require('./rpc');
|
||||
const createMenu = require('./menu');
|
||||
// Native
|
||||
const {resolve} = require('path');
|
||||
|
||||
// Packages
|
||||
const {parse: parseUrl} = require('url');
|
||||
const {gitDescribe} = require('git-describe');
|
||||
const {app, BrowserWindow, shell, Menu} = require('electron');
|
||||
const uuid = require('uuid');
|
||||
const { resolve } = require('path');
|
||||
const { parse: parseUrl } = require('url');
|
||||
const fileUriToPath = require('file-uri-to-path');
|
||||
const isDev = require('electron-is-dev');
|
||||
|
||||
// Ours
|
||||
const AutoUpdater = require('./auto-updater');
|
||||
const toElectronBackgroundColor = require('./utils/to-electron-background-color');
|
||||
const createMenu = require('./menu');
|
||||
const createRPC = require('./rpc');
|
||||
const notify = require('./notify');
|
||||
const { gitDescribe } = require('git-describe');
|
||||
|
||||
app.commandLine.appendSwitch('js-flags', '--harmony');
|
||||
|
||||
// set up config
|
||||
const config = require('./config');
|
||||
|
||||
config.init();
|
||||
|
||||
const plugins = require('./plugins');
|
||||
const Session = require('./session');
|
||||
|
||||
|
|
@ -29,7 +36,9 @@ app.getWindows = () => new Set([...windowSet]); // return a clone
|
|||
// function to retrive the last focused window in windowSet;
|
||||
// added to app object in order to expose it to plugins.
|
||||
app.getLastFocusedWindow = () => {
|
||||
if (!windowSet.size) return null;
|
||||
if (!windowSet.size) {
|
||||
return null;
|
||||
}
|
||||
return Array.from(windowSet).reduce((lastWindow, win) => {
|
||||
return win.focusTime > lastWindow.focusTime ? win : lastWindow;
|
||||
});
|
||||
|
|
@ -40,7 +49,9 @@ if (isDev) {
|
|||
|
||||
// Overide default appVersion which is set from package.json
|
||||
gitDescribe({customArguments: ['--tags']}, (error, gitInfo) => {
|
||||
if (!error) app.setVersion(gitInfo.raw);
|
||||
if (!error) {
|
||||
app.setVersion(gitInfo.raw);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('running in prod mode');
|
||||
|
|
@ -54,11 +65,11 @@ const url = 'file://' + resolve(
|
|||
console.log('electron will open', url);
|
||||
|
||||
app.on('ready', () => {
|
||||
function createWindow (fn) {
|
||||
function createWindow(fn) {
|
||||
let cfg = plugins.getDecoratedConfig();
|
||||
|
||||
const [width, height] = cfg.windowSize || [540, 380];
|
||||
const { screen } = require('electron');
|
||||
const {screen} = require('electron');
|
||||
|
||||
let startX = 50;
|
||||
let startY = 50;
|
||||
|
|
@ -69,7 +80,7 @@ app.on('ready', () => {
|
|||
const focusedWindow = BrowserWindow.getFocusedWindow() || app.getLastFocusedWindow();
|
||||
if (focusedWindow) {
|
||||
const points = focusedWindow.getPosition();
|
||||
const currentScreen = screen.getDisplayNearestPoint({ x: points[0], y: points[1] });
|
||||
const currentScreen = screen.getDisplayNearestPoint({x: points[0], y: points[1]});
|
||||
|
||||
const biggestX = ((points[0] + 100 + width) - currentScreen.bounds.x);
|
||||
const biggestY = ((points[1] + 100 + height) - currentScreen.bounds.y);
|
||||
|
|
@ -138,7 +149,9 @@ app.on('ready', () => {
|
|||
|
||||
// If no callback is passed to createWindow,
|
||||
// a new session will be created by default.
|
||||
if (!fn) fn = (win) => win.rpc.emit('session add req');
|
||||
if (!fn) {
|
||||
fn = win => win.rpc.emit('session add req');
|
||||
}
|
||||
|
||||
// app.windowCallback is the createWindow callback
|
||||
// that can be setted before the 'ready' app event
|
||||
|
|
@ -155,11 +168,11 @@ app.on('ready', () => {
|
|||
}
|
||||
});
|
||||
|
||||
rpc.on('new', ({ rows = 40, cols = 100, cwd = process.env.HOME }) => {
|
||||
rpc.on('new', ({rows = 40, cols = 100, cwd = process.env.HOME}) => {
|
||||
const shell = cfg.shell;
|
||||
const shellArgs = cfg.shellArgs && Array.from(cfg.shellArgs);
|
||||
|
||||
initSession({ rows, cols, cwd, shell, shellArgs }, (uid, session) => {
|
||||
initSession({rows, cols, cwd, shell, shellArgs}, (uid, session) => {
|
||||
sessions.set(uid, session);
|
||||
rpc.emit('session add', {
|
||||
uid,
|
||||
|
|
@ -167,17 +180,17 @@ app.on('ready', () => {
|
|||
pid: session.pty.pid
|
||||
});
|
||||
|
||||
session.on('data', (data) => {
|
||||
rpc.emit('session data', { uid, data });
|
||||
session.on('data', data => {
|
||||
rpc.emit('session data', {uid, data});
|
||||
});
|
||||
|
||||
session.on('title', (title) => {
|
||||
session.on('title', title => {
|
||||
win.setTitle(title);
|
||||
rpc.emit('session title', { uid, title });
|
||||
rpc.emit('session title', {uid, title});
|
||||
});
|
||||
|
||||
session.on('exit', () => {
|
||||
rpc.emit('session exit', { uid });
|
||||
rpc.emit('session exit', {uid});
|
||||
sessions.delete(uid);
|
||||
});
|
||||
});
|
||||
|
|
@ -186,7 +199,7 @@ app.on('ready', () => {
|
|||
// TODO: this goes away when we are able to poll
|
||||
// for the title ourseleves, instead of relying
|
||||
// on Session and focus/blur to subscribe
|
||||
rpc.on('focus', ({ uid }) => {
|
||||
rpc.on('focus', ({uid}) => {
|
||||
const session = sessions.get(uid);
|
||||
if (typeof session !== 'undefined' && typeof session.lastTitle !== 'undefined') {
|
||||
win.setTitle(session.lastTitle);
|
||||
|
|
@ -197,7 +210,7 @@ app.on('ready', () => {
|
|||
console.log('session not found by', uid);
|
||||
}
|
||||
});
|
||||
rpc.on('blur', ({ uid }) => {
|
||||
rpc.on('blur', ({uid}) => {
|
||||
const session = sessions.get(uid);
|
||||
|
||||
if (session) {
|
||||
|
|
@ -207,7 +220,7 @@ app.on('ready', () => {
|
|||
}
|
||||
});
|
||||
|
||||
rpc.on('exit', ({ uid }) => {
|
||||
rpc.on('exit', ({uid}) => {
|
||||
const session = sessions.get(uid);
|
||||
|
||||
if (session) {
|
||||
|
|
@ -225,17 +238,17 @@ app.on('ready', () => {
|
|||
win.maximize();
|
||||
});
|
||||
|
||||
rpc.on('resize', ({ cols, rows }) => {
|
||||
sessions.forEach((session) => {
|
||||
session.resize({ cols, rows });
|
||||
rpc.on('resize', ({cols, rows}) => {
|
||||
sessions.forEach(session => {
|
||||
session.resize({cols, rows});
|
||||
});
|
||||
});
|
||||
|
||||
rpc.on('data', ({ uid, data }) => {
|
||||
rpc.on('data', ({uid, data}) => {
|
||||
sessions.get(uid).write(data);
|
||||
});
|
||||
|
||||
rpc.on('open external', ({ url }) => {
|
||||
rpc.on('open external', ({url}) => {
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
|
|
@ -263,11 +276,11 @@ app.on('ready', () => {
|
|||
// If file is dropped onto the terminal window, navigate event is prevented
|
||||
// and his path is added to active session.
|
||||
win.webContents.on('will-navigate', (event, url) => {
|
||||
var protocol = typeof url === 'string' && parseUrl(url).protocol;
|
||||
const protocol = typeof url === 'string' && parseUrl(url).protocol;
|
||||
if (protocol === 'file:') {
|
||||
event.preventDefault();
|
||||
let path = fileUriToPath(url).replace(/ /g, '\\ ');
|
||||
rpc.emit('session data send', { data: path });
|
||||
const path = fileUriToPath(url).replace(/ /g, '\\ ');
|
||||
rpc.emit('session data send', {data: path});
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -282,7 +295,7 @@ app.on('ready', () => {
|
|||
// load plugins
|
||||
load();
|
||||
|
||||
const pluginsUnsubscribe = plugins.subscribe((err) => {
|
||||
const pluginsUnsubscribe = plugins.subscribe(err => {
|
||||
if (!err) {
|
||||
load();
|
||||
win.webContents.send('plugins change');
|
||||
|
|
@ -337,16 +350,18 @@ app.on('ready', () => {
|
|||
const tpl = plugins.decorateMenu(createMenu({
|
||||
createWindow,
|
||||
updatePlugins: () => {
|
||||
plugins.updatePlugins({ force: true });
|
||||
plugins.updatePlugins({force: true});
|
||||
}
|
||||
}));
|
||||
|
||||
// If we're on Mac make a Dock Menu
|
||||
if (process.platform === 'darwin') {
|
||||
const { app, Menu } = require('electron');
|
||||
const dockMenu = Menu.buildFromTemplate([
|
||||
{label: 'New Window', click () { createWindow(); }}
|
||||
]);
|
||||
const dockMenu = Menu.buildFromTemplate([{
|
||||
label: 'New Window',
|
||||
click() {
|
||||
createWindow();
|
||||
}
|
||||
}]);
|
||||
app.dock.setMenu(dockMenu);
|
||||
}
|
||||
|
||||
|
|
@ -362,16 +377,16 @@ app.on('ready', () => {
|
|||
plugins.subscribe(load);
|
||||
});
|
||||
|
||||
function initSession (opts, fn) {
|
||||
function initSession(opts, fn) {
|
||||
fn(uuid.v4(), new Session(opts));
|
||||
}
|
||||
|
||||
app.on('open-file', (event, path) => {
|
||||
const lastWindow = app.getLastFocusedWindow();
|
||||
const callback = win => win.rpc.emit('open file', { path });
|
||||
const callback = win => win.rpc.emit('open file', {path});
|
||||
if (lastWindow) {
|
||||
callback(lastWindow);
|
||||
} else if (!lastWindow && app.hasOwnProperty('createWindow')) {
|
||||
} else if (!lastWindow && {}.hasOwnProperty.call(app, 'createWindow')) {
|
||||
app.createWindow(callback);
|
||||
} else {
|
||||
// if createWindow not exists yet ('ready' event was not fired),
|
||||
|
|
|
|||
47
app/menu.js
47
app/menu.js
|
|
@ -1,12 +1,13 @@
|
|||
const os = require('os');
|
||||
const path = require('path');
|
||||
const { app, shell, dialog } = require('electron');
|
||||
const {app, shell, dialog} = require('electron');
|
||||
|
||||
const appName = app.getName();
|
||||
|
||||
// based on and inspired by
|
||||
// https://github.com/sindresorhus/anatine/blob/master/menu.js
|
||||
|
||||
module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
||||
module.exports = function createMenu({createWindow, updatePlugins}) {
|
||||
return [
|
||||
{
|
||||
label: 'Application',
|
||||
|
|
@ -20,7 +21,7 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'Preferences...',
|
||||
accelerator: 'Cmd+,',
|
||||
click (item, focusedWindow) {
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('preferences');
|
||||
} else {
|
||||
|
|
@ -61,14 +62,14 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'New Window',
|
||||
accelerator: 'CmdOrCtrl+N',
|
||||
click (item, focusedWindow) {
|
||||
click() {
|
||||
createWindow();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'New Tab',
|
||||
accelerator: 'CmdOrCtrl+T',
|
||||
click (item, focusedWindow) {
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('session add req');
|
||||
} else {
|
||||
|
|
@ -82,7 +83,7 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'Close',
|
||||
accelerator: 'CmdOrCtrl+W',
|
||||
click (item, focusedWindow) {
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('session close req');
|
||||
}
|
||||
|
|
@ -125,7 +126,7 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'Clear',
|
||||
accelerator: 'CmdOrCtrl+K',
|
||||
click (item, focusedWindow) {
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('session clear req');
|
||||
}
|
||||
|
|
@ -139,7 +140,7 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'Reload',
|
||||
accelerator: 'CmdOrCtrl+R',
|
||||
click (item, focusedWindow) {
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('reload');
|
||||
}
|
||||
|
|
@ -148,7 +149,7 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'Full Reload',
|
||||
accelerator: 'CmdOrCtrl+Shift+R',
|
||||
click (item, focusedWindow) {
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.reload();
|
||||
}
|
||||
|
|
@ -157,7 +158,7 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'Toggle Developer Tools',
|
||||
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
click (item, focusedWindow) {
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.webContents.toggleDevTools();
|
||||
}
|
||||
|
|
@ -169,7 +170,7 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'Reset Zoom Level',
|
||||
accelerator: 'CmdOrCtrl+0',
|
||||
click (item, focusedWindow) {
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('reset fontSize req');
|
||||
}
|
||||
|
|
@ -178,7 +179,7 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'Zoom In',
|
||||
accelerator: 'CmdOrCtrl+plus',
|
||||
click (item, focusedWindow) {
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('increase fontSize req');
|
||||
}
|
||||
|
|
@ -187,7 +188,7 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'Zoom Out',
|
||||
accelerator: 'CmdOrCtrl+-',
|
||||
click (item, focusedWindow) {
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('decrease fontSize req');
|
||||
}
|
||||
|
|
@ -201,7 +202,7 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'Update All Now',
|
||||
accelerator: 'CmdOrCtrl+Shift+U',
|
||||
click () {
|
||||
click() {
|
||||
updatePlugins();
|
||||
}
|
||||
}
|
||||
|
|
@ -222,7 +223,7 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'Show Previous Tab',
|
||||
accelerator: 'CmdOrCtrl+Option+Left',
|
||||
click (item, focusedWindow) {
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('move left req');
|
||||
}
|
||||
|
|
@ -231,7 +232,7 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
{
|
||||
label: 'Show Next Tab',
|
||||
accelerator: 'CmdOrCtrl+Option+Right',
|
||||
click (item, focusedWindow) {
|
||||
click(item, focusedWindow) {
|
||||
if (focusedWindow) {
|
||||
focusedWindow.rpc.emit('move right req');
|
||||
}
|
||||
|
|
@ -253,13 +254,13 @@ module.exports = function createMenu ({ createWindow, updatePlugins }) {
|
|||
submenu: [
|
||||
{
|
||||
label: `${appName} Website`,
|
||||
click () {
|
||||
click() {
|
||||
shell.openExternal('https://hyperterm.now.sh');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Report an Issue...',
|
||||
click () {
|
||||
click() {
|
||||
const body = `
|
||||
<!-- Please succinctly describe your issue and steps to reproduce it. -->
|
||||
|
||||
|
|
@ -273,12 +274,13 @@ ${process.platform} ${process.arch} ${os.release()}`;
|
|||
}
|
||||
},
|
||||
...(
|
||||
'darwin' !== process.platform
|
||||
? [
|
||||
{ type: 'separator' },
|
||||
process.platform === 'darwin' ?
|
||||
[] :
|
||||
[
|
||||
{type: 'separator'},
|
||||
{
|
||||
role: 'about',
|
||||
click () {
|
||||
click() {
|
||||
dialog.showMessageBox({
|
||||
title: `About ${appName}`,
|
||||
message: `${appName} ${app.getVersion()}`,
|
||||
|
|
@ -289,7 +291,6 @@ ${process.platform} ${process.arch} ${os.release()}`;
|
|||
}
|
||||
}
|
||||
]
|
||||
: []
|
||||
)
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const { app, BrowserWindow } = require('electron');
|
||||
const {resolve} = require('path');
|
||||
|
||||
const {app, BrowserWindow} = require('electron');
|
||||
const isDev = require('electron-is-dev');
|
||||
const { resolve } = require('path');
|
||||
|
||||
let win;
|
||||
|
||||
|
|
@ -29,10 +30,10 @@ app.on('ready', () => {
|
|||
});
|
||||
});
|
||||
|
||||
function notify (title, body) {
|
||||
function notify(title, body) {
|
||||
console.log(`[Notification] ${title}: ${body}`);
|
||||
if (win) {
|
||||
win.webContents.send('notification', { title, body });
|
||||
win.webContents.send('notification', {title, body});
|
||||
} else {
|
||||
buffer.push([title, body]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
const { app, dialog } = require('electron');
|
||||
const { homedir } = require('os');
|
||||
const { resolve, basename } = require('path');
|
||||
const { writeFileSync } = require('fs');
|
||||
const config = require('./config');
|
||||
const { sync: mkdirpSync } = require('mkdirp');
|
||||
const { exec } = require('child_process');
|
||||
const {exec} = require('child_process');
|
||||
const {homedir} = require('os');
|
||||
const {resolve, basename} = require('path');
|
||||
const {writeFileSync} = require('fs');
|
||||
|
||||
const {app, dialog} = require('electron');
|
||||
const {sync: mkdirpSync} = require('mkdirp');
|
||||
const Config = require('electron-config');
|
||||
const ms = require('ms');
|
||||
const notify = require('./notify');
|
||||
const shellEnv = require('shell-env');
|
||||
|
||||
const config = require('./config');
|
||||
const notify = require('./notify');
|
||||
|
||||
// local storage
|
||||
const cache = new Config();
|
||||
|
||||
|
|
@ -34,7 +36,7 @@ let paths = getPaths(plugins);
|
|||
let id = getId(plugins);
|
||||
let modules = requirePlugins();
|
||||
|
||||
function getId (plugins_) {
|
||||
function getId(plugins_) {
|
||||
return JSON.stringify(plugins_);
|
||||
}
|
||||
|
||||
|
|
@ -56,12 +58,14 @@ config.subscribe(() => {
|
|||
|
||||
let updating = false;
|
||||
|
||||
function updatePlugins ({ force = false } = {}) {
|
||||
if (updating) return notify('Plugin update in progress');
|
||||
function updatePlugins({force = false} = {}) {
|
||||
if (updating) {
|
||||
return notify('Plugin update in progress');
|
||||
}
|
||||
updating = true;
|
||||
syncPackageJSON();
|
||||
const id_ = id;
|
||||
install((err) => {
|
||||
install(err => {
|
||||
updating = false;
|
||||
|
||||
if (err) {
|
||||
|
|
@ -109,15 +113,15 @@ function updatePlugins ({ force = false } = {}) {
|
|||
'No changes!'
|
||||
);
|
||||
}
|
||||
watchers.forEach((fn) => fn(err, { force }));
|
||||
watchers.forEach(fn => fn(err, {force}));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function getPluginVersions () {
|
||||
function getPluginVersions() {
|
||||
const paths_ = paths.plugins.concat(paths.localPlugins);
|
||||
return paths_.map((path) => {
|
||||
return paths_.map(path => {
|
||||
let version = null;
|
||||
try {
|
||||
version = require(resolve(path, 'package.json')).version;
|
||||
|
|
@ -129,10 +133,12 @@ function getPluginVersions () {
|
|||
});
|
||||
}
|
||||
|
||||
function clearCache (mod) {
|
||||
function clearCache() {
|
||||
// trigger unload hooks
|
||||
modules.forEach((mod) => {
|
||||
if (mod.onUnload) mod.onUnload(app);
|
||||
modules.forEach(mod => {
|
||||
if (mod.onUnload) {
|
||||
mod.onUnload(app);
|
||||
}
|
||||
});
|
||||
|
||||
// clear require cache
|
||||
|
|
@ -159,7 +165,7 @@ if (cache.get('hyperterm.plugins') !== id || process.env.HYPERTERM_FORCE_UPDATE)
|
|||
// otherwise update plugins every 5 hours
|
||||
setInterval(updatePlugins, ms('5h'));
|
||||
|
||||
function syncPackageJSON () {
|
||||
function syncPackageJSON() {
|
||||
const dependencies = toDependencies(plugins);
|
||||
const pkg = {
|
||||
name: 'hyperterm-plugins',
|
||||
|
|
@ -180,16 +186,16 @@ function syncPackageJSON () {
|
|||
}
|
||||
}
|
||||
|
||||
function alert (message) {
|
||||
function alert(message) {
|
||||
dialog.showMessageBox({
|
||||
message,
|
||||
buttons: ['Ok']
|
||||
});
|
||||
}
|
||||
|
||||
function toDependencies (plugins) {
|
||||
function toDependencies(plugins) {
|
||||
const obj = {};
|
||||
plugins.plugins.forEach((plugin) => {
|
||||
plugins.plugins.forEach(plugin => {
|
||||
const regex = /.(@|#)/;
|
||||
const match = regex.exec(plugin);
|
||||
|
||||
|
|
@ -207,22 +213,28 @@ function toDependencies (plugins) {
|
|||
return obj;
|
||||
}
|
||||
|
||||
function install (fn) {
|
||||
const { shell: cfgShell, npmRegistry } = exports.getDecoratedConfig();
|
||||
function install(fn) {
|
||||
const {shell: cfgShell, npmRegistry} = exports.getDecoratedConfig();
|
||||
|
||||
const shell = cfgShell && cfgShell !== '' ? cfgShell : undefined;
|
||||
|
||||
shellEnv(shell).then((env) => {
|
||||
if (npmRegistry) env.NPM_CONFIG_REGISTRY = npmRegistry;
|
||||
shellEnv(shell).then(env => {
|
||||
if (npmRegistry) {
|
||||
env.NPM_CONFIG_REGISTRY = npmRegistry;
|
||||
}
|
||||
/* eslint-disable camelcase */
|
||||
env.npm_config_runtime = 'electron';
|
||||
env.npm_config_target = '1.3.0';
|
||||
env.npm_config_disturl = 'https://atom.io/download/atom-shell';
|
||||
/* eslint-enable camelcase */
|
||||
exec('npm prune; npm install --production', {
|
||||
cwd: path,
|
||||
env,
|
||||
shell
|
||||
}, (err, stdout, stderr) => {
|
||||
if (err) return fn(err);
|
||||
}, err => {
|
||||
if (err) {
|
||||
return fn(err);
|
||||
}
|
||||
fn(null);
|
||||
});
|
||||
}).catch(fn);
|
||||
|
|
@ -235,12 +247,12 @@ exports.subscribe = function (fn) {
|
|||
};
|
||||
};
|
||||
|
||||
function getPaths () {
|
||||
function getPaths() {
|
||||
return {
|
||||
plugins: plugins.plugins.map((name) => {
|
||||
plugins: plugins.plugins.map(name => {
|
||||
return resolve(path, 'node_modules', name.split('#')[0]);
|
||||
}),
|
||||
localPlugins: plugins.localPlugins.map((name) => {
|
||||
localPlugins: plugins.localPlugins.map(name => {
|
||||
return resolve(localPath, name);
|
||||
})
|
||||
};
|
||||
|
|
@ -251,13 +263,13 @@ exports.getPaths = getPaths;
|
|||
|
||||
// get paths from renderer
|
||||
exports.getBasePaths = function () {
|
||||
return { path, localPath };
|
||||
return {path, localPath};
|
||||
};
|
||||
|
||||
function requirePlugins () {
|
||||
const { plugins, localPlugins } = paths;
|
||||
function requirePlugins() {
|
||||
const {plugins, localPlugins} = paths;
|
||||
|
||||
const load = (path) => {
|
||||
const load = path => {
|
||||
let mod;
|
||||
try {
|
||||
mod = require(path);
|
||||
|
|
@ -279,11 +291,11 @@ function requirePlugins () {
|
|||
|
||||
return plugins.map(load)
|
||||
.concat(localPlugins.map(load))
|
||||
.filter(v => !!v);
|
||||
.filter(v => Boolean(v));
|
||||
}
|
||||
|
||||
exports.onApp = function (app) {
|
||||
modules.forEach((plugin) => {
|
||||
modules.forEach(plugin => {
|
||||
if (plugin.onApp) {
|
||||
plugin.onApp(app);
|
||||
}
|
||||
|
|
@ -291,7 +303,7 @@ exports.onApp = function (app) {
|
|||
};
|
||||
|
||||
exports.onWindow = function (win) {
|
||||
modules.forEach((plugin) => {
|
||||
modules.forEach(plugin => {
|
||||
if (plugin.onWindow) {
|
||||
plugin.onWindow(win);
|
||||
}
|
||||
|
|
@ -300,12 +312,12 @@ exports.onWindow = function (win) {
|
|||
|
||||
// decorates the base object by calling plugin[key]
|
||||
// for all the available plugins
|
||||
function decorateObject (base, key) {
|
||||
function decorateObject(base, key) {
|
||||
let decorated = base;
|
||||
modules.forEach((plugin) => {
|
||||
modules.forEach(plugin => {
|
||||
if (plugin[key]) {
|
||||
const res = plugin[key](decorated);
|
||||
if (res && 'object' === typeof res) {
|
||||
if (res && typeof res === 'object') {
|
||||
decorated = res;
|
||||
} else {
|
||||
notify('Plugin error!', `"${plugin._name}": invalid return type for \`${key}\``);
|
||||
|
|
|
|||
22
app/rpc.js
22
app/rpc.js
|
|
@ -1,15 +1,17 @@
|
|||
const { EventEmitter } = require('events');
|
||||
const { ipcMain } = require('electron');
|
||||
const {EventEmitter} = require('events');
|
||||
const {ipcMain} = require('electron');
|
||||
const uuid = require('uuid');
|
||||
|
||||
class Server extends EventEmitter {
|
||||
|
||||
constructor (win) {
|
||||
constructor(win) {
|
||||
super();
|
||||
this.win = win;
|
||||
this.ipcListener = this.ipcListener.bind(this);
|
||||
|
||||
if (this.destroyed) return;
|
||||
if (this.destroyed) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uid = uuid.v4();
|
||||
this.id = uid;
|
||||
|
|
@ -24,19 +26,19 @@ class Server extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
get wc () {
|
||||
get wc() {
|
||||
return this.win.webContents;
|
||||
}
|
||||
|
||||
ipcListener (event, { ev, data }) {
|
||||
ipcListener(event, {ev, data}) {
|
||||
super.emit(ev, data);
|
||||
}
|
||||
|
||||
emit (ch, data) {
|
||||
this.wc.send(this.id, { ch, data });
|
||||
emit(ch, data) {
|
||||
this.wc.send(this.id, {ch, data});
|
||||
}
|
||||
|
||||
destroy () {
|
||||
destroy() {
|
||||
this.removeAllListeners();
|
||||
this.wc.removeAllListeners();
|
||||
if (this.id) {
|
||||
|
|
@ -49,6 +51,6 @@ class Server extends EventEmitter {
|
|||
|
||||
}
|
||||
|
||||
module.exports = function createRPC (win) {
|
||||
module.exports = function createRPC(win) {
|
||||
return new Server(win);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
const { app } = require('electron');
|
||||
const { EventEmitter } = require('events');
|
||||
const { exec } = require('child_process');
|
||||
const {exec} = require('child_process');
|
||||
const {EventEmitter} = require('events');
|
||||
|
||||
const {app} = require('electron');
|
||||
const defaultShell = require('default-shell');
|
||||
const { getDecoratedEnv } = require('./plugins');
|
||||
const { productName, version } = require('./package');
|
||||
|
||||
const {getDecoratedEnv} = require('./plugins');
|
||||
const {productName, version} = require('./package');
|
||||
const config = require('./config');
|
||||
|
||||
let spawn;
|
||||
|
|
@ -24,7 +26,7 @@ const envFromConfig = config.getConfig().env || {};
|
|||
|
||||
module.exports = class Session extends EventEmitter {
|
||||
|
||||
constructor ({ rows, cols: columns, cwd, shell, shellArgs }) {
|
||||
constructor({rows, cols: columns, cwd, shell, shellArgs}) {
|
||||
super();
|
||||
const baseEnv = Object.assign({}, process.env, {
|
||||
LANG: app.getLocale().replace('-', '_') + '.UTF-8',
|
||||
|
|
@ -42,7 +44,7 @@ module.exports = class Session extends EventEmitter {
|
|||
env: getDecoratedEnv(baseEnv)
|
||||
});
|
||||
|
||||
this.pty.stdout.on('data', (data) => {
|
||||
this.pty.stdout.on('data', data => {
|
||||
if (this.ended) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -60,19 +62,24 @@ module.exports = class Session extends EventEmitter {
|
|||
this.getTitle();
|
||||
}
|
||||
|
||||
focus () {
|
||||
focus() {
|
||||
this.subscribed = true;
|
||||
this.getTitle();
|
||||
}
|
||||
|
||||
blur () {
|
||||
blur() {
|
||||
this.subscribed = false;
|
||||
clearTimeout(this.titlePoll);
|
||||
}
|
||||
|
||||
getTitle () {
|
||||
if ('win32' === process.platform) return;
|
||||
if (this.fetching) return;
|
||||
getTitle() {
|
||||
if (process.platform === 'win32') {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.fetching) {
|
||||
return;
|
||||
}
|
||||
this.fetching = true;
|
||||
|
||||
let tty = this.pty.stdout.ttyname;
|
||||
|
|
@ -86,8 +93,12 @@ module.exports = class Session extends EventEmitter {
|
|||
// TODO: only tested on mac
|
||||
exec(`ps uxac | grep ${tty} | head -n 1`, (err, out) => {
|
||||
this.fetching = false;
|
||||
if (this.ended) return;
|
||||
if (err) return;
|
||||
if (this.ended) {
|
||||
return;
|
||||
}
|
||||
if (err) {
|
||||
return;
|
||||
}
|
||||
let title = out.split(' ').pop();
|
||||
if (title) {
|
||||
title = title.replace(/^\(/, '');
|
||||
|
|
@ -104,23 +115,23 @@ module.exports = class Session extends EventEmitter {
|
|||
});
|
||||
}
|
||||
|
||||
exit () {
|
||||
exit() {
|
||||
this.destroy();
|
||||
}
|
||||
|
||||
write (data) {
|
||||
write(data) {
|
||||
this.pty.stdin.write(data);
|
||||
}
|
||||
|
||||
resize ({ cols: columns, rows }) {
|
||||
resize({cols: columns, rows}) {
|
||||
try {
|
||||
this.pty.stdout.resize({ columns, rows });
|
||||
this.pty.stdout.resize({columns, rows});
|
||||
} catch (err) {
|
||||
console.error(err.stack);
|
||||
}
|
||||
}
|
||||
|
||||
destroy () {
|
||||
destroy() {
|
||||
try {
|
||||
this.pty.kill('SIGHUP');
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -3,12 +3,11 @@ const Color = require('color');
|
|||
// returns a background color that's in hex
|
||||
// format including the alpha channel (e.g.: `#00000050`)
|
||||
// input can be any css value (rgb, hsl, string…)
|
||||
module.exports = function toElectronBackgroundColor (bgColor) {
|
||||
module.exports = function toElectronBackgroundColor(bgColor) {
|
||||
const color = Color(bgColor);
|
||||
if (1 !== color.alpha()) {
|
||||
// (╯°□°)╯︵ ┻━┻
|
||||
return '#' + Math.floor(color.alpha() * 100) + color.hexString().substr(1);
|
||||
} else {
|
||||
if (color.alpha() === 1) {
|
||||
return color.hexString();
|
||||
}
|
||||
// (╯°□°)╯︵ ┻━┻
|
||||
return '#' + Math.floor(color.alpha() * 100) + color.hexString().substr(1);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { CONFIG_LOAD, CONFIG_RELOAD } from '../constants/config';
|
||||
import {CONFIG_LOAD, CONFIG_RELOAD} from '../constants/config';
|
||||
|
||||
export function loadConfig (config) {
|
||||
export function loadConfig(config) {
|
||||
return {
|
||||
type: CONFIG_LOAD,
|
||||
config
|
||||
};
|
||||
}
|
||||
|
||||
export function reloadConfig (config) {
|
||||
export function reloadConfig(config) {
|
||||
return {
|
||||
type: CONFIG_RELOAD,
|
||||
config
|
||||
|
|
|
|||
|
|
@ -1,48 +1,49 @@
|
|||
import { CLOSE_TAB, CHANGE_TAB } from '../constants/tabs';
|
||||
import { UI_WINDOW_MAXIMIZE, UI_WINDOW_UNMAXIMIZE } from '../constants/ui';
|
||||
import { userExitSession, setActiveSession } from './sessions';
|
||||
import {CLOSE_TAB, CHANGE_TAB} from '../constants/tabs';
|
||||
import {UI_WINDOW_MAXIMIZE, UI_WINDOW_UNMAXIMIZE} from '../constants/ui';
|
||||
import rpc from '../rpc';
|
||||
|
||||
export function closeTab (uid) {
|
||||
return (dispatch, getState) => {
|
||||
import {userExitSession, setActiveSession} from './sessions';
|
||||
|
||||
export function closeTab(uid) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: CLOSE_TAB,
|
||||
uid,
|
||||
effect () {
|
||||
effect() {
|
||||
dispatch(userExitSession(uid));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function changeTab (uid) {
|
||||
return (dispatch, getState) => {
|
||||
export function changeTab(uid) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: CHANGE_TAB,
|
||||
uid,
|
||||
effect () {
|
||||
effect() {
|
||||
dispatch(setActiveSession(uid));
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function maximize () {
|
||||
return (dispatch, getState) => {
|
||||
export function maximize() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: UI_WINDOW_MAXIMIZE,
|
||||
effect () {
|
||||
effect() {
|
||||
rpc.emit('maximize');
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function unmaximize () {
|
||||
return (dispatch, getState) => {
|
||||
export function unmaximize() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: UI_WINDOW_UNMAXIMIZE,
|
||||
effect () {
|
||||
effect() {
|
||||
rpc.emit('unmaximize');
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import rpc from '../rpc';
|
||||
|
||||
export function init () {
|
||||
export function init() {
|
||||
rpc.emit('init');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { NOTIFICATION_DISMISS } from '../constants/notifications';
|
||||
import {NOTIFICATION_DISMISS} from '../constants/notifications';
|
||||
|
||||
export function dismissNotification (id) {
|
||||
export function dismissNotification(id) {
|
||||
return {
|
||||
type: NOTIFICATION_DISMISS,
|
||||
id
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import rpc from '../rpc';
|
||||
import getURL from '../utils/url-command';
|
||||
import { keys } from '../utils/object';
|
||||
import {keys} from '../utils/object';
|
||||
import {
|
||||
SESSION_ADD,
|
||||
SESSION_RESIZE,
|
||||
|
|
@ -19,8 +19,8 @@ import {
|
|||
SESSION_SET_PROCESS_TITLE
|
||||
} from '../constants/sessions';
|
||||
|
||||
export function addSession (uid, shell, pid) {
|
||||
return (dispatch, getState) => {
|
||||
export function addSession(uid, shell, pid) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: SESSION_ADD,
|
||||
uid,
|
||||
|
|
@ -30,26 +30,26 @@ export function addSession (uid, shell, pid) {
|
|||
};
|
||||
}
|
||||
|
||||
export function requestSession (uid) {
|
||||
export function requestSession() {
|
||||
return (dispatch, getState) => {
|
||||
const { ui } = getState();
|
||||
const { cols, rows, cwd } = ui;
|
||||
const {ui} = getState();
|
||||
const {cols, rows, cwd} = ui;
|
||||
dispatch({
|
||||
type: SESSION_REQUEST,
|
||||
effect: () => {
|
||||
rpc.emit('new', { cols, rows, cwd });
|
||||
rpc.emit('new', {cols, rows, cwd});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function addSessionData (uid, data) {
|
||||
export function addSessionData(uid, data) {
|
||||
return function (dispatch, getState) {
|
||||
dispatch({
|
||||
type: SESSION_ADD_DATA,
|
||||
data,
|
||||
effect () {
|
||||
const { shell } = getState().sessions.sessions[uid];
|
||||
effect() {
|
||||
const {shell} = getState().sessions.sessions[uid];
|
||||
|
||||
const enterKey = Boolean(data.match(/\n/));
|
||||
const url = enterKey ? getURL(shell, data) : null;
|
||||
|
|
@ -72,12 +72,12 @@ export function addSessionData (uid, data) {
|
|||
};
|
||||
}
|
||||
|
||||
export function sessionExit (uid) {
|
||||
export function sessionExit(uid) {
|
||||
return (dispatch, getState) => {
|
||||
return dispatch({
|
||||
type: SESSION_PTY_EXIT,
|
||||
uid,
|
||||
effect () {
|
||||
effect() {
|
||||
// we reiterate the same logic as below
|
||||
// for SESSION_USER_EXIT since the exit
|
||||
// could happen pty side or optimistic
|
||||
|
|
@ -92,13 +92,13 @@ export function sessionExit (uid) {
|
|||
|
||||
// we want to distinguish an exit
|
||||
// that's UI initiated vs pty initiated
|
||||
export function userExitSession (uid) {
|
||||
export function userExitSession(uid) {
|
||||
return (dispatch, getState) => {
|
||||
return dispatch({
|
||||
type: SESSION_USER_EXIT,
|
||||
uid,
|
||||
effect () {
|
||||
rpc.emit('exit', { uid });
|
||||
effect() {
|
||||
rpc.emit('exit', {uid});
|
||||
const sessions = keys(getState().sessions.sessions);
|
||||
if (!sessions.length) {
|
||||
window.close();
|
||||
|
|
@ -108,11 +108,11 @@ export function userExitSession (uid) {
|
|||
};
|
||||
}
|
||||
|
||||
export function userExitActiveSession () {
|
||||
export function userExitActiveSession() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: SESSION_USER_EXIT_ACTIVE,
|
||||
effect () {
|
||||
effect() {
|
||||
const uid = getState().sessions.activeUid;
|
||||
dispatch(userExitSession(uid));
|
||||
}
|
||||
|
|
@ -120,31 +120,33 @@ export function userExitActiveSession () {
|
|||
};
|
||||
}
|
||||
|
||||
export function setActiveSession (uid) {
|
||||
export function setActiveSession(uid) {
|
||||
return (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const prevUid = state.sessions.activeUid;
|
||||
dispatch({
|
||||
type: SESSION_SET_ACTIVE,
|
||||
uid,
|
||||
effect () {
|
||||
effect() {
|
||||
// TODO: this goes away when we are able to poll
|
||||
// for the title ourseleves, instead of relying
|
||||
// on Session and focus/blur to subscribe
|
||||
if (prevUid) rpc.emit('blur', { uid: prevUid });
|
||||
rpc.emit('focus', { uid });
|
||||
if (prevUid) {
|
||||
rpc.emit('blur', {uid: prevUid});
|
||||
}
|
||||
rpc.emit('focus', {uid});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function clearActiveSession () {
|
||||
export function clearActiveSession() {
|
||||
return {
|
||||
type: SESSION_CLEAR_ACTIVE
|
||||
};
|
||||
}
|
||||
|
||||
export function setSessionProcessTitle (uid, title) {
|
||||
export function setSessionProcessTitle(uid, title) {
|
||||
return {
|
||||
type: SESSION_SET_PROCESS_TITLE,
|
||||
uid,
|
||||
|
|
@ -152,7 +154,7 @@ export function setSessionProcessTitle (uid, title) {
|
|||
};
|
||||
}
|
||||
|
||||
export function setSessionXtermTitle (uid, title) {
|
||||
export function setSessionXtermTitle(uid, title) {
|
||||
return {
|
||||
type: SESSION_SET_XTERM_TITLE,
|
||||
uid,
|
||||
|
|
@ -160,32 +162,32 @@ export function setSessionXtermTitle (uid, title) {
|
|||
};
|
||||
}
|
||||
|
||||
export function resizeSession (uid, cols, rows) {
|
||||
export function resizeSession(uid, cols, rows) {
|
||||
return {
|
||||
type: SESSION_RESIZE,
|
||||
cols,
|
||||
rows,
|
||||
effect () {
|
||||
rpc.emit('resize', { cols, rows });
|
||||
effect() {
|
||||
rpc.emit('resize', {cols, rows});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function sendSessionData (uid, data) {
|
||||
export function sendSessionData(uid, data) {
|
||||
return function (dispatch, getState) {
|
||||
dispatch({
|
||||
type: SESSION_USER_DATA,
|
||||
data,
|
||||
effect () {
|
||||
effect() {
|
||||
// If no uid is passed, data is sended to the active session.
|
||||
const targetUid = uid || getState().sessions.activeUid;
|
||||
rpc.emit('data', { uid: targetUid, data });
|
||||
rpc.emit('data', {uid: targetUid, data});
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function exitSessionBrowser (uid) {
|
||||
export function exitSessionBrowser(uid) {
|
||||
return {
|
||||
type: SESSION_URL_UNSET,
|
||||
uid
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import * as shellEscape from 'php-escape-shell';
|
||||
import { setActiveSession } from './sessions';
|
||||
import { keys } from '../utils/object';
|
||||
import { last } from '../utils/array';
|
||||
import { isExecutable } from '../utils/file';
|
||||
|
||||
import {keys} from '../utils/object';
|
||||
import {last} from '../utils/array';
|
||||
import {isExecutable} from '../utils/file';
|
||||
import notify from '../utils/notify';
|
||||
import rpc from '../rpc';
|
||||
import {
|
||||
|
|
@ -23,13 +23,15 @@ import {
|
|||
UI_OPEN_FILE
|
||||
} from '../constants/ui';
|
||||
|
||||
const { stat } = window.require('fs');
|
||||
import {setActiveSession} from './sessions';
|
||||
|
||||
export function increaseFontSize () {
|
||||
const {stat} = window.require('fs');
|
||||
|
||||
export function increaseFontSize() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: UI_FONT_SIZE_INCR,
|
||||
effect () {
|
||||
effect() {
|
||||
const state = getState();
|
||||
const old = state.ui.fontSizeOverride || state.ui.fontSize;
|
||||
const value = old + 1;
|
||||
|
|
@ -42,11 +44,11 @@ export function increaseFontSize () {
|
|||
};
|
||||
}
|
||||
|
||||
export function decreaseFontSize () {
|
||||
export function decreaseFontSize() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: UI_FONT_SIZE_DECR,
|
||||
effect () {
|
||||
effect() {
|
||||
const state = getState();
|
||||
const old = state.ui.fontSizeOverride || state.ui.fontSize;
|
||||
const value = old - 1;
|
||||
|
|
@ -59,19 +61,19 @@ export function decreaseFontSize () {
|
|||
};
|
||||
}
|
||||
|
||||
export function resetFontSize () {
|
||||
export function resetFontSize() {
|
||||
return {
|
||||
type: UI_FONT_SIZE_RESET
|
||||
};
|
||||
}
|
||||
|
||||
export function setFontSmoothing () {
|
||||
return (dispatch) => {
|
||||
export function setFontSmoothing() {
|
||||
return dispatch => {
|
||||
setTimeout(() => {
|
||||
const devicePixelRatio = window.devicePixelRatio;
|
||||
const fontSmoothing = devicePixelRatio < 2
|
||||
? 'subpixel-antialiased'
|
||||
: 'antialiased';
|
||||
const fontSmoothing = devicePixelRatio < 2 ?
|
||||
'subpixel-antialiased' :
|
||||
'antialiased';
|
||||
|
||||
dispatch({
|
||||
type: UI_FONT_SMOOTHING_SET,
|
||||
|
|
@ -81,12 +83,12 @@ export function setFontSmoothing () {
|
|||
};
|
||||
}
|
||||
|
||||
export function moveLeft () {
|
||||
export function moveLeft() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: UI_MOVE_LEFT,
|
||||
effect () {
|
||||
const { sessions } = getState();
|
||||
effect() {
|
||||
const {sessions} = getState();
|
||||
const uid = sessions.activeUid;
|
||||
const sessionUids = keys(sessions.sessions);
|
||||
const index = sessionUids.indexOf(uid);
|
||||
|
|
@ -101,12 +103,12 @@ export function moveLeft () {
|
|||
};
|
||||
}
|
||||
|
||||
export function moveRight () {
|
||||
export function moveRight() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: UI_MOVE_RIGHT,
|
||||
effect () {
|
||||
const { sessions } = getState();
|
||||
effect() {
|
||||
const {sessions} = getState();
|
||||
const uid = sessions.activeUid;
|
||||
const sessionUids = keys(sessions.sessions);
|
||||
const index = sessionUids.indexOf(uid);
|
||||
|
|
@ -121,36 +123,36 @@ export function moveRight () {
|
|||
};
|
||||
}
|
||||
|
||||
export function moveTo (i) {
|
||||
export function moveTo(i) {
|
||||
return (dispatch, getState) => {
|
||||
dispatch({
|
||||
type: UI_MOVE_TO,
|
||||
index: i,
|
||||
effect () {
|
||||
const { sessions } = getState();
|
||||
effect() {
|
||||
const {sessions} = getState();
|
||||
const uid = sessions.activeUid;
|
||||
const sessionUids = keys(sessions.sessions);
|
||||
if (uid === sessionUids[i]) {
|
||||
console.log('ignoring same uid');
|
||||
} else if (null != sessionUids[i]) {
|
||||
dispatch(setActiveSession(sessionUids[i]));
|
||||
} else {
|
||||
} else if (sessionUids[i] === null) {
|
||||
console.log('ignoring inexistent index', i);
|
||||
} else {
|
||||
dispatch(setActiveSession(sessionUids[i]));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function showPreferences () {
|
||||
export function showPreferences() {
|
||||
const editorFallback = process.platform === 'win32' ? 'notepad' : 'nano';
|
||||
return (dispatch, getState) => {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: UI_SHOW_PREFERENCES,
|
||||
effect () {
|
||||
effect() {
|
||||
dispatch(requestSession());
|
||||
// TODO: replace this hack with an async action
|
||||
rpc.once('session add', ({ uid }) => {
|
||||
rpc.once('session add', ({uid}) => {
|
||||
rpc.once('session data', () => {
|
||||
dispatch(sendSessionData(
|
||||
uid,
|
||||
|
|
@ -168,22 +170,22 @@ export function showPreferences () {
|
|||
};
|
||||
}
|
||||
|
||||
export function windowMove () {
|
||||
return (dispatch) => {
|
||||
export function windowMove() {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: UI_WINDOW_MOVE,
|
||||
effect () {
|
||||
effect() {
|
||||
dispatch(setFontSmoothing());
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
export function openFile (path) {
|
||||
return (dispatch, getState) => {
|
||||
export function openFile(path) {
|
||||
return dispatch => {
|
||||
dispatch({
|
||||
type: UI_OPEN_FILE,
|
||||
effect () {
|
||||
effect() {
|
||||
stat(path, (err, stats) => {
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
|
|
@ -198,7 +200,7 @@ export function openFile (path) {
|
|||
} else if (stats.isFile() && isExecutable(stats)) {
|
||||
command += '\n';
|
||||
}
|
||||
rpc.once('session add', ({ uid }) => {
|
||||
rpc.once('session add', ({uid}) => {
|
||||
rpc.once('session data', () => {
|
||||
dispatch(sendSessionData(uid, command));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import {
|
|||
} from '../constants/updater';
|
||||
import rpc from '../rpc';
|
||||
|
||||
export function installUpdate () {
|
||||
export function installUpdate() {
|
||||
return {
|
||||
type: UPDATE_INSTALL,
|
||||
effect: () => {
|
||||
|
|
@ -13,7 +13,7 @@ export function installUpdate () {
|
|||
};
|
||||
}
|
||||
|
||||
export function updateAvailable (version, notes) {
|
||||
export function updateAvailable(version, notes) {
|
||||
return {
|
||||
type: UPDATE_AVAILABLE,
|
||||
version,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { StyleSheet, css } from 'aphrodite-simple';
|
||||
import { shouldComponentUpdate } from 'react-addons-pure-render-mixin';
|
||||
import {StyleSheet, css} from 'aphrodite-simple';
|
||||
import {shouldComponentUpdate} from 'react-addons-pure-render-mixin';
|
||||
|
||||
export default class Component extends React.Component {
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
super();
|
||||
this.styles_ = this.createStyleSheet();
|
||||
this.cssHelper = this.cssHelper.bind(this);
|
||||
|
|
@ -13,14 +13,14 @@ export default class Component extends React.Component {
|
|||
}
|
||||
}
|
||||
|
||||
createStyleSheet () {
|
||||
createStyleSheet() {
|
||||
if (!this.styles) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const styles = this.styles();
|
||||
|
||||
if ('object' !== typeof styles) {
|
||||
if (typeof styles !== 'object') {
|
||||
throw new TypeError('Component `styles` returns a non-object');
|
||||
}
|
||||
|
||||
|
|
@ -34,9 +34,9 @@ export default class Component extends React.Component {
|
|||
// or user agent extensions
|
||||
// - the user doesn't need to keep track of both `css`
|
||||
// and `style`, and we make that whole ordeal easier
|
||||
cssHelper (...args) {
|
||||
cssHelper(...args) {
|
||||
const classes = args
|
||||
.map((c) => {
|
||||
.map(c => {
|
||||
if (c) {
|
||||
// we compute the global name from the given
|
||||
// css class and we prepend the component name
|
||||
|
|
@ -48,19 +48,20 @@ export default class Component extends React.Component {
|
|||
const globalName = `${component}_${c}`;
|
||||
return [globalName, css(this.styles_[c])];
|
||||
}
|
||||
return null;
|
||||
})
|
||||
// skip nulls
|
||||
.filter((v) => !!v)
|
||||
.filter(v => Boolean(v))
|
||||
// flatten
|
||||
.reduce((a, b) => a.concat(b));
|
||||
return classes.length ? classes.join(' ') : null;
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
// convert static objects from `babel-plugin-transform-jsx`
|
||||
// to `React.Element`.
|
||||
if (!this.template) {
|
||||
throw new TypeError("Component doesn't define `template`");
|
||||
throw new TypeError('Component doesn\'t define `template`');
|
||||
}
|
||||
|
||||
// invoke the template creator passing our css helper
|
||||
|
|
|
|||
|
|
@ -1,20 +1,22 @@
|
|||
import React from 'react';
|
||||
import Tabs_ from './tabs';
|
||||
|
||||
import Component from '../component';
|
||||
import { decorate, getTabsProps } from '../utils/plugins';
|
||||
import {decorate, getTabsProps} from '../utils/plugins';
|
||||
|
||||
import Tabs_ from './tabs';
|
||||
|
||||
const Tabs = decorate(Tabs_, 'Tabs');
|
||||
|
||||
export default class Header extends Component {
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
super();
|
||||
this.onChangeIntent = this.onChangeIntent.bind(this);
|
||||
this.onHeaderClick = this.onHeaderClick.bind(this);
|
||||
this.onHeaderMouseDown = this.onHeaderMouseDown.bind(this);
|
||||
this.handleHeaderClick = this.handleHeaderClick.bind(this);
|
||||
this.handleHeaderMouseDown = this.handleHeaderMouseDown.bind(this);
|
||||
}
|
||||
|
||||
onChangeIntent (active) {
|
||||
onChangeIntent(active) {
|
||||
// we ignore clicks if they're a byproduct of a drag
|
||||
// motion to move the window
|
||||
if (window.screenX !== this.headerMouseDownWindowX ||
|
||||
|
|
@ -25,12 +27,12 @@ export default class Header extends Component {
|
|||
this.props.onChangeTab(active);
|
||||
}
|
||||
|
||||
onHeaderMouseDown () {
|
||||
handleHeaderMouseDown() {
|
||||
this.headerMouseDownWindowX = window.screenX;
|
||||
this.headerMouseDownWindowY = window.screenY;
|
||||
}
|
||||
|
||||
onHeaderClick (event) {
|
||||
handleHeaderClick(event) {
|
||||
this.clicks = this.clicks || 0;
|
||||
|
||||
// Reset clicks if mouse moved between clicks
|
||||
|
|
@ -59,30 +61,31 @@ export default class Header extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
delete this.clicks;
|
||||
clearTimeout(this.clickTimer);
|
||||
}
|
||||
|
||||
template (css) {
|
||||
const { isMac } = this.props;
|
||||
template(css) {
|
||||
const {isMac} = this.props;
|
||||
const props = getTabsProps(this.props, {
|
||||
tabs: this.props.tabs,
|
||||
borderColor: this.props.borderColor,
|
||||
onClose: this.props.onCloseTab,
|
||||
onChange: this.onChangeIntent
|
||||
});
|
||||
return <header
|
||||
className={ css('header', isMac && 'headerRounded') }
|
||||
onClick={ this.onHeaderClick }
|
||||
onMouseDown={ this.onHeaderMouseDown }>
|
||||
{ this.props.customChildrenBefore }
|
||||
<Tabs {...props} />
|
||||
{ this.props.customChildren }
|
||||
</header>;
|
||||
return (<header
|
||||
className={css('header', isMac && 'headerRounded')}
|
||||
onClick={this.handleHeaderClick}
|
||||
onMouseDown={this.handleHeaderMouseDown}
|
||||
>
|
||||
{ this.props.customChildrenBefore }
|
||||
<Tabs {...props}/>
|
||||
{ this.props.customChildren }
|
||||
</header>);
|
||||
}
|
||||
|
||||
styles () {
|
||||
styles() {
|
||||
return {
|
||||
header: {
|
||||
position: 'fixed',
|
||||
|
|
|
|||
|
|
@ -3,22 +3,22 @@ import Component from '../component';
|
|||
|
||||
export default class Notification extends Component {
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
dismissing: false
|
||||
};
|
||||
this.dismiss = this.dismiss.bind(this);
|
||||
this.handleDismiss = this.handleDismiss.bind(this);
|
||||
this.onElement = this.onElement.bind(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
componentDidMount() {
|
||||
if (this.props.dismissAfter) {
|
||||
this.setDismissTimer();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps (next) {
|
||||
componentWillReceiveProps(next) {
|
||||
// if we have a timer going and the notification text
|
||||
// changed we reset the timer
|
||||
if (next.text !== this.props.text) {
|
||||
|
|
@ -26,23 +26,23 @@ export default class Notification extends Component {
|
|||
this.resetDismissTimer();
|
||||
}
|
||||
if (this.state.dismissing) {
|
||||
this.setState({ dismissing: false });
|
||||
this.setState({dismissing: false});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dismiss () {
|
||||
this.setState({ dismissing: true });
|
||||
handleDismiss() {
|
||||
this.setState({dismissing: true});
|
||||
}
|
||||
|
||||
onElement (el) {
|
||||
onElement(el) {
|
||||
if (el) {
|
||||
el.addEventListener('webkitTransitionEnd', () => {
|
||||
if (this.state.dismissing) {
|
||||
this.props.onDismiss();
|
||||
}
|
||||
});
|
||||
const { backgroundColor } = this.props;
|
||||
const {backgroundColor} = this.props;
|
||||
if (backgroundColor) {
|
||||
el.style.setProperty(
|
||||
'background-color',
|
||||
|
|
@ -53,42 +53,44 @@ export default class Notification extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
setDismissTimer (after) {
|
||||
setDismissTimer() {
|
||||
this.dismissTimer = setTimeout(() => {
|
||||
this.dismiss();
|
||||
this.handleDismiss();
|
||||
}, this.props.dismissAfter);
|
||||
}
|
||||
|
||||
resetDismissTimer (after) {
|
||||
resetDismissTimer() {
|
||||
clearTimeout(this.dismissTimer);
|
||||
this.setDismissTimer();
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.dismissTimer);
|
||||
}
|
||||
|
||||
template (css) {
|
||||
const { backgroundColor } = this.props;
|
||||
template(css) {
|
||||
const {backgroundColor} = this.props;
|
||||
const opacity = this.state.dismissing ? 0 : 1;
|
||||
return <div
|
||||
style={{ opacity, backgroundColor }}
|
||||
ref={ this.onElement }
|
||||
className={ css('indicator') }>
|
||||
return (<div
|
||||
style={{opacity, backgroundColor}}
|
||||
ref={this.onElement}
|
||||
className={css('indicator')}
|
||||
>
|
||||
{ this.props.customChildrenBefore }
|
||||
{ this.props.children || this.props.text }
|
||||
{
|
||||
this.props.userDismissable
|
||||
? <a
|
||||
className={ css('dismissLink') }
|
||||
onClick={ this.dismiss }>[x]</a>
|
||||
: null
|
||||
this.props.userDismissable ?
|
||||
<a
|
||||
className={css('dismissLink')}
|
||||
onClick={this.handleDismiss}
|
||||
>[x]</a> :
|
||||
null
|
||||
}
|
||||
{ this.props.customChildren }
|
||||
</div>;
|
||||
</div>);
|
||||
}
|
||||
|
||||
styles () {
|
||||
styles() {
|
||||
return {
|
||||
indicator: {
|
||||
display: 'inline-block',
|
||||
|
|
|
|||
|
|
@ -1,68 +1,80 @@
|
|||
import React from 'react';
|
||||
|
||||
import Component from '../component';
|
||||
import {decorate} from '../utils/plugins';
|
||||
|
||||
import Notification_ from './notification';
|
||||
import { decorate } from '../utils/plugins';
|
||||
|
||||
const Notification = decorate(Notification_);
|
||||
|
||||
export default class Notifications extends Component {
|
||||
|
||||
template (css) {
|
||||
return <div className={ css('view') }>
|
||||
template(css) {
|
||||
return (<div className={css('view')}>
|
||||
{ this.props.customChildrenBefore }
|
||||
{
|
||||
this.props.fontShowing &&
|
||||
<Notification
|
||||
key='font'
|
||||
backgroundColor='rgba(255, 255, 255, .2)'
|
||||
key="font"
|
||||
backgroundColor="rgba(255, 255, 255, .2)"
|
||||
text={`${this.props.fontSize}px`}
|
||||
userDismissable={ false }
|
||||
onDismiss={ this.props.onDismissFont }
|
||||
dismissAfter={ 1000 } />
|
||||
userDismissable={false}
|
||||
onDismiss={this.props.onDismissFont}
|
||||
dismissAfter={1000}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
this.props.resizeShowing &&
|
||||
<Notification
|
||||
key='resize'
|
||||
backgroundColor='rgba(255, 255, 255, .2)'
|
||||
key="resize"
|
||||
backgroundColor="rgba(255, 255, 255, .2)"
|
||||
text={`${this.props.cols}x${this.props.rows}`}
|
||||
userDismissable={ false }
|
||||
onDismiss={ this.props.onDismissResize }
|
||||
dismissAfter={ 1000 } />
|
||||
userDismissable={false}
|
||||
onDismiss={this.props.onDismissResize}
|
||||
dismissAfter={1000}
|
||||
/>
|
||||
}
|
||||
|
||||
{
|
||||
this.props.updateShowing &&
|
||||
<Notification
|
||||
key='update'
|
||||
backgroundColor='#7ED321'
|
||||
key="update"
|
||||
backgroundColor="#7ED321"
|
||||
text={`Version ${this.props.updateVersion} ready`}
|
||||
onDismiss={ this.props.onDismissUpdate }
|
||||
userDismissable={ true }>
|
||||
onDismiss={this.props.onDismissUpdate}
|
||||
userDismissable
|
||||
>
|
||||
Version <b>{ this.props.updateVersion}</b> ready.
|
||||
{ this.props.updateNote && ` ${this.props.updateNote.trim().replace(/\.$/, '')}` }
|
||||
{ ' ' }
|
||||
(<a
|
||||
style={{ color: '#fff' }}
|
||||
onClick={ (ev) => { window.require('electron').shell.openExternal(ev.target.href); ev.preventDefault(); } }
|
||||
href={`https://github.com/zeit/hyperterm/releases/tag/${this.props.updateVersion}`}>notes</a>).
|
||||
style={{color: '#fff'}}
|
||||
onClick={ev => {
|
||||
window.require('electron').shell.openExternal(ev.target.href);
|
||||
ev.preventDefault();
|
||||
}}
|
||||
href={`https://github.com/zeit/hyperterm/releases/tag/${this.props.updateVersion}`}
|
||||
>notes</a>).
|
||||
{ ' ' }
|
||||
<a style={{
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'underline',
|
||||
fontWeight: 'bold' }}
|
||||
onClick={ this.props.onUpdateInstall }>
|
||||
<a
|
||||
style={{
|
||||
cursor: 'pointer',
|
||||
textDecoration: 'underline',
|
||||
fontWeight: 'bold'
|
||||
}}
|
||||
onClick={this.props.onUpdateInstall}
|
||||
>
|
||||
Restart
|
||||
</a>.
|
||||
{ ' ' }
|
||||
</Notification>
|
||||
}
|
||||
{ this.props.customChildren }
|
||||
</div>;
|
||||
</div>);
|
||||
}
|
||||
|
||||
styles () {
|
||||
styles() {
|
||||
return {
|
||||
view: {
|
||||
position: 'fixed',
|
||||
|
|
|
|||
|
|
@ -2,78 +2,93 @@ import React from 'react';
|
|||
import Component from '../component';
|
||||
|
||||
export default class Tab extends Component {
|
||||
constructor () {
|
||||
constructor() {
|
||||
super();
|
||||
this.hover = this.hover.bind(this);
|
||||
this.blur = this.blur.bind(this);
|
||||
|
||||
this.handleHover = this.handleHover.bind(this);
|
||||
this.handleBlur = this.handleBlur.bind(this);
|
||||
this.handleClick = this.handleClick.bind(this);
|
||||
|
||||
this.state = {
|
||||
hovered: false
|
||||
};
|
||||
}
|
||||
|
||||
hover () {
|
||||
this.setState({ hovered: true });
|
||||
shouldComponentUpdate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
blur () {
|
||||
this.setState({ hovered: false });
|
||||
handleHover() {
|
||||
this.setState({
|
||||
hovered: true
|
||||
});
|
||||
}
|
||||
|
||||
handleClick (event) {
|
||||
handleBlur() {
|
||||
this.setState({
|
||||
hovered: false
|
||||
});
|
||||
}
|
||||
|
||||
handleClick(event) {
|
||||
const isLeftClick = event.nativeEvent.which === 1;
|
||||
const isMiddleClick = event.nativeEvent.which === 2;
|
||||
|
||||
if (isLeftClick) {
|
||||
this.props.isActive ? null : this.props.onSelect();
|
||||
if (this.props.isActive === null) {
|
||||
this.props.onSelect();
|
||||
}
|
||||
} else if (isMiddleClick) {
|
||||
this.props.onClose();
|
||||
}
|
||||
}
|
||||
|
||||
template (css) {
|
||||
const { isActive, isFirst, isLast, borderColor, hasActivity } = this.props;
|
||||
const { hovered } = this.state;
|
||||
template(css) {
|
||||
const {isActive, isFirst, isLast, borderColor, hasActivity} = this.props;
|
||||
const {hovered} = this.state;
|
||||
|
||||
return <li
|
||||
onMouseEnter={ this.hover }
|
||||
onMouseLeave={ this.blur }
|
||||
onClick={ this.props.onClick }
|
||||
style={{ borderColor }}
|
||||
className={ css(
|
||||
return (<li
|
||||
onMouseEnter={this.handleHover}
|
||||
onMouseLeave={this.handleBlur}
|
||||
onClick={this.props.onClick}
|
||||
style={{borderColor}}
|
||||
className={css(
|
||||
'tab',
|
||||
isFirst && 'first',
|
||||
isActive && 'active',
|
||||
isFirst && isActive && 'firstActive',
|
||||
hasActivity && 'hasActivity'
|
||||
) }>
|
||||
{ this.props.customChildrenBefore }
|
||||
<span
|
||||
className={ css(
|
||||
'text',
|
||||
isLast && 'textLast',
|
||||
isActive && 'textActive'
|
||||
) }
|
||||
onClick={ this.handleClick }>
|
||||
<span className={ css('textInner') }>
|
||||
{ this.props.text }
|
||||
</span>
|
||||
)}
|
||||
>
|
||||
{ this.props.customChildrenBefore }
|
||||
<span
|
||||
className={css(
|
||||
'text',
|
||||
isLast && 'textLast',
|
||||
isActive && 'textActive'
|
||||
)}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<span className={css('textInner')}>
|
||||
{ this.props.text }
|
||||
</span>
|
||||
<i
|
||||
className={ css(
|
||||
'icon',
|
||||
hovered && 'iconHovered'
|
||||
) }
|
||||
onClick={ this.props.onClose }>
|
||||
<svg className={ css('shape') }>
|
||||
<use xlinkHref='./dist/assets/icons.svg#close'></use>
|
||||
</svg>
|
||||
</i>
|
||||
{ this.props.customChildren }
|
||||
</li>;
|
||||
</span>
|
||||
<i
|
||||
className={css(
|
||||
'icon',
|
||||
hovered && 'iconHovered'
|
||||
)}
|
||||
onClick={this.props.onClose}
|
||||
>
|
||||
<svg className={css('shape')}>
|
||||
<use xlinkHref="./dist/assets/icons.svg#close"/>
|
||||
</svg>
|
||||
</i>
|
||||
{ this.props.customChildren }
|
||||
</li>);
|
||||
}
|
||||
|
||||
styles () {
|
||||
styles() {
|
||||
return {
|
||||
tab: {
|
||||
color: '#ccc',
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import Tab_ from './tab';
|
||||
import React from 'react';
|
||||
|
||||
import Component from '../component';
|
||||
import { decorate, getTabProps } from '../utils/plugins';
|
||||
import {decorate, getTabProps} from '../utils/plugins';
|
||||
|
||||
import Tab_ from './tab';
|
||||
|
||||
const Tab = decorate(Tab_, 'Tab');
|
||||
const isMac = /Mac/.test(navigator.userAgent);
|
||||
|
||||
export default class Tabs extends Component {
|
||||
|
||||
template (css) {
|
||||
template(css) {
|
||||
const {
|
||||
tabs = [],
|
||||
borderColor,
|
||||
|
|
@ -16,43 +18,45 @@ export default class Tabs extends Component {
|
|||
onClose
|
||||
} = this.props;
|
||||
|
||||
return <nav className={ css('nav') }>
|
||||
return (<nav className={css('nav')}>
|
||||
{ this.props.customChildrenBefore }
|
||||
{
|
||||
tabs.length
|
||||
? 1 === tabs.length
|
||||
? <div className={ css('title') }>{ tabs[0].title }</div>
|
||||
: [
|
||||
tabs.length ?
|
||||
tabs.length === 1 ?
|
||||
<div className={css('title')}>{tabs[0].title}</div> :
|
||||
[
|
||||
<ul
|
||||
className={ css('list') }>
|
||||
className={css('list')}
|
||||
>
|
||||
{
|
||||
tabs.map((tab, i) => {
|
||||
const { uid, title, isActive, hasActivity } = tab;
|
||||
const {uid, title, isActive, hasActivity} = tab;
|
||||
const props = getTabProps(tab, this.props, {
|
||||
text: '' === title ? 'Shell' : title,
|
||||
isFirst: 0 === i,
|
||||
text: title === '' ? 'Shell' : title,
|
||||
isFirst: i === 0,
|
||||
isLast: tabs.length - 1 === i,
|
||||
borderColor: borderColor,
|
||||
borderColor,
|
||||
isActive,
|
||||
hasActivity,
|
||||
onSelect: onChange.bind(null, uid),
|
||||
onClose: onClose.bind(null, uid)
|
||||
});
|
||||
return <Tab key={`tab-${uid}`} {...props} />;
|
||||
return <Tab key={`tab-${uid}`} {...props}/>;
|
||||
})
|
||||
}
|
||||
</ul>,
|
||||
isMac && <div
|
||||
style={{ borderColor }}
|
||||
className={ css('borderShim') }></div>
|
||||
]
|
||||
: null
|
||||
style={{borderColor}}
|
||||
className={css('borderShim')}
|
||||
/>
|
||||
] :
|
||||
null
|
||||
}
|
||||
{ this.props.customChildren }
|
||||
</nav>;
|
||||
</nav>);
|
||||
}
|
||||
|
||||
styles () {
|
||||
styles() {
|
||||
return {
|
||||
nav: {
|
||||
fontSize: '12px',
|
||||
|
|
|
|||
|
|
@ -3,21 +3,21 @@ import React from 'react';
|
|||
import Color from 'color';
|
||||
import hterm from '../hterm';
|
||||
import Component from '../component';
|
||||
import { getColorList } from '../utils/colors';
|
||||
import {getColorList} from '../utils/colors';
|
||||
import notify from '../utils/notify';
|
||||
|
||||
export default class Term extends Component {
|
||||
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.onWheel = this.onWheel.bind(this);
|
||||
this.onScrollEnter = this.onScrollEnter.bind(this);
|
||||
this.onScrollLeave = this.onScrollLeave.bind(this);
|
||||
this.handleScrollEnter = this.handleScrollEnter.bind(this);
|
||||
this.handleScrollLeave = this.handleScrollLeave.bind(this);
|
||||
props.ref_(this);
|
||||
}
|
||||
|
||||
componentDidMount () {
|
||||
const { props } = this;
|
||||
componentDidMount() {
|
||||
const {props} = this;
|
||||
this.term = new hterm.Terminal();
|
||||
|
||||
// the first term that's created has unknown size
|
||||
|
|
@ -66,13 +66,15 @@ export default class Term extends Component {
|
|||
};
|
||||
this.term.decorate(this.refs.term);
|
||||
this.term.installKeyboard();
|
||||
if (this.props.onTerminal) this.props.onTerminal(this.term);
|
||||
if (this.props.onTerminal) {
|
||||
this.props.onTerminal(this.term);
|
||||
}
|
||||
|
||||
const iframeWindow = this.getTermDocument().defaultView;
|
||||
iframeWindow.addEventListener('wheel', this.onWheel);
|
||||
}
|
||||
|
||||
onWheel (e) {
|
||||
onWheel(e) {
|
||||
if (this.props.onWheel) {
|
||||
this.props.onWheel(e);
|
||||
}
|
||||
|
|
@ -85,28 +87,28 @@ export default class Term extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
onScrollEnter () {
|
||||
handleScrollEnter() {
|
||||
clearTimeout(this.scrollbarsHideTimer);
|
||||
this.term.prefs_.set('scrollbar-visible', true);
|
||||
this.scrollMouseEnter = true;
|
||||
}
|
||||
|
||||
onScrollLeave () {
|
||||
handleScrollLeave() {
|
||||
this.term.prefs_.set('scrollbar-visible', false);
|
||||
this.scrollMouseEnter = false;
|
||||
}
|
||||
|
||||
write (data) {
|
||||
write(data) {
|
||||
requestAnimationFrame(() => {
|
||||
this.term.io.writeUTF8(data);
|
||||
});
|
||||
}
|
||||
|
||||
focus () {
|
||||
focus() {
|
||||
this.term.focus();
|
||||
}
|
||||
|
||||
clear () {
|
||||
clear() {
|
||||
this.term.clearPreserveCursorRow();
|
||||
|
||||
// If cursor is still not at the top, a command is probably
|
||||
|
|
@ -117,53 +119,53 @@ export default class Term extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
moveWordLeft () {
|
||||
moveWordLeft() {
|
||||
this.term.onVTKeystroke('\x1bb');
|
||||
}
|
||||
|
||||
moveWordRight () {
|
||||
moveWordRight() {
|
||||
this.term.onVTKeystroke('\x1bf');
|
||||
}
|
||||
|
||||
deleteWordLeft () {
|
||||
deleteWordLeft() {
|
||||
this.term.onVTKeystroke('\x1b\x7f');
|
||||
}
|
||||
|
||||
deleteWordRight () {
|
||||
deleteWordRight() {
|
||||
this.term.onVTKeystroke('\x1bd');
|
||||
}
|
||||
|
||||
deleteLine () {
|
||||
deleteLine() {
|
||||
this.term.onVTKeystroke('\x1bw');
|
||||
}
|
||||
|
||||
moveToStart () {
|
||||
moveToStart() {
|
||||
this.term.onVTKeystroke('\x01');
|
||||
}
|
||||
|
||||
moveToEnd () {
|
||||
moveToEnd() {
|
||||
this.term.onVTKeystroke('\x05');
|
||||
}
|
||||
|
||||
selectAll () {
|
||||
selectAll() {
|
||||
this.term.selectAll();
|
||||
}
|
||||
|
||||
getTermDocument () {
|
||||
getTermDocument() {
|
||||
return this.term.document_;
|
||||
}
|
||||
|
||||
getStylesheet (css) {
|
||||
getStylesheet(css) {
|
||||
const blob = new Blob([`
|
||||
.cursor-node[focus="false"] {
|
||||
border-width: 1px !important;
|
||||
}
|
||||
${css}
|
||||
`], { type: 'text/css' });
|
||||
`], {type: 'text/css'});
|
||||
return URL.createObjectURL(blob);
|
||||
}
|
||||
|
||||
validateColor (color, alternative = 'rgb(255,255,255)') {
|
||||
validateColor(color, alternative = 'rgb(255,255,255)') {
|
||||
try {
|
||||
return Color(color).rgbString();
|
||||
} catch (err) {
|
||||
|
|
@ -172,7 +174,7 @@ export default class Term extends Component {
|
|||
return alternative;
|
||||
}
|
||||
|
||||
componentWillReceiveProps (nextProps) {
|
||||
componentWillReceiveProps(nextProps) {
|
||||
if (this.props.url !== nextProps.url) {
|
||||
// when the url prop changes, we make sure
|
||||
// the terminal starts or stops ignoring
|
||||
|
|
@ -180,8 +182,8 @@ export default class Term extends Component {
|
|||
// with the <webview>
|
||||
if (nextProps.url) {
|
||||
const io = this.term.io.push();
|
||||
io.onVTKeystroke = io.sendString = (str) => {
|
||||
if (1 === str.length && 3 === str.charCodeAt(0) /* Ctrl + C */) {
|
||||
io.onVTKeystroke = io.sendString = str => {
|
||||
if (str.length === 1 && str.charCodeAt(0) === 3 /* Ctrl + C */) {
|
||||
this.props.onURLAbort();
|
||||
}
|
||||
};
|
||||
|
|
@ -239,37 +241,39 @@ export default class Term extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
clearTimeout(this.scrollbarsHideTimer);
|
||||
this.props.ref_(null);
|
||||
}
|
||||
|
||||
template (css) {
|
||||
return <div className={ css('fit') }>
|
||||
template(css) {
|
||||
return (<div className={css('fit')}>
|
||||
{ this.props.customChildrenBefore }
|
||||
<div ref='term' className={ css('fit', 'term') } />
|
||||
{ this.props.url
|
||||
? <webview
|
||||
src={this.props.url}
|
||||
style={{
|
||||
background: '#000',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
display: 'inline-flex',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}></webview>
|
||||
: <div
|
||||
className={ css('scrollbarShim') }
|
||||
onMouseEnter={ this.onScrollEnter }
|
||||
onMouseLeave={ this.onScrollLeave } />
|
||||
<div ref="term" className={css('fit', 'term')}/>
|
||||
{ this.props.url ?
|
||||
<webview
|
||||
src={this.props.url}
|
||||
style={{
|
||||
background: '#000',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
display: 'inline-flex',
|
||||
width: '100%',
|
||||
height: '100%'
|
||||
}}
|
||||
/> :
|
||||
<div
|
||||
className={css('scrollbarShim')}
|
||||
onMouseEnter={this.handleScrollEnter}
|
||||
onMouseLeave={this.handleScrollLeave}
|
||||
/>
|
||||
}
|
||||
{ this.props.customChildren }
|
||||
</div>;
|
||||
</div>);
|
||||
}
|
||||
|
||||
styles () {
|
||||
styles() {
|
||||
return {
|
||||
fit: {
|
||||
width: '100%',
|
||||
|
|
|
|||
|
|
@ -1,14 +1,16 @@
|
|||
import React from 'react';
|
||||
import Term_ from './term';
|
||||
|
||||
import Component from '../component';
|
||||
import { last } from '../utils/array';
|
||||
import { decorate, getTermProps } from '../utils/plugins';
|
||||
import {last} from '../utils/array';
|
||||
import {decorate, getTermProps} from '../utils/plugins';
|
||||
|
||||
import Term_ from './term';
|
||||
|
||||
const Term = decorate(Term_, 'Term');
|
||||
|
||||
export default class Terms extends Component {
|
||||
|
||||
constructor (props, context) {
|
||||
constructor(props, context) {
|
||||
super(props, context);
|
||||
this.terms = {};
|
||||
this.bound = new WeakMap();
|
||||
|
|
@ -16,8 +18,8 @@ export default class Terms extends Component {
|
|||
props.ref_(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (next) {
|
||||
const { write } = next;
|
||||
componentWillReceiveProps(next) {
|
||||
const {write} = next;
|
||||
if (write && this.props.write !== write) {
|
||||
this.getTermByUid(write.uid).write(write.data);
|
||||
}
|
||||
|
|
@ -42,7 +44,7 @@ export default class Terms extends Component {
|
|||
const curActive = this.props.activeSession;
|
||||
|
||||
// if we closed an item that wasn't focused, nothing changes
|
||||
if (~newUids.indexOf(curActive)) {
|
||||
if (newUids.indexOf(curActive) !== -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -58,15 +60,19 @@ export default class Terms extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
shouldComponentUpdate (nextProps) {
|
||||
shouldComponentUpdate(nextProps) {
|
||||
for (const i in nextProps) {
|
||||
if ('write' === i) continue;
|
||||
if (i === 'write') {
|
||||
continue;
|
||||
}
|
||||
if (this.props[i] !== nextProps[i]) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
for (const i in this.props) {
|
||||
if ('write' === i) continue;
|
||||
if (i === 'write') {
|
||||
continue;
|
||||
}
|
||||
if (this.props[i] !== nextProps[i]) {
|
||||
return true;
|
||||
}
|
||||
|
|
@ -74,7 +80,7 @@ export default class Terms extends Component {
|
|||
return false;
|
||||
}
|
||||
|
||||
onRef (uid, term) {
|
||||
onRef(uid, term) {
|
||||
if (term) {
|
||||
this.terms[uid] = term;
|
||||
} else {
|
||||
|
|
@ -82,19 +88,19 @@ export default class Terms extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
getTermByUid (uid) {
|
||||
getTermByUid(uid) {
|
||||
return this.terms[uid];
|
||||
}
|
||||
|
||||
getActiveTerm () {
|
||||
getActiveTerm() {
|
||||
return this.getTermByUid(this.props.activeSession);
|
||||
}
|
||||
|
||||
getLastTermIndex () {
|
||||
getLastTermIndex() {
|
||||
return this.props.sessions.length - 1;
|
||||
}
|
||||
|
||||
bind (fn, thisObj, uid) {
|
||||
bind(fn, thisObj, uid) {
|
||||
if (!this.bound.has(fn)) {
|
||||
this.bound.set(fn, {});
|
||||
}
|
||||
|
|
@ -105,25 +111,26 @@ export default class Terms extends Component {
|
|||
return map[uid];
|
||||
}
|
||||
|
||||
getTermProps (uid) {
|
||||
getTermProps(uid) {
|
||||
return getTermProps(uid, this.props);
|
||||
}
|
||||
|
||||
onTerminal (uid, term) {
|
||||
onTerminal(uid, term) {
|
||||
this.terms[uid] = term;
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
componentWillUnmount() {
|
||||
this.props.ref_(null);
|
||||
}
|
||||
|
||||
template (css) {
|
||||
return <div
|
||||
style={{ padding: this.props.padding }}
|
||||
className={ css('terms') }>
|
||||
template(css) {
|
||||
return (<div
|
||||
style={{padding: this.props.padding}}
|
||||
className={css('terms')}
|
||||
>
|
||||
{ this.props.customChildrenBefore }
|
||||
{
|
||||
this.props.sessions.map((session) => {
|
||||
this.props.sessions.map(session => {
|
||||
const uid = session.uid;
|
||||
const isActive = uid === this.props.activeSession;
|
||||
const props = getTermProps(uid, this.props, {
|
||||
|
|
@ -147,21 +154,23 @@ export default class Terms extends Component {
|
|||
bellSoundURL: this.props.bellSoundURL,
|
||||
copyOnSelect: this.props.copyOnSelect
|
||||
});
|
||||
return <div
|
||||
return (<div
|
||||
key={`d${uid}`}
|
||||
className={css('term', isActive && 'termActive')}>
|
||||
<Term
|
||||
key={uid}
|
||||
ref_={this.bind(this.onRef, this, uid)}
|
||||
{...props} />
|
||||
</div>;
|
||||
className={css('term', isActive && 'termActive')}
|
||||
>
|
||||
<Term
|
||||
key={uid}
|
||||
ref_={this.bind(this.onRef, this, uid)}
|
||||
{...props}
|
||||
/>
|
||||
</div>);
|
||||
})
|
||||
}
|
||||
{ this.props.customChildren }
|
||||
</div>;
|
||||
</div>);
|
||||
}
|
||||
|
||||
styles () {
|
||||
styles() {
|
||||
return {
|
||||
terms: {
|
||||
position: 'absolute',
|
||||
|
|
@ -188,9 +197,9 @@ export default class Terms extends Component {
|
|||
}
|
||||
|
||||
// little memoized helper to compute a map of uids
|
||||
function uids (sessions) {
|
||||
function uids(sessions) {
|
||||
if (!sessions._uids) {
|
||||
sessions._uids = sessions.map((s) => s.uid);
|
||||
sessions._uids = sessions.map(s => s.uid);
|
||||
}
|
||||
return sessions._uids;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,17 +1,18 @@
|
|||
import {createSelector} from 'reselect';
|
||||
|
||||
import Header from '../components/header';
|
||||
import { closeTab, changeTab, maximize, unmaximize } from '../actions/header';
|
||||
import { values } from '../utils/object';
|
||||
import { createSelector } from 'reselect';
|
||||
import { connect } from '../utils/plugins';
|
||||
import {closeTab, changeTab, maximize, unmaximize} from '../actions/header';
|
||||
import {values} from '../utils/object';
|
||||
import {connect} from '../utils/plugins';
|
||||
|
||||
const isMac = /Mac/.test(navigator.userAgent);
|
||||
|
||||
const getSessions = (sessions) => sessions.sessions;
|
||||
const getActiveUid = (sessions) => sessions.activeUid;
|
||||
const getSessions = sessions => sessions.sessions;
|
||||
const getActiveUid = sessions => sessions.activeUid;
|
||||
const getActivityMarkers = (sessions, ui) => ui.activityMarkers;
|
||||
const getTabs = createSelector(
|
||||
[getSessions, getActiveUid, getActivityMarkers],
|
||||
(sessions, activeUid, activityMarkers) => values(sessions).map((s) => {
|
||||
(sessions, activeUid, activityMarkers) => values(sessions).map(s => {
|
||||
return {
|
||||
uid: s.uid,
|
||||
title: s.title,
|
||||
|
|
@ -22,7 +23,7 @@ const getTabs = createSelector(
|
|||
);
|
||||
|
||||
const HeaderContainer = connect(
|
||||
(state) => {
|
||||
state => {
|
||||
return {
|
||||
// active is an index
|
||||
isMac,
|
||||
|
|
@ -33,13 +34,13 @@ const HeaderContainer = connect(
|
|||
maximized: state.ui.maximized
|
||||
};
|
||||
},
|
||||
(dispatch) => {
|
||||
dispatch => {
|
||||
return {
|
||||
onCloseTab: (i) => {
|
||||
onCloseTab: i => {
|
||||
dispatch(closeTab(i));
|
||||
},
|
||||
|
||||
onChangeTab: (i) => {
|
||||
onChangeTab: i => {
|
||||
dispatch(changeTab(i));
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -1,22 +1,24 @@
|
|||
import Mousetrap from 'mousetrap';
|
||||
import React from 'react';
|
||||
|
||||
import Component from '../component';
|
||||
import {connect} from '../utils/plugins';
|
||||
import * as uiActions from '../actions/ui';
|
||||
|
||||
import HeaderContainer from './header';
|
||||
import TermsContainer from './terms';
|
||||
import NotificationsContainer from './notifications';
|
||||
import Component from '../component';
|
||||
import Mousetrap from 'mousetrap';
|
||||
import * as uiActions from '../actions/ui';
|
||||
import { connect } from '../utils/plugins';
|
||||
|
||||
const isMac = /Mac/.test(navigator.userAgent);
|
||||
|
||||
class HyperTerm extends Component {
|
||||
constructor (props) {
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.focusActive = this.focusActive.bind(this);
|
||||
this.handleFocusActive = this.handleFocusActive.bind(this);
|
||||
this.onTermsRef = this.onTermsRef.bind(this);
|
||||
}
|
||||
|
||||
componentWillReceiveProps (next) {
|
||||
componentWillReceiveProps(next) {
|
||||
if (this.props.backgroundColor !== next.backgroundColor) {
|
||||
// this can be removed when `setBackgroundColor` in electron
|
||||
// starts working again
|
||||
|
|
@ -24,15 +26,19 @@ class HyperTerm extends Component {
|
|||
}
|
||||
}
|
||||
|
||||
focusActive () {
|
||||
handleFocusActive() {
|
||||
const term = this.terms.getActiveTerm();
|
||||
if (term) term.focus();
|
||||
if (term) {
|
||||
term.focus();
|
||||
}
|
||||
}
|
||||
|
||||
attachKeyListeners () {
|
||||
const { moveTo, moveLeft, moveRight } = this.props;
|
||||
attachKeyListeners() {
|
||||
const {moveTo, moveLeft, moveRight} = this.props;
|
||||
const term = this.terms.getActiveTerm();
|
||||
if (!term) return;
|
||||
if (!term) {
|
||||
return;
|
||||
}
|
||||
const lastIndex = this.terms.getLastTermIndex();
|
||||
const document = term.getTermDocument();
|
||||
const keys = new Mousetrap(document);
|
||||
|
|
@ -55,7 +61,7 @@ class HyperTerm extends Component {
|
|||
keys.bind('ctrl+shift+tab', moveLeft);
|
||||
keys.bind('ctrl+tab', moveRight);
|
||||
|
||||
const bound = method => { return term[method].bind(term); };
|
||||
const bound = method => term[method].bind(term);
|
||||
keys.bind('alt+left', bound('moveWordLeft'));
|
||||
keys.bind('alt+right', bound('moveWordRight'));
|
||||
keys.bind('alt+backspace', bound('deleteWordLeft'));
|
||||
|
|
@ -67,40 +73,45 @@ class HyperTerm extends Component {
|
|||
this.keys = keys;
|
||||
}
|
||||
|
||||
onTermsRef (terms) {
|
||||
onTermsRef(terms) {
|
||||
this.terms = terms;
|
||||
}
|
||||
|
||||
componentDidUpdate (prev) {
|
||||
componentDidUpdate(prev) {
|
||||
if (prev.activeSession !== this.props.activeSession) {
|
||||
if (this.keys) this.keys.reset();
|
||||
this.focusActive();
|
||||
if (this.keys) {
|
||||
this.keys.reset();
|
||||
}
|
||||
this.handleFocusActive();
|
||||
this.attachKeyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
if (this.keys) this.keys.reset();
|
||||
componentWillUnmount() {
|
||||
if (this.keys) {
|
||||
this.keys.reset();
|
||||
}
|
||||
document.body.style.backgroundColor = 'inherit';
|
||||
}
|
||||
|
||||
template (css) {
|
||||
const { isMac, customCSS, borderColor } = this.props;
|
||||
return <div onClick={ this.focusActive }>
|
||||
template(css) {
|
||||
const {isMac, customCSS, borderColor} = this.props;
|
||||
return (<div onClick={this.handleFocusActive}>
|
||||
<div
|
||||
style={{ borderColor }}
|
||||
className={ css('main', isMac && 'mainRounded') }>
|
||||
<HeaderContainer />
|
||||
<TermsContainer ref_={this.onTermsRef} />
|
||||
style={{borderColor}}
|
||||
className={css('main', isMac && 'mainRounded')}
|
||||
>
|
||||
<HeaderContainer/>
|
||||
<TermsContainer ref_={this.onTermsRef}/>
|
||||
</div>
|
||||
|
||||
<NotificationsContainer />
|
||||
<style dangerouslySetInnerHTML={{ __html: customCSS }} />
|
||||
<NotificationsContainer/>
|
||||
<style dangerouslySetInnerHTML={{__html: customCSS}}/>
|
||||
{ this.props.customChildren }
|
||||
</div>;
|
||||
</div>);
|
||||
}
|
||||
|
||||
styles () {
|
||||
styles() {
|
||||
return {
|
||||
main: {
|
||||
position: 'fixed',
|
||||
|
|
@ -120,7 +131,7 @@ class HyperTerm extends Component {
|
|||
}
|
||||
|
||||
const HyperTermContainer = connect(
|
||||
(state) => {
|
||||
state => {
|
||||
return {
|
||||
isMac,
|
||||
customCSS: state.ui.css,
|
||||
|
|
@ -129,9 +140,9 @@ const HyperTermContainer = connect(
|
|||
backgroundColor: state.ui.backgroundColor
|
||||
};
|
||||
},
|
||||
(dispatch) => {
|
||||
dispatch => {
|
||||
return {
|
||||
moveTo: (i) => {
|
||||
moveTo: i => {
|
||||
dispatch(uiActions.moveTo(i));
|
||||
},
|
||||
|
||||
|
|
@ -145,7 +156,7 @@ const HyperTermContainer = connect(
|
|||
};
|
||||
},
|
||||
null,
|
||||
{ withRef: true }
|
||||
{withRef: true}
|
||||
)(HyperTerm, 'HyperTerm');
|
||||
|
||||
export default HyperTermContainer;
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
import Notifications from '../components/notifications';
|
||||
import { installUpdate } from '../actions/updater';
|
||||
import { connect } from '../utils/plugins';
|
||||
import { dismissNotification } from '../actions/notifications';
|
||||
import {installUpdate} from '../actions/updater';
|
||||
import {connect} from '../utils/plugins';
|
||||
import {dismissNotification} from '../actions/notifications';
|
||||
|
||||
const NotificationsContainer = connect(
|
||||
(state) => {
|
||||
const { ui } = state;
|
||||
const { notifications } = ui;
|
||||
state => {
|
||||
const {ui} = state;
|
||||
const {notifications} = ui;
|
||||
const state_ = {};
|
||||
|
||||
if (notifications.font) {
|
||||
|
|
@ -40,7 +40,7 @@ const NotificationsContainer = connect(
|
|||
|
||||
return state_;
|
||||
},
|
||||
(dispatch) => {
|
||||
dispatch => {
|
||||
return {
|
||||
onDismissFont: () => {
|
||||
dispatch(dismissNotification('font'));
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import Terms from '../components/terms';
|
||||
import { values } from '../utils/object';
|
||||
import { connect } from '../utils/plugins';
|
||||
import {values} from '../utils/object';
|
||||
import {connect} from '../utils/plugins';
|
||||
import {
|
||||
resizeSession,
|
||||
sendSessionData,
|
||||
|
|
@ -10,7 +10,7 @@ import {
|
|||
} from '../actions/sessions';
|
||||
|
||||
const TermsContainer = connect(
|
||||
(state) => {
|
||||
state => {
|
||||
const sessions = state.sessions.sessions;
|
||||
return {
|
||||
cols: state.ui.cols,
|
||||
|
|
@ -19,9 +19,9 @@ const TermsContainer = connect(
|
|||
activeSession: state.sessions.activeUid,
|
||||
customCSS: state.ui.termCSS,
|
||||
write: state.sessions.write,
|
||||
fontSize: state.ui.fontSizeOverride
|
||||
? state.ui.fontSizeOverride
|
||||
: state.ui.fontSize,
|
||||
fontSize: state.ui.fontSizeOverride ?
|
||||
state.ui.fontSizeOverride :
|
||||
state.ui.fontSize,
|
||||
fontFamily: state.ui.fontFamily,
|
||||
fontSmoothing: state.ui.fontSmoothingOverride,
|
||||
padding: state.ui.padding,
|
||||
|
|
@ -36,31 +36,31 @@ const TermsContainer = connect(
|
|||
copyOnSelect: state.ui.copyOnSelect
|
||||
};
|
||||
},
|
||||
(dispatch) => {
|
||||
dispatch => {
|
||||
return {
|
||||
onData (uid, data) {
|
||||
onData(uid, data) {
|
||||
dispatch(sendSessionData(uid, data));
|
||||
},
|
||||
|
||||
onTitle (uid, title) {
|
||||
onTitle(uid, title) {
|
||||
dispatch(setSessionXtermTitle(uid, title));
|
||||
},
|
||||
|
||||
onResize (uid, cols, rows) {
|
||||
onResize(uid, cols, rows) {
|
||||
dispatch(resizeSession(uid, cols, rows));
|
||||
},
|
||||
|
||||
onURLAbort (uid) {
|
||||
onURLAbort(uid) {
|
||||
dispatch(exitSessionBrowser(uid));
|
||||
},
|
||||
|
||||
onActive (uid) {
|
||||
onActive(uid) {
|
||||
dispatch(setActiveSession(uid));
|
||||
}
|
||||
};
|
||||
},
|
||||
null,
|
||||
{ withRef: true }
|
||||
{withRef: true}
|
||||
)(Terms, 'Terms');
|
||||
|
||||
export default TermsContainer;
|
||||
|
|
|
|||
41
lib/hterm.js
41
lib/hterm.js
|
|
@ -1,4 +1,5 @@
|
|||
import { hterm, lib } from 'hterm-umdjs';
|
||||
import {hterm, lib} from 'hterm-umdjs';
|
||||
|
||||
const selection = require('./utils/selection');
|
||||
|
||||
hterm.defaultStorage = new lib.Storage.Memory();
|
||||
|
|
@ -13,7 +14,7 @@ hterm.Terminal.prototype.selectAll = function () {
|
|||
// override double click behavior to copy
|
||||
const oldMouse = hterm.Terminal.prototype.onMouse_;
|
||||
hterm.Terminal.prototype.onMouse_ = function (e) {
|
||||
if ('dblclick' === e.type) {
|
||||
if (e.type === 'dblclick') {
|
||||
selection.extend(this);
|
||||
console.log('[hyperterm+hterm] ignore double click');
|
||||
return;
|
||||
|
|
@ -28,8 +29,8 @@ hterm.Terminal.prototype.overlaySize = function () {};
|
|||
// a non-collapsed selection whose text is '', and results
|
||||
// in an infinite copy loop
|
||||
hterm.Terminal.prototype.copySelectionToClipboard = function () {
|
||||
var text = this.getSelectionText();
|
||||
if (text != null && text !== '') {
|
||||
const text = this.getSelectionText();
|
||||
if (text !== null && text !== '') {
|
||||
this.copyStringToClipboard(text);
|
||||
}
|
||||
};
|
||||
|
|
@ -45,7 +46,7 @@ hterm.Keyboard.prototype.onKeyDown_ = function (e) {
|
|||
*/
|
||||
if (e.key === 'Dead') {
|
||||
if (e.code === 'Quote' && e.shiftKey === false) {
|
||||
this.terminal.onVTKeystroke("'");
|
||||
this.terminal.onVTKeystroke('\'');
|
||||
return;
|
||||
}
|
||||
if (e.code === 'Quote' && e.shiftKey === true) {
|
||||
|
|
@ -103,11 +104,10 @@ hterm.Keyboard.prototype.onKeyDown_ = function (e) {
|
|||
|
||||
if (e.metaKey || e.altKey || (e.ctrlKey && e.code === 'Tab')) {
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
if ((!e.ctrlKey || e.code !== 'ControlLeft') && !e.shiftKey && e.code !== 'CapsLock') {
|
||||
// Test for valid keys in order to clear the terminal selection
|
||||
if ((!e.ctrlKey || e.code !== 'ControlLeft') && !e.shiftKey && e.code !== 'CapsLock') {
|
||||
selection.clear(this.terminal);
|
||||
}
|
||||
selection.clear(this.terminal);
|
||||
}
|
||||
return oldKeyDown.call(this, e);
|
||||
};
|
||||
|
|
@ -116,9 +116,8 @@ const oldKeyPress = hterm.Keyboard.prototype.onKeyPress_;
|
|||
hterm.Keyboard.prototype.onKeyPress_ = function (e) {
|
||||
if (e.metaKey) {
|
||||
return;
|
||||
} else {
|
||||
selection.clear(this.terminal);
|
||||
}
|
||||
selection.clear(this.terminal);
|
||||
return oldKeyPress.call(this, e);
|
||||
};
|
||||
|
||||
|
|
@ -130,7 +129,7 @@ hterm.Terminal.prototype.clearPreserveCursorRow = function () {
|
|||
this.scrollbackRows_.length = 0;
|
||||
this.scrollPort_.resetCache();
|
||||
|
||||
[this.primaryScreen_, this.alternateScreen_].forEach((screen) => {
|
||||
[this.primaryScreen_, this.alternateScreen_].forEach(screen => {
|
||||
const bottom = screen.getHeight();
|
||||
if (bottom > 0) {
|
||||
this.renumberRows_(0, bottom);
|
||||
|
|
@ -168,17 +167,19 @@ hterm.Terminal.prototype.clearPreserveCursorRow = function () {
|
|||
// fixes a bug in hterm, where the shorthand hex
|
||||
// is not properly converted to rgb
|
||||
lib.colors.hexToRGB = function (arg) {
|
||||
var hex16 = lib.colors.re_.hex16;
|
||||
var hex24 = lib.colors.re_.hex24;
|
||||
const hex16 = lib.colors.re_.hex16;
|
||||
const hex24 = lib.colors.re_.hex24;
|
||||
|
||||
function convert (hex) {
|
||||
function convert(hex) {
|
||||
if (hex.length === 4) {
|
||||
hex = hex.replace(hex16, function (h, r, g, b) {
|
||||
hex = hex.replace(hex16, (h, r, g, b) => {
|
||||
return '#' + r + r + g + g + b + b;
|
||||
});
|
||||
}
|
||||
var ary = hex.match(hex24);
|
||||
if (!ary) return null;
|
||||
const ary = hex.match(hex24);
|
||||
if (!ary) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return 'rgb(' +
|
||||
parseInt(ary[1], 16) + ', ' +
|
||||
|
|
@ -188,7 +189,7 @@ lib.colors.hexToRGB = function (arg) {
|
|||
}
|
||||
|
||||
if (arg instanceof Array) {
|
||||
for (var i = 0; i < arg.length; i++) {
|
||||
for (let i = 0; i < arg.length; i++) {
|
||||
arg[i] = convert(arg[i]);
|
||||
}
|
||||
} else {
|
||||
|
|
@ -199,4 +200,4 @@ lib.colors.hexToRGB = function (arg) {
|
|||
};
|
||||
|
||||
export default hterm;
|
||||
export { lib };
|
||||
export {lib};
|
||||
|
|
|
|||
35
lib/index.js
35
lib/index.js
|
|
@ -1,21 +1,22 @@
|
|||
import rpc from './rpc';
|
||||
import {createStore, applyMiddleware} from 'redux';
|
||||
import forceUpdate from 'react-deep-force-update';
|
||||
import {Provider} from 'react-redux';
|
||||
import React from 'react';
|
||||
import { render } from 'react-dom';
|
||||
import {render} from 'react-dom';
|
||||
import thunk from 'redux-thunk';
|
||||
import { Provider } from 'react-redux';
|
||||
import { init } from './actions/index';
|
||||
import {webFrame} from 'electron';
|
||||
|
||||
import rpc from './rpc';
|
||||
import {init} from './actions/index';
|
||||
import effects from './utils/effects';
|
||||
import * as config from './utils/config';
|
||||
import rootReducer from './reducers/index';
|
||||
import * as plugins from './utils/plugins';
|
||||
import * as uiActions from './actions/ui';
|
||||
import forceUpdate from 'react-deep-force-update';
|
||||
import * as updaterActions from './actions/updater';
|
||||
import * as sessionActions from './actions/sessions';
|
||||
import { createStore, applyMiddleware } from 'redux';
|
||||
import HyperTermContainer from './containers/hyperterm';
|
||||
import { loadConfig, reloadConfig } from './actions/config';
|
||||
import { webFrame } from 'electron';
|
||||
import {loadConfig, reloadConfig} from './actions/config';
|
||||
|
||||
// Disable pinch zoom
|
||||
webFrame.setZoomLevelLimits(1, 1);
|
||||
|
|
@ -48,23 +49,23 @@ rpc.on('ready', () => {
|
|||
store_.dispatch(uiActions.setFontSmoothing());
|
||||
});
|
||||
|
||||
rpc.on('session add', ({ uid, shell, pid }) => {
|
||||
rpc.on('session add', ({uid, shell, pid}) => {
|
||||
store_.dispatch(sessionActions.addSession(uid, shell, pid));
|
||||
});
|
||||
|
||||
rpc.on('session data', ({ uid, data }) => {
|
||||
rpc.on('session data', ({uid, data}) => {
|
||||
store_.dispatch(sessionActions.addSessionData(uid, data));
|
||||
});
|
||||
|
||||
rpc.on('session data send', ({ uid, data }) => {
|
||||
rpc.on('session data send', ({uid, data}) => {
|
||||
store_.dispatch(sessionActions.sendSessionData(uid, data));
|
||||
});
|
||||
|
||||
rpc.on('session title', ({ uid, title }) => {
|
||||
rpc.on('session title', ({uid, title}) => {
|
||||
store_.dispatch(sessionActions.setSessionProcessTitle(uid, title));
|
||||
});
|
||||
|
||||
rpc.on('session exit', ({ uid }) => {
|
||||
rpc.on('session exit', ({uid}) => {
|
||||
store_.dispatch(sessionActions.sessionExit(uid));
|
||||
});
|
||||
|
||||
|
|
@ -104,11 +105,11 @@ rpc.on('preferences', () => {
|
|||
store_.dispatch(uiActions.showPreferences());
|
||||
});
|
||||
|
||||
rpc.on('open file', ({ path }) => {
|
||||
rpc.on('open file', ({path}) => {
|
||||
store_.dispatch(uiActions.openFile(path));
|
||||
});
|
||||
|
||||
rpc.on('update available', ({ releaseName, releaseNotes }) => {
|
||||
rpc.on('update available', ({releaseName, releaseNotes}) => {
|
||||
store_.dispatch(updaterActions.updateAvailable(releaseName, releaseNotes));
|
||||
});
|
||||
|
||||
|
|
@ -117,8 +118,8 @@ rpc.on('move', () => {
|
|||
});
|
||||
|
||||
const app = render(
|
||||
<Provider store={ store_ }>
|
||||
<HyperTermContainer />
|
||||
<Provider store={store_}>
|
||||
<HyperTermContainer/>
|
||||
</Provider>,
|
||||
document.getElementById('mount')
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import {combineReducers} from 'redux';
|
||||
import ui from './ui';
|
||||
import sessions from './sessions';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import Immutable from 'seamless-immutable';
|
||||
import { decorateSessionsReducer } from '../utils/plugins';
|
||||
import {decorateSessionsReducer} from '../utils/plugins';
|
||||
import {
|
||||
SESSION_ADD,
|
||||
SESSION_PTY_EXIT,
|
||||
|
|
@ -19,7 +19,7 @@ const initialState = Immutable({
|
|||
activeUid: null
|
||||
});
|
||||
|
||||
function Session (obj) {
|
||||
function Session(obj) {
|
||||
return Immutable({
|
||||
uid: '',
|
||||
title: '',
|
||||
|
|
@ -31,7 +31,7 @@ function Session (obj) {
|
|||
}).merge(obj);
|
||||
}
|
||||
|
||||
function Write (obj) {
|
||||
function Write(obj) {
|
||||
return Immutable({
|
||||
uid: '',
|
||||
data: ''
|
||||
|
|
@ -63,7 +63,7 @@ const reducer = (state = initialState, action) => {
|
|||
cleared: true
|
||||
}
|
||||
}
|
||||
}, { deep: true });
|
||||
}, {deep: true});
|
||||
|
||||
case SESSION_PTY_DATA:
|
||||
return state
|
||||
|
|
@ -74,15 +74,14 @@ const reducer = (state = initialState, action) => {
|
|||
cleared: false
|
||||
}
|
||||
}
|
||||
}, { deep: true });
|
||||
}, {deep: true});
|
||||
|
||||
case SESSION_PTY_EXIT:
|
||||
if (state.sessions[action.uid]) {
|
||||
return deleteSession(state, action.uid);
|
||||
} else {
|
||||
console.log('ignore pty exit: session removed by user');
|
||||
return state;
|
||||
}
|
||||
console.log('ignore pty exit: session removed by user');
|
||||
return state;
|
||||
|
||||
case SESSION_USER_EXIT:
|
||||
return deleteSession(state, action.uid);
|
||||
|
|
@ -98,8 +97,8 @@ const reducer = (state = initialState, action) => {
|
|||
|
||||
export default decorateSessionsReducer(reducer);
|
||||
|
||||
function deleteSession (state, uid) {
|
||||
return state.updateIn(['sessions'], (sessions) => {
|
||||
function deleteSession(state, uid) {
|
||||
return state.updateIn(['sessions'], sessions => {
|
||||
const sessions_ = sessions.asMutable();
|
||||
delete sessions_[uid];
|
||||
return sessions_;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import Immutable from 'seamless-immutable';
|
||||
import { decorateUIReducer } from '../utils/plugins';
|
||||
import { CONFIG_LOAD, CONFIG_RELOAD } from '../constants/config';
|
||||
import {decorateUIReducer} from '../utils/plugins';
|
||||
import {CONFIG_LOAD, CONFIG_RELOAD} from '../constants/config';
|
||||
import {
|
||||
UI_FONT_SIZE_SET,
|
||||
UI_FONT_SIZE_RESET,
|
||||
|
|
@ -8,7 +8,7 @@ import {
|
|||
UI_WINDOW_MAXIMIZE,
|
||||
UI_WINDOW_UNMAXIMIZE
|
||||
} from '../constants/ui';
|
||||
import { NOTIFICATION_DISMISS } from '../constants/notifications';
|
||||
import {NOTIFICATION_DISMISS} from '../constants/notifications';
|
||||
import {
|
||||
SESSION_ADD,
|
||||
SESSION_RESIZE,
|
||||
|
|
@ -17,8 +17,8 @@ import {
|
|||
SESSION_SET_ACTIVE,
|
||||
SESSION_SET_CWD
|
||||
} from '../constants/sessions';
|
||||
import { UPDATE_AVAILABLE } from '../constants/updater';
|
||||
import { values } from '../utils/object';
|
||||
import {UPDATE_AVAILABLE} from '../constants/updater';
|
||||
import {values} from '../utils/object';
|
||||
|
||||
const allowedCursorShapes = new Set(['BEAM', 'BLOCK', 'UNDERLINE']);
|
||||
const allowedBells = new Set(['SOUND', false]);
|
||||
|
|
@ -77,10 +77,10 @@ const initial = Immutable({
|
|||
const reducer = (state = initial, action) => {
|
||||
let state_ = state;
|
||||
|
||||
switch (action.type) {
|
||||
switch (action.type) { // eslint-disable-line default-case
|
||||
case CONFIG_LOAD:
|
||||
case CONFIG_RELOAD:
|
||||
const { config } = action;
|
||||
case CONFIG_RELOAD: // eslint-disable-line no-case-declarations
|
||||
const {config} = action;
|
||||
state_ = state
|
||||
// we unset the user font size override if the
|
||||
// font size changed from the config
|
||||
|
|
@ -91,15 +91,15 @@ const reducer = (state = initial, action) => {
|
|||
ret.fontSizeOverride = null;
|
||||
}
|
||||
|
||||
if (null != config.fontSize) {
|
||||
if (config.fontSize !== null) {
|
||||
ret.fontSize = config.fontSize;
|
||||
}
|
||||
|
||||
if (null != config.fontFamily) {
|
||||
if (config.fontFamily !== null) {
|
||||
ret.fontFamily = config.fontFamily;
|
||||
}
|
||||
|
||||
if (null != config.cursorColor) {
|
||||
if (config.cursorColor !== null) {
|
||||
ret.cursorColor = config.cursorColor;
|
||||
}
|
||||
|
||||
|
|
@ -107,27 +107,27 @@ const reducer = (state = initial, action) => {
|
|||
ret.cursorShape = config.cursorShape;
|
||||
}
|
||||
|
||||
if (null != config.borderColor) {
|
||||
if (config.borderColor !== null) {
|
||||
ret.borderColor = config.borderColor;
|
||||
}
|
||||
|
||||
if (null != config.padding) {
|
||||
if (config.padding !== null) {
|
||||
ret.padding = config.padding;
|
||||
}
|
||||
|
||||
if (null != config.foregroundColor) {
|
||||
if (config.foregroundColor !== null) {
|
||||
ret.foregroundColor = config.foregroundColor;
|
||||
}
|
||||
|
||||
if (null != config.backgroundColor) {
|
||||
if (config.backgroundColor !== null) {
|
||||
ret.backgroundColor = config.backgroundColor;
|
||||
}
|
||||
|
||||
if (null != config.css) {
|
||||
if (config.css !== null) {
|
||||
ret.css = config.css;
|
||||
}
|
||||
|
||||
if (null != config.termCSS) {
|
||||
if (config.termCSS !== null) {
|
||||
ret.termCSS = config.termCSS;
|
||||
}
|
||||
|
||||
|
|
@ -135,27 +135,25 @@ const reducer = (state = initial, action) => {
|
|||
ret.bell = config.bell;
|
||||
}
|
||||
|
||||
if (null !== config.bellSoundURL) {
|
||||
if (config.bellSoundURL !== null) {
|
||||
ret.bellSoundURL = config.bellSoundURL || initial.bellSoundURL;
|
||||
}
|
||||
|
||||
if (null !== config.copyOnSelect) {
|
||||
if (config.copyOnSelect !== null) {
|
||||
ret.copyOnSelect = config.copyOnSelect;
|
||||
}
|
||||
|
||||
if (null != config.colors) {
|
||||
if (config.colors !== null) {
|
||||
if (Array.isArray(config.colors)) {
|
||||
const stateColors = Array.isArray(state.colors)
|
||||
? state.colors
|
||||
: values(state.colors);
|
||||
const stateColors = Array.isArray(state.colors) ?
|
||||
state.colors :
|
||||
values(state.colors);
|
||||
|
||||
if (stateColors.toString() !== config.colors.toString()) {
|
||||
ret.colors = config.colors;
|
||||
}
|
||||
} else {
|
||||
if (JSON.stringify(state.colors) !== JSON.stringify(config.colors)) {
|
||||
ret.colors = config.colors;
|
||||
}
|
||||
} else if (JSON.stringify(state.colors) !== JSON.stringify(config.colors)) {
|
||||
ret.colors = config.colors;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -168,7 +166,7 @@ const reducer = (state = initial, action) => {
|
|||
openAt: {
|
||||
[action.uid]: Date.now()
|
||||
}
|
||||
}, { deep: true });
|
||||
}, {deep: true});
|
||||
break;
|
||||
|
||||
case SESSION_RESIZE:
|
||||
|
|
@ -181,12 +179,12 @@ const reducer = (state = initial, action) => {
|
|||
|
||||
case SESSION_PTY_EXIT:
|
||||
state_ = state
|
||||
.updateIn(['openAt'], (times) => {
|
||||
.updateIn(['openAt'], times => {
|
||||
const times_ = times.asMutable();
|
||||
delete times_[action.uid];
|
||||
return times_;
|
||||
})
|
||||
.updateIn(['activityMarkers'], (markers) => {
|
||||
.updateIn(['activityMarkers'], markers => {
|
||||
const markers_ = markers.asMutable();
|
||||
delete markers_[action.uid];
|
||||
return markers_;
|
||||
|
|
@ -199,18 +197,22 @@ const reducer = (state = initial, action) => {
|
|||
activityMarkers: {
|
||||
[action.uid]: false
|
||||
}
|
||||
}, { deep: true });
|
||||
}, {deep: true});
|
||||
break;
|
||||
|
||||
case SESSION_PTY_DATA:
|
||||
case SESSION_PTY_DATA: // eslint-disable-line no-case-declarations
|
||||
// ignore activity markers for current tab
|
||||
if (action.uid === state.activeUid) break;
|
||||
if (action.uid === state.activeUid) {
|
||||
break;
|
||||
}
|
||||
|
||||
// current time for comparisons
|
||||
let now = Date.now();
|
||||
const now = Date.now();
|
||||
|
||||
// if first data events after open, ignore
|
||||
if (now - state.openAt[action.uid] < 1000) break;
|
||||
if (now - state.openAt[action.uid] < 1000) {
|
||||
break;
|
||||
}
|
||||
|
||||
// we ignore activity markers that are within
|
||||
// proximity of a resize event, since we
|
||||
|
|
@ -221,7 +223,7 @@ const reducer = (state = initial, action) => {
|
|||
activityMarkers: {
|
||||
[action.uid]: true
|
||||
}
|
||||
}, { deep: true });
|
||||
}, {deep: true});
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
@ -254,7 +256,7 @@ const reducer = (state = initial, action) => {
|
|||
notifications: {
|
||||
[action.id]: false
|
||||
}
|
||||
}, { deep: true });
|
||||
}, {deep: true});
|
||||
break;
|
||||
|
||||
case UPDATE_AVAILABLE:
|
||||
|
|
@ -270,18 +272,18 @@ const reducer = (state = initial, action) => {
|
|||
if (CONFIG_LOAD !== action.type) {
|
||||
if (state_.fontSize !== state.fontSize ||
|
||||
state_.fontSizeOverride !== state.fontSizeOverride) {
|
||||
state_ = state_.merge({ notifications: { font: true } }, { deep: true });
|
||||
state_ = state_.merge({notifications: {font: true}}, {deep: true});
|
||||
}
|
||||
}
|
||||
|
||||
if (null != state.cols && null != state.rows &&
|
||||
if (state.cols !== null && state.rows !== null &&
|
||||
(state.rows !== state_.rows ||
|
||||
state.cols !== state_.cols)) {
|
||||
state_ = state_.merge({ notifications: { resize: true } }, { deep: true });
|
||||
state_ = state_.merge({notifications: {resize: true}}, {deep: true});
|
||||
}
|
||||
|
||||
if (state.updateVersion !== state_.updateVersion) {
|
||||
state_ = state_.merge({ notifications: { updates: true } }, { deep: true });
|
||||
state_ = state_.merge({notifications: {updates: true}}, {deep: true});
|
||||
}
|
||||
|
||||
return state_;
|
||||
|
|
|
|||
|
|
@ -1,2 +1,3 @@
|
|||
import RPC from './utils/rpc';
|
||||
|
||||
export default new RPC();
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
export function last (arr) {
|
||||
export function last(arr) {
|
||||
return arr[arr.length - 1];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,13 +19,13 @@ const colorList = [
|
|||
'grayscale'
|
||||
];
|
||||
|
||||
export function getColorList (colors) {
|
||||
export function getColorList(colors) {
|
||||
// For backwards compatibility, return early if it's already an array
|
||||
if (Array.isArray(colors)) {
|
||||
return colors;
|
||||
}
|
||||
|
||||
return colorList.map((colorName) => {
|
||||
return colorList.map(colorName => {
|
||||
return colors[colorName];
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import { ipcRenderer, remote } from 'electron';
|
||||
import {ipcRenderer, remote} from 'electron';
|
||||
|
||||
const plugins = remote.require('./plugins');
|
||||
|
||||
export function getConfig () {
|
||||
export function getConfig() {
|
||||
return plugins.getDecoratedConfig();
|
||||
}
|
||||
|
||||
export function subscribe (fn) {
|
||||
export function subscribe(fn) {
|
||||
ipcRenderer.on('config change', fn);
|
||||
ipcRenderer.on('plugins change', fn);
|
||||
return () => {
|
||||
|
|
|
|||
2
lib/utils/effects.js
vendored
2
lib/utils/effects.js
vendored
|
|
@ -5,7 +5,7 @@
|
|||
// defer or add to existing side effects at will
|
||||
// as the result of an action being triggered
|
||||
|
||||
export default (store) => (next) => (action) => {
|
||||
export default () => next => action => {
|
||||
const ret = next(action);
|
||||
if (action.effect) {
|
||||
action.effect();
|
||||
|
|
|
|||
|
|
@ -6,12 +6,14 @@
|
|||
// Issue: https://github.com/kevva/executable/issues/9
|
||||
// PR: https://github.com/kevva/executable/pull/10
|
||||
|
||||
export function isExecutable (fileStat) {
|
||||
if (process.platform === 'win32') return true;
|
||||
export function isExecutable(fileStat) {
|
||||
if (process.platform === 'win32') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Boolean(
|
||||
(fileStat['mode'] & parseInt('0001', 8)) ||
|
||||
(fileStat['mode'] & parseInt('0010', 8)) ||
|
||||
(fileStat['mode'] & parseInt('0100', 8))
|
||||
(fileStat.mode & parseInt('0001', 8)) ||
|
||||
(fileStat.mode & parseInt('0010', 8)) ||
|
||||
(fileStat.mode & parseInt('0100', 8))
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/* global Notification */
|
||||
/* eslint no-new:0 */
|
||||
export default function notify (title, body) {
|
||||
export default function notify(title, body) {
|
||||
console.log(`[Notification] ${title}: ${body}`);
|
||||
new Notification(title, { body });
|
||||
new Notification(title, {body});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import vals from 'object-values';
|
||||
|
||||
const valsCache = new WeakMap();
|
||||
export function values (imm) {
|
||||
export function values(imm) {
|
||||
if (!valsCache.has(imm)) {
|
||||
valsCache.set(imm, vals(imm));
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ export function values (imm) {
|
|||
}
|
||||
|
||||
const keysCache = new WeakMap();
|
||||
export function keys (imm) {
|
||||
export function keys(imm) {
|
||||
if (!keysCache.has(imm)) {
|
||||
keysCache.set(imm, Object.keys(imm));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { remote } from 'electron';
|
||||
import { connect as reduxConnect } from 'react-redux';
|
||||
import {remote} from 'electron';
|
||||
import {connect as reduxConnect} from 'react-redux';
|
||||
|
||||
// we expose these two deps to component decorators
|
||||
import React from 'react';
|
||||
import Notification from '../components/notification';
|
||||
import notify from './notify';
|
||||
|
||||
var Module = require('module');
|
||||
var originalLoad = Module._load;
|
||||
const Module = require('module'); // eslint-disable-line import/newline-after-import
|
||||
const originalLoad = Module._load;
|
||||
Module._load = function (path) {
|
||||
if (path === 'react') {
|
||||
return React;
|
||||
|
|
@ -16,7 +16,7 @@ Module._load = function (path) {
|
|||
};
|
||||
|
||||
// remote interface to `../plugins`
|
||||
let plugins = remote.require('./plugins');
|
||||
const plugins = remote.require('./plugins');
|
||||
|
||||
// `require`d modules
|
||||
let modules;
|
||||
|
|
@ -34,12 +34,14 @@ let tabsPropsDecorators;
|
|||
let termPropsDecorators;
|
||||
|
||||
// the fs locations where usr plugins are stored
|
||||
const { path, localPath } = plugins.getBasePaths();
|
||||
const {path, localPath} = plugins.getBasePaths();
|
||||
|
||||
const clearModulesCache = () => {
|
||||
// trigger unload hooks
|
||||
modules.forEach((mod) => {
|
||||
if (mod.onRendererUnload) mod.onRendererUnload(window);
|
||||
modules.forEach(mod => {
|
||||
if (mod.onRendererUnload) {
|
||||
mod.onRendererUnload(window);
|
||||
}
|
||||
});
|
||||
|
||||
// clear require cache
|
||||
|
|
@ -51,7 +53,7 @@ const clearModulesCache = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const getPluginName = (path) => window.require('path').basename(path);
|
||||
const getPluginName = path => window.require('path').basename(path);
|
||||
|
||||
const loadModules = () => {
|
||||
console.log('(re)loading renderer plugins');
|
||||
|
|
@ -59,10 +61,10 @@ const loadModules = () => {
|
|||
|
||||
// initialize cache that we populate with extension methods
|
||||
connectors = {
|
||||
Terms: { state: [], dispatch: [] },
|
||||
Header: { state: [], dispatch: [] },
|
||||
HyperTerm: { state: [], dispatch: [] },
|
||||
Notifications: { state: [], dispatch: [] }
|
||||
Terms: {state: [], dispatch: []},
|
||||
Header: {state: [], dispatch: []},
|
||||
HyperTerm: {state: [], dispatch: []},
|
||||
Notifications: {state: [], dispatch: []}
|
||||
};
|
||||
uiReducers = [];
|
||||
middlewares = [];
|
||||
|
|
@ -72,7 +74,7 @@ const loadModules = () => {
|
|||
termPropsDecorators = [];
|
||||
|
||||
modules = paths.plugins.concat(paths.localPlugins)
|
||||
.map((path) => {
|
||||
.map(path => {
|
||||
let mod;
|
||||
const pluginName = getPluginName(path);
|
||||
|
||||
|
|
@ -83,11 +85,13 @@ const loadModules = () => {
|
|||
} catch (err) {
|
||||
console.error(err.stack);
|
||||
notify('Plugin load error', `"${pluginName}" failed to load in the renderer process. Check Developer Tools for details.`);
|
||||
return;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
for (const i in mod) {
|
||||
mod[i]._pluginName = pluginName;
|
||||
if ({}.hasOwnProperty.call(mod, i)) {
|
||||
mod[i]._pluginName = pluginName;
|
||||
}
|
||||
}
|
||||
|
||||
if (mod.middleware) {
|
||||
|
|
@ -152,13 +156,13 @@ const loadModules = () => {
|
|||
|
||||
return mod;
|
||||
})
|
||||
.filter((mod) => !!mod);
|
||||
.filter(mod => Boolean(mod));
|
||||
};
|
||||
|
||||
// load modules for initial decoration
|
||||
loadModules();
|
||||
|
||||
export function reload () {
|
||||
export function reload() {
|
||||
clearModulesCache();
|
||||
loadModules();
|
||||
// trigger re-decoration when components
|
||||
|
|
@ -166,13 +170,15 @@ export function reload () {
|
|||
decorated = {};
|
||||
}
|
||||
|
||||
export function getTermProps (uid, parentProps, props) {
|
||||
export function getTermProps(uid, parentProps, props) {
|
||||
let props_;
|
||||
|
||||
termPropsDecorators.forEach((fn) => {
|
||||
termPropsDecorators.forEach(fn => {
|
||||
let ret_;
|
||||
|
||||
if (!props_) props_ = Object.assign({}, props);
|
||||
if (!props_) {
|
||||
props_ = Object.assign({}, props);
|
||||
}
|
||||
|
||||
try {
|
||||
ret_ = fn(uid, parentProps, props_);
|
||||
|
|
@ -182,7 +188,7 @@ export function getTermProps (uid, parentProps, props) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!ret_ || 'object' !== typeof ret_) {
|
||||
if (!ret_ || typeof ret_ !== 'object') {
|
||||
notify('Plugin error', `${fn._pluginName}: Invalid return value of \`getTermProps\` (object expected).`);
|
||||
return;
|
||||
}
|
||||
|
|
@ -193,13 +199,15 @@ export function getTermProps (uid, parentProps, props) {
|
|||
return props_ || props;
|
||||
}
|
||||
|
||||
export function getTabsProps (parentProps, props) {
|
||||
export function getTabsProps(parentProps, props) {
|
||||
let props_;
|
||||
|
||||
tabsPropsDecorators.forEach((fn) => {
|
||||
tabsPropsDecorators.forEach(fn => {
|
||||
let ret_;
|
||||
|
||||
if (!props_) props_ = Object.assign({}, props);
|
||||
if (!props_) {
|
||||
props_ = Object.assign({}, props);
|
||||
}
|
||||
|
||||
try {
|
||||
ret_ = fn(parentProps, props_);
|
||||
|
|
@ -209,7 +217,7 @@ export function getTabsProps (parentProps, props) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!ret_ || 'object' !== typeof ret_) {
|
||||
if (!ret_ || typeof ret_ !== 'object') {
|
||||
notify('Plugin error', `${fn._pluginName}: Invalid return value of \`getTabsProps\` (object expected).`);
|
||||
return;
|
||||
}
|
||||
|
|
@ -220,13 +228,15 @@ export function getTabsProps (parentProps, props) {
|
|||
return props_ || props;
|
||||
}
|
||||
|
||||
export function getTabProps (tab, parentProps, props) {
|
||||
export function getTabProps(tab, parentProps, props) {
|
||||
let props_;
|
||||
|
||||
tabPropsDecorators.forEach((fn) => {
|
||||
tabPropsDecorators.forEach(fn => {
|
||||
let ret_;
|
||||
|
||||
if (!props_) props_ = Object.assign({}, props);
|
||||
if (!props_) {
|
||||
props_ = Object.assign({}, props);
|
||||
}
|
||||
|
||||
try {
|
||||
ret_ = fn(tab, parentProps, props_);
|
||||
|
|
@ -236,7 +246,7 @@ export function getTabProps (tab, parentProps, props) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!ret_ || 'object' !== typeof ret_) {
|
||||
if (!ret_ || typeof ret_ !== 'object') {
|
||||
notify('Plugin error', `${fn._pluginName}: Invalid return value of \`getTabProps\` (object expected).`);
|
||||
return;
|
||||
}
|
||||
|
|
@ -250,12 +260,12 @@ export function getTabProps (tab, parentProps, props) {
|
|||
// connects + decorates a class
|
||||
// plugins can override mapToState, dispatchToProps
|
||||
// and the class gets decorated (proxied)
|
||||
export function connect (stateFn, dispatchFn, c, d = {}) {
|
||||
return function (Class, name) {
|
||||
export function connect(stateFn, dispatchFn, c, d = {}) {
|
||||
return (Class, name) => {
|
||||
return reduxConnect(
|
||||
function (state) {
|
||||
state => {
|
||||
let ret = stateFn(state);
|
||||
connectors[name].state.forEach((fn) => {
|
||||
connectors[name].state.forEach(fn => {
|
||||
let ret_;
|
||||
|
||||
try {
|
||||
|
|
@ -266,7 +276,7 @@ export function connect (stateFn, dispatchFn, c, d = {}) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!ret_ || 'object' !== typeof ret_) {
|
||||
if (!ret_ || typeof ret_ !== 'object') {
|
||||
notify('Plugin error', `${fn._pluginName}: Invalid return value of \`map${name}State\` (object expected).`);
|
||||
return;
|
||||
}
|
||||
|
|
@ -275,9 +285,9 @@ export function connect (stateFn, dispatchFn, c, d = {}) {
|
|||
});
|
||||
return ret;
|
||||
},
|
||||
function (dispatch) {
|
||||
dispatch => {
|
||||
let ret = dispatchFn(dispatch);
|
||||
connectors[name].dispatch.forEach((fn) => {
|
||||
connectors[name].dispatch.forEach(fn => {
|
||||
let ret_;
|
||||
|
||||
try {
|
||||
|
|
@ -288,7 +298,7 @@ export function connect (stateFn, dispatchFn, c, d = {}) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!ret_ || 'object' !== typeof ret_) {
|
||||
if (!ret_ || typeof ret_ !== 'object') {
|
||||
notify('Plugin error', `${fn._pluginName}: Invalid return value of \`map${name}Dispatch\` (object expected).`);
|
||||
return;
|
||||
}
|
||||
|
|
@ -303,11 +313,11 @@ export function connect (stateFn, dispatchFn, c, d = {}) {
|
|||
};
|
||||
}
|
||||
|
||||
export function decorateUIReducer (fn) {
|
||||
export function decorateUIReducer(fn) {
|
||||
return (state, action) => {
|
||||
let state_ = fn(state, action);
|
||||
|
||||
uiReducers.forEach((pluginReducer) => {
|
||||
uiReducers.forEach(pluginReducer => {
|
||||
let state__;
|
||||
|
||||
try {
|
||||
|
|
@ -318,7 +328,7 @@ export function decorateUIReducer (fn) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!state__ || 'object' !== typeof state__) {
|
||||
if (!state__ || typeof state__ !== 'object') {
|
||||
notify('Plugin error', `${fn._pluginName}: Invalid return value of \`reduceUI\`.`);
|
||||
return;
|
||||
}
|
||||
|
|
@ -330,11 +340,11 @@ export function decorateUIReducer (fn) {
|
|||
};
|
||||
}
|
||||
|
||||
export function decorateSessionsReducer (fn) {
|
||||
export function decorateSessionsReducer(fn) {
|
||||
return (state, action) => {
|
||||
let state_ = fn(state, action);
|
||||
|
||||
sessionsReducers.forEach((pluginReducer) => {
|
||||
sessionsReducers.forEach(pluginReducer => {
|
||||
let state__;
|
||||
|
||||
try {
|
||||
|
|
@ -345,7 +355,7 @@ export function decorateSessionsReducer (fn) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (!state__ || 'object' !== typeof state__) {
|
||||
if (!state__ || typeof state__ !== 'object') {
|
||||
notify('Plugin error', `${fn._pluginName}: Invalid return value of \`reduceSessions\`.`);
|
||||
return;
|
||||
}
|
||||
|
|
@ -358,18 +368,18 @@ export function decorateSessionsReducer (fn) {
|
|||
}
|
||||
|
||||
// redux middleware generator
|
||||
export const middleware = (store) => (next) => (action) => {
|
||||
const nextMiddleware = remaining => action => remaining.length
|
||||
? remaining[0](store)(nextMiddleware(remaining.slice(1)))(action)
|
||||
: next(action);
|
||||
export const middleware = store => next => action => {
|
||||
const nextMiddleware = remaining => action => remaining.length ?
|
||||
remaining[0](store)(nextMiddleware(remaining.slice(1)))(action) :
|
||||
next(action);
|
||||
nextMiddleware(middlewares)(action);
|
||||
};
|
||||
|
||||
function getDecorated (parent, name) {
|
||||
function getDecorated(parent, name) {
|
||||
if (!decorated[name]) {
|
||||
let class_ = parent;
|
||||
|
||||
modules.forEach((mod) => {
|
||||
modules.forEach(mod => {
|
||||
const method = 'decorate' + name;
|
||||
const fn = mod[method];
|
||||
|
||||
|
|
@ -377,14 +387,14 @@ function getDecorated (parent, name) {
|
|||
let class__;
|
||||
|
||||
try {
|
||||
class__ = fn(class_, { React, Notification, notify });
|
||||
class__ = fn(class_, {React, Notification, notify});
|
||||
} catch (err) {
|
||||
console.error(err.stack);
|
||||
notify('Plugin error', `${fn._pluginName}: Error occurred in \`${method}\`. Check Developer Tools for details`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!class__ || 'function' !== typeof class__.prototype.render) {
|
||||
if (!class__ || typeof class__.prototype.render !== 'function') {
|
||||
notify('Plugin error', `${fn._pluginName}: Invalid return value of \`${method}\`. No \`render\` method found. Please return a \`React.Component\`.`);
|
||||
return;
|
||||
}
|
||||
|
|
@ -402,11 +412,11 @@ function getDecorated (parent, name) {
|
|||
// for each component, we return a higher-order component
|
||||
// that wraps with the higher-order components
|
||||
// exposed by plugins
|
||||
export function decorate (Component, name) {
|
||||
export function decorate(Component, name) {
|
||||
return class extends React.Component {
|
||||
render () {
|
||||
render() {
|
||||
const Sub = getDecorated(Component, name);
|
||||
return <Sub {...this.props} />;
|
||||
return <Sub {...this.props}/>;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
export default class Client {
|
||||
|
||||
constructor () {
|
||||
constructor() {
|
||||
const electron = window.require('electron');
|
||||
const EventEmitter = window.require('events');
|
||||
this.emitter = new EventEmitter();
|
||||
|
|
@ -25,32 +25,34 @@ export default class Client {
|
|||
}
|
||||
}
|
||||
|
||||
ipcListener (event, { ch, data }) {
|
||||
ipcListener(event, {ch, data}) {
|
||||
this.emitter.emit(ch, data);
|
||||
}
|
||||
|
||||
on (ev, fn) {
|
||||
on(ev, fn) {
|
||||
this.emitter.on(ev, fn);
|
||||
}
|
||||
|
||||
once (ev, fn) {
|
||||
once(ev, fn) {
|
||||
this.emitter.once(ev, fn);
|
||||
}
|
||||
|
||||
emit (ev, data) {
|
||||
if (!this.id) throw new Error('Not ready');
|
||||
this.ipc.send(this.id, { ev, data });
|
||||
emit(ev, data) {
|
||||
if (!this.id) {
|
||||
throw new Error('Not ready');
|
||||
}
|
||||
this.ipc.send(this.id, {ev, data});
|
||||
}
|
||||
|
||||
removeListener (ev, fn) {
|
||||
removeListener(ev, fn) {
|
||||
this.emitter.removeListener(ev, fn);
|
||||
}
|
||||
|
||||
removeAllListeners () {
|
||||
removeAllListeners() {
|
||||
this.emitter.removeAllListeners();
|
||||
}
|
||||
|
||||
destroy () {
|
||||
destroy() {
|
||||
this.removeAllListeners();
|
||||
this.ipc.removeAllListeners();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ exports.clear = function (terminal) {
|
|||
|
||||
// Use selection extend upon dblclick
|
||||
exports.extend = function (terminal) {
|
||||
let sel = terminal.document_.getSelection();
|
||||
const sel = terminal.document_.getSelection();
|
||||
|
||||
// Test if focusNode exist and nodeName is #text
|
||||
if (sel.focusNode && sel.focusNode.nodeName === '#text') {
|
||||
|
|
@ -17,10 +17,13 @@ exports.extend = function (terminal) {
|
|||
// Fix a bug in ScrollPort selectAll behavior
|
||||
// Select all rows in the viewport
|
||||
exports.all = function (terminal) {
|
||||
let scrollPort = terminal.scrollPort_;
|
||||
let firstRow, lastRowIndex, lastRow;
|
||||
const scrollPort = terminal.scrollPort_;
|
||||
let firstRow;
|
||||
let lastRow;
|
||||
|
||||
if (scrollPort.topFold_.nextSibling.rowIndex !== 0) {
|
||||
if (scrollPort.topFold_.nextSibling.rowIndex === 0) {
|
||||
firstRow = scrollPort.topFold_.nextSibling;
|
||||
} else {
|
||||
while (scrollPort.topFold_.previousSibling) {
|
||||
scrollPort.rowNodes_.removeChild(scrollPort.topFold_.previousSibling);
|
||||
}
|
||||
|
|
@ -28,21 +31,19 @@ exports.all = function (terminal) {
|
|||
firstRow = scrollPort.fetchRowNode_(0);
|
||||
scrollPort.rowNodes_.insertBefore(firstRow, scrollPort.topFold_);
|
||||
scrollPort.syncRowNodesDimensions_();
|
||||
} else {
|
||||
firstRow = scrollPort.topFold_.nextSibling;
|
||||
}
|
||||
|
||||
lastRowIndex = scrollPort.rowProvider_.getRowCount() - 1;
|
||||
const lastRowIndex = scrollPort.rowProvider_.getRowCount() - 1;
|
||||
|
||||
if (scrollPort.bottomFold_.previousSibling.rowIndex !== lastRowIndex) {
|
||||
if (scrollPort.bottomFold_.previousSibling.rowIndex === lastRowIndex) {
|
||||
lastRow = scrollPort.bottomFold_.previousSibling.rowIndex;
|
||||
} else {
|
||||
while (scrollPort.bottomFold_.nextSibling) {
|
||||
scrollPort.rowNodes_.removeChild(scrollPort.bottomFold_.nextSibling);
|
||||
}
|
||||
|
||||
lastRow = scrollPort.fetchRowNode_(lastRowIndex);
|
||||
scrollPort.rowNodes_.appendChild(lastRow);
|
||||
} else {
|
||||
lastRow = scrollPort.bottomFold_.previousSibling.rowIndex;
|
||||
}
|
||||
|
||||
scrollPort.selection.sync();
|
||||
|
|
|
|||
|
|
@ -2,12 +2,16 @@ import * as regex from './url-regex';
|
|||
|
||||
export const domainRegex = /\b((?=[a-z0-9-]{1,63}\.)(xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,63}\b|^localhost$|^127(?:\.[0-9]+){0,2}\.[0-9]+$|^(?:0*:)*?:?0*1$/;
|
||||
|
||||
export default function isUrlCommand (shell, data) {
|
||||
const matcher = regex[shell];
|
||||
if (undefined === matcher || !data) return null;
|
||||
export default function isUrlCommand(shell, data) {
|
||||
const matcher = regex[shell]; // eslint-disable-line import/namespace
|
||||
if (undefined === matcher || !data) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const match = data.match(matcher);
|
||||
if (!match) return null;
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
const protocol = match[1];
|
||||
const path = match[2];
|
||||
|
||||
|
|
|
|||
68
package.json
68
package.json
|
|
@ -36,51 +36,47 @@
|
|||
"devDependencies": {
|
||||
"babel-cli": "^6.11.4",
|
||||
"babel-core": "^6.11.4",
|
||||
"babel-eslint": "^6.1.2",
|
||||
"babel-loader": "^6.2.4",
|
||||
"babel-preset-react": "^6.11.1",
|
||||
"copy-webpack-plugin": "^3.0.1",
|
||||
"electron-builder": "^7.0.1",
|
||||
"electron": "1.4.0",
|
||||
"eslint": "^3.2.0",
|
||||
"eslint-config-standard": "^5.3.5",
|
||||
"eslint-plugin-promise": "^2.0.0",
|
||||
"eslint-plugin-react": "^6.0.0",
|
||||
"eslint-plugin-standard": "^2.0.0",
|
||||
"eslint-config-xo-react": "^0.9.0",
|
||||
"eslint-plugin-react": "^6.2.2",
|
||||
"husky": "^0.11.6",
|
||||
"webpack": "^2.1.0-beta.15"
|
||||
"webpack": "^2.1.0-beta.15",
|
||||
"xo": "^0.16.0"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": "standard",
|
||||
"plugins": [
|
||||
"react"
|
||||
"xo": {
|
||||
"extends": "xo-react",
|
||||
"esnext": true,
|
||||
"space": true,
|
||||
"env": [
|
||||
"browser",
|
||||
"node",
|
||||
"mocha"
|
||||
],
|
||||
"rules": {
|
||||
"yoda": "off",
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-unused-vars": "error",
|
||||
"no-extra-semi": "error",
|
||||
"semi-spacing": [
|
||||
"error",
|
||||
{
|
||||
"before": false,
|
||||
"after": true
|
||||
}
|
||||
],
|
||||
"react/jsx-uses-react": "warn",
|
||||
"react/jsx-uses-vars": "warn"
|
||||
"react/jsx-filename-extension": 0,
|
||||
"react/prop-types": 0,
|
||||
"babel/new-cap": 0,
|
||||
"quote-props": 0,
|
||||
"import/no-extraneous-dependencies": 0,
|
||||
"no-warning-comments": 0,
|
||||
"complexity": 0,
|
||||
"react/no-danger": 0,
|
||||
"react/no-string-refs": 0,
|
||||
"react/jsx-key": 0,
|
||||
"no-nested-ternary": 0,
|
||||
"react/jsx-no-bind": 0
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaFeatures": {
|
||||
"jsx": true
|
||||
}
|
||||
},
|
||||
"env": {
|
||||
"mocha": true
|
||||
}
|
||||
"ignore": [
|
||||
"dist",
|
||||
"build",
|
||||
"app/dist",
|
||||
"app/static",
|
||||
"assets"
|
||||
]
|
||||
},
|
||||
"babel": {
|
||||
"presets": [
|
||||
|
|
@ -106,7 +102,7 @@
|
|||
},
|
||||
"scripts": {
|
||||
"dev": "webpack --watch",
|
||||
"lint": "eslint .",
|
||||
"lint": "xo",
|
||||
"build": "NODE_ENV=production webpack",
|
||||
"test": "npm run lint && electron-mocha test/*",
|
||||
"start": "electron app",
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable prefer-arrow-callback */
|
||||
require('../setup');
|
||||
|
||||
const {_toDependencies} = require('../../app/plugins');
|
||||
|
|
@ -9,7 +10,7 @@ describe('plugins', function () {
|
|||
'@org1/project4#1.0.0', '@org2/project5@alpha',
|
||||
'@org3/project6'];
|
||||
|
||||
_toDependencies({plugins: plugins}).should.be.eql({
|
||||
_toDependencies({plugins}).should.be.eql({
|
||||
'project1': 'latest',
|
||||
'project2': '1.0.0',
|
||||
'project3': 'beta',
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
const path = require('path');
|
||||
|
||||
const webpack = require('webpack');
|
||||
const Copy = require('copy-webpack-plugin');
|
||||
const path = require('path');
|
||||
|
||||
const nodeEnv = process.env.NODE_ENV || 'development';
|
||||
const isProd = nodeEnv === 'production';
|
||||
|
|
@ -27,7 +28,7 @@ module.exports = {
|
|||
},
|
||||
plugins: [
|
||||
new webpack.DefinePlugin({
|
||||
'process.env': {
|
||||
'process.env': { // eslint-disable-line quote-props
|
||||
'NODE_ENV': JSON.stringify(nodeEnv)
|
||||
}
|
||||
}),
|
||||
|
|
|
|||
Loading…
Reference in a new issue