Add prettier and resolve all lint errors

This commit is contained in:
CHaBou 2017-09-10 15:35:10 +02:00
parent 0fbf7cfc97
commit 1155bb54b1
No known key found for this signature in database
GPG key ID: EF8D073B729A0B33
64 changed files with 1120 additions and 1129 deletions

View file

@ -4,7 +4,8 @@ const ms = require('ms');
const retry = require('async-retry');
// Utilities
const notify = require('./notify'); // eslint-disable-line no-unused-vars
// eslint-disable-next-line no-unused-vars
const notify = require('./notify');
const {version} = require('./package');
const {getConfig} = require('./config');
@ -14,6 +15,7 @@ let isInit = false;
function init() {
autoUpdater.on('error', (err, msg) => {
//eslint-disable-next-line no-console
console.error('Error fetching updates', msg + ' (' + err.stack + ')');
});
@ -51,7 +53,7 @@ function init() {
isInit = true;
}
module.exports = function (win) {
module.exports = win => {
if (!isInit) {
init();
}

View file

@ -12,7 +12,7 @@ const watchCfg = process.platform === 'win32' ? {interval: 2000} : {};
let cfg = {};
let _watcher;
const _watch = function () {
const _watch = function() {
if (_watcher) {
return _watcher;
}
@ -23,79 +23,74 @@ const _watch = function () {
cfg = _import();
notify('Configuration updated', 'Hyper configuration reloaded!');
watchers.forEach(fn => fn());
checkDeprecatedConfig()
checkDeprecatedConfig();
});
_watcher.on('error', error => {
//eslint-disable-next-line no-console
console.error('error watching config', error);
});
};
exports.subscribe = function (fn) {
exports.subscribe = fn => {
watchers.push(fn);
return () => {
watchers.splice(watchers.indexOf(fn), 1);
};
};
exports.getConfigDir = function () {
exports.getConfigDir = () => {
// expose config directory to load plugin from the right place
return cfgDir;
};
exports.getConfig = function () {
exports.getConfig = () => {
return cfg.config;
};
exports.openConfig = function () {
exports.openConfig = () => {
return _openConfig();
};
exports.getPlugins = function () {
exports.getPlugins = () => {
return {
plugins: cfg.plugins,
localPlugins: cfg.localPlugins
};
};
exports.getKeymaps = function () {
exports.getKeymaps = () => {
return cfg.keymaps;
};
exports.extendKeymaps = function (keymaps) {
exports.extendKeymaps = keymaps => {
if (keymaps) {
cfg.keymaps = keymaps;
}
};
exports.setup = function () {
exports.setup = () => {
cfg = _import();
_watch();
checkDeprecatedConfig()
checkDeprecatedConfig();
};
exports.getWin = win.get;
exports.winRecord = win.recordState;
const getDeprecatedCSS = function (config) {
const getDeprecatedCSS = function(config) {
const deprecated = [];
const deprecatedCSS = [
'x-screen',
'x-row',
'cursor-node',
'::selection'
];
const deprecatedCSS = ['x-screen', 'x-row', 'cursor-node', '::selection'];
deprecatedCSS.forEach(css => {
if ((config.css && config.css.indexOf(css) !== -1) ||
(config.termCSS && config.termCSS.indexOf(css) !== -1)) {
if ((config.css && config.css.indexOf(css) !== -1) || (config.termCSS && config.termCSS.indexOf(css) !== -1)) {
deprecated.push(css);
}
})
});
return deprecated;
}
};
exports.getDeprecatedCSS = getDeprecatedCSS;
const checkDeprecatedConfig = function () {
const checkDeprecatedConfig = function() {
if (!cfg.config) {
return;
}
@ -104,22 +99,22 @@ const checkDeprecatedConfig = function () {
return;
}
const deprecatedStr = deprecated.join(', ');
notify('Configuration warning', `Your configuration uses some deprecated CSS classes (${deprecatedStr})`)
}
notify('Configuration warning', `Your configuration uses some deprecated CSS classes (${deprecatedStr})`);
};
exports.htermConfigTranslate = (config) => {
exports.htermConfigTranslate = config => {
const cssReplacements = {
'x-screen x-row([ \{\.\[])': '.xterm-rows > div$1',
'.cursor-node([ \{\.\[])': '.terminal-cursor$1',
'::selection([ \{\.\[])': '.terminal .xterm-selection div$1',
'x-screen a([ \{\.\[])': '.terminal a$1',
'x-row a([ \{\.\[])': '.terminal a$1'
}
'x-screen x-row([ {.[])': '.xterm-rows > div$1',
'.cursor-node([ {.[])': '.terminal-cursor$1',
'::selection([ {.[])': '.terminal .xterm-selection div$1',
'x-screen a([ {.[])': '.terminal a$1',
'x-row a([ {.[])': '.terminal a$1'
};
Object.keys(cssReplacements).forEach(pattern => {
const searchvalue = new RegExp(pattern, 'g');
const newvalue = cssReplacements[pattern];
config.css = config.css.replace(searchvalue, newvalue);
config.termCSS = config.termCSS.replace(searchvalue, newvalue);
})
});
return config;
}
};

View file

@ -4,18 +4,18 @@ const {defaultCfg, cfgPath, plugs} = require('./paths');
const _init = require('./init');
const _keymaps = require('./keymaps');
const _write = function (path, data) {
const _write = function(path, data) {
// This method will take text formatted as Unix line endings and transform it
// to text formatted with DOS line endings. We do this because the default
// text editor on Windows (notepad) doesn't Deal with LF files. Still. In 2017.
const crlfify = function (str) {
const crlfify = function(str) {
return str.replace(/\r?\n/g, '\r\n');
};
const format = process.platform === 'win32' ? crlfify(data.toString()) : data;
writeFileSync(path, format, 'utf8');
};
const _importConf = function () {
const _importConf = function() {
// init plugin directories if not present
mkdirpSync(plugs.base);
mkdirpSync(plugs.local);
@ -30,11 +30,12 @@ const _importConf = function () {
return {userCfg: {}, defaultCfg: _defaultCfg};
}
} catch (err) {
//eslint-disable-next-line no-console
console.log(err);
}
};
const _import = function () {
const _import = function() {
const cfg = _init(_importConf());
if (cfg) {

View file

@ -2,7 +2,7 @@ const vm = require('vm');
const merge = require('lodash/merge');
const notify = require('../notify');
const _extract = function (script) {
const _extract = function(script) {
const module = {};
script.runInNewContext({module});
if (!module.exports) {
@ -11,21 +11,22 @@ const _extract = function (script) {
return module.exports;
};
const _syntaxValidation = function (cfg) {
const _syntaxValidation = function(cfg) {
try {
return new vm.Script(cfg, {filename: '.hyper.js', displayErrors: true});
} catch (err) {
notify(`Error loading config: ${err.name}, see DevTools for more info`);
//eslint-disable-next-line no-console
console.error('Error loading config:', err);
}
};
const _extractDefault = function (cfg) {
const _extractDefault = function(cfg) {
return _extract(_syntaxValidation(cfg));
};
// init config
const _init = function (cfg) {
const _init = function(cfg) {
const script = _syntaxValidation(cfg.userCfg);
if (script) {
const _cfg = _extract(script);

View file

@ -4,7 +4,7 @@ const {defaultPlatformKeyPath} = require('./paths');
const commands = {};
const keys = {};
const _setKeysForCommands = function (keymap) {
const _setKeysForCommands = function(keymap) {
for (const command in keymap) {
if (command) {
commands[command] = keymap[command].toLowerCase();
@ -12,15 +12,15 @@ const _setKeysForCommands = function (keymap) {
}
};
const _setCommandsForKeys = function (commands) {
for (const command in commands) {
const _setCommandsForKeys = function(commands_) {
for (const command in commands_) {
if (command) {
keys[commands[command]] = command;
keys[commands_[command]] = command;
}
}
};
const _import = function (customsKeys) {
const _import = function(customsKeys) {
try {
const mapping = JSON.parse(readFileSync(defaultPlatformKeyPath()));
_setKeysForCommands(mapping);
@ -28,10 +28,13 @@ const _import = function (customsKeys) {
_setCommandsForKeys(commands);
return {commands, keys};
} catch (err) {}
} catch (err) {
//eslint-disable-next-line no-console
console.error(err);
}
};
const _extend = function (customsKeys) {
const _extend = function(customsKeys) {
if (customsKeys) {
for (const command in customsKeys) {
if (command) {

View file

@ -9,34 +9,39 @@ if (process.platform === 'win32') {
// Windows opens .js files with WScript.exe by default
// If the user hasn't set up an editor for .js files, we fallback to notepad.
const getFileExtKeys = () => new Promise((resolve, reject) => {
Registry({
hive: Registry.HKCU,
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.js'
})
.keys((error, keys) => {
if (error) {
reject(error);
} else {
resolve(keys || []);
}
const getFileExtKeys = () =>
new Promise((resolve, reject) => {
Registry({
hive: Registry.HKCU,
key: '\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\FileExts\\.js'
}).keys((error, keys) => {
if (error) {
reject(error);
} else {
resolve(keys || []);
}
});
});
});
const hasDefaultSet = async () => {
const keys = await getFileExtKeys();
const valueGroups = await Promise.all(keys.map(key => new Promise((resolve, reject) => {
key.values((error, items) => {
if (error) {
reject(error);
}
resolve(items.map(item => item.value || '') || []);
});
})));
const valueGroups = await Promise.all(
keys.map(
key =>
new Promise((resolve, reject) => {
key.values((error, items) => {
if (error) {
reject(error);
}
resolve(items.map(item => item.value || '') || []);
});
})
)
);
const values = valueGroups
.reduce((allValues, groupValues) => ([...allValues, ...groupValues]), [])
.reduce((allValues, groupValues) => [...allValues, ...groupValues], [])
.filter(value => value && typeof value === 'string');
// No default app set
@ -49,29 +54,33 @@ if (process.platform === 'win32') {
const userDefaults = values.filter(value => value.endsWith('.exe') && !value.includes('WScript.exe'));
// WScript.exe is overidden
return (userDefaults.length > 0);
return userDefaults.length > 0;
}
return true;
};
// This mimics shell.openItem, true if it worked, false if not.
const openNotepad = file => new Promise(resolve => {
exec(`start notepad.exe ${file}`, error => {
resolve(!error);
const openNotepad = file =>
new Promise(resolve => {
exec(`start notepad.exe ${file}`, error => {
resolve(!error);
});
});
});
module.exports = () => hasDefaultSet()
.then(yes => {
if (yes) {
return shell.openItem(cfgPath);
}
console.warn('No default app set for .js files, using notepad.exe fallback');
return openNotepad(cfgPath);
})
.catch(err => {
console.error('Open config with default app error:', err);
return openNotepad(cfgPath);
});
module.exports = () =>
hasDefaultSet()
.then(yes => {
if (yes) {
return shell.openItem(cfgPath);
}
//eslint-disable-next-line no-console
console.warn('No default app set for .js files, using notepad.exe fallback');
return openNotepad(cfgPath);
})
.catch(err => {
//eslint-disable-next-line no-console
console.error('Open config with default app error:', err);
return openNotepad(cfgPath);
});
}

View file

@ -32,10 +32,14 @@ const linuxKeys = join(keymapPath, 'linux.json');
const defaultPlatformKeyPath = () => {
switch (process.platform) {
case 'darwin': return darwinKeys;
case 'win32': return win32Keys;
case 'linux': return linuxKeys;
default: return darwinKeys;
case 'darwin':
return darwinKeys;
case 'win32':
return win32Keys;
case 'linux':
return linuxKeys;
default:
return darwinKeys;
}
};
@ -45,6 +49,7 @@ if (isDev) {
statSync(devCfg);
cfgPath = devCfg;
cfgDir = devDir;
//eslint-disable-next-line no-console
console.log('using config file:', cfgPath);
} catch (err) {
// ignore
@ -52,5 +57,12 @@ if (isDev) {
}
module.exports = {
cfgDir, cfgPath, cfgFile, defaultCfg, icon, defaultPlatformKeyPath, plugs, yarn
cfgDir,
cfgPath,
cfgFile,
defaultCfg,
icon,
defaultPlatformKeyPath,
plugs,
yarn
};

View file

@ -2,8 +2,11 @@
if (['--help', '-v', '--version'].includes(process.argv[1])) {
const {version} = require('./package');
const configLocation = process.platform === 'win32' ? process.env.userprofile + '\\.hyper.js' : '~/.hyper.js';
//eslint-disable-next-line no-console
console.log(`Hyper version ${version}`);
//eslint-disable-next-line no-console
console.log('Hyper does not accept any command line arguments. Please modify the config file instead.');
//eslint-disable-next-line no-console
console.log(`Hyper configuration file located at: ${configLocation}`);
// eslint-disable-next-line unicorn/no-process-exit
process.exit();
@ -14,6 +17,7 @@ const checkSquirrel = () => {
try {
squirrel = require('electron-squirrel-startup');
//eslint-disable-next-line no-empty
} catch (err) {}
if (squirrel) {
@ -81,6 +85,7 @@ app.getLastFocusedWindow = () => {
};
if (isDev) {
//eslint-disable-next-line no-console
console.log('running in dev mode');
// Overide default appVersion which is set from package.json
@ -90,123 +95,132 @@ if (isDev) {
}
});
} else {
//eslint-disable-next-line no-console
console.log('running in prod mode');
}
const url = 'file://' + resolve(
isDev ? __dirname : app.getAppPath(),
'index.html'
);
const url = 'file://' + resolve(isDev ? __dirname : app.getAppPath(), 'index.html');
//eslint-disable-next-line no-console
console.log('electron will open', url);
app.on('ready', () => installDevExtensions(isDev).then(() => {
function createWindow(fn, options = {}) {
const cfg = plugins.getDecoratedConfig();
app.on('ready', () =>
installDevExtensions(isDev)
.then(() => {
function createWindow(fn, options = {}) {
const cfg = plugins.getDecoratedConfig();
const winSet = config.getWin();
let [startX, startY] = winSet.position;
const winSet = config.getWin();
let [startX, startY] = winSet.position;
const [width, height] = options.size ? options.size : (cfg.windowSize || winSet.size);
const {screen} = require('electron');
const [width, height] = options.size ? options.size : cfg.windowSize || winSet.size;
const {screen} = require('electron');
const winPos = options.position;
const winPos = options.position;
// Open the new window roughly the height of the header away from the
// previous window. This also ensures in multi monitor setups that the
// new terminal is on the correct screen.
const focusedWindow = BrowserWindow.getFocusedWindow() || app.getLastFocusedWindow();
// In case of options defaults position and size, we should ignore the focusedWindow.
if (winPos !== undefined) {
[startX, startY] = winPos;
} else if (focusedWindow) {
const points = focusedWindow.getPosition();
const currentScreen = screen.getDisplayNearestPoint({x: points[0], y: points[1]});
// Open the new window roughly the height of the header away from the
// previous window. This also ensures in multi monitor setups that the
// new terminal is on the correct screen.
const focusedWindow = BrowserWindow.getFocusedWindow() || app.getLastFocusedWindow();
// In case of options defaults position and size, we should ignore the focusedWindow.
if (winPos !== undefined) {
[startX, startY] = winPos;
} else if (focusedWindow) {
const points = focusedWindow.getPosition();
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);
const biggestX = points[0] + 100 + width - currentScreen.bounds.x;
const biggestY = points[1] + 100 + height - currentScreen.bounds.y;
if (biggestX > currentScreen.size.width) {
startX = 50;
} else {
startX = points[0] + 34;
if (biggestX > currentScreen.size.width) {
startX = 50;
} else {
startX = points[0] + 34;
}
if (biggestY > currentScreen.size.height) {
startY = 50;
} else {
startY = points[1] + 34;
}
}
const hwin = new Window({width, height, x: startX, y: startY}, cfg, fn);
windowSet.add(hwin);
hwin.loadURL(url);
// the window can be closed by the browser process itself
hwin.on('close', () => {
hwin.clean();
windowSet.delete(hwin);
});
hwin.on('closed', () => {
if (process.platform !== 'darwin' && windowSet.size === 0) {
app.quit();
}
});
return hwin;
}
if (biggestY > currentScreen.size.height) {
startY = 50;
} else {
startY = points[1] + 34;
}
}
const hwin = new Window({width, height, x: startX, y: startY}, cfg, fn);
windowSet.add(hwin);
hwin.loadURL(url);
// the window can be closed by the browser process itself
hwin.on('close', () => {
hwin.clean();
windowSet.delete(hwin);
});
hwin.on('closed', () => {
if (process.platform !== 'darwin' && windowSet.size === 0) {
app.quit();
}
});
return hwin;
}
// when opening create a new window
createWindow();
// expose to plugins
app.createWindow = createWindow;
// mac only. when the dock icon is clicked
// and we don't have any active windows open,
// we open one
app.on('activate', () => {
if (!windowSet.size) {
// when opening create a new window
createWindow();
}
});
const makeMenu = () => {
const menu = plugins.decorateMenu(
AppMenu(createWindow, () => {
plugins.updatePlugins({force: true});
},
plugins.getLoadedPluginVersions
));
// expose to plugins
app.createWindow = createWindow;
// If we're on Mac make a Dock Menu
if (process.platform === 'darwin') {
const dockMenu = Menu.buildFromTemplate([{
label: 'New Window',
click() {
// mac only. when the dock icon is clicked
// and we don't have any active windows open,
// we open one
app.on('activate', () => {
if (!windowSet.size) {
createWindow();
}
}]);
app.dock.setMenu(dockMenu);
}
});
Menu.setApplicationMenu(
Menu.buildFromTemplate(menu)
);
};
const makeMenu = () => {
const menu = plugins.decorateMenu(
AppMenu(
createWindow,
() => {
plugins.updatePlugins({force: true});
},
plugins.getLoadedPluginVersions
)
);
const load = () => {
plugins.onApp(app);
plugins.extendKeymaps();
makeMenu();
};
// If we're on Mac make a Dock Menu
if (process.platform === 'darwin') {
const dockMenu = Menu.buildFromTemplate([
{
label: 'New Window',
click() {
createWindow();
}
}
]);
app.dock.setMenu(dockMenu);
}
load();
plugins.subscribe(load);
}).catch(err => {
console.error('Error while loading devtools extensions', err);
}));
Menu.setApplicationMenu(Menu.buildFromTemplate(menu));
};
const load = () => {
plugins.onApp(app);
plugins.extendKeymaps();
makeMenu();
};
load();
plugins.subscribe(load);
})
.catch(err => {
//eslint-disable-next-line no-console
console.error('Error while loading devtools extensions', err);
})
);
app.on('open-file', (event, path) => {
const lastWindow = app.getLastFocusedWindow();
@ -222,17 +236,14 @@ app.on('open-file', (event, path) => {
}
});
function installDevExtensions(isDev) {
if (!isDev) {
function installDevExtensions(isDev_) {
if (!isDev_) {
return Promise.resolve();
}
// eslint-disable-next-line import/no-extraneous-dependencies
const installer = require('electron-devtools-installer');
const extensions = [
'REACT_DEVELOPER_TOOLS',
'REDUX_DEVTOOLS'
];
const extensions = ['REACT_DEVELOPER_TOOLS', 'REDUX_DEVTOOLS'];
const forceDownload = Boolean(process.env.UPGRADE_EXTENSIONS);
return Promise.all(extensions.map(name => installer.default(installer[name], forceDownload)));

View file

@ -27,9 +27,8 @@ module.exports = (createWindow, updatePlugins, getLoadedPluginVersions) => {
const showAbout = () => {
const loadedPlugins = getLoadedPluginVersions();
const pluginList = loadedPlugins.length === 0 ?
'none' :
loadedPlugins.map(plugin => `\n ${plugin.name} (${plugin.version})`);
const pluginList =
loadedPlugins.length === 0 ? 'none' : loadedPlugins.map(plugin => `\n ${plugin.name} (${plugin.version})`);
dialog.showMessageBox({
title: `About ${appName}`,

View file

@ -3,7 +3,7 @@
const {app} = require('electron');
const {openConfig} = require('../../config');
module.exports = function (commands, showAbout) {
module.exports = (commands, showAbout) => {
return {
label: `${app.getName()}`,
submenu: [

View file

@ -1,6 +1,6 @@
const {openConfig} = require('../../config');
module.exports = function (commands) {
module.exports = commands => {
const submenu = [
{
role: 'undo',

View file

@ -1,7 +1,7 @@
const os = require('os');
const {app, shell} = require('electron');
module.exports = function (commands, showAbout) {
module.exports = (commands, showAbout) => {
const submenu = [
{
label: `${app.getName()} Website`,

View file

@ -1,4 +1,4 @@
module.exports = function (commands, update) {
module.exports = (commands, update) => {
return {
label: 'Plugins',
submenu: [

View file

@ -1,4 +1,4 @@
module.exports = function (commands, createWindow) {
module.exports = (commands, createWindow) => {
const isMac = process.platform === 'darwin';
return {

View file

@ -1,4 +1,4 @@
module.exports = function (commands) {
module.exports = commands => {
return {
label: 'View',
submenu: [

View file

@ -1,4 +1,4 @@
module.exports = function (commands) {
module.exports = commands => {
return {
role: 'window',
submenu: [
@ -9,7 +9,8 @@ module.exports = function (commands) {
{
type: 'separator'
},
{ // It's the same thing as clicking the green traffc-light on macOS
{
// It's the same thing as clicking the green traffc-light on macOS
role: 'zoom',
accelerator: commands['window:zoom']
},

View file

@ -10,10 +10,11 @@ module.exports = function fetchNotifications(win) {
const retry = err => {
setTimeout(() => fetchNotifications(win), ms('30m'));
if (err) {
//eslint-disable-next-line no-console
console.error('Notification messages fetch error', err.stack);
}
};
//eslint-disable-next-line no-console
console.log('Checking for notification messages');
fetch(NEWS_URL, {
headers: {
@ -21,19 +22,20 @@ module.exports = function fetchNotifications(win) {
'X-Hyper-Platform': process.platform
}
})
.then(res => res.json())
.then(data => {
const {message} = data || {};
if (typeof message !== 'object' && message !== '') {
throw new Error('Bad response');
}
if (message === '') {
console.log('No matching notification messages');
} else {
rpc.emit('add notification', message);
}
.then(res => res.json())
.then(data => {
const {message} = data || {};
if (typeof message !== 'object' && message !== '') {
throw new Error('Bad response');
}
if (message === '') {
//eslint-disable-next-line no-console
console.log('No matching notification messages');
} else {
rpc.emit('add notification', message);
}
retry();
})
.catch(retry);
retry();
})
.catch(retry);
};

View file

@ -16,10 +16,7 @@ app.on('ready', () => {
const win_ = new BrowserWindow({
show: false
});
const url = 'file://' + resolve(
isDev ? __dirname : app.getAppPath(),
'notify.html'
);
const url = 'file://' + resolve(isDev ? __dirname : app.getAppPath(), 'notify.html');
win_.loadURL(url);
win_.webContents.on('dom-ready', () => {
win = win_;
@ -31,6 +28,7 @@ app.on('ready', () => {
});
function notify(title, body) {
//eslint-disable-next-line no-console
console.log(`[Notification] ${title}: ${body}`);
if (win) {
win.webContents.send('notification', {title, body});

View file

@ -56,11 +56,9 @@ function updatePlugins({force = false} = {}) {
updating = false;
if (err) {
//eslint-disable-next-line no-console
console.error(err.stack);
notify(
'Error updating plugins.',
err.message
);
notify('Error updating plugins.', err.message);
} else {
// flag successful plugin update
cache.set('hyper.plugins', id_);
@ -83,15 +81,9 @@ function updatePlugins({force = false} = {}) {
// notify watchers
if (force || changed) {
if (changed) {
notify(
'Plugins Updated',
'Restart the app or hot-reload with "View" > "Reload" to enjoy the updates!'
);
notify('Plugins Updated', 'Restart the app or hot-reload with "View" > "Reload" to enjoy the updates!');
} else {
notify(
'Plugins Updated',
'No changes!'
);
notify('Plugins Updated', 'No changes!');
}
watchers.forEach(fn => fn(err, {force}));
}
@ -101,16 +93,14 @@ function updatePlugins({force = false} = {}) {
function getPluginVersions() {
const paths_ = paths.plugins.concat(paths.localPlugins);
return paths_.map(path => {
return paths_.map(path_ => {
let version = null;
try {
// eslint-disable-next-line import/no-dynamic-require
version = require(resolve(path, 'package.json')).version;
} catch (err) { }
return [
basename(path),
version
];
//eslint-disable-next-line import/no-dynamic-require
version = require(resolve(path_, 'package.json')).version;
//eslint-disable-next-line no-empty
} catch (err) {}
return [basename(path_), version];
});
}
@ -132,7 +122,7 @@ function clearCache() {
exports.updatePlugins = updatePlugins;
exports.getLoadedPluginVersions = function () {
exports.getLoadedPluginVersions = () => {
return modules.map(mod => ({name: mod._name, version: mod._version}));
};
@ -141,6 +131,7 @@ exports.getLoadedPluginVersions = function () {
// to prevent slowness
if (cache.get('hyper.plugins') !== id || process.env.HYPER_FORCE_UPDATE) {
// install immediately if the user changed plugins
//eslint-disable-next-line no-console
console.log('plugins have changed / not init, scheduling plugins installation');
setTimeout(() => {
updatePlugins();
@ -178,9 +169,9 @@ function alert(message) {
});
}
function toDependencies(plugins) {
function toDependencies(plugins_) {
const obj = {};
plugins.plugins.forEach(plugin => {
plugins_.plugins.forEach(plugin => {
const regex = /.(@|#)/;
const match = regex.exec(plugin);
@ -198,7 +189,7 @@ function toDependencies(plugins) {
return obj;
}
exports.subscribe = function (fn) {
exports.subscribe = fn => {
watchers.push(fn);
return () => {
watchers.splice(watchers.indexOf(fn), 1);
@ -220,56 +211,59 @@ function getPaths() {
exports.getPaths = getPaths;
// get paths from renderer
exports.getBasePaths = function () {
exports.getBasePaths = () => {
return {path, localPath};
};
function requirePlugins() {
const {plugins, localPlugins} = paths;
const {plugins: plugins_, localPlugins} = paths;
const load = path => {
const load = path_ => {
let mod;
try {
// eslint-disable-next-line import/no-dynamic-require
mod = require(path);
mod = require(path_);
const exposed = mod && Object.keys(mod).some(key => availableExtensions.has(key));
if (!exposed) {
notify('Plugin error!', `Plugin "${basename(path)}" does not expose any ` +
'Hyper extension API methods');
notify('Plugin error!', `Plugin "${basename(path_)}" does not expose any ` + 'Hyper extension API methods');
return;
}
// populate the name for internal errors here
mod._name = basename(path);
mod._name = basename(path_);
try {
// eslint-disable-next-line import/no-dynamic-require
mod._version = require(resolve(path, 'package.json')).version;
mod._version = require(resolve(path_, 'package.json')).version;
} catch (err) {
console.warn(`No package.json found in ${path}`);
//eslint-disable-next-line no-console
console.warn(`No package.json found in ${path_}`);
}
//eslint-disable-next-line no-console
console.log(`Plugin ${mod._name} (${mod._version}) loaded.`);
return mod;
} catch (err) {
//eslint-disable-next-line no-console
console.error(err);
notify('Plugin error!', `Plugin "${basename(path)}" failed to load (${err.message})`);
notify('Plugin error!', `Plugin "${basename(path_)}" failed to load (${err.message})`);
}
};
return plugins.map(load)
return plugins_
.map(load)
.concat(localPlugins.map(load))
.filter(v => Boolean(v));
}
exports.onApp = function (app) {
exports.onApp = app_ => {
modules.forEach(plugin => {
if (plugin.onApp) {
plugin.onApp(app);
plugin.onApp(app_);
}
});
};
exports.onWindow = function (win) {
exports.onWindow = win => {
modules.forEach(plugin => {
if (plugin.onWindow) {
plugin.onWindow(win);
@ -295,7 +289,7 @@ function decorateObject(base, key) {
return decorated;
}
exports.extendKeymaps = function () {
exports.extendKeymaps = () => {
modules.forEach(plugin => {
if (plugin.extendKeymaps) {
const keys = _keys.extend(plugin.extendKeymaps());
@ -318,26 +312,26 @@ exports.getDeprecatedConfig = () => {
return;
}
deprecated[plugin._name] = {css: pluginCSSDeprecated};
})
});
return deprecated;
}
};
exports.decorateMenu = function (tpl) {
exports.decorateMenu = tpl => {
return decorateObject(tpl, 'decorateMenu');
};
exports.getDecoratedEnv = function (baseEnv) {
exports.getDecoratedEnv = baseEnv => {
return decorateObject(baseEnv, 'decorateEnv');
};
exports.getDecoratedConfig = function () {
exports.getDecoratedConfig = () => {
const baseConfig = config.getConfig();
const decoratedConfig = decorateObject(baseConfig, 'decorateConfig');
const translatedConfig = config.htermConfigTranslate(decoratedConfig);
return translatedConfig;
};
exports.getDecoratedBrowserOptions = function (defaults) {
exports.getDecoratedBrowserOptions = defaults => {
return decorateObject(defaults, 'decorateBrowserOptions');
};

View file

@ -1,18 +1,39 @@
module.exports = {
availableExtensions: new Set([
'onApp', 'onWindow', 'onRendererWindow', 'onUnload', 'middleware',
'reduceUI', 'reduceSessions', 'reduceTermGroups',
'decorateMenu', 'decorateTerm', 'decorateHyper',
'onApp',
'onWindow',
'onRendererWindow',
'onUnload',
'middleware',
'reduceUI',
'reduceSessions',
'reduceTermGroups',
'decorateMenu',
'decorateTerm',
'decorateHyper',
'decorateHyperTerm', // for backwards compatibility with hyperterm
'decorateHeader', 'decorateTerms', 'decorateTab',
'decorateNotification', 'decorateNotifications',
'decorateTabs', 'decorateConfig', 'decorateEnv',
'decorateTermGroup', 'decorateSplitPane', 'getTermProps',
'getTabProps', 'getTabsProps', 'getTermGroupProps',
'mapHyperTermState', 'mapTermsState',
'mapHeaderState', 'mapNotificationsState',
'mapHyperTermDispatch', 'mapTermsDispatch',
'mapHeaderDispatch', 'mapNotificationsDispatch',
'decorateHeader',
'decorateTerms',
'decorateTab',
'decorateNotification',
'decorateNotifications',
'decorateTabs',
'decorateConfig',
'decorateEnv',
'decorateTermGroup',
'decorateSplitPane',
'getTermProps',
'getTabProps',
'getTabsProps',
'getTermGroupProps',
'mapHyperTermState',
'mapTermsState',
'mapHeaderState',
'mapNotificationsState',
'mapHyperTermDispatch',
'mapTermsDispatch',
'mapHeaderDispatch',
'mapNotificationsDispatch',
'extendKeymaps'
])
};

View file

@ -13,24 +13,29 @@ module.exports = {
};
spawnQueue.push(end => {
const cmd = [process.execPath, yarn].concat(args).join(' ');
//eslint-disable-next-line no-console
console.log('Launching yarn:', cmd);
cp.exec(cmd, {
cwd: plugs.base,
env,
shell: true,
timeout: ms('5m'),
stdio: ['ignore', 'ignore', 'inherit']
}, err => {
if (err) {
cb(err);
} else {
cb(null);
}
cp.exec(
cmd,
{
cwd: plugs.base,
env,
shell: true,
timeout: ms('5m'),
stdio: ['ignore', 'ignore', 'inherit']
},
err => {
if (err) {
cb(err);
} else {
cb(null);
}
end();
spawnQueue.start();
});
end();
spawnQueue.start();
}
);
});
spawnQueue.start();

View file

@ -3,7 +3,6 @@ const {ipcMain} = require('electron');
const uuid = require('uuid');
class Server extends EventEmitter {
constructor(win) {
super();
this.win = win;
@ -48,7 +47,6 @@ class Server extends EventEmitter {
this.destroyed = true;
}
}
}
module.exports = win => {

View file

@ -8,7 +8,10 @@ const {getDecoratedEnv} = require('./plugins');
const {productName, version} = require('./package');
const config = require('./config');
const createNodePtyError = () => new Error('`node-pty` failed to load. Typically this means that it was built incorrectly. Please check the `readme.md` to more info.');
const createNodePtyError = () =>
new Error(
'`node-pty` failed to load. Typically this means that it was built incorrectly. Please check the `readme.md` to more info.'
);
let spawn;
try {
@ -20,15 +23,19 @@ try {
const envFromConfig = config.getConfig().env || {};
module.exports = class Session extends EventEmitter {
constructor({rows, cols: columns, cwd, shell, shellArgs}) {
super();
const baseEnv = Object.assign({}, process.env, {
LANG: app.getLocale().replace('-', '_') + '.UTF-8',
TERM: 'xterm-256color',
TERM_PROGRAM: productName,
TERM_PROGRAM_VERSION: version
}, envFromConfig);
const baseEnv = Object.assign(
{},
process.env,
{
LANG: app.getLocale().replace('-', '_') + '.UTF-8',
TERM: 'xterm-256color',
TERM_PROGRAM: productName,
TERM_PROGRAM_VERSION: version
},
envFromConfig
);
// Electron has a default value for process.env.GOOGLE_API_KEY
// We don't want to leak this to the shell
@ -85,6 +92,7 @@ module.exports = class Session extends EventEmitter {
try {
this.pty.resize(cols, rows);
} catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack);
}
}
@ -93,10 +101,10 @@ module.exports = class Session extends EventEmitter {
try {
this.pty.kill();
} catch (err) {
//eslint-disable-next-line no-console
console.error('exit error', err.stack);
}
this.emit('exit');
this.ended = true;
}
};

View file

@ -3,23 +3,26 @@ const Registry = require('winreg');
const appPath = `"${process.execPath}"`;
const regKey = `\\Software\\Classes\\Directory\\background\\shell\\Hyper`;
const regParts = [
{key: 'command', name: '', value: `${appPath} "%V"`},
{name: '', value: 'Open Hyper here'},
{name: 'Icon', value: `${appPath}`}
{key: 'command', name: '', value: `${appPath} "%V"`},
{name: '', value: 'Open Hyper here'},
{name: 'Icon', value: `${appPath}`}
];
function addValues(hyperKey, commandKey, callback) {
hyperKey.set(regParts[1].name, Registry.REG_SZ, regParts[1].value, err => {
if (err) {
console.error(err.message);
hyperKey.set(regParts[1].name, Registry.REG_SZ, regParts[1].value, error => {
if (error) {
//eslint-disable-next-line no-console
console.error(error.message);
}
hyperKey.set(regParts[2].name, Registry.REG_SZ, regParts[2].value, err => {
if (err) {
//eslint-disable-next-line no-console
console.error(err.message);
}
commandKey.set(regParts[0].name, Registry.REG_SZ, regParts[0].value, err => {
if (err) {
console.error(err.message);
commandKey.set(regParts[0].name, Registry.REG_SZ, regParts[0].value, err_ => {
if (err_) {
//eslint-disable-next-line no-console
console.error(err_.message);
}
callback();
});
@ -27,24 +30,30 @@ function addValues(hyperKey, commandKey, callback) {
});
}
exports.add = function (callback) {
exports.add = callback => {
const hyperKey = new Registry({hive: 'HKCU', key: regKey});
const commandKey = new Registry({hive: 'HKCU', key: `${regKey}\\${regParts[0].key}`});
const commandKey = new Registry({
hive: 'HKCU',
key: `${regKey}\\${regParts[0].key}`
});
hyperKey.keyExists((err, exists) => {
if (err) {
console.error(err.message);
hyperKey.keyExists((error, exists) => {
if (error) {
//eslint-disable-next-line no-console
console.error(error.message);
}
if (exists) {
commandKey.keyExists((err, exists) => {
if (err) {
console.error(err.message);
commandKey.keyExists((err_, exists_) => {
if (err_) {
//eslint-disable-next-line no-console
console.error(err_.message);
}
if (exists) {
if (exists_) {
addValues(hyperKey, commandKey, callback);
} else {
commandKey.create(err => {
if (err) {
//eslint-disable-next-line no-console
console.error(err.message);
}
addValues(hyperKey, commandKey, callback);
@ -54,11 +63,13 @@ exports.add = function (callback) {
} else {
hyperKey.create(err => {
if (err) {
//eslint-disable-next-line no-console
console.error(err.message);
}
commandKey.create(err => {
if (err) {
console.error(err.message);
commandKey.create(err_ => {
if (err_) {
//eslint-disable-next-line no-console
console.error(err_.message);
}
addValues(hyperKey, commandKey, callback);
});
@ -67,9 +78,10 @@ exports.add = function (callback) {
});
};
exports.remove = function (callback) {
exports.remove = callback => {
new Registry({hive: 'HKCU', key: regKey}).destroy(err => {
if (err) {
//eslint-disable-next-line no-console
console.error(err.message);
}
callback();

View file

@ -13,21 +13,24 @@ const fetchNotifications = require('../notifications');
const Session = require('../session');
module.exports = class Window {
constructor(options, cfg, fn) {
const opts = Object.assign({
minWidth: 370,
minHeight: 190,
backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'),
titleBarStyle: 'hidden-inset',
title: 'Hyper.app',
// we want to go frameless on windows and linux
frame: process.platform === 'darwin',
transparent: process.platform === 'darwin',
icon,
show: process.env.HYPER_DEBUG || process.env.HYPERTERM_DEBUG || isDev,
acceptFirstMouse: true
}, options);
const window = new BrowserWindow(app.plugins.getDecoratedBrowserOptions(opts));
constructor(options_, cfg, fn) {
const winOpts = Object.assign(
{
minWidth: 370,
minHeight: 190,
backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'),
titleBarStyle: 'hidden-inset',
title: 'Hyper.app',
// we want to go frameless on windows and linux
frame: process.platform === 'darwin',
transparent: process.platform === 'darwin',
icon,
show: process.env.HYPER_DEBUG || process.env.HYPERTERM_DEBUG || isDev,
acceptFirstMouse: true
},
options_
);
const window = new BrowserWindow(app.plugins.getDecoratedBrowserOptions(winOpts));
const rpc = createRPC(window);
const sessions = new Map();
@ -39,12 +42,8 @@ module.exports = class Window {
window.webContents.send('config change');
// notify user that shell changes require new sessions
if (cfg_.shell !== cfg.shell ||
JSON.stringify(cfg_.shellArgs) !== JSON.stringify(cfg.shellArgs)) {
notify(
'Shell configuration changed!',
'Open a new tab or window to start using the new shell'
);
if (cfg_.shell !== cfg.shell || JSON.stringify(cfg_.shellArgs) !== JSON.stringify(cfg.shellArgs)) {
notify('Shell configuration changed!', 'Open a new tab or window to start using the new shell');
}
// update background color if necessary
@ -66,37 +65,41 @@ module.exports = class Window {
// and createWindow deifinition. It's executed in place of
// the callback passed as parameter, and deleted right after.
(app.windowCallback || fn)(window);
delete (app.windowCallback);
delete app.windowCallback;
fetchNotifications(window);
// auto updates
if (!isDev && process.platform !== 'linux') {
AutoUpdater(window);
} else {
//eslint-disable-next-line no-console
console.log('ignoring auto updates during dev');
}
});
rpc.on('new', options => {
const opts = Object.assign({
rows: 40,
cols: 100,
cwd: process.argv[1] && isAbsolute(process.argv[1]) ? process.argv[1] : cfgDir,
splitDirection: undefined,
shell: cfg.shell,
shellArgs: cfg.shellArgs && Array.from(cfg.shellArgs)
}, options);
const sessionOpts = Object.assign(
{
rows: 40,
cols: 100,
cwd: process.argv[1] && isAbsolute(process.argv[1]) ? process.argv[1] : cfgDir,
splitDirection: undefined,
shell: cfg.shell,
shellArgs: cfg.shellArgs && Array.from(cfg.shellArgs)
},
options
);
const initSession = (opts, fn) => {
fn(uuid.v4(), new Session(opts));
const initSession = (opts, fn_) => {
fn_(uuid.v4(), new Session(opts));
};
initSession(opts, (uid, session) => {
initSession(sessionOpts, (uid, session) => {
sessions.set(uid, session);
rpc.emit('session add', {
rows: opts.rows,
cols: opts.cols,
rows: sessionOpts.rows,
cols: sessionOpts.cols,
uid,
splitDirection: opts.splitDirection,
splitDirection: sessionOpts.splitDirection,
shell: session.shell,
pid: session.pty.pid
});
@ -116,6 +119,7 @@ module.exports = class Window {
if (session) {
session.exit();
} else {
//eslint-disable-next-line no-console
console.log('session not found by', uid);
}
});
@ -136,9 +140,9 @@ module.exports = class Window {
const session = sessions.get(uid);
if (escaped) {
const escapedData = session.shell.endsWith('cmd.exe') ?
`"${data}"` : // This is how cmd.exe does it
`'${data.replace(/'/g, `'\\''`)}'`; // Inside a single-quoted string nothing is interpreted
const escapedData = session.shell.endsWith('cmd.exe')
? `"${data}"` // This is how cmd.exe does it
: `'${data.replace(/'/g, `'\\''`)}'`; // Inside a single-quoted string nothing is interpreted
session.write(escapedData);
} else {

View file

@ -13,5 +13,12 @@ module.exports = bgColor => {
// http://stackoverflow.com/a/11019879/1202488
const alphaHex = Math.round(color.alpha() * 255).toString(16);
return '#' + alphaHex + color.hex().toString().substr(1);
return (
'#' +
alphaHex +
color
.hex()
.toString()
.substr(1)
);
};

View file

@ -1,5 +1,11 @@
import {CLOSE_TAB, CHANGE_TAB} from '../constants/tabs';
import {UI_WINDOW_MAXIMIZE, UI_WINDOW_UNMAXIMIZE, UI_OPEN_HAMBURGER_MENU, UI_WINDOW_MINIMIZE, UI_WINDOW_CLOSE} from '../constants/ui';
import {
UI_WINDOW_MAXIMIZE,
UI_WINDOW_UNMAXIMIZE,
UI_OPEN_HAMBURGER_MENU,
UI_WINDOW_MINIMIZE,
UI_WINDOW_CLOSE
} from '../constants/ui';
import rpc from '../rpc';
import {userExitTermGroup, setActiveGroup} from './term-groups';

View file

@ -1,7 +1,4 @@
import {
NOTIFICATION_MESSAGE,
NOTIFICATION_DISMISS
} from '../constants/notifications';
import {NOTIFICATION_MESSAGE, NOTIFICATION_DISMISS} from '../constants/notifications';
export function dismissNotification(id) {
return {

View file

@ -48,7 +48,7 @@ export function requestSession() {
}
export function addSessionData(uid, data) {
return function (dispatch, getState) {
return (dispatch, getState) => {
dispatch({
type: SESSION_ADD_DATA,
data,
@ -141,7 +141,7 @@ export function resizeSession(uid, cols, rows) {
}
export function sendSessionData(uid, data, escaped) {
return function (dispatch, getState) {
return (dispatch, getState) => {
dispatch({
type: SESSION_USER_DATA,
data,

View file

@ -5,11 +5,7 @@ import getRootGroups from '../selectors';
import findBySession from '../utils/term-groups';
import notify from '../utils/notify';
import rpc from '../rpc';
import {
requestSession,
sendSessionData,
setActiveSession
} from '../actions/sessions';
import {requestSession, sendSessionData, setActiveSession} from '../actions/sessions';
import {
UI_FONT_SIZE_SET,
UI_FONT_SIZE_INCR,
@ -74,9 +70,7 @@ 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,
@ -100,11 +94,7 @@ const findChildSessions = (termGroups, uid) => {
return [uid];
}
return group
.children
.reduce((total, childUid) => total.concat(
findChildSessions(termGroups, childUid)
), []);
return group.children.reduce((total, childUid) => total.concat(findChildSessions(termGroups, childUid)), []);
};
// Get the index of the next or previous group,
@ -126,6 +116,7 @@ function moveToNeighborPane(type) {
const {uid} = findBySession(termGroups, sessions.activeUid);
const childGroups = findChildSessions(termGroups.termGroups, termGroups.activeRootGroup);
if (childGroups.length === 1) {
//eslint-disable-next-line no-console
console.log('ignoring move for single group');
} else {
const index = getNeighborIndex(childGroups, uid, type);
@ -156,6 +147,7 @@ export function moveLeft() {
const index = groupUids.indexOf(uid);
const next = groupUids[index - 1] || last(groupUids);
if (!next || uid === next) {
//eslint-disable-next-line no-console
console.log('ignoring left move action');
} else {
dispatch(setActiveGroup(next));
@ -176,6 +168,7 @@ export function moveRight() {
const index = groupUids.indexOf(uid);
const next = groupUids[index + 1] || groupUids[0];
if (!next || uid === next) {
//eslint-disable-next-line no-console
console.log('ignoring right move action');
} else {
dispatch(setActiveGroup(next));
@ -195,10 +188,12 @@ export function moveTo(i) {
const groupUids = getGroupUids(state);
const uid = state.termGroups.activeRootGroup;
if (uid === groupUids[i]) {
//eslint-disable-next-line no-console
console.log('ignoring same uid');
} else if (groupUids[i]) {
dispatch(setActiveGroup(groupUids[i]));
} else {
//eslint-disable-next-line no-console
console.log('ignoring inexistent index', i);
}
}
@ -235,6 +230,7 @@ export function openFile(path) {
effect() {
stat(path, (err, stats) => {
if (err) {
//eslint-disable-next-line no-console
console.error(err.stack);
notify('Unable to open path', `"${path}" doesn't exist.`);
} else {

View file

@ -1,7 +1,4 @@
import {
UPDATE_INSTALL,
UPDATE_AVAILABLE
} from '../constants/updater';
import {UPDATE_INSTALL, UPDATE_AVAILABLE} from '../constants/updater';
import rpc from '../rpc';
export function installUpdate() {

View file

@ -2,7 +2,6 @@ import React from 'react';
import {StyleSheet, css} from 'aphrodite-simple';
export default class Component extends React.PureComponent {
constructor() {
super();
this.styles_ = this.createStyleSheet();
@ -39,9 +38,7 @@ export default class Component extends React.PureComponent {
//
// it's important classes never get mangled by
// uglifiers so that we can avoid collisions
const component = this.constructor.name
.toString()
.toLowerCase();
const component = this.constructor.name.toString().toLowerCase();
const globalName = `${component}_${c}`;
return [globalName, css(this.styles_[c])];
}
@ -58,11 +55,10 @@ export default class Component extends React.PureComponent {
// 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
return this.template(this.cssHelper);
}
}

View file

@ -8,7 +8,6 @@ import Tabs_ from './tabs';
const Tabs = decorate(Tabs_, 'Tabs');
export default class Header extends Component {
constructor() {
super();
this.onChangeIntent = this.onChangeIntent.bind(this);
@ -22,8 +21,7 @@ export default class Header extends Component {
onChangeIntent(active) {
// we ignore clicks if they're a byproduct of a drag
// motion to move the window
if (window.screenX !== this.headerMouseDownWindowX ||
window.screenY !== this.headerMouseDownWindowY) {
if (window.screenX !== this.headerMouseDownWindowX || window.screenY !== this.headerMouseDownWindowY) {
return;
}
@ -105,60 +103,47 @@ export default class Header extends Component {
}
const {hambMenu, winCtrls} = this.getWindowHeaderConfig();
const left = winCtrls === 'left';
const maxButtonHref = this.props.maximized ?
'./renderer/assets/icons.svg#restore-window' :
'./renderer/assets/icons.svg#maximize-window';
const maxButtonHref = this.props.maximized
? './renderer/assets/icons.svg#restore-window'
: './renderer/assets/icons.svg#maximize-window';
return (<header
className={css('header', isMac && 'headerRounded')}
onMouseDown={this.handleHeaderMouseDown}
onDoubleClick={this.handleMaximizeClick}
return (
<header
className={css('header', isMac && 'headerRounded')}
onMouseDown={this.handleHeaderMouseDown}
onDoubleClick={this.handleMaximizeClick}
>
{
!isMac &&
<div
className={css('windowHeader', props.tabs.length > 1 && 'windowHeaderWithBorder')}
style={{borderColor}}
>
{
hambMenu &&
<svg
className={css('shape', (left && 'hamburgerMenuRight') || 'hamburgerMenuLeft')}
onClick={this.handleHamburgerMenuClick}
{!isMac && (
<div className={css('windowHeader', props.tabs.length > 1 && 'windowHeaderWithBorder')} style={{borderColor}}>
{hambMenu && (
<svg
className={css('shape', (left && 'hamburgerMenuRight') || 'hamburgerMenuLeft')}
onClick={this.handleHamburgerMenuClick}
>
<use xlinkHref="./renderer/assets/icons.svg#hamburger-menu"/>
</svg>
}
<span className={css('appTitle')}>{title}</span>
{
winCtrls &&
<div className={css('windowControls', left && 'windowControlsLeft')}>
<svg
className={css('shape', left && 'minimizeWindowLeft')}
onClick={this.handleMinimizeClick}
>
<use xlinkHref="./renderer/assets/icons.svg#minimize-window"/>
<use xlinkHref="./renderer/assets/icons.svg#hamburger-menu" />
</svg>
<svg
className={css('shape', left && 'maximizeWindowLeft')}
onClick={this.handleMaximizeClick}
>
<use xlinkHref={maxButtonHref}/>
</svg>
<svg
className={css('shape', 'closeWindow', left && 'closeWindowLeft')}
onClick={this.handleCloseClick}
>
<use xlinkHref="./renderer/assets/icons.svg#close-window"/>
</svg>
</div>
}
</div>
}
{ this.props.customChildrenBefore }
<Tabs {...props}/>
{ this.props.customChildren }
</header>);
)}
<span className={css('appTitle')}>{title}</span>
{winCtrls && (
<div className={css('windowControls', left && 'windowControlsLeft')}>
<svg className={css('shape', left && 'minimizeWindowLeft')} onClick={this.handleMinimizeClick}>
<use xlinkHref="./renderer/assets/icons.svg#minimize-window" />
</svg>
<svg className={css('shape', left && 'maximizeWindowLeft')} onClick={this.handleMaximizeClick}>
<use xlinkHref={maxButtonHref} />
</svg>
<svg className={css('shape', 'closeWindow', left && 'closeWindowLeft')} onClick={this.handleCloseClick}>
<use xlinkHref="./renderer/assets/icons.svg#close-window" />
</svg>
</div>
)}
</div>
)}
{this.props.customChildrenBefore}
<Tabs {...props} />
{this.props.customChildren}
</header>
);
}
styles() {
@ -248,5 +233,4 @@ export default class Header extends Component {
closeWindow: {':hover': {color: '#FE354E'}, ':active': {color: '#FE354E'}}
};
}
}

View file

@ -2,7 +2,6 @@ import React from 'react';
import Component from '../component';
export default class Notification extends Component {
constructor() {
super();
this.state = {
@ -44,11 +43,7 @@ export default class Notification extends Component {
});
const {backgroundColor} = this.props;
if (backgroundColor) {
el.style.setProperty(
'background-color',
backgroundColor,
'important'
);
el.style.setProperty('background-color', backgroundColor, 'important');
}
}
}
@ -71,24 +66,18 @@ export default class Notification extends Component {
template(css) {
const {backgroundColor} = this.props;
const opacity = this.state.dismissing ? 0 : 1;
return (<div
ref={this.onElement}
style={{opacity, backgroundColor}}
className={css('indicator')}
>
{ this.props.customChildrenBefore }
{ this.props.children || this.props.text }
{
this.props.userDismissable ?
<a
className={css('dismissLink')}
onClick={this.handleDismiss}
style={{color: this.props.userDismissColor}}
>[x]</a> :
null
}
{ this.props.customChildren }
</div>);
return (
<div ref={this.onElement} style={{opacity, backgroundColor}} className={css('indicator')}>
{this.props.customChildrenBefore}
{this.props.children || this.props.text}
{this.props.userDismissable ? (
<a className={css('dismissLink')} onClick={this.handleDismiss} style={{color: this.props.userDismissColor}}>
[x]
</a>
) : null}
{this.props.customChildren}
</div>
);
}
styles() {
@ -121,5 +110,4 @@ export default class Notification extends Component {
}
};
}
}

View file

@ -8,12 +8,11 @@ import Notification_ from './notification';
const Notification = decorate(Notification_);
export default class Notifications extends Component {
template(css) {
return (<div className={css('view')}>
{ this.props.customChildrenBefore }
{
this.props.fontShowing &&
return (
<div className={css('view')}>
{this.props.customChildrenBefore}
{this.props.fontShowing && (
<Notification
key="font"
backgroundColor="rgba(255, 255, 255, .2)"
@ -21,11 +20,10 @@ export default class Notifications extends Component {
userDismissable={false}
onDismiss={this.props.onDismissFont}
dismissAfter={1000}
/>
}
/>
)}
{
this.props.resizeShowing &&
{this.props.resizeShowing && (
<Notification
key="resize"
backgroundColor="rgba(255, 255, 255, .2)"
@ -33,11 +31,10 @@ export default class Notifications extends Component {
userDismissable={false}
onDismiss={this.props.onDismissResize}
dismissAfter={1000}
/>
}
/>
)}
{
this.props.messageShowing &&
{this.props.messageShowing && (
<Notification
key="message"
backgroundColor="#FE354E"
@ -45,8 +42,9 @@ export default class Notifications extends Component {
onDismiss={this.props.onDismissMessage}
userDismissable={this.props.messageDismissable}
userDismissColor="#AA2D3C"
>{
this.props.messageURL ? [
>
{this.props.messageURL ? (
[
this.props.messageText,
' (',
<a
@ -57,34 +55,34 @@ export default class Notifications extends Component {
ev.preventDefault();
}}
href={this.props.messageURL}
>more</a>,
>
more
</a>,
')'
] : null
}
]
) : null}
</Notification>
}
)}
{
this.props.updateShowing &&
{this.props.updateShowing && (
<Notification
key="update"
backgroundColor="#7ED321"
text={`Version ${this.props.updateVersion} ready`}
onDismiss={this.props.onDismissUpdate}
userDismissable
>
>
Version <b>{this.props.updateVersion}</b> ready.
{this.props.updateNote && ` ${this.props.updateNote.trim().replace(/\.$/, '')}`}
{' '}
(<a
{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/hyper/releases/tag/${this.props.updateVersion}`}
>notes</a>).
{' '}
>
notes
</a>).{' '}
<a
style={{
cursor: 'pointer',
@ -92,14 +90,14 @@ export default class Notifications extends Component {
fontWeight: 'bold'
}}
onClick={this.props.onUpdateInstall}
>
Restart
</a>.
{ ' ' }
>
Restart
</a>.{' '}
</Notification>
}
{ this.props.customChildren }
</div>);
)}
{this.props.customChildren}
</div>
);
}
styles() {
@ -111,5 +109,4 @@ export default class Notifications extends Component {
}
};
}
}

View file

@ -3,7 +3,6 @@ import React from 'react';
import Component from '../component';
export default class SplitPane extends Component {
constructor(props) {
super(props);
this.handleDragStart = this.handleDragStart.bind(this);
@ -82,7 +81,7 @@ export default class SplitPane extends Component {
template(css) {
const children = this.props.children;
const {direction, borderColor} = this.props;
const sizeProperty = direction === 'horizontal' ? 'height' : 'flexBasis'
const sizeProperty = direction === 'horizontal' ? 'height' : 'flexBasis';
let {sizes} = this.props;
if (!sizes) {
// workaround for the fact that if we don't specify
@ -90,39 +89,32 @@ export default class SplitPane extends Component {
// right height for the horizontal panes
sizes = new Array(children.length).fill(1 / children.length);
}
return (<div className={css('panes', `panes_${direction}`)}>
{
React.Children.map(children, (child, i) => {
return (
<div className={css('panes', `panes_${direction}`)}>
{React.Children.map(children, (child, i) => {
const style = {
// flexBasis doesn't work for the first horizontal pane, height need to be specified
[sizeProperty]: (sizes[i] * 100) + '%',
flexBasis: (sizes[i] * 100) + '%',
[sizeProperty]: sizes[i] * 100 + '%',
flexBasis: sizes[i] * 100 + '%',
flexGrow: 0
};
return [
<div
key="pane"
className={css('pane')}
style={style}
>
{ child }
<div key="pane" className={css('pane')} style={style}>
{child}
</div>,
i < children.length - 1 ?
i < children.length - 1 ? (
<div
key="divider"
onMouseDown={this.handleDragStart}
style={{backgroundColor: borderColor}}
className={css('divider', `divider_${direction}`)}
/> :
null
/>
) : null
];
})
}
<div
style={{display: this.state.dragging ? 'block' : 'none'}}
className={css('shim')}
/>
</div>);
})}
<div style={{display: this.state.dragging ? 'block' : 'none'}} className={css('shim')} />
</div>
);
}
styles() {
@ -136,11 +128,11 @@ export default class SplitPane extends Component {
height: '100%'
},
'panes_vertical': {
panes_vertical: {
flexDirection: 'row'
},
'panes_horizontal': {
panes_horizontal: {
flexDirection: 'column'
},
@ -157,7 +149,7 @@ export default class SplitPane extends Component {
flexShrink: 0
},
'divider_vertical': {
divider_vertical: {
borderLeft: '5px solid rgba(255, 255, 255, 0)',
borderRight: '5px solid rgba(255, 255, 255, 0)',
width: '11px',
@ -165,7 +157,7 @@ export default class SplitPane extends Component {
cursor: 'col-resize'
},
'divider_horizontal': {
divider_horizontal: {
height: '11px',
margin: '-5px 0',
borderTop: '5px solid rgba(255, 255, 255, 0)',
@ -195,5 +187,4 @@ export default class SplitPane extends Component {
this.onDragEnd();
}
}
}

View file

@ -1,7 +1,7 @@
import React from 'react'
import React from 'react';
export default class StyleSheet extends React.PureComponent {
render () {
render() {
const {
customCSS,
colors,
@ -11,11 +11,12 @@ export default class StyleSheet extends React.PureComponent {
fontSmoothing,
foregroundColor,
borderColor
} = this.props
} = this.props;
return (
<style dangerouslySetInnerHTML={{
__html: `
<style
dangerouslySetInnerHTML={{
__html: `
.terminal {
${foregroundColor ? `color: ${foregroundColor};` : ''}
${fontFamily ? `font-family: ${fontFamily};` : ''}
@ -2261,7 +2262,8 @@ export default class StyleSheet extends React.PureComponent {
${customCSS}
`
}} />
)
}}
/>
);
}
}

View file

@ -45,48 +45,34 @@ export default class Tab extends Component {
const {isActive, isFirst, isLast, borderColor, hasActivity} = this.props;
const {hovered} = this.state;
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'
)}
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
title={this.props.text}
className={css('textInner')}
>
{ this.props.text }
{this.props.customChildrenBefore}
<span className={css('text', isLast && 'textLast', isActive && 'textActive')} onClick={this.handleClick}>
<span title={this.props.text} className={css('textInner')}>
{this.props.text}
</span>
</span>
</span>
<i
className={css(
'icon',
hovered && 'iconHovered'
)}
onClick={this.props.onClose}
>
<svg className={css('shape')}>
<use xlinkHref="./renderer/assets/icons.svg#close-tab"/>
</svg>
</i>
{ this.props.customChildren }
</li>);
<i className={css('icon', hovered && 'iconHovered')} onClick={this.props.onClose}>
<svg className={css('shape')}>
<use xlinkHref="./renderer/assets/icons.svg#close-tab" />
</svg>
</i>
{this.props.customChildren}
</li>
);
}
styles() {
@ -196,5 +182,4 @@ export default class Tab extends Component {
}
};
}
}

View file

@ -9,33 +9,19 @@ const Tab = decorate(Tab_, 'Tab');
const isMac = /Mac/.test(navigator.userAgent);
export default class Tabs extends Component {
template(css) {
const {
tabs = [],
borderColor,
onChange,
onClose
} = this.props;
const {tabs = [], borderColor, onChange, onClose} = this.props;
const hide = !isMac && tabs.length === 1;
return (<nav className={css('nav', hide && 'hiddenNav')}>
{ this.props.customChildrenBefore }
{
tabs.length === 1 && isMac ?
<div className={css('title')}>{tabs[0].title}</div> :
null
}
{
tabs.length > 1 ?
[
<ul
key="list"
className={css('list')}
>
{
tabs.map((tab, i) => {
return (
<nav className={css('nav', hide && 'hiddenNav')}>
{this.props.customChildrenBefore}
{tabs.length === 1 && isMac ? <div className={css('title')}>{tabs[0].title}</div> : null}
{tabs.length > 1 ? (
[
<ul key="list" className={css('list')}>
{tabs.map((tab, i) => {
const {uid, title, isActive, hasActivity} = tab;
const props = getTabProps(tab, this.props, {
text: title === '' ? 'Shell' : title,
@ -47,20 +33,15 @@ export default class Tabs extends Component {
onSelect: onChange.bind(null, uid),
onClose: onClose.bind(null, uid)
});
return <Tab key={`tab-${uid}`} {...props}/>;
})
}
</ul>,
isMac && <div
key="shim"
style={{borderColor}}
className={css('borderShim')}
/>
] :
null
}
{ this.props.customChildren }
</nav>);
return <Tab key={`tab-${uid}`} {...props} />;
})}
</ul>,
isMac && <div key="shim" style={{borderColor}} className={css('borderShim')} />
]
) : null}
{this.props.customChildren}
</nav>
);
}
styles() {
@ -104,5 +85,4 @@ export default class Tabs extends Component {
}
};
}
}

View file

@ -10,11 +10,10 @@ const Term = decorate(Term_, 'Term');
const SplitPane = decorate(SplitPane_, 'SplitPane');
class TermGroup_ extends Component {
constructor(props, context) {
super(props, context);
this.bound = new WeakMap();
this.termRefs = {}
this.termRefs = {};
this.sizeChanged = false;
this.onTermRef = this.onTermRef.bind(this);
}
@ -37,19 +36,21 @@ class TermGroup_ extends Component {
}
const direction = this.props.termGroup.direction.toLowerCase();
return (<SplitPane
direction={direction}
sizes={this.props.termGroup.sizes}
onResize={this.props.onTermGroupResize}
borderColor={this.props.borderColor}
return (
<SplitPane
direction={direction}
sizes={this.props.termGroup.sizes}
onResize={this.props.onTermGroupResize}
borderColor={this.props.borderColor}
>
{ groups }
</SplitPane>);
{groups}
</SplitPane>
);
}
onTermRef(uid, term) {
this.term = term;
this.props.ref_(uid, term)
this.props.ref_(uid, term);
}
renderTerm(uid) {
@ -85,15 +86,10 @@ class TermGroup_ extends Component {
// This will create a new ref_ function for every render,
// which is inefficient. Should maybe do something similar
// to this.bind.
return (<Term
ref_={this.onTermRef}
key={uid}
{...props}
/>);
return <Term ref_={this.onTermRef} key={uid} {...props} />;
}
componentWillReceiveProps (nextProps) {
componentWillReceiveProps(nextProps) {
if (this.props.termGroup.sizes != nextProps.termGroup.sizes || nextProps.sizeChanged) {
this.term && this.term.measureResize();
// Indicate to children that their size has changed even if their ratio hasn't
@ -101,7 +97,6 @@ class TermGroup_ extends Component {
} else {
this.sizeChanged = false;
}
}
template() {
@ -111,15 +106,16 @@ class TermGroup_ extends Component {
}
const groups = childGroups.map(child => {
const props = getTermGroupProps(child.uid, this.props.parentProps, Object.assign({}, this.props, {
termGroup: child,
sizeChanged: this.sizeChanged
}));
const props = getTermGroupProps(
child.uid,
this.props.parentProps,
Object.assign({}, this.props, {
termGroup: child,
sizeChanged: this.sizeChanged
})
);
return (<DecoratedTermGroup
key={child.uid}
{...props}
/>);
return <DecoratedTermGroup key={child.uid} {...props} />;
});
return this.renderSplit(groups);
@ -128,9 +124,7 @@ class TermGroup_ extends Component {
const TermGroup = connect(
(state, ownProps) => ({
childGroups: ownProps.termGroup.children.map(uid =>
state.termGroups.termGroups[uid]
)
childGroups: ownProps.termGroup.children.map(uid => state.termGroups.termGroups[uid])
}),
(dispatch, ownProps) => ({
onTermGroupResize(splitSizes) {

View file

@ -11,20 +11,19 @@ const CURSOR_STYLES = {
BEAM: 'bar',
UNDERLINE: 'underline',
BLOCK: 'block'
}
};
export default class Term extends Component {
constructor(props) {
super(props);
props.ref_(props.uid, this);
this.termRef = null
this.termWrapperRef = null
this.termRect = null
this.onOpen = this.onOpen.bind(this)
this.onWindowResize = this.onWindowResize.bind(this)
this.onTermRef = this.onTermRef.bind(this)
this.onTermWrapperRef = this.onTermWrapperRef.bind(this)
this.termRef = null;
this.termWrapperRef = null;
this.termRect = null;
this.onOpen = this.onOpen.bind(this);
this.onWindowResize = this.onWindowResize.bind(this);
this.onTermRef = this.onTermRef.bind(this);
this.onTermWrapperRef = this.onTermWrapperRef.bind(this);
}
componentDidMount() {
@ -34,59 +33,49 @@ export default class Term extends Component {
// as we move the term around splits, until xterm adds
// support for getState / setState
if (props.term) {
this.term = props.term
this.termRef.appendChild(this.term.element)
this.onOpen()
this.term = props.term;
this.termRef.appendChild(this.term.element);
this.onOpen();
} else {
this.term = props.term || new Terminal({
cursorStyle: CURSOR_STYLES[props.cursorShape],
cursorBlink: props.cursorBlink
})
this.term.attachCustomKeyEventHandler(this.keyboardHandler)
this.term.on('open', this.onOpen)
this.term =
props.term ||
new Terminal({
cursorStyle: CURSOR_STYLES[props.cursorShape],
cursorBlink: props.cursorBlink
});
this.term.attachCustomKeyEventHandler(this.keyboardHandler);
this.term.on('open', this.onOpen);
this.term.open(this.termRef, {
focus: false
})
});
}
if (props.onTitle) {
this.term.on(
'title',
props.onTitle
)
this.term.on('title', props.onTitle);
}
if (props.onActive) {
this.term.on(
'focus',
props.onActive
)
this.term.on('focus', props.onActive);
}
if (props.onData) {
this.term.on(
'data',
props.onData
)
this.term.on('data', props.onData);
}
if (props.onResize) {
this.term.on(
'resize',
({ cols, rows }) => {
props.onResize(cols, rows)
}
)
this.term.on('resize', ({cols, rows}) => {
props.onResize(cols, rows);
});
}
window.addEventListener('resize', this.onWindowResize, {
passive: true
})
});
terms[this.props.uid] = this;
}
onOpen () {
onOpen() {
// we need to delay one frame so that aphrodite styles
// get applied and we can make an accurate measurement
// of the container width and height
@ -97,26 +86,26 @@ export default class Term extends Component {
// we force it instead
this.term.charMeasure.measure();
this.measureResize();
})
});
}
getTermDocument () {
getTermDocument() {
// eslint-disable-next-line no-console
console.error('unimplemented')
console.error('unimplemented');
}
// measures the container and makes the decision
// whether to resize the term to fit the container
measureResize () {
console.log('performing measure resize')
const termRect = this.termWrapperRef.getBoundingClientRect()
measureResize() {
//eslint-disable-next-line no-console
console.log('performing measure resize');
const termRect = this.termWrapperRef.getBoundingClientRect();
if (!this.termRect ||
termRect.width !== this.termRect.width ||
termRect.height !== this.termRect.height) {
if (!this.termRect || termRect.width !== this.termRect.width || termRect.height !== this.termRect.height) {
this.termRect = termRect;
console.log('performing fit resize')
this.fitResize()
//eslint-disable-next-line no-console
console.log('performing fit resize');
this.fitResize();
}
}
@ -136,24 +125,20 @@ export default class Term extends Component {
this.term.clear();
}
reset () {
reset() {
this.term.reset();
}
resize (cols, rows) {
resize(cols, rows) {
this.term.resize(cols, rows);
}
fitResize () {
const cols = Math.floor(
this.termRect.width / this.term.charMeasure.width
)
const rows = Math.floor(
this.termRect.height / this.term.charMeasure.height
)
fitResize() {
const cols = Math.floor(this.termRect.width / this.term.charMeasure.width);
const rows = Math.floor(this.termRect.height / this.term.charMeasure.height);
if (cols !== this.props.cols || rows !== this.props.rows) {
this.resize(cols, rows)
this.resize(cols, rows);
}
}
@ -173,19 +158,17 @@ export default class Term extends Component {
this.clear();
}
if (this.props.fontSize !== nextProps.fontSize ||
this.props.fontFamily !== nextProps.fontFamily) {
if (this.props.fontSize !== nextProps.fontSize || this.props.fontFamily !== nextProps.fontFamily) {
// invalidate xterm cache about how wide each
// character is
this.term.charMeasure.measure()
this.term.charMeasure.measure();
// resize to fit the container
this.fitResize()
this.fitResize();
}
if (nextProps.rows !== this.props.rows ||
nextProps.cols !== this.props.cols) {
this.resize(nextProps.cols, nextProps.rows)
if (nextProps.rows !== this.props.rows || nextProps.cols !== this.props.cols) {
this.resize(nextProps.cols, nextProps.rows);
}
}
@ -202,33 +185,26 @@ export default class Term extends Component {
this.props.ref_(this.props.uid, null);
// to clean up the terminal, we remove the listeners
// instead of invoking `destroy`, since it will make the
// instead of invoking `destroy`, since it will make the
// term insta un-attachable in the future (which we need
// to do in case of splitting, see `componentDidMount`
this.term._events = {}
this.term._events = {};
window.removeEventListener('resize', this.onWindowResize, {
passive: true
})
});
}
template(css) {
return (<div
className={css('fit', this.props.isTermActive && 'active')}
style={{padding: this.props.padding}}
>
{ this.props.customChildrenBefore }
<div
ref={this.onTermWrapperRef}
className={css('fit', 'wrapper')}
>
<div
ref={this.onTermRef}
className={css('term')}
/>
return (
<div className={css('fit', this.props.isTermActive && 'active')} style={{padding: this.props.padding}}>
{this.props.customChildrenBefore}
<div ref={this.onTermWrapperRef} className={css('fit', 'wrapper')}>
<div ref={this.onTermRef} className={css('term')} />
</div>
{this.props.customChildren}
</div>
{ this.props.customChildren }
</div>);
);
}
styles() {

View file

@ -11,7 +11,6 @@ const StyleSheet = decorate(StyleSheet_, 'StyleSheet');
const isMac = /Mac/.test(navigator.userAgent);
export default class Terms extends Component {
constructor(props, context) {
super(props, context);
this.terms = {};
@ -71,12 +70,10 @@ export default class Terms extends Component {
template(css) {
const shift = !isMac && this.props.termGroups.length > 1;
return (<div
className={css('terms', shift && 'termsShifted')}
>
{ this.props.customChildrenBefore }
{
this.props.termGroups.map(termGroup => {
return (
<div className={css('terms', shift && 'termsShifted')}>
{this.props.customChildrenBefore}
{this.props.termGroups.map(termGroup => {
const {uid} = termGroup;
const isActive = uid === this.props.activeRootGroup;
const props = getTermGroupProps(uid, this.props, {
@ -105,31 +102,24 @@ export default class Terms extends Component {
});
return (
<div
key={`d${uid}`}
className={css('termGroup', isActive && 'termGroupActive')}
>
<TermGroup
key={uid}
ref_={this.onRef}
{...props}
/>
<div key={`d${uid}`} className={css('termGroup', isActive && 'termGroupActive')}>
<TermGroup key={uid} ref_={this.onRef} {...props} />
</div>
);
})
}
{ this.props.customChildren }
<StyleSheet
colors={this.props.colors}
customCSS={this.props.customCSS}
cursorColor={this.props.cursorColor}
fontSize={this.props.fontSize}
fontFamily={this.props.fontFamily}
fontSmoothing={this.props.fontSmoothing}
foregroundColor={this.props.foregroundColor}
borderColor={this.props.borderColor}
/>
</div>);
})}
{this.props.customChildren}
<StyleSheet
colors={this.props.colors}
customCSS={this.props.customCSS}
cursorColor={this.props.cursorColor}
fontSize={this.props.fontSize}
fontFamily={this.props.fontFamily}
fontSmoothing={this.props.fontSmoothing}
foregroundColor={this.props.foregroundColor}
borderColor={this.props.borderColor}
/>
</div>
);
}
styles() {

View file

@ -14,16 +14,17 @@ const getActiveSessions = ({termGroups}) => termGroups.activeSessions;
const getActivityMarkers = ({ui}) => ui.activityMarkers;
const getTabs = createSelector(
[getSessions, getRootGroups, getActiveSessions, getActiveRootGroup, getActivityMarkers],
(sessions, rootGroups, activeSessions, activeRootGroup, activityMarkers) => rootGroups.map(t => {
const activeSessionUid = activeSessions[t.uid];
const session = sessions[activeSessionUid];
return {
uid: t.uid,
title: session.title,
isActive: t.uid === activeRootGroup,
hasActivity: activityMarkers[session.uid]
};
})
(sessions, rootGroups, activeSessions, activeRootGroup, activityMarkers) =>
rootGroups.map(t => {
const activeSessionUid = activeSessions[t.uid];
const session = sessions[activeSessionUid];
return {
uid: t.uid,
title: session.title,
isActive: t.uid === activeRootGroup,
hasActivity: activityMarkers[session.uid]
};
})
);
const HeaderContainer = connect(

View file

@ -36,7 +36,7 @@ class Hyper extends Component {
attachKeyListeners() {
// eslint-disable-next-line no-console
console.error('removed key listeners')
console.error('removed key listeners');
}
onTermsRef(terms) {
@ -62,8 +62,7 @@ class Hyper extends Component {
template(css) {
const {isMac: isMac_, customCSS, uiFontFamily, borderColor, maximized} = this.props;
const borderWidth = isMac_ ? '' :
`${maximized ? '0' : '1'}px`;
const borderWidth = isMac_ ? '' : `${maximized ? '0' : '1'}px`;
return (
<div>
@ -71,14 +70,14 @@ class Hyper extends Component {
style={{fontFamily: uiFontFamily, borderColor, borderWidth}}
className={css('main', isMac_ && 'mainRounded')}
>
<HeaderContainer/>
<TermsContainer ref_={this.onTermsRef}/>
{ this.props.customInnerChildren }
<HeaderContainer />
<TermsContainer ref_={this.onTermsRef} />
{this.props.customInnerChildren}
</div>
<NotificationsContainer/>
<style dangerouslySetInnerHTML={{__html: customCSS}}/>
{ this.props.customChildren }
<NotificationsContainer />
<style dangerouslySetInnerHTML={{__html: customCSS}} />
{this.props.customChildren}
</div>
);
}

View file

@ -21,9 +21,7 @@ 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,
uiFontFamily: state.ui.uiFontFamily,
fontSmoothing: state.ui.fontSmoothingOverride,
@ -49,7 +47,6 @@ const TermsContainer = connect(
},
onTitle(uid, title) {
console.log(title)
dispatch(setSessionXtermTitle(uid, title));
},

View file

@ -9,7 +9,7 @@ import CommandRegistry from './command-registry';
hterm.defaultStorage = new lib.Storage.Memory();
// Provide selectAll to terminal viewport
hterm.Terminal.prototype.selectAll = function () {
hterm.Terminal.prototype.selectAll = () => {
// If the cursorNode_ having hyperCaret we need to remove it
if (this.cursorNode_.contains(this.hyperCaret)) {
this.cursorNode_.removeChild(this.hyperCaret);
@ -21,9 +21,10 @@ hterm.Terminal.prototype.selectAll = function () {
// override double click behavior to copy
const oldMouse = hterm.Terminal.prototype.onMouse_;
hterm.Terminal.prototype.onMouse_ = function (e) {
hterm.Terminal.prototype.onMouse_ = e => {
if (e.type === 'dblclick') {
selection.extend(this);
//eslint-disable-next-line no-console
console.log('[hyper+hterm] ignore double click');
return;
}
@ -35,20 +36,23 @@ function containsNonLatinCodepoints(s) {
}
// hterm Unicode patch
hterm.TextAttributes.splitWidecharString = function (str) {
const context = runes(str).reduce((ctx, rune) => {
const code = rune.codePointAt(0);
if (code < 128 || lib.wc.charWidth(code) === 1) {
ctx.acc += rune;
hterm.TextAttributes.splitWidecharString = str => {
const context = runes(str).reduce(
(ctx, rune) => {
const code = rune.codePointAt(0);
if (code < 128 || lib.wc.charWidth(code) === 1) {
ctx.acc += rune;
return ctx;
}
if (ctx.acc) {
ctx.items.push({str: ctx.acc});
ctx.acc = '';
}
ctx.items.push({str: rune, wcNode: true});
return ctx;
}
if (ctx.acc) {
ctx.items.push({str: ctx.acc});
ctx.acc = '';
}
ctx.items.push({str: rune, wcNode: true});
return ctx;
}, {items: [], acc: ''});
},
{items: [], acc: ''}
);
if (context.acc) {
context.items.push({str: context.acc});
}
@ -57,7 +61,7 @@ hterm.TextAttributes.splitWidecharString = function (str) {
// hterm Unicode patch
const cache = [];
lib.wc.strWidth = function (str) {
lib.wc.strWidth = str => {
const shouldCache = str.length === 1;
if (shouldCache && cache[str] !== undefined) {
return cache[str];
@ -72,7 +76,7 @@ lib.wc.strWidth = function (str) {
if (width < 0) {
return -1;
}
rv += width * ((codePoint <= 0xFFFF) ? 1 : 2);
rv += width * (codePoint <= 0xffff ? 1 : 2);
}
if (shouldCache) {
cache[str] = rv;
@ -81,7 +85,7 @@ lib.wc.strWidth = function (str) {
};
// hterm Unicode patch
lib.wc.substr = function (str, start, optWidth) {
lib.wc.substr = (str, start, optWidth) => {
const chars = runes(str);
let startIndex;
let endIndex;
@ -90,7 +94,7 @@ lib.wc.substr = function (str, start, optWidth) {
for (let i = 0; i < chars.length; i++) {
const codePoint = chars[i].codePointAt(0);
const charWidth = lib.wc.charWidth(codePoint);
if ((width + charWidth) > start) {
if (width + charWidth > start) {
startIndex = i;
break;
}
@ -112,14 +116,14 @@ lib.wc.substr = function (str, start, optWidth) {
};
// MacOS emoji bar support
hterm.Keyboard.prototype.onTextInput_ = function (e) {
hterm.Keyboard.prototype.onTextInput_ = e => {
if (!e.data) {
return;
}
runes(e.data).forEach(this.terminal.onVTKeystroke.bind(this.terminal));
};
hterm.Terminal.IO.prototype.writeUTF8 = function (string) {
hterm.Terminal.IO.prototype.writeUTF8 = string => {
if (this.terminal_.io !== this) {
throw new Error('Attempt to print from inactive IO object.');
}
@ -137,12 +141,12 @@ hterm.Terminal.IO.prototype.writeUTF8 = function (string) {
};
const oldIsDefault = hterm.TextAttributes.prototype.isDefault;
hterm.TextAttributes.prototype.isDefault = function () {
hterm.TextAttributes.prototype.isDefault = () => {
return !this.unicodeNode && oldIsDefault.call(this);
};
const oldSetFontSize = hterm.Terminal.prototype.setFontSize;
hterm.Terminal.prototype.setFontSize = function (px) {
hterm.Terminal.prototype.setFontSize = px => {
oldSetFontSize.call(this, px);
const doc = this.getDocument();
let unicodeNodeStyle = doc.getElementById('hyper-unicode-styles');
@ -161,7 +165,7 @@ hterm.Terminal.prototype.setFontSize = function (px) {
};
const oldCreateContainer = hterm.TextAttributes.prototype.createContainer;
hterm.TextAttributes.prototype.createContainer = function (text) {
hterm.TextAttributes.prototype.createContainer = text => {
const container = oldCreateContainer.call(this, text);
if (container.style && runes(text).length === 1 && containsNonLatinCodepoints(text)) {
container.className += ' unicode-node';
@ -171,19 +175,17 @@ hterm.TextAttributes.prototype.createContainer = function (text) {
// Do not match containers when one of them has unicode text (unicode chars need to be alone in their containers)
const oldMatchesContainer = hterm.TextAttributes.prototype.matchesContainer;
hterm.TextAttributes.prototype.matchesContainer = function (obj) {
return oldMatchesContainer.call(this, obj) &&
!this.unicodeNode &&
!containsNonLatinCodepoints(obj.textContent);
hterm.TextAttributes.prototype.matchesContainer = obj => {
return oldMatchesContainer.call(this, obj) && !this.unicodeNode && !containsNonLatinCodepoints(obj.textContent);
};
// there's no option to turn off the size overlay
hterm.Terminal.prototype.overlaySize = function () {};
hterm.Terminal.prototype.overlaySize = () => {};
// fixing a bug in hterm where a double click triggers
// a non-collapsed selection whose text is '', and results
// in an infinite copy loop
hterm.Terminal.prototype.copySelectionToClipboard = function () {
hterm.Terminal.prototype.copySelectionToClipboard = () => {
const text = this.getSelectionText();
if (text) {
this.copyStringToClipboard(text);
@ -195,7 +197,7 @@ let lastEventKey;
// passthrough all the commands that are meant to control
// hyper and not the terminal itself
const oldKeyDown = hterm.Keyboard.prototype.onKeyDown_;
hterm.Keyboard.prototype.onKeyDown_ = function (e) {
hterm.Keyboard.prototype.onKeyDown_ = e => {
const modifierKeysConf = this.terminal.modifierKeys;
if (e.timeStamp === lastEventTimeStamp && e.key === lastEventKey) {
// Event was already processed.
@ -207,25 +209,29 @@ hterm.Keyboard.prototype.onKeyDown_ = function (e) {
lastEventTimeStamp = e.timeStamp;
lastEventKey = e.key;
if (e.altKey &&
e.which !== 16 && // Ignore other modifer keys
e.which !== 17 &&
e.which !== 18 &&
e.which !== 91 &&
modifierKeysConf.altIsMeta) {
if (
e.altKey &&
e.which !== 16 && // Ignore other modifer keys
e.which !== 17 &&
e.which !== 18 &&
e.which !== 91 &&
modifierKeysConf.altIsMeta
) {
const char = fromCharCode(e);
this.terminal.onVTKeystroke('\x1b' + char);
e.preventDefault();
}
if (e.metaKey &&
e.code !== 'MetaLeft' &&
e.code !== 'MetaRight' &&
e.which !== 16 &&
e.which !== 17 &&
e.which !== 18 &&
e.which !== 91 &&
modifierKeysConf.cmdIsMeta) {
if (
e.metaKey &&
e.code !== 'MetaLeft' &&
e.code !== 'MetaRight' &&
e.which !== 16 &&
e.which !== 17 &&
e.which !== 18 &&
e.which !== 91 &&
modifierKeysConf.cmdIsMeta
) {
const char = fromCharCode(e);
this.terminal.onVTKeystroke('\x1b' + char);
e.preventDefault();
@ -248,14 +254,8 @@ hterm.Keyboard.prototype.onKeyDown_ = function (e) {
}
// Test for valid keys in order to accept clear status
const clearBlacklist = [
'control',
'shift',
'capslock',
'dead'
];
if (!clearBlacklist.includes(e.code.toLowerCase()) &&
!clearBlacklist.includes(e.key.toLowerCase())) {
const clearBlacklist = ['control', 'shift', 'capslock', 'dead'];
if (!clearBlacklist.includes(e.code.toLowerCase()) && !clearBlacklist.includes(e.key.toLowerCase())) {
// Since Electron 1.6.X, there is a race condition with character composition
// if this selection clearing is made synchronously. See #2140.
setTimeout(() => selection.clear(this.terminal), 0);
@ -269,7 +269,7 @@ hterm.Keyboard.prototype.onKeyDown_ = function (e) {
};
const oldOnMouse = hterm.Terminal.prototype.onMouse_;
hterm.Terminal.prototype.onMouse_ = function (e) {
hterm.Terminal.prototype.onMouse_ = e => {
// override `preventDefault` to not actually
// prevent default when the type of event is
// mousedown, so that we can still trigger
@ -278,14 +278,14 @@ hterm.Terminal.prototype.onMouse_ = function (e) {
// case of programs like `vtop` that allow for
// the user to click on rows
if (e.type === 'mousedown') {
e.preventDefault = function () { };
e.preventDefault = () => {};
return;
}
return oldOnMouse.call(this, e);
};
hterm.Terminal.prototype.onMouseDown_ = function (e) {
hterm.Terminal.prototype.onMouseDown_ = e => {
// copy/paste on right click
if (e.button === 2) {
const text = this.getSelectionText();
@ -300,29 +300,25 @@ hterm.Terminal.prototype.onMouseDown_ = function (e) {
// override `ScrollPort.resize` to avoid an expensive calculation
// just to get the size of the scrollbar, which for Hyper is always
// set to overlay (hence with `0`)
hterm.ScrollPort.prototype.resize = function () {
hterm.ScrollPort.prototype.resize = () => {
this.currentScrollbarWidthPx = 0;
this.syncScrollHeight();
this.syncRowNodesDimensions_();
this.publish(
'resize',
{scrollPort: this},
() => {
this.scrollRowToBottom(this.rowProvider_.getRowCount());
this.scheduleRedraw();
}
);
this.publish('resize', {scrollPort: this}, () => {
this.scrollRowToBottom(this.rowProvider_.getRowCount());
this.scheduleRedraw();
});
};
// make background transparent to avoid transparency issues
hterm.ScrollPort.prototype.setBackgroundColor = function () {
hterm.ScrollPort.prototype.setBackgroundColor = () => {
this.screen_.style.backgroundColor = 'transparent';
};
// will be called by the <Term/> right after the `hterm.Terminal` is instantiated
hterm.Terminal.prototype.onHyperCaret = function (caret) {
hterm.Terminal.prototype.onHyperCaret = caret => {
this.hyperCaret = caret;
let ongoingComposition = false;
@ -389,14 +385,14 @@ hterm.Terminal.prototype.onHyperCaret = function (caret) {
// ensure that our contenteditable caret is injected
// inside the term's cursor node and that it's focused
hterm.Terminal.prototype.focusHyperCaret = function () {
hterm.Terminal.prototype.focusHyperCaret = () => {
if (!this.hyperCaret.parentNode !== this.cursorNode_) {
this.cursorNode_.appendChild(this.hyperCaret);
}
this.hyperCaret.focus();
};
hterm.Screen.prototype.syncSelectionCaret = function () {
hterm.Screen.prototype.syncSelectionCaret = () => {
const p = this.terminal.hyperCaret;
const doc = this.terminal.document_;
const win = doc.defaultView;
@ -413,14 +409,14 @@ hterm.Screen.prototype.syncSelectionCaret = function () {
// the original function) was causing the issue. So right now we're overriding
// the function to prevent the `iframe_` from being focused.
// This shouldn't create any side effects we're _stealing_ the focus from `htem` anyways.
hterm.ScrollPort.prototype.focus = function () {
hterm.ScrollPort.prototype.focus = () => {
this.screen_.focus();
};
// fixes a bug in hterm, where the cursor goes back to `BLOCK`
// after the bell rings
const oldRingBell = hterm.Terminal.prototype.ringBell;
hterm.Terminal.prototype.ringBell = function () {
hterm.Terminal.prototype.ringBell = () => {
oldRingBell.call(this);
setTimeout(() => {
this.restyleCursor_();
@ -429,7 +425,7 @@ hterm.Terminal.prototype.ringBell = function () {
// fixes a bug in hterm, where the shorthand hex
// is not properly converted to rgb
lib.colors.hexToRGB = function (arg) {
lib.colors.hexToRGB = arg => {
const hex16 = lib.colors.re_.hex16;
const hex24 = lib.colors.re_.hex24;
@ -444,11 +440,7 @@ lib.colors.hexToRGB = function (arg) {
return null;
}
return 'rgb(' +
parseInt(ary[1], 16) + ', ' +
parseInt(ary[2], 16) + ', ' +
parseInt(ary[3], 16) +
')';
return 'rgb(' + parseInt(ary[1], 16) + ', ' + parseInt(ary[2], 16) + ', ' + parseInt(ary[3], 16) + ')';
}
if (Array.isArray(arg)) {
@ -463,7 +455,7 @@ lib.colors.hexToRGB = function (arg) {
};
// add support for cursor styles 5 and 6, fixes #270
hterm.VT.CSI[' q'] = function (parseState) {
hterm.VT.CSI[' q'] = parseState => {
const arg = parseState.args[0];
if (arg === '0' || arg === '1') {
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BLOCK);
@ -484,6 +476,7 @@ hterm.VT.CSI[' q'] = function (parseState) {
this.terminal.setCursorShape(hterm.Terminal.cursorShape.BEAM);
this.terminal.setCursorBlink(false);
} else {
//eslint-disable-next-line no-console
console.warn('Unknown cursor style: ' + arg);
}
};

View file

@ -148,7 +148,7 @@ rpc.on('add notification', ({text, url, dismissable}) => {
const app = render(
<Provider store={store_}>
<HyperContainer/>
<HyperContainer />
</Provider>,
document.getElementById('mount')
);

View file

@ -35,15 +35,16 @@ function Session(obj) {
const reducer = (state = initialState, action) => {
switch (action.type) {
case SESSION_ADD:
return state
.set('activeUid', action.uid)
.setIn(['sessions', action.uid], Session({
return state.set('activeUid', action.uid).setIn(
['sessions', action.uid],
Session({
cols: action.cols,
rows: action.rows,
uid: action.uid,
shell: action.shell.split('/').pop(),
pid: action.pid
}));
})
);
case SESSION_URL_SET:
return state.setIn(['sessions', action.uid, 'url'], action.url);
@ -55,27 +56,31 @@ const reducer = (state = initialState, action) => {
return state.set('activeUid', action.uid);
case SESSION_CLEAR_ACTIVE:
return state.merge({
sessions: {
[state.activeUid]: {
cleared: true
return state.merge(
{
sessions: {
[state.activeUid]: {
cleared: true
}
}
}
}, {deep: true});
},
{deep: true}
);
case SESSION_PTY_DATA:
// we avoid a direct merge for perf reasons
// as this is the most common action
if (state.sessions[action.uid] &&
state.sessions[action.uid].cleared) {
return state
.merge({
if (state.sessions[action.uid] && state.sessions[action.uid].cleared) {
return state.merge(
{
sessions: {
[action.uid]: {
cleared: false
}
}
}, {deep: true});
},
{deep: true}
);
}
return state;
@ -99,11 +104,14 @@ const reducer = (state = initialState, action) => {
);
case SESSION_RESIZE:
return state.setIn(['sessions', action.uid], state.sessions[action.uid].merge({
rows: action.rows,
cols: action.cols,
resizeAt: Date.now()
}));
return state.setIn(
['sessions', action.uid],
state.sessions[action.uid].merge({
rows: action.rows,
cols: action.cols,
resizeAt: Date.now()
})
);
case SESSION_SET_CWD:
return state.setIn(['sessions', state.activeUid, 'cwd'], action.cwd);

View file

@ -40,9 +40,7 @@ const setActiveGroup = (state, action) => {
const childGroup = findBySession(state, action.uid);
const rootGroup = findRootGroup(state.termGroups, childGroup.uid);
return state
.set('activeRootGroup', rootGroup.uid)
.setIn(['activeSessions', rootGroup.uid], action.uid);
return state.set('activeRootGroup', rootGroup.uid).setIn(['activeSessions', rootGroup.uid], action.uid);
};
// Reduce existing sizes to fit a new split:
@ -50,7 +48,7 @@ const insertRebalance = (oldSizes, index) => {
const newSize = 1 / (oldSizes.length + 1);
// We spread out how much each pane should be reduced
// with based on their existing size:
const balanced = oldSizes.map(size => size - (newSize * size));
const balanced = oldSizes.map(size => size - newSize * size);
return [...balanced.slice(0, index), newSize, ...balanced.slice(index)];
};
@ -58,9 +56,7 @@ const insertRebalance = (oldSizes, index) => {
const removalRebalance = (oldSizes, index) => {
const removedSize = oldSizes[index];
const increase = removedSize / (oldSizes.length - 1);
return oldSizes
.filter((_size, i) => i !== index)
.map(size => size + increase);
return oldSizes.filter((_size, i) => i !== index).map(size => size + increase);
};
const splitGroup = (state, action) => {
@ -96,23 +92,27 @@ const splitGroup = (state, action) => {
parentUid: parentGroup.uid
});
return state
.setIn(['termGroups', existingSession.uid], existingSession)
.setIn(['termGroups', parentGroup.uid], parentGroup.merge({
return state.setIn(['termGroups', existingSession.uid], existingSession).setIn(
['termGroups', parentGroup.uid],
parentGroup.merge({
sessionUid: null,
direction: splitDirection,
children: [existingSession.uid, newSession.uid]
}));
})
);
}
const {children} = parentGroup;
// Insert the new child pane right after the active one:
const index = children.indexOf(activeGroup.uid) + 1;
const newChildren = [...children.slice(0, index), newSession.uid, ...children.slice(index)];
state = state.setIn(['termGroups', parentGroup.uid], parentGroup.merge({
direction: splitDirection,
children: newChildren
}));
state = state.setIn(
['termGroups', parentGroup.uid],
parentGroup.merge({
direction: splitDirection,
children: newChildren
})
);
if (parentGroup.sizes) {
const newSizes = insertRebalance(parentGroup.sizes, index);
@ -131,17 +131,13 @@ const replaceParent = (state, parent, child) => {
// If the parent we're replacing has a parent,
// we need to change the uid in its children array
// with `child`:
const newChildren = parentParent.children.map(uid =>
uid === parent.uid ? child.uid : uid
);
const newChildren = parentParent.children.map(uid => (uid === parent.uid ? child.uid : uid));
state = state.setIn(['termGroups', parentParent.uid, 'children'], newChildren);
} else {
// This means the given child will be
// a root group, so we need to set it up as such:
const newSessions = state.activeSessions
.without(parent.uid)
.set(child.uid, state.activeSessions[parent.uid]);
const newSessions = state.activeSessions.without(parent.uid).set(child.uid, state.activeSessions[parent.uid]);
state = state
.set('activeTermGroup', child.uid)
@ -185,10 +181,7 @@ const resizeGroup = (state, uid, sizes) => {
return state;
}
return state.setIn(
['termGroups', uid, 'sizes'],
sizes
);
return state.setIn(['termGroups', uid, 'sizes'], sizes);
};
const reducer = (state = initialState, action) => {

View file

@ -40,7 +40,8 @@ const initial = Immutable({
fontSize: 12,
padding: '12px 14px',
fontFamily: 'Menlo, "DejaVu Sans Mono", "Lucida Console", monospace',
uiFontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif',
uiFontFamily:
'-apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif',
fontSizeOverride: null,
fontSmoothingOverride: 'antialiased',
css: '',
@ -97,128 +98,128 @@ const currentWindow = remote.getCurrentWindow();
const reducer = (state = initial, action) => {
let state_ = state;
let isMax;
switch (action.type) { // eslint-disable-line default-case
//eslint-disable-next-line default-case
switch (action.type) {
case CONFIG_LOAD:
case CONFIG_RELOAD: // eslint-disable-line no-case-declarations
// eslint-disable-next-line no-case-declarations, no-fallthrough
case CONFIG_RELOAD:
const {config} = action;
state_ = state
// unset the user font size override if the
// font size changed from the config
.merge((() => {
const ret = {};
.merge(
(() => {
const ret = {};
if (state.fontSizeOverride && config.fontSize !== state.fontSize) {
ret.fontSizeOverride = null;
}
if (state.fontSizeOverride && config.fontSize !== state.fontSize) {
ret.fontSizeOverride = null;
}
if (config.fontSize) {
ret.fontSize = config.fontSize;
}
if (config.fontSize) {
ret.fontSize = config.fontSize;
}
if (config.fontFamily) {
ret.fontFamily = config.fontFamily;
}
if (config.fontFamily) {
ret.fontFamily = config.fontFamily;
}
if (config.uiFontFamily) {
ret.uiFontFamily = config.uiFontFamily;
}
if (config.uiFontFamily) {
ret.uiFontFamily = config.uiFontFamily;
}
if (config.cursorColor) {
ret.cursorColor = config.cursorColor;
}
if (config.cursorColor) {
ret.cursorColor = config.cursorColor;
}
if (allowedCursorShapes.has(config.cursorShape)) {
ret.cursorShape = config.cursorShape;
}
if (allowedCursorShapes.has(config.cursorShape)) {
ret.cursorShape = config.cursorShape;
}
if (allowedCursorBlinkValues.has(config.cursorBlink)) {
ret.cursorBlink = config.cursorBlink;
}
if (allowedCursorBlinkValues.has(config.cursorBlink)) {
ret.cursorBlink = config.cursorBlink;
}
if (config.borderColor) {
ret.borderColor = config.borderColor;
}
if (config.borderColor) {
ret.borderColor = config.borderColor;
}
if (typeof (config.padding) !== 'undefined' &&
config.padding !== null) {
ret.padding = config.padding;
}
if (typeof config.padding !== 'undefined' && config.padding !== null) {
ret.padding = config.padding;
}
if (config.foregroundColor) {
ret.foregroundColor = config.foregroundColor;
}
if (config.foregroundColor) {
ret.foregroundColor = config.foregroundColor;
}
if (config.backgroundColor) {
ret.backgroundColor = config.backgroundColor;
}
if (config.backgroundColor) {
ret.backgroundColor = config.backgroundColor;
}
if (config.css) {
ret.css = config.css;
}
if (config.css) {
ret.css = config.css;
}
if (config.termCSS) {
ret.termCSS = config.termCSS;
}
if (config.termCSS) {
ret.termCSS = config.termCSS;
}
if (allowedBells.has(config.bell)) {
ret.bell = config.bell;
}
if (allowedBells.has(config.bell)) {
ret.bell = config.bell;
}
if (config.bellSoundURL) {
ret.bellSoundURL = config.bellSoundURL || initial.bellSoundURL;
}
if (config.bellSoundURL) {
ret.bellSoundURL = config.bellSoundURL || initial.bellSoundURL;
}
if (typeof (config.copyOnSelect) !== 'undefined' &&
config.copyOnSelect !== null) {
ret.copyOnSelect = config.copyOnSelect;
}
if (typeof config.copyOnSelect !== 'undefined' && config.copyOnSelect !== null) {
ret.copyOnSelect = config.copyOnSelect;
}
if (config.colors) {
if (Array.isArray(config.colors)) {
const stateColors = Array.isArray(state.colors) ?
state.colors :
values(state.colors);
if (config.colors) {
if (Array.isArray(config.colors)) {
const stateColors = Array.isArray(state.colors) ? state.colors : values(state.colors);
if (stateColors.toString() !== config.colors.toString()) {
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;
}
}
if (config.modifierKeys) {
ret.modifierKeys = config.modifierKeys;
}
if (config.modifierKeys) {
ret.modifierKeys = config.modifierKeys;
}
if (allowedHamburgerMenuValues.has(config.showHamburgerMenu)) {
ret.showHamburgerMenu = config.showHamburgerMenu;
}
if (allowedHamburgerMenuValues.has(config.showHamburgerMenu)) {
ret.showHamburgerMenu = config.showHamburgerMenu;
}
if (allowedWindowControlsValues.has(config.showWindowControls)) {
ret.showWindowControls = config.showWindowControls;
}
if (allowedWindowControlsValues.has(config.showWindowControls)) {
ret.showWindowControls = config.showWindowControls;
}
if (process.platform === 'win32' &&
(config.quickEdit === undefined || config.quickEdit === null)) {
ret.quickEdit = true;
} else if (typeof (config.quickEdit) !== 'undefined' &&
config.quickEdit !== null) {
ret.quickEdit = config.quickEdit;
}
if (process.platform === 'win32' && (config.quickEdit === undefined || config.quickEdit === null)) {
ret.quickEdit = true;
} else if (typeof config.quickEdit !== 'undefined' && config.quickEdit !== null) {
ret.quickEdit = config.quickEdit;
}
return ret;
})());
return ret;
})()
);
break;
case SESSION_ADD:
state_ = state.merge({
activeUid: action.uid,
openAt: {
[action.uid]: Date.now()
}
}, {deep: true});
state_ = state.merge(
{
activeUid: action.uid,
openAt: {
[action.uid]: Date.now()
}
},
{deep: true}
);
break;
case SESSION_RESIZE:
@ -250,15 +251,19 @@ const reducer = (state = initial, action) => {
break;
case SESSION_SET_ACTIVE:
state_ = state.merge({
activeUid: action.uid,
activityMarkers: {
[action.uid]: false
}
}, {deep: true});
state_ = state.merge(
{
activeUid: action.uid,
activityMarkers: {
[action.uid]: false
}
},
{deep: true}
);
break;
case SESSION_PTY_DATA: // eslint-disable-line no-case-declarations
// eslint-disable-next-line no-case-declarations
case SESSION_PTY_DATA:
// ignore activity markers for current tab
if (action.uid === state.activeUid) {
break;
@ -277,11 +282,14 @@ const reducer = (state = initial, action) => {
// expect to get data packets from the resize
// of the ptys as a result
if (!state.resizeAt || now - state.resizeAt > 1000) {
state_ = state.merge({
activityMarkers: {
[action.uid]: true
}
}, {deep: true});
state_ = state.merge(
{
activityMarkers: {
[action.uid]: true
}
},
{deep: true}
);
}
break;
@ -318,11 +326,14 @@ const reducer = (state = initial, action) => {
break;
case NOTIFICATION_DISMISS:
state_ = state.merge({
notifications: {
[action.id]: false
}
}, {deep: true});
state_ = state.merge(
{
notifications: {
[action.id]: false
}
},
{deep: true}
);
break;
case NOTIFICATION_MESSAGE:
@ -343,15 +354,17 @@ const reducer = (state = initial, action) => {
// Show a notification if any of the font size values have changed
if (CONFIG_LOAD !== action.type) {
if (state_.fontSize !== state.fontSize ||
state_.fontSizeOverride !== state.fontSizeOverride) {
if (state_.fontSize !== state.fontSize || state_.fontSizeOverride !== state.fontSizeOverride) {
state_ = state_.merge({notifications: {font: true}}, {deep: true});
}
}
if ((typeof (state.cols) !== 'undefined' && state.cols !== null) &&
(typeof (state.rows) !== 'undefined' && state.rows !== null) &&
(state.rows !== state_.rows || state.cols !== state_.cols)) {
if (
typeof state.cols !== 'undefined' &&
state.cols !== null &&
(typeof state.rows !== 'undefined' && state.rows !== null) &&
(state.rows !== state_.rows || state.cols !== state_.cols)
) {
state_ = state_.merge({notifications: {resize: true}}, {deep: true});
}

View file

@ -1,9 +1,8 @@
import {createSelector} from 'reselect';
const getTermGroups = ({termGroups}) => termGroups.termGroups;
const getRootGroups = createSelector(
getTermGroups,
termGroups => Object.keys(termGroups)
const getRootGroups = createSelector(getTermGroups, termGroups =>
Object.keys(termGroups)
.map(uid => termGroups[uid])
.filter(({parentUid}) => !parentUid)
);

View file

@ -13,19 +13,9 @@ export default () => {
});
const enhancer = compose(
applyMiddleware(
thunk,
plugins.middleware,
thunk,
effects,
writeMiddleware,
logger
),
applyMiddleware(thunk, plugins.middleware, thunk, effects, writeMiddleware, logger),
window.devToolsExtension()
);
return createStore(
rootReducer,
enhancer
);
return createStore(rootReducer, enhancer);
};

View file

@ -6,13 +6,4 @@ import * as plugins from '../utils/plugins';
import writeMiddleware from './write-middleware';
export default () =>
createStore(
rootReducer,
applyMiddleware(
thunk,
plugins.middleware,
thunk,
effects,
writeMiddleware
)
);
createStore(rootReducer, applyMiddleware(thunk, plugins.middleware, thunk, effects, writeMiddleware));

View file

@ -15,9 +15,5 @@ export default function isExecutable(fileStat) {
return true;
}
return Boolean(
(fileStat.mode & 0o0001) ||
(fileStat.mode & 0o0010) ||
(fileStat.mode & 0o0100)
);
return Boolean(fileStat.mode & 0o0001 || fileStat.mode & 0o0010 || fileStat.mode & 0o0100);
}

View file

@ -17,7 +17,7 @@ const _toAscii = {
173: '45',
187: '61', // IE Key codes
186: '59', // IE Key codes
189: '45' // IE Key codes
189: '45' // IE Key codes
};
const _shiftUps = {
@ -38,7 +38,7 @@ const _shiftUps = {
93: '}',
92: '|',
59: ':',
39: '\'',
39: "'",
44: '<',
46: '>',
47: '?'

View file

@ -1,6 +1,7 @@
/* global Notification */
/* eslint no-new:0 */
export default function notify(title, body) {
//eslint-disable-next-line no-console
console.log(`[Notification] ${title}: ${body}`);
new Notification(title, {body});
}

View file

@ -10,9 +10,10 @@ import Component from '../component';
import Notification from '../components/notification';
import notify from './notify';
const Module = require('module'); // eslint-disable-line import/newline-after-import
//eslint-disable-next-line import/newline-after-import
const Module = require('module');
const originalLoad = Module._load;
Module._load = function (path) {
Module._load = function _load(path) {
switch (path) {
case 'react':
return React;
@ -53,10 +54,10 @@ let termGroupPropsDecorators;
let propsDecorators;
let reducersDecorators;
// the fs locations where user plugins are stored
const {path, localPath} = plugins.getBasePaths();
const clearModulesCache = () => {
// the fs locations where user plugins are stored
const {path, localPath} = plugins.getBasePaths();
// trigger unload hooks
modules.forEach(mod => {
if (mod.onRendererUnload) {
@ -82,12 +83,14 @@ const getPluginVersion = path => {
try {
version = window.require(pathModule.resolve(path, 'package.json')).version;
} catch (err) {
//eslint-disable-next-line no-console
console.warn(`No package.json found in ${path}`);
}
return version;
};
const loadModules = () => {
//eslint-disable-next-line no-console
console.log('(re)loading renderer plugins');
const paths = plugins.getPaths();
@ -120,7 +123,8 @@ const loadModules = () => {
reduceTermGroups: termGroupsReducers
};
modules = paths.plugins.concat(paths.localPlugins)
modules = paths.plugins
.concat(paths.localPlugins)
.map(path => {
let mod;
const pluginName = getPluginName(path);
@ -131,8 +135,12 @@ const loadModules = () => {
try {
mod = window.require(path);
} catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack);
notify('Plugin load error', `"${pluginName}" failed to load in the renderer process. Check Developer Tools for details.`);
notify(
'Plugin load error',
`"${pluginName}" failed to load in the renderer process. Check Developer Tools for details.`
);
return undefined;
}
@ -146,12 +154,14 @@ const loadModules = () => {
// mapHyperTermState mapping for backwards compatibility with hyperterm
if (mod.mapHyperTermState) {
mod.mapHyperState = mod.mapHyperTermState;
//eslint-disable-next-line no-console
console.error('mapHyperTermState is deprecated. Use mapHyperState instead.');
}
// mapHyperTermDispatch mapping for backwards compatibility with hyperterm
if (mod.mapHyperTermDispatch) {
mod.mapHyperDispatch = mod.mapHyperTermDispatch;
//eslint-disable-next-line no-console
console.error('mapHyperTermDispatch is deprecated. Use mapHyperDispatch instead.');
}
@ -222,7 +232,7 @@ const loadModules = () => {
if (mod.onRendererWindow) {
mod.onRendererWindow(window);
}
//eslint-disable-next-line no-console
console.log(`Plugin ${pluginName} (${pluginVersion}) loaded.`);
return mod;
@ -231,8 +241,9 @@ const loadModules = () => {
const deprecatedPlugins = plugins.getDeprecatedConfig();
Object.keys(deprecatedPlugins).forEach(name => {
const { css } = deprecatedPlugins[name];
const {css} = deprecatedPlugins[name];
if (css) {
//eslint-disable-next-line no-console
console.warn(`Warning: "${name}" plugin uses some deprecated CSS classes (${css.join(', ')}).`);
}
});
@ -263,6 +274,7 @@ function getProps(name, props, ...fnArgs) {
try {
ret_ = fn(...fnArgs, props_);
} catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack);
notify('Plugin error', `${fn._pluginName}: Error occurred in \`${name}\`. Check Developer Tools for details.`);
return;
@ -309,8 +321,12 @@ export function connect(stateFn, dispatchFn, c, d = {}) {
try {
ret_ = fn(state, ret);
} catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack);
notify('Plugin error', `${fn._pluginName}: Error occurred in \`map${name}State\`. Check Developer Tools for details.`);
notify(
'Plugin error',
`${fn._pluginName}: Error occurred in \`map${name}State\`. Check Developer Tools for details.`
);
return;
}
@ -331,13 +347,20 @@ export function connect(stateFn, dispatchFn, c, d = {}) {
try {
ret_ = fn(dispatch, ret);
} catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack);
notify('Plugin error', `${fn._pluginName}: Error occurred in \`map${name}Dispatch\`. Check Developer Tools for details.`);
notify(
'Plugin error',
`${fn._pluginName}: Error occurred in \`map${name}Dispatch\`. Check Developer Tools for details.`
);
return;
}
if (!ret_ || typeof ret_ !== 'object') {
notify('Plugin error', `${fn._pluginName}: Invalid return value of \`map${name}Dispatch\` (object expected).`);
notify(
'Plugin error',
`${fn._pluginName}: Invalid return value of \`map${name}Dispatch\` (object expected).`
);
return;
}
@ -362,6 +385,7 @@ function decorateReducer(name, fn) {
try {
state__ = pluginReducer(state_, action);
} catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack);
notify('Plugin error', `${fn._pluginName}: Error occurred in \`${name}\`. Check Developer Tools for details.`);
return;
@ -393,26 +417,25 @@ 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);
const nextMiddleware = remaining => action_ =>
remaining.length ? remaining[0](store)(nextMiddleware(remaining.slice(1)))(action_) : next(action_);
nextMiddleware(middlewares)(action);
};
// expose decorated component instance to the higher-order components
function exposeDecorated(Component) {
return class extends React.Component {
function exposeDecorated(Component_) {
return class DecoratedComponent extends React.Component {
constructor(props, context) {
super(props, context);
this.onRef = this.onRef.bind(this);
}
onRef(decorated) {
onRef(decorated_) {
if (this.props.onDecorated) {
this.props.onDecorated(decorated);
this.props.onDecorated(decorated_);
}
}
render() {
return React.createElement(Component, Object.assign({}, this.props, {ref: this.onRef}));
return React.createElement(Component_, Object.assign({}, this.props, {ref: this.onRef}));
}
};
}
@ -433,13 +456,20 @@ function getDecorated(parent, name) {
class__ = fn(class_, {React, Component, Notification, notify});
class__.displayName = `${fn._pluginName}(${name})`;
} catch (err) {
//eslint-disable-next-line no-console
console.error(err.stack);
notify('Plugin error', `${fn._pluginName}: Error occurred in \`${method}\`. Check Developer Tools for details`);
notify(
'Plugin error',
`${fn._pluginName}: Error occurred in \`${method}\`. Check Developer Tools for details`
);
return;
}
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\`.`);
notify(
'Plugin error',
`${fn._pluginName}: Invalid return value of \`${method}\`. No \`render\` method found. Please return a \`React.Component\`.`
);
return;
}
@ -456,10 +486,10 @@ 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) {
return class extends React.Component {
export function decorate(Component_, name) {
return class DecoratedCompenent extends React.Component {
render() {
const Sub = getDecorated(Component, name);
const Sub = getDecorated(Component_, name);
return React.createElement(Sub, this.props);
}
};

View file

@ -1,5 +1,4 @@
export default class Client {
constructor() {
const electron = window.require('electron');
const EventEmitter = window.require('events');
@ -56,5 +55,4 @@ export default class Client {
this.removeAllListeners();
this.ipc.removeAllListeners();
}
}

View file

@ -1,11 +1,11 @@
// Clear selection range of current selected term view
// Fix event when terminal text is selected and keyboard action is invoked
exports.clear = function (terminal) {
exports.clear = terminal => {
terminal.document_.getSelection().removeAllRanges();
};
// Use selection extend upon dblclick
exports.extend = function (terminal) {
exports.extend = terminal => {
const sel = terminal.document_.getSelection();
// Test if focusNode exist and nodeName is #text
@ -19,7 +19,7 @@ exports.extend = function (terminal) {
// Fix a bug in ScrollPort selectAll behavior
// Select all rows in the viewport
exports.all = function (terminal) {
exports.all = terminal => {
const scrollPort = terminal.scrollPort_;
let firstRow;
let lastRow;

View file

@ -2,7 +2,7 @@ import path from 'path';
import * as regex from './url-regex';
export default function isUrlCommand(shell, data) {
const matcher = regex[path.parse(shell).name]; // eslint-disable-line import/namespace
const matcher = regex[path.parse(shell).name];
if (undefined === matcher || !data) {
return null;
}

View file

@ -17,11 +17,13 @@
},
"eslintConfig": {
"plugins": [
"react"
"react",
"prettier"
],
"extends": [
"eslint:recommended",
"plugin:react/recommended"
"plugin:react/recommended",
"prettier"
],
"parserOptions": {
"ecmaVersion": 8,
@ -49,7 +51,21 @@
"react/react-in-jsx-scope": 0,
"react/no-unescaped-entities": 0,
"react/jsx-no-target-blank": 0,
"react/no-string-refs": 0
"react/no-string-refs": 0,
"prettier/prettier": [
"error",
{
"printWidth": 120,
"tabWidth": 2,
"singleQuote": true,
"trailingComma": "none",
"bracketSpacing": false,
"semi": true,
"useTabs": false,
"parser": "babylon",
"jsxBracketSameLine": false
}
]
}
},
"babel": {
@ -88,7 +104,7 @@
"appId": "co.zeit.hyper",
"extraResources": "./bin/yarn-standalone.js",
"linux": {
"category": "TerminalEmulator",
"category": "TerminalEmulator",
"target": [
{
"target": "deb",
@ -162,9 +178,12 @@
"electron-devtools-installer": "2.2.0",
"electron-rebuild": "1.6.0",
"eslint": "3.19.0",
"eslint-config-prettier": "2.4.0",
"eslint-plugin-prettier": "2.2.0",
"eslint-plugin-react": "7.3.0",
"husky": "0.14.3",
"node-gyp": "3.6.2",
"prettier": "1.6.1",
"redux-logger": "3.0.6",
"spectron": "3.7.2",
"style-loader": "0.18.2",

View file

@ -14,39 +14,15 @@ test(`returns a color that's in hex`, t => {
const hslaColor = 'hsl(15, 100%, 50%, 1)';
const colorKeyword = 'pink';
t.true(
isHexColor(
toElectronBackgroundColor(hexColor)
)
);
t.true(isHexColor(toElectronBackgroundColor(hexColor)));
t.true(
isHexColor(
toElectronBackgroundColor(rgbColor)
)
);
t.true(isHexColor(toElectronBackgroundColor(rgbColor)));
t.true(
isHexColor(
toElectronBackgroundColor(rgbaColor)
)
);
t.true(isHexColor(toElectronBackgroundColor(rgbaColor)));
t.true(
isHexColor(
toElectronBackgroundColor(hslColor)
)
);
t.true(isHexColor(toElectronBackgroundColor(hslColor)));
t.true(
isHexColor(
toElectronBackgroundColor(hslaColor)
)
);
t.true(isHexColor(toElectronBackgroundColor(hslaColor)));
t.true(
isHexColor(
toElectronBackgroundColor(colorKeyword)
)
);
t.true(isHexColor(toElectronBackgroundColor(colorKeyword)));
});

View file

@ -36,7 +36,7 @@ module.exports = {
},
plugins: [
new webpack.DefinePlugin({
'process.env': { // eslint-disable-line quote-props
'process.env': {
NODE_ENV: JSON.stringify(nodeEnv)
}
}),

View file

@ -2385,6 +2385,19 @@ escope@^3.6.0:
esrecurse "^4.1.0"
estraverse "^4.1.1"
eslint-config-prettier@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-2.4.0.tgz#fb7cf29c0ab2ba61af5164fb1930f9bef3be2872"
dependencies:
get-stdin "^5.0.1"
eslint-plugin-prettier@2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.2.0.tgz#f2837ad063903d73c621e7188fb3d41486434088"
dependencies:
fast-diff "^1.1.1"
jest-docblock "^20.0.1"
eslint-plugin-react@7.3.0:
version "7.3.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.3.0.tgz#ca9368da36f733fbdc05718ae4e91f778f38e344"
@ -2835,6 +2848,10 @@ get-stdin@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-4.0.1.tgz#b968c6b0a04384324902e8bf1a5df32579a450fe"
get-stdin@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398"
get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
@ -3483,6 +3500,10 @@ isstream@~0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
jest-docblock@^20.0.1:
version "20.0.3"
resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.3.tgz#17bea984342cc33d83c50fbe1545ea0efaa44712"
js-base64@^2.1.9:
version "2.2.0"
resolved "https://registry.yarnpkg.com/js-base64/-/js-base64-2.2.0.tgz#5e8a8d193a908198dd23d1704826d207b0e5a8f6"
@ -4739,6 +4760,10 @@ preserve@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b"
prettier@1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.6.1.tgz#850f411a3116226193e32ea5acfc21c0f9a76d7d"
pretty-bytes@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/pretty-bytes/-/pretty-bytes-1.0.4.tgz#0a22e8210609ad35542f8c8d5d2159aff0751c84"