mirror of
https://github.com/quine-global/hyper.git
synced 2026-01-12 20:18:41 -09:00
Merge branch 'master' into xterm
* master: (62 commits) 1.4.3 Disable ia32 linux releases (#2164) Fixed writing composed characters (#2158) Doc: Add yarn install to contribute instructions (#2117) Change "Close Session" shortcut on Linux/Windows (#2160) Notice for plugins (#2114) Updated dependencies to the latest version (#2146) 1.4.2 Reverted class names to as they were before (#2139) 1.4.1 AppVeyor environment variables are now on the platform (#2137) Brought back the icon for closing tabs (#2136) Brought back keymap documentation to the website (#2133) 1.4.0 Don't build on master, except for releases (#2132) Ensured that `async-retry` is added to the bundle (#2131) Ensure correct update channel is displayed in About window (#2130) Retry loading it if config doesn't exist in auto updater (#2129) Write contents of default config to hyper.js (#2128) Use a string for setting the update channel (#2127) ...
This commit is contained in:
commit
944f9d7e89
52 changed files with 129442 additions and 2306 deletions
|
|
@ -1,6 +1,8 @@
|
|||
build
|
||||
app/dist
|
||||
app/renderer
|
||||
app/static
|
||||
app/bin
|
||||
app/node_modules
|
||||
assets
|
||||
website
|
||||
node_modules
|
||||
bin
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
|||
# build output
|
||||
dist
|
||||
app/renderer
|
||||
|
||||
# dependencies
|
||||
node_modules
|
||||
|
|
|
|||
1
.npmrc
1
.npmrc
|
|
@ -1 +0,0 @@
|
|||
save-exact=true
|
||||
13
.travis.yml
13
.travis.yml
|
|
@ -5,16 +5,16 @@ language: node_js
|
|||
|
||||
matrix:
|
||||
include:
|
||||
- os: osx
|
||||
node_js: 7.4
|
||||
- os: linux
|
||||
node_js: 7.4
|
||||
node_js: 8.4.0
|
||||
env: CC=clang CXX=clang++ npm_config_clang=1
|
||||
compiler: clang
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- gcc-multilib
|
||||
- g++-multilib
|
||||
- libgnome-keyring-dev
|
||||
- icnsutils
|
||||
- graphicsmagick
|
||||
|
|
@ -24,11 +24,10 @@ addons:
|
|||
- snapd
|
||||
|
||||
before_install:
|
||||
- mkdir -p /tmp/git-lfs && curl -L https://github.com/github/git-lfs/releases/download/v1.2.1/git-lfs-$([ "$TRAVIS_OS_NAME" == "linux" ] && echo "linux" || echo "darwin")-amd64-1.2.1.tar.gz | tar -xz -C /tmp/git-lfs --strip-components 1 && /tmp/git-lfs/git-lfs pull
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then export DISPLAY=:99.0; sh -e /etc/init.d/xvfb start; sleep 3; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install yarn; fi
|
||||
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo add-apt-repository ppa:snappy-dev/tools-proposed -y && sudo apt update && sudo apt install snapcraft; fi
|
||||
- npm install -g yarn
|
||||
|
||||
cache: yarn
|
||||
|
||||
install:
|
||||
- yarn
|
||||
|
|
|
|||
1
.yarnrc
Normal file
1
.yarnrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
save-prefix false
|
||||
|
|
@ -1 +0,0 @@
|
|||
save-exact=true
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
// Packages
|
||||
const {autoUpdater} = require('electron');
|
||||
const ms = require('ms');
|
||||
const retry = require('async-retry');
|
||||
|
||||
// Utilities
|
||||
const notify = require('./notify'); // eslint-disable-line no-unused-vars
|
||||
const {version} = require('./package');
|
||||
const {getConfig} = require('./config');
|
||||
|
||||
const {platform} = process;
|
||||
|
||||
// accepted values: `osx`, `win32`
|
||||
// https://nuts.gitbook.com/update-windows.html
|
||||
const platform = process.platform === 'darwin' ?
|
||||
'osx' :
|
||||
process.platform;
|
||||
const FEED_URL = `https://hyper-updates.now.sh/update/${platform}`;
|
||||
let isInit = false;
|
||||
|
||||
function init() {
|
||||
|
|
@ -17,7 +17,28 @@ function init() {
|
|||
console.error('Error fetching updates', msg + ' (' + err.stack + ')');
|
||||
});
|
||||
|
||||
autoUpdater.setFeedURL(`${FEED_URL}/${version}`);
|
||||
const config = retry(() => {
|
||||
const content = getConfig();
|
||||
|
||||
if (!content) {
|
||||
throw new Error('No config content loaded');
|
||||
}
|
||||
|
||||
return content;
|
||||
});
|
||||
|
||||
// Default to the "stable" update channel
|
||||
let canaryUpdates = false;
|
||||
|
||||
// If defined in the config, switch to the "canary" channel
|
||||
if (config.updateChannel && config.updateChannel === 'canary') {
|
||||
canaryUpdates = true;
|
||||
}
|
||||
|
||||
const updatePrefix = canaryUpdates ? 'releases-canary' : 'releases';
|
||||
const feedURL = `https://${updatePrefix}.hyper.is/update/${platform}`;
|
||||
|
||||
autoUpdater.setFeedURL(`${feedURL}/${version}`);
|
||||
|
||||
setTimeout(() => {
|
||||
autoUpdater.checkForUpdates();
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
const gaze = require('gaze');
|
||||
const chokidar = require('chokidar');
|
||||
const notify = require('./notify');
|
||||
const _import = require('./config/import');
|
||||
const _openConfig = require('./config/open');
|
||||
|
|
@ -10,20 +10,23 @@ const watchers = [];
|
|||
// https://github.com/zeit/hyper/pull/1772
|
||||
const watchCfg = process.platform === 'win32' ? {interval: 2000} : {};
|
||||
let cfg = {};
|
||||
let _watcher;
|
||||
|
||||
const _watch = function () {
|
||||
gaze(cfgPath, watchCfg, function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
this.on('changed', () => {
|
||||
cfg = _import();
|
||||
notify('Configuration updated', 'Hyper configuration reloaded!');
|
||||
watchers.forEach(fn => fn());
|
||||
});
|
||||
this.on('error', () => {
|
||||
// Ignore file watching errors
|
||||
});
|
||||
if (_watcher) {
|
||||
return _watcher;
|
||||
}
|
||||
|
||||
_watcher = chokidar.watch(cfgPath, watchCfg);
|
||||
|
||||
_watcher.on('change', () => {
|
||||
cfg = _import();
|
||||
notify('Configuration updated', 'Hyper configuration reloaded!');
|
||||
watchers.forEach(fn => fn());
|
||||
});
|
||||
|
||||
_watcher.on('error', error => {
|
||||
console.error('error watching config', error);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,10 @@
|
|||
|
||||
module.exports = {
|
||||
config: {
|
||||
// Choose either "stable" for receiving highly polished,
|
||||
// or "canary" for less polished but more frequent updates
|
||||
updateChannel: 'stable',
|
||||
|
||||
// default font size in pixels for all tabs
|
||||
fontSize: 12,
|
||||
|
||||
|
|
@ -116,5 +120,10 @@ module.exports = {
|
|||
// in development, you can create a directory under
|
||||
// `~/.hyper_plugins/local/` and include it here
|
||||
// to load it and avoid it being `npm install`ed
|
||||
localPlugins: []
|
||||
localPlugins: [],
|
||||
|
||||
keymaps: {
|
||||
// Example
|
||||
// 'window:devtools': 'cmd+alt+o',
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
const {writeFileSync, readFileSync} = require('fs');
|
||||
const {defaultCfg, cfgPath} = require('./paths');
|
||||
const {sync: mkdirpSync} = require('mkdirp');
|
||||
const {defaultCfg, cfgPath, plugs} = require('./paths');
|
||||
const _init = require('./init');
|
||||
const _keymaps = require('./keymaps');
|
||||
|
||||
|
|
@ -15,13 +16,17 @@ const _write = function (path, data) {
|
|||
};
|
||||
|
||||
const _importConf = function () {
|
||||
// init plugin directories if not present
|
||||
mkdirpSync(plugs.base);
|
||||
mkdirpSync(plugs.local);
|
||||
|
||||
try {
|
||||
const _defaultCfg = readFileSync(defaultCfg, 'utf8');
|
||||
try {
|
||||
const _cfgPath = readFileSync(cfgPath, 'utf8');
|
||||
return {userCfg: _cfgPath, defaultCfg: _defaultCfg};
|
||||
} catch (err) {
|
||||
_write(cfgPath, defaultCfg);
|
||||
_write(cfgPath, _defaultCfg);
|
||||
return {userCfg: {}, defaultCfg: _defaultCfg};
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ const _syntaxValidation = function (cfg) {
|
|||
} catch (err) {
|
||||
notify(`Error loading config: ${err.name}, see DevTools for more info`);
|
||||
console.error('Error loading config:', err);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -36,6 +35,9 @@ const _init = function (cfg) {
|
|||
notify('Error reading configuration: `config` key is missing');
|
||||
return _extractDefault(cfg.defaultCfg);
|
||||
}
|
||||
// Ignore undefined values in plugin and localPlugins array Issue #1862
|
||||
_cfg.plugins = (_cfg.plugins && _cfg.plugins.filter(Boolean)) || [];
|
||||
_cfg.localPlugins = (_cfg.localPlugins && _cfg.localPlugins.filter(Boolean)) || [];
|
||||
return _cfg;
|
||||
}
|
||||
return _extractDefault(cfg.defaultCfg);
|
||||
|
|
|
|||
|
|
@ -4,7 +4,56 @@ const {cfgPath} = require('./paths');
|
|||
module.exports = () => Promise.resolve(shell.openItem(cfgPath));
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const exec = require('child_process').exec;
|
||||
const Registry = require('winreg');
|
||||
const {exec} = require('child_process');
|
||||
|
||||
// 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 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 values = valueGroups
|
||||
.reduce((allValues, groupValues) => ([...allValues, ...groupValues]), [])
|
||||
.filter(value => value && typeof value === 'string');
|
||||
|
||||
// No default app set
|
||||
if (values.length === 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// WScript is in default apps list
|
||||
if (values.some(value => value.includes('WScript.exe'))) {
|
||||
const userDefaults = values.filter(value => value.endsWith('.exe') && !value.includes('WScript.exe'));
|
||||
|
||||
// WScript.exe is overidden
|
||||
return (userDefaults.length > 0);
|
||||
}
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
// This mimics shell.openItem, true if it worked, false if not.
|
||||
const openNotepad = file => new Promise(resolve => {
|
||||
|
|
@ -13,21 +62,16 @@ 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 canOpenNative = () => new Promise((resolve, reject) => {
|
||||
exec('ftype JSFile', (error, stdout) => {
|
||||
if (error) {
|
||||
reject(error);
|
||||
} else if (stdout && stdout.includes('WScript.exe')) {
|
||||
reject(new Error('WScript is the default editor for .js files'));
|
||||
} else {
|
||||
resolve(true);
|
||||
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 = () => canOpenNative()
|
||||
.then(() => shell.openItem(cfgPath))
|
||||
.catch(() => openNotepad(cfgPath));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,14 @@ const devDir = resolve(__dirname, '../..');
|
|||
const devCfg = join(devDir, cfgFile);
|
||||
const defaultCfg = resolve(__dirname, defaultCfgFile);
|
||||
|
||||
const plugins = resolve(cfgDir, '.hyper_plugins');
|
||||
const plugs = {
|
||||
base: plugins,
|
||||
local: resolve(plugins, 'local'),
|
||||
cache: resolve(plugins, 'cache')
|
||||
};
|
||||
const yarn = resolve(__dirname, '../../bin/yarn-standalone.js');
|
||||
|
||||
const icon = resolve(__dirname, '../static/icon.png');
|
||||
|
||||
const keymapPath = resolve(__dirname, '../keymaps');
|
||||
|
|
@ -44,5 +52,5 @@ if (isDev) {
|
|||
}
|
||||
|
||||
module.exports = {
|
||||
cfgDir, cfgPath, cfgFile, defaultCfg, icon, defaultPlatformKeyPath
|
||||
cfgDir, cfgPath, cfgFile, defaultCfg, icon, defaultPlatformKeyPath, plugs, yarn
|
||||
};
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@
|
|||
<div id="mount"></div>
|
||||
|
||||
<script>start = performance.now();</script>
|
||||
<script src="dist/bundle.js"></script>
|
||||
<script src="renderer/bundle.js"></script>
|
||||
<script>console.log('total init time', performance.now() - start);</script>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
286
app/index.js
286
app/index.js
|
|
@ -9,6 +9,19 @@ if (['--help', '-v', '--version'].includes(process.argv[1])) {
|
|||
process.exit();
|
||||
}
|
||||
|
||||
const checkSquirrel = () => {
|
||||
let squirrel;
|
||||
|
||||
try {
|
||||
squirrel = require('electron-squirrel-startup');
|
||||
} catch (err) {}
|
||||
|
||||
if (squirrel) {
|
||||
// eslint-disable-next-line unicorn/no-process-exit
|
||||
process.exit();
|
||||
}
|
||||
};
|
||||
|
||||
// handle startup squirrel events
|
||||
if (process.platform === 'win32') {
|
||||
// eslint-disable-next-line import/order
|
||||
|
|
@ -18,50 +31,36 @@ if (process.platform === 'win32') {
|
|||
case '--squirrel-install':
|
||||
case '--squirrel-updated':
|
||||
systemContextMenu.add(() => {
|
||||
// eslint-disable-next-line curly, unicorn/no-process-exit
|
||||
if (require('electron-squirrel-startup')) process.exit();
|
||||
checkSquirrel();
|
||||
});
|
||||
break;
|
||||
case '--squirrel-uninstall':
|
||||
systemContextMenu.remove(() => {
|
||||
// eslint-disable-next-line curly, unicorn/no-process-exit
|
||||
if (require('electron-squirrel-startup')) process.exit();
|
||||
checkSquirrel();
|
||||
});
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line curly, unicorn/no-process-exit
|
||||
if (require('electron-squirrel-startup')) process.exit();
|
||||
checkSquirrel();
|
||||
}
|
||||
}
|
||||
|
||||
// Native
|
||||
const {resolve, isAbsolute} = require('path');
|
||||
const {homedir} = require('os');
|
||||
const {resolve} = require('path');
|
||||
|
||||
// Packages
|
||||
const {parse: parseUrl} = require('url');
|
||||
const {app, BrowserWindow, shell, Menu} = require('electron');
|
||||
const {app, BrowserWindow, Menu} = require('electron');
|
||||
const {gitDescribe} = require('git-describe');
|
||||
const uuid = require('uuid');
|
||||
const fileUriToPath = require('file-uri-to-path');
|
||||
const isDev = require('electron-is-dev');
|
||||
|
||||
// Ours
|
||||
const AutoUpdater = require('./auto-updater');
|
||||
const toElectronBackgroundColor = require('./utils/to-electron-background-color');
|
||||
const AppMenu = require('./menus/menu');
|
||||
const createRPC = require('./rpc');
|
||||
const notify = require('./notify');
|
||||
const fetchNotifications = require('./notifications');
|
||||
const config = require('./config');
|
||||
|
||||
app.commandLine.appendSwitch('js-flags', '--harmony-async-await');
|
||||
|
||||
// set up config
|
||||
config.setup();
|
||||
|
||||
const plugins = require('./plugins');
|
||||
const Session = require('./session');
|
||||
|
||||
const Window = require('./ui/window');
|
||||
|
||||
const windowSet = new Set([]);
|
||||
|
||||
|
|
@ -103,7 +102,7 @@ console.log('electron will open', url);
|
|||
|
||||
app.on('ready', () => installDevExtensions(isDev).then(() => {
|
||||
function createWindow(fn, options = {}) {
|
||||
let cfg = plugins.getDecoratedConfig();
|
||||
const cfg = plugins.getDecoratedConfig();
|
||||
|
||||
const winSet = config.getWin();
|
||||
let [startX, startY] = winSet.position;
|
||||
|
|
@ -139,239 +138,23 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
|
|||
}
|
||||
}
|
||||
|
||||
const browserDefaults = {
|
||||
width,
|
||||
height,
|
||||
minHeight: 190,
|
||||
minWidth: 370,
|
||||
titleBarStyle: 'hidden-inset', // macOS only
|
||||
title: 'Hyper.app',
|
||||
backgroundColor: toElectronBackgroundColor(cfg.backgroundColor || '#000'),
|
||||
// we want to go frameless on windows and linux
|
||||
frame: process.platform === 'darwin',
|
||||
transparent: process.platform === 'darwin',
|
||||
icon: resolve(__dirname, 'static/icon.png'),
|
||||
// we only want to show when the prompt is ready for user input
|
||||
// HYPERTERM_DEBUG for backwards compatibility with hyperterm
|
||||
show: process.env.HYPER_DEBUG || process.env.HYPERTERM_DEBUG || isDev,
|
||||
x: startX,
|
||||
y: startY,
|
||||
acceptFirstMouse: true
|
||||
};
|
||||
const browserOptions = plugins.getDecoratedBrowserOptions(browserDefaults);
|
||||
|
||||
const win = new BrowserWindow(browserOptions);
|
||||
windowSet.add(win);
|
||||
|
||||
win.loadURL(url);
|
||||
|
||||
const rpc = createRPC(win);
|
||||
const sessions = new Map();
|
||||
|
||||
// config changes
|
||||
const cfgUnsubscribe = config.subscribe(() => {
|
||||
const cfg_ = plugins.getDecoratedConfig();
|
||||
|
||||
// notify renderer
|
||||
win.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'
|
||||
);
|
||||
}
|
||||
|
||||
// update background color if necessary
|
||||
cfg = cfg_;
|
||||
});
|
||||
|
||||
rpc.on('init', () => {
|
||||
// we update the backgroundColor once the init is called.
|
||||
// when we do a win.reload() we need need to reset the backgroundColor
|
||||
win.setBackgroundColor(toElectronBackgroundColor(cfg.backgroundColor || '#000'));
|
||||
win.show();
|
||||
|
||||
// If no callback is passed to createWindow,
|
||||
// a new session will be created by default.
|
||||
if (!fn) {
|
||||
fn = win => win.rpc.emit('termgroup add req');
|
||||
}
|
||||
|
||||
// app.windowCallback is the createWindow callback
|
||||
// that can be set before the 'ready' app event
|
||||
// and createWindow deifinition. It's executed in place of
|
||||
// the callback passed as parameter, and deleted right after.
|
||||
(app.windowCallback || fn)(win);
|
||||
delete (app.windowCallback);
|
||||
|
||||
fetchNotifications(win);
|
||||
// auto updates
|
||||
if (!isDev && process.platform !== 'linux') {
|
||||
AutoUpdater(win);
|
||||
} else {
|
||||
console.log('ignoring auto updates during dev');
|
||||
}
|
||||
});
|
||||
|
||||
rpc.on('new', ({rows = 40, cols = 100, cwd = process.argv[1] && isAbsolute(process.argv[1]) ? process.argv[1] : homedir(), splitDirection}) => {
|
||||
const shell = cfg.shell;
|
||||
const shellArgs = cfg.shellArgs && Array.from(cfg.shellArgs);
|
||||
|
||||
initSession({rows, cols, cwd, shell, shellArgs}, (uid, session) => {
|
||||
sessions.set(uid, session);
|
||||
rpc.emit('session add', {
|
||||
rows,
|
||||
cols,
|
||||
uid,
|
||||
splitDirection,
|
||||
shell: session.shell,
|
||||
pid: session.pty.pid
|
||||
});
|
||||
|
||||
session.on('data', data => {
|
||||
rpc.emit('session data', uid + data);
|
||||
});
|
||||
|
||||
session.on('exit', () => {
|
||||
rpc.emit('session exit', {uid});
|
||||
sessions.delete(uid);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
rpc.on('exit', ({uid}) => {
|
||||
const session = sessions.get(uid);
|
||||
if (session) {
|
||||
session.exit();
|
||||
} else {
|
||||
console.log('session not found by', uid);
|
||||
}
|
||||
});
|
||||
|
||||
rpc.on('unmaximize', () => {
|
||||
win.unmaximize();
|
||||
});
|
||||
|
||||
rpc.on('maximize', () => {
|
||||
win.maximize();
|
||||
});
|
||||
|
||||
rpc.on('resize', ({uid, cols, rows}) => {
|
||||
const session = sessions.get(uid);
|
||||
session.resize({cols, rows});
|
||||
});
|
||||
|
||||
rpc.on('data', ({uid, data}) => {
|
||||
sessions.get(uid).write(data);
|
||||
});
|
||||
|
||||
rpc.on('open external', ({url}) => {
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
rpc.win.on('move', () => {
|
||||
rpc.emit('move');
|
||||
});
|
||||
|
||||
rpc.on('open hamburger menu', ({x, y}) => {
|
||||
Menu.getApplicationMenu().popup(Math.ceil(x), Math.ceil(y));
|
||||
});
|
||||
|
||||
rpc.on('minimize', () => {
|
||||
win.minimize();
|
||||
});
|
||||
|
||||
rpc.on('close', () => {
|
||||
win.close();
|
||||
});
|
||||
|
||||
const deleteSessions = () => {
|
||||
sessions.forEach((session, key) => {
|
||||
session.removeAllListeners();
|
||||
session.destroy();
|
||||
sessions.delete(key);
|
||||
});
|
||||
};
|
||||
|
||||
// we reset the rpc channel only upon
|
||||
// subsequent refreshes (ie: F5)
|
||||
let i = 0;
|
||||
win.webContents.on('did-navigate', () => {
|
||||
if (i++) {
|
||||
deleteSessions();
|
||||
}
|
||||
});
|
||||
|
||||
// If file is dropped onto the terminal window, navigate event is prevented
|
||||
// and his path is added to active session.
|
||||
win.webContents.on('will-navigate', (event, url) => {
|
||||
const protocol = typeof url === 'string' && parseUrl(url).protocol;
|
||||
if (protocol === 'file:') {
|
||||
event.preventDefault();
|
||||
const path = fileUriToPath(url).replace(/ /g, '\\ ');
|
||||
rpc.emit('session data send', {data: path});
|
||||
} else if (protocol === 'http:' || protocol === 'https:') {
|
||||
event.preventDefault();
|
||||
rpc.emit('session data send', {data: url});
|
||||
}
|
||||
});
|
||||
|
||||
// expose internals to extension authors
|
||||
win.rpc = rpc;
|
||||
win.sessions = sessions;
|
||||
|
||||
const load = () => {
|
||||
plugins.onWindow(win);
|
||||
};
|
||||
|
||||
// load plugins
|
||||
load();
|
||||
|
||||
const pluginsUnsubscribe = plugins.subscribe(err => {
|
||||
if (!err) {
|
||||
load();
|
||||
win.webContents.send('plugins change');
|
||||
}
|
||||
});
|
||||
|
||||
// Keep track of focus time of every window, to figure out
|
||||
// which one of the existing window is the last focused.
|
||||
// Works nicely even if a window is closed and removed.
|
||||
const updateFocusTime = () => {
|
||||
win.focusTime = process.uptime();
|
||||
};
|
||||
win.on('focus', () => {
|
||||
updateFocusTime();
|
||||
});
|
||||
// Ensure focusTime is set on window open. The focus event doesn't
|
||||
// fire from the dock (see bug #583)
|
||||
updateFocusTime();
|
||||
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
|
||||
win.on('close', () => {
|
||||
config.winRecord(win);
|
||||
windowSet.delete(win);
|
||||
rpc.destroy();
|
||||
deleteSessions();
|
||||
cfgUnsubscribe();
|
||||
pluginsUnsubscribe();
|
||||
hwin.on('close', () => {
|
||||
hwin.clean();
|
||||
windowSet.delete(hwin);
|
||||
});
|
||||
|
||||
// Same deal as above, grabbing the window titlebar when the window
|
||||
// is maximized on Windows results in unmaximize, without hitting any
|
||||
// app buttons
|
||||
for (const ev of ['maximize', 'unmaximize', 'minimize', 'restore']) {
|
||||
win.on(ev, () => rpc.emit('windowGeometry change'));
|
||||
}
|
||||
|
||||
win.on('closed', () => {
|
||||
hwin.on('closed', () => {
|
||||
if (process.platform !== 'darwin' && windowSet.size === 0) {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
return hwin;
|
||||
}
|
||||
|
||||
// when opening create a new window
|
||||
|
|
@ -393,8 +176,9 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
|
|||
const menu = plugins.decorateMenu(
|
||||
AppMenu(createWindow, () => {
|
||||
plugins.updatePlugins({force: true});
|
||||
})
|
||||
);
|
||||
},
|
||||
plugins.getLoadedPluginVersions
|
||||
));
|
||||
|
||||
// If we're on Mac make a Dock Menu
|
||||
if (process.platform === 'darwin') {
|
||||
|
|
@ -424,10 +208,6 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
|
|||
console.error('Error while loading devtools extensions', err);
|
||||
}));
|
||||
|
||||
function initSession(opts, fn) {
|
||||
fn(uuid.v4(), new Session(opts));
|
||||
}
|
||||
|
||||
app.on('open-file', (event, path) => {
|
||||
const lastWindow = app.getLastFocusedWindow();
|
||||
const callback = win => win.rpc.emit('open file', {path});
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"pane:prev": "ctrl+pagedown",
|
||||
"pane:splitVertical": "ctrl+shift+d",
|
||||
"pane:splitHorizontal": "ctrl+shift+e",
|
||||
"pane:close": "ctrl+shift+w",
|
||||
"pane:close": "ctrl+alt+w",
|
||||
"editor:undo": "ctrl+shift+z",
|
||||
"editor:redo": "ctrl+shift+y",
|
||||
"editor:cut": "ctrl+shift+x",
|
||||
|
|
@ -27,4 +27,4 @@
|
|||
"editor:selectAll": "ctrl+shift+a",
|
||||
"editor:clearBuffer": "ctrl+shift+k",
|
||||
"plugins:update": "ctrl+shift+u"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
"pane:prev": "ctrl+pagedown",
|
||||
"pane:splitVertical": "ctrl+shift+d",
|
||||
"pane:splitHorizontal": "ctrl+shift+e",
|
||||
"pane:close": "ctrl+shift+w",
|
||||
"pane:close": "ctrl+alt+w",
|
||||
"editor:undo": "ctrl+shift+z",
|
||||
"editor:redo": "ctrl+shift+y",
|
||||
"editor:cut": "ctrl+shift+x",
|
||||
|
|
@ -27,4 +27,4 @@
|
|||
"editor:selectAll": "ctrl+shift+a",
|
||||
"editor:clearBuffer": "ctrl+shift+k",
|
||||
"plugins:update": "ctrl+shift+u"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
const {getKeymaps} = require('../config');
|
||||
// Packages
|
||||
const {app, dialog} = require('electron');
|
||||
|
||||
// menus
|
||||
// Utilities
|
||||
const {getKeymaps, getConfig} = require('../config');
|
||||
const {icon} = require('../config/paths');
|
||||
const viewMenu = require('./menus/view');
|
||||
const shellMenu = require('./menus/shell');
|
||||
const editMenu = require('./menus/edit');
|
||||
|
|
@ -9,16 +12,41 @@ const windowMenu = require('./menus/window');
|
|||
const helpMenu = require('./menus/help');
|
||||
const darwinMenu = require('./menus/darwin');
|
||||
|
||||
module.exports = (createWindow, updatePlugins) => {
|
||||
const commands = getKeymaps().commands;
|
||||
const appName = app.getName();
|
||||
const appVersion = app.getVersion();
|
||||
|
||||
module.exports = (createWindow, updatePlugins, getLoadedPluginVersions) => {
|
||||
const config = getConfig();
|
||||
const {commands} = getKeymaps();
|
||||
|
||||
let updateChannel = 'stable';
|
||||
|
||||
if (config && config.updateChannel && config.updateChannel === 'canary') {
|
||||
updateChannel = 'canary';
|
||||
}
|
||||
|
||||
const showAbout = () => {
|
||||
const loadedPlugins = getLoadedPluginVersions();
|
||||
const pluginList = loadedPlugins.length === 0 ?
|
||||
'none' :
|
||||
loadedPlugins.map(plugin => `\n ${plugin.name} (${plugin.version})`);
|
||||
|
||||
dialog.showMessageBox({
|
||||
title: `About ${appName}`,
|
||||
message: `${appName} ${appVersion} (${updateChannel})`,
|
||||
detail: `Plugins: ${pluginList}\n\nCreated by Guillermo Rauch\nCopyright © 2017 Zeit, Inc.`,
|
||||
buttons: [],
|
||||
icon
|
||||
});
|
||||
};
|
||||
const menu = [
|
||||
...(process.platform === 'darwin' ? [darwinMenu(commands)] : []),
|
||||
...(process.platform === 'darwin' ? [darwinMenu(commands, showAbout)] : []),
|
||||
shellMenu(commands, createWindow),
|
||||
editMenu(commands),
|
||||
viewMenu(commands),
|
||||
pluginsMenu(commands, updatePlugins),
|
||||
windowMenu(commands),
|
||||
helpMenu(commands)
|
||||
helpMenu(commands, showAbout)
|
||||
];
|
||||
|
||||
return menu;
|
||||
|
|
|
|||
|
|
@ -3,12 +3,15 @@
|
|||
const {app} = require('electron');
|
||||
const {openConfig} = require('../../config');
|
||||
|
||||
module.exports = function (commands) {
|
||||
module.exports = function (commands, showAbout) {
|
||||
return {
|
||||
label: `${app.getName()}`,
|
||||
submenu: [
|
||||
{
|
||||
role: 'about'
|
||||
label: 'About Hyper',
|
||||
click() {
|
||||
showAbout();
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'separator'
|
||||
|
|
|
|||
|
|
@ -1,8 +1,7 @@
|
|||
const os = require('os');
|
||||
const {app, shell, dialog} = require('electron');
|
||||
const {icon} = require('../../config/paths');
|
||||
const {app, shell} = require('electron');
|
||||
|
||||
module.exports = function () {
|
||||
module.exports = function (commands, showAbout) {
|
||||
const submenu = [
|
||||
{
|
||||
label: `${app.getName()} Website`,
|
||||
|
|
@ -31,13 +30,7 @@ module.exports = function () {
|
|||
{
|
||||
role: 'about',
|
||||
click() {
|
||||
dialog.showMessageBox({
|
||||
title: `About ${app.getName()}`,
|
||||
message: `${app.getName()} ${app.getVersion()}`,
|
||||
detail: 'Created by Guillermo Rauch',
|
||||
icon,
|
||||
buttons: []
|
||||
});
|
||||
showAbout();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "hyper",
|
||||
"productName": "Hyper",
|
||||
"description": "A terminal built on web technologies",
|
||||
"version": "1.3.3",
|
||||
"version": "1.4.3",
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "Zeit, Inc.",
|
||||
|
|
@ -11,22 +11,24 @@
|
|||
"repository": "zeit/hyper",
|
||||
"xo": false,
|
||||
"dependencies": {
|
||||
"color": "0.11.3",
|
||||
"async-retry": "1.1.3",
|
||||
"chokidar": "1.7.0",
|
||||
"color": "2.0.0",
|
||||
"convert-css-color-name-to-hex": "0.1.1",
|
||||
"default-shell": "1.0.1",
|
||||
"electron-config": "0.2.1",
|
||||
"electron-is-dev": "0.1.1",
|
||||
"electron-config": "1.0.0",
|
||||
"electron-is-dev": "0.3.0",
|
||||
"electron-squirrel-startup": "1.0.0",
|
||||
"file-uri-to-path": "0.0.2",
|
||||
"gaze": "1.1.2",
|
||||
"git-describe": "3.0.2",
|
||||
"file-uri-to-path": "1.0.0",
|
||||
"git-describe": "4.0.2",
|
||||
"mkdirp": "0.5.1",
|
||||
"ms": "0.7.1",
|
||||
"node-fetch": "1.6.3",
|
||||
"node-pty": "0.6.6",
|
||||
"semver": "5.3.0",
|
||||
"shell-env": "0.2.0",
|
||||
"uuid": "3.0.0",
|
||||
"winreg": "1.2.2"
|
||||
"ms": "2.0.0",
|
||||
"node-fetch": "1.7.2",
|
||||
"node-pty": "0.7.0",
|
||||
"queue": "4.4.0",
|
||||
"semver": "5.4.1",
|
||||
"shell-env": "0.3.0",
|
||||
"uuid": "3.1.0",
|
||||
"winreg": "1.2.4"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
103
app/plugins.js
103
app/plugins.js
|
|
@ -1,43 +1,21 @@
|
|||
const {exec} = require('child_process');
|
||||
const {app, dialog} = require('electron');
|
||||
const {resolve, basename} = require('path');
|
||||
const {writeFileSync} = require('fs');
|
||||
|
||||
const {app, dialog} = require('electron');
|
||||
const {sync: mkdirpSync} = require('mkdirp');
|
||||
const Config = require('electron-config');
|
||||
const ms = require('ms');
|
||||
const shellEnv = require('shell-env');
|
||||
|
||||
const config = require('./config');
|
||||
const notify = require('./notify');
|
||||
const _keys = require('./config/keymaps');
|
||||
const {availableExtensions} = require('./plugins/extensions');
|
||||
const {install} = require('./plugins/install');
|
||||
const {plugs} = require('./config/paths');
|
||||
|
||||
// local storage
|
||||
const cache = new Config();
|
||||
|
||||
// modules path
|
||||
const path = resolve(config.getConfigDir(), '.hyper_plugins');
|
||||
const localPath = resolve(path, 'local');
|
||||
const availableExtensions = new Set([
|
||||
'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',
|
||||
'extendKeymaps'
|
||||
]);
|
||||
|
||||
// init plugin directories if not present
|
||||
mkdirpSync(path);
|
||||
mkdirpSync(localPath);
|
||||
const path = plugs.base;
|
||||
const localPath = plugs.local;
|
||||
|
||||
// caches
|
||||
let plugins = config.getPlugins();
|
||||
|
|
@ -79,17 +57,10 @@ function updatePlugins({force = false} = {}) {
|
|||
|
||||
if (err) {
|
||||
console.error(err.stack);
|
||||
if (/not a recognized/.test(err.message) || /command not found/.test(err.message)) {
|
||||
notify(
|
||||
'Error updating plugins.',
|
||||
'We could not find the `npm` command. Make sure it\'s in $PATH'
|
||||
);
|
||||
} else {
|
||||
notify(
|
||||
'Error updating plugins.',
|
||||
'Check `~/.hyper_plugins/npm-debug.log` for more information.'
|
||||
);
|
||||
}
|
||||
notify(
|
||||
'Error updating plugins.',
|
||||
err.message
|
||||
);
|
||||
} else {
|
||||
// flag successful plugin update
|
||||
cache.set('hyper.plugins', id_);
|
||||
|
|
@ -161,6 +132,10 @@ function clearCache() {
|
|||
|
||||
exports.updatePlugins = updatePlugins;
|
||||
|
||||
exports.getLoadedPluginVersions = function () {
|
||||
return modules.map(mod => ({name: mod._name, version: mod._version}));
|
||||
};
|
||||
|
||||
// we schedule the initial plugins update
|
||||
// a bit after the user launches the terminal
|
||||
// to prevent slowness
|
||||
|
|
@ -223,49 +198,6 @@ function toDependencies(plugins) {
|
|||
return obj;
|
||||
}
|
||||
|
||||
function install(fn) {
|
||||
const {shell: cfgShell, npmRegistry} = exports.getDecoratedConfig();
|
||||
|
||||
const shell = cfgShell && cfgShell !== '' ? cfgShell : undefined;
|
||||
|
||||
shellEnv(shell).then(env => {
|
||||
if (npmRegistry) {
|
||||
env.NPM_CONFIG_REGISTRY = npmRegistry;
|
||||
}
|
||||
/* eslint-disable camelcase */
|
||||
env.npm_config_runtime = 'electron';
|
||||
env.npm_config_target = process.versions.electron;
|
||||
env.npm_config_disturl = 'https://atom.io/download/atom-shell';
|
||||
/* eslint-enable camelcase */
|
||||
// Shell-specific installation commands
|
||||
const installCommands = {
|
||||
fish: 'npm prune; and npm install --production --no-shrinkwrap',
|
||||
posix: 'npm prune && npm install --production --no-shrinkwrap'
|
||||
};
|
||||
// determine the shell we're running in
|
||||
const whichShell = (typeof cfgShell === 'string' && cfgShell.match(/fish/)) ? 'fish' : 'posix';
|
||||
const execOptions = {
|
||||
cwd: path,
|
||||
env
|
||||
};
|
||||
|
||||
// https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback
|
||||
// node.js requires command line parsing should be compatible with cmd.exe on Windows, should able to accept `/d /s /c`
|
||||
// but most custom shell doesn't. Instead, falls back to default shell
|
||||
if (process.platform !== 'win32') {
|
||||
execOptions.shell = shell;
|
||||
}
|
||||
|
||||
// Use the install command that is appropriate for our shell
|
||||
exec(installCommands[whichShell], execOptions, err => {
|
||||
if (err) {
|
||||
return fn(err);
|
||||
}
|
||||
fn(null);
|
||||
});
|
||||
}).catch(fn);
|
||||
}
|
||||
|
||||
exports.subscribe = function (fn) {
|
||||
watchers.push(fn);
|
||||
return () => {
|
||||
|
|
@ -309,6 +241,13 @@ function requirePlugins() {
|
|||
|
||||
// populate the name for internal errors here
|
||||
mod._name = basename(path);
|
||||
try {
|
||||
// eslint-disable-next-line import/no-dynamic-require
|
||||
mod._version = require(resolve(path, 'package.json')).version;
|
||||
} catch (err) {
|
||||
console.warn(`No package.json found in ${path}`);
|
||||
}
|
||||
console.log(`Plugin ${mod._name} (${mod._version}) loaded.`);
|
||||
|
||||
return mod;
|
||||
} catch (err) {
|
||||
|
|
|
|||
18
app/plugins/extensions.js
Normal file
18
app/plugins/extensions.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
module.exports = {
|
||||
availableExtensions: new Set([
|
||||
'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',
|
||||
'extendKeymaps'
|
||||
])
|
||||
};
|
||||
46
app/plugins/install.js
Normal file
46
app/plugins/install.js
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
const cp = require('child_process');
|
||||
const queue = require('queue');
|
||||
const ms = require('ms');
|
||||
const {yarn, plugs} = require('../config/paths');
|
||||
|
||||
module.exports = {
|
||||
install: fn => {
|
||||
const spawnQueue = queue({concurrency: 1});
|
||||
function yarnFn(args, cb) {
|
||||
const env = {
|
||||
NODE_ENV: 'production',
|
||||
ELECTRON_RUN_AS_NODE: 'true'
|
||||
};
|
||||
spawnQueue.push(end => {
|
||||
const cmd = [process.execPath, yarn].concat(args).join(' ');
|
||||
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);
|
||||
}
|
||||
|
||||
end();
|
||||
spawnQueue.start();
|
||||
});
|
||||
});
|
||||
|
||||
spawnQueue.start();
|
||||
}
|
||||
|
||||
yarnFn(['install', '--no-emoji', '--no-lockfile', '--cache-folder', plugs.cache], err => {
|
||||
if (err) {
|
||||
return fn(err);
|
||||
}
|
||||
fn(null);
|
||||
});
|
||||
}
|
||||
};
|
||||
240
app/ui/window.js
Normal file
240
app/ui/window.js
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
const {app, BrowserWindow, shell, Menu} = require('electron');
|
||||
const {isAbsolute} = require('path');
|
||||
const {parse: parseUrl} = require('url');
|
||||
const uuid = require('uuid');
|
||||
const fileUriToPath = require('file-uri-to-path');
|
||||
const isDev = require('electron-is-dev');
|
||||
const AutoUpdater = require('../auto-updater');
|
||||
const toElectronBackgroundColor = require('../utils/to-electron-background-color');
|
||||
const {icon, cfgDir} = require('../config/paths');
|
||||
const createRPC = require('../rpc');
|
||||
const notify = require('../notify');
|
||||
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));
|
||||
const rpc = createRPC(window);
|
||||
const sessions = new Map();
|
||||
|
||||
// config changes
|
||||
const cfgUnsubscribe = app.config.subscribe(() => {
|
||||
const cfg_ = app.plugins.getDecoratedConfig();
|
||||
|
||||
// notify renderer
|
||||
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'
|
||||
);
|
||||
}
|
||||
|
||||
// update background color if necessary
|
||||
cfg = cfg_;
|
||||
});
|
||||
|
||||
rpc.on('init', () => {
|
||||
window.setBackgroundColor(toElectronBackgroundColor(cfg.backgroundColor || '#000'));
|
||||
window.show();
|
||||
|
||||
// If no callback is passed to createWindow,
|
||||
// a new session will be created by default.
|
||||
if (!fn) {
|
||||
fn = win => win.rpc.emit('termgroup add req');
|
||||
}
|
||||
|
||||
// app.windowCallback is the createWindow callback
|
||||
// that can be set before the 'ready' app event
|
||||
// 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);
|
||||
fetchNotifications(window);
|
||||
// auto updates
|
||||
if (!isDev && process.platform !== 'linux') {
|
||||
AutoUpdater(window);
|
||||
} else {
|
||||
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 initSession = (opts, fn) => {
|
||||
fn(uuid.v4(), new Session(opts));
|
||||
};
|
||||
|
||||
initSession(opts, (uid, session) => {
|
||||
sessions.set(uid, session);
|
||||
rpc.emit('session add', {
|
||||
rows: opts.rows,
|
||||
cols: opts.cols,
|
||||
uid,
|
||||
splitDirection: opts.splitDirection,
|
||||
shell: session.shell,
|
||||
pid: session.pty.pid
|
||||
});
|
||||
|
||||
session.on('data', data => {
|
||||
rpc.emit('session data', uid + data);
|
||||
});
|
||||
|
||||
session.on('exit', () => {
|
||||
rpc.emit('session exit', {uid});
|
||||
});
|
||||
});
|
||||
});
|
||||
rpc.on('exit', ({uid}) => {
|
||||
const session = sessions.get(uid);
|
||||
if (session) {
|
||||
session.exit();
|
||||
} else {
|
||||
console.log('session not found by', uid);
|
||||
}
|
||||
});
|
||||
rpc.on('unmaximize', () => {
|
||||
window.unmaximize();
|
||||
});
|
||||
rpc.on('maximize', () => {
|
||||
window.maximize();
|
||||
});
|
||||
rpc.on('minimize', () => {
|
||||
window.minimize();
|
||||
});
|
||||
rpc.on('resize', ({uid, cols, rows}) => {
|
||||
const session = sessions.get(uid);
|
||||
session.resize({cols, rows});
|
||||
});
|
||||
rpc.on('data', ({uid, data, escaped}) => {
|
||||
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
|
||||
|
||||
session.write(escapedData);
|
||||
} else {
|
||||
session.write(data);
|
||||
}
|
||||
});
|
||||
rpc.on('open external', ({url}) => {
|
||||
shell.openExternal(url);
|
||||
});
|
||||
rpc.on('open hamburger menu', ({x, y}) => {
|
||||
Menu.getApplicationMenu().popup(Math.ceil(x), Math.ceil(y));
|
||||
});
|
||||
// Same deal as above, grabbing the window titlebar when the window
|
||||
// is maximized on Windows results in unmaximize, without hitting any
|
||||
// app buttons
|
||||
for (const ev of ['maximize', 'unmaximize', 'minimize', 'restore']) {
|
||||
window.on(ev, () => rpc.emit('windowGeometry change'));
|
||||
}
|
||||
rpc.win.on('move', () => {
|
||||
rpc.emit('move');
|
||||
});
|
||||
rpc.on('close', () => {
|
||||
window.close();
|
||||
});
|
||||
const deleteSessions = () => {
|
||||
sessions.forEach((session, key) => {
|
||||
session.removeAllListeners();
|
||||
session.destroy();
|
||||
sessions.delete(key);
|
||||
});
|
||||
};
|
||||
// we reset the rpc channel only upon
|
||||
// subsequent refreshes (ie: F5)
|
||||
let i = 0;
|
||||
window.webContents.on('did-navigate', () => {
|
||||
if (i++) {
|
||||
deleteSessions();
|
||||
}
|
||||
});
|
||||
|
||||
// If file is dropped onto the terminal window, navigate event is prevented
|
||||
// and his path is added to active session.
|
||||
window.webContents.on('will-navigate', (event, url) => {
|
||||
const protocol = typeof url === 'string' && parseUrl(url).protocol;
|
||||
if (protocol === 'file:') {
|
||||
event.preventDefault();
|
||||
|
||||
const path = fileUriToPath(url);
|
||||
|
||||
rpc.emit('session data send', {data: path, escaped: true});
|
||||
} else if (protocol === 'http:' || protocol === 'https:') {
|
||||
event.preventDefault();
|
||||
rpc.emit('session data send', {data: url});
|
||||
}
|
||||
});
|
||||
|
||||
// expose internals to extension authors
|
||||
window.rpc = rpc;
|
||||
window.sessions = sessions;
|
||||
|
||||
const load = () => {
|
||||
app.plugins.onWindow(window);
|
||||
};
|
||||
|
||||
// load plugins
|
||||
load();
|
||||
|
||||
const pluginsUnsubscribe = app.plugins.subscribe(err => {
|
||||
if (!err) {
|
||||
load();
|
||||
window.webContents.send('plugins change');
|
||||
}
|
||||
});
|
||||
|
||||
// Keep track of focus time of every window, to figure out
|
||||
// which one of the existing window is the last focused.
|
||||
// Works nicely even if a window is closed and removed.
|
||||
const updateFocusTime = () => {
|
||||
window.focusTime = process.uptime();
|
||||
};
|
||||
|
||||
window.on('focus', () => {
|
||||
updateFocusTime();
|
||||
});
|
||||
|
||||
// the window can be closed by the browser process itself
|
||||
window.clean = () => {
|
||||
app.config.winRecord(window);
|
||||
rpc.destroy();
|
||||
deleteSessions();
|
||||
cfgUnsubscribe();
|
||||
pluginsUnsubscribe();
|
||||
};
|
||||
// Ensure focusTime is set on window open. The focus event doesn't
|
||||
// fire from the dock (see bug #583)
|
||||
updateFocusTime();
|
||||
|
||||
return window;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
// Packages
|
||||
const Color = require('color');
|
||||
|
||||
// returns a background color that's in hex
|
||||
|
|
@ -5,11 +6,12 @@ const Color = require('color');
|
|||
// input can be any css value (rgb, hsl, string…)
|
||||
module.exports = bgColor => {
|
||||
const color = Color(bgColor);
|
||||
|
||||
if (color.alpha() === 1) {
|
||||
return color.hexString();
|
||||
return color.hex().toString();
|
||||
}
|
||||
|
||||
// http://stackoverflow.com/a/11019879/1202488
|
||||
const alphaHex = Math.round(color.alpha() * 255).toString(16);
|
||||
return '#' + alphaHex + color.hexString().substr(1);
|
||||
return '#' + alphaHex + color.hex().toString().substr(1);
|
||||
};
|
||||
|
|
|
|||
1167
app/yarn.lock
1167
app/yarn.lock
File diff suppressed because it is too large
Load diff
|
|
@ -3,8 +3,6 @@
|
|||
environment:
|
||||
matrix:
|
||||
- platform: x64
|
||||
GH_TOKEN:
|
||||
secure: tEHrAaRFMrUzagNJsnU+6Esvaw/FdUXZKWz7a690VAy6zzEThB0lThHLFVKZrQrP
|
||||
|
||||
image: Visual Studio 2015
|
||||
|
||||
|
|
@ -12,12 +10,15 @@ init:
|
|||
- yarn config set msvs_version 2015 # we need this to build `pty.js`
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 6 x64
|
||||
- ps: Install-Product node 8 x64
|
||||
- set CI=true
|
||||
- yarn
|
||||
|
||||
build: off
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
shallow_clone: true
|
||||
|
||||
test_script:
|
||||
|
|
|
|||
3716
bin/rimraf-standalone.js
Normal file
3716
bin/rimraf-standalone.js
Normal file
File diff suppressed because it is too large
Load diff
121409
bin/yarn-standalone.js
Normal file
121409
bin/yarn-standalone.js
Normal file
File diff suppressed because one or more lines are too long
31
circle.yml
Normal file
31
circle.yml
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
machine:
|
||||
xcode:
|
||||
version: 8.2
|
||||
pre:
|
||||
- mkdir ~/.yarn-cache
|
||||
|
||||
dependencies:
|
||||
pre:
|
||||
- npm install -g yarn
|
||||
- yarn config set cache-folder ~/.yarn-cache
|
||||
cache_directories:
|
||||
- ~/.yarn-cache
|
||||
override:
|
||||
- yarn
|
||||
|
||||
test:
|
||||
override:
|
||||
- yarn test
|
||||
|
||||
deployment:
|
||||
artifacts:
|
||||
branch: /^(?!master$).*$/
|
||||
owner: zeit
|
||||
commands:
|
||||
- yarn run dist -- -p 'never'
|
||||
- cp dist/*.zip $CIRCLE_ARTIFACTS
|
||||
release:
|
||||
tag: /.*/
|
||||
owner: zeit
|
||||
commands:
|
||||
- yarn run dist
|
||||
|
|
@ -140,7 +140,7 @@ export function resizeSession(uid, cols, rows) {
|
|||
};
|
||||
}
|
||||
|
||||
export function sendSessionData(uid, data) {
|
||||
export function sendSessionData(uid, data, escaped) {
|
||||
return function (dispatch, getState) {
|
||||
dispatch({
|
||||
type: SESSION_USER_DATA,
|
||||
|
|
@ -148,7 +148,8 @@ export function sendSessionData(uid, data) {
|
|||
effect() {
|
||||
// If no uid is passed, data is sent to the active session.
|
||||
const targetUid = uid || getState().sessions.activeUid;
|
||||
rpc.emit('data', {uid: targetUid, data});
|
||||
|
||||
rpc.emit('data', {uid: targetUid, data, escaped});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -106,8 +106,8 @@ export default class Header extends Component {
|
|||
const {hambMenu, winCtrls} = this.getWindowHeaderConfig();
|
||||
const left = winCtrls === 'left';
|
||||
const maxButtonHref = this.props.maximized ?
|
||||
'./dist/assets/icons.svg#restore-window' :
|
||||
'./dist/assets/icons.svg#maximize-window';
|
||||
'./renderer/assets/icons.svg#restore-window' :
|
||||
'./renderer/assets/icons.svg#maximize-window';
|
||||
|
||||
return (<header
|
||||
className={css('header', isMac && 'headerRounded')}
|
||||
|
|
@ -126,7 +126,7 @@ export default class Header extends Component {
|
|||
className={css('shape', (left && 'hamburgerMenuRight') || 'hamburgerMenuLeft')}
|
||||
onClick={this.handleHamburgerMenuClick}
|
||||
>
|
||||
<use xlinkHref="./dist/assets/icons.svg#hamburger-menu"/>
|
||||
<use xlinkHref="./renderer/assets/icons.svg#hamburger-menu"/>
|
||||
</svg>
|
||||
}
|
||||
<span className={css('appTitle')}>{title}</span>
|
||||
|
|
@ -137,7 +137,7 @@ export default class Header extends Component {
|
|||
className={css('shape', left && 'minimizeWindowLeft')}
|
||||
onClick={this.handleMinimizeClick}
|
||||
>
|
||||
<use xlinkHref="./dist/assets/icons.svg#minimize-window"/>
|
||||
<use xlinkHref="./renderer/assets/icons.svg#minimize-window"/>
|
||||
</svg>
|
||||
<svg
|
||||
className={css('shape', left && 'maximizeWindowLeft')}
|
||||
|
|
@ -149,7 +149,7 @@ export default class Header extends Component {
|
|||
className={css('shape', 'closeWindow', left && 'closeWindowLeft')}
|
||||
onClick={this.handleCloseClick}
|
||||
>
|
||||
<use xlinkHref="./dist/assets/icons.svg#close-window"/>
|
||||
<use xlinkHref="./renderer/assets/icons.svg#close-window"/>
|
||||
</svg>
|
||||
</div>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,8 +72,8 @@ export default class Notification extends Component {
|
|||
const {backgroundColor} = this.props;
|
||||
const opacity = this.state.dismissing ? 0 : 1;
|
||||
return (<div
|
||||
style={{opacity, backgroundColor}}
|
||||
ref={this.onElement}
|
||||
style={{opacity, backgroundColor}}
|
||||
className={css('indicator')}
|
||||
>
|
||||
{ this.props.customChildrenBefore }
|
||||
|
|
|
|||
|
|
@ -47,12 +47,17 @@ export default class SplitPane extends Component {
|
|||
onDrag(ev) {
|
||||
let {sizes} = this.props;
|
||||
let sizes_;
|
||||
|
||||
if (sizes) {
|
||||
sizes_ = [].concat(sizes);
|
||||
} else {
|
||||
const total = this.props.children.length;
|
||||
sizes = sizes_ = new Array(total).fill(1 / total);
|
||||
const count = new Array(total).fill(1 / total);
|
||||
|
||||
sizes = count;
|
||||
sizes_ = count;
|
||||
}
|
||||
|
||||
const i = this.paneIndex;
|
||||
const pos = ev[this.d3];
|
||||
const d = Math.abs(this.dragPanePosition - pos) / this.panesSize;
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ export default class Tab extends Component {
|
|||
onClick={this.props.onClose}
|
||||
>
|
||||
<svg className={css('shape')}>
|
||||
<use xlinkHref="./dist/assets/icons.svg#close-tab"/>
|
||||
<use xlinkHref="./renderer/assets/icons.svg#close-tab"/>
|
||||
</svg>
|
||||
</i>
|
||||
{ this.props.customChildren }
|
||||
|
|
|
|||
|
|
@ -65,20 +65,22 @@ class Hyper extends Component {
|
|||
const borderWidth = isMac_ ? '' :
|
||||
`${maximized ? '0' : '1'}px`;
|
||||
|
||||
return (<div>
|
||||
<div
|
||||
style={{fontFamily: uiFontFamily, borderColor, borderWidth}}
|
||||
className={css('main', isMac_ && 'mainRounded')}
|
||||
return (
|
||||
<div>
|
||||
<div
|
||||
style={{fontFamily: uiFontFamily, borderColor, borderWidth}}
|
||||
className={css('main', isMac_ && 'mainRounded')}
|
||||
>
|
||||
<HeaderContainer/>
|
||||
<TermsContainer ref_={this.onTermsRef}/>
|
||||
{ this.props.customInnerChildren }
|
||||
</div>
|
||||
<HeaderContainer/>
|
||||
<TermsContainer ref_={this.onTermsRef}/>
|
||||
{ this.props.customInnerChildren }
|
||||
</div>
|
||||
|
||||
<NotificationsContainer/>
|
||||
<style dangerouslySetInnerHTML={{__html: customCSS}}/>
|
||||
{ this.props.customChildren }
|
||||
</div>);
|
||||
<NotificationsContainer/>
|
||||
<style dangerouslySetInnerHTML={{__html: customCSS}}/>
|
||||
{ this.props.customChildren }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
styles() {
|
||||
|
|
|
|||
|
|
@ -72,7 +72,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;
|
||||
|
|
@ -256,7 +256,9 @@ hterm.Keyboard.prototype.onKeyDown_ = function (e) {
|
|||
];
|
||||
if (!clearBlacklist.includes(e.code.toLowerCase()) &&
|
||||
!clearBlacklist.includes(e.key.toLowerCase())) {
|
||||
selection.clear(this.terminal);
|
||||
// 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);
|
||||
}
|
||||
|
||||
// If the `hyperCaret` was removed on `selectAll`, we need to insert it back
|
||||
|
|
@ -449,7 +451,7 @@ lib.colors.hexToRGB = function (arg) {
|
|||
')';
|
||||
}
|
||||
|
||||
if (arg instanceof Array) {
|
||||
if (Array.isArray(arg)) {
|
||||
for (let i = 0; i < arg.length; i++) {
|
||||
arg[i] = convert(arg[i]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -70,8 +70,8 @@ rpc.on('session data', d => {
|
|||
}
|
||||
});
|
||||
|
||||
rpc.on('session data send', ({uid, data}) => {
|
||||
store_.dispatch(sessionActions.sendSessionData(uid, data));
|
||||
rpc.on('session data send', ({uid, data, escaped}) => {
|
||||
store_.dispatch(sessionActions.sendSessionData(uid, data, escaped));
|
||||
});
|
||||
|
||||
rpc.on('session exit', ({uid}) => {
|
||||
|
|
|
|||
|
|
@ -73,7 +73,19 @@ const clearModulesCache = () => {
|
|||
}
|
||||
};
|
||||
|
||||
const getPluginName = path => window.require('path').basename(path);
|
||||
const pathModule = window.require('path');
|
||||
|
||||
const getPluginName = path => pathModule.basename(path);
|
||||
|
||||
const getPluginVersion = path => {
|
||||
let version = null;
|
||||
try {
|
||||
version = window.require(pathModule.resolve(path, 'package.json')).version;
|
||||
} catch (err) {
|
||||
console.warn(`No package.json found in ${path}`);
|
||||
}
|
||||
return version;
|
||||
};
|
||||
|
||||
const loadModules = () => {
|
||||
console.log('(re)loading renderer plugins');
|
||||
|
|
@ -112,6 +124,7 @@ const loadModules = () => {
|
|||
.map(path => {
|
||||
let mod;
|
||||
const pluginName = getPluginName(path);
|
||||
const pluginVersion = getPluginVersion(path);
|
||||
|
||||
// window.require allows us to ensure this doesn't get
|
||||
// in the way of our build
|
||||
|
|
@ -126,6 +139,7 @@ const loadModules = () => {
|
|||
for (const i in mod) {
|
||||
if ({}.hasOwnProperty.call(mod, i)) {
|
||||
mod[i]._pluginName = pluginName;
|
||||
mod[i]._pluginVersion = pluginVersion;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -209,6 +223,8 @@ const loadModules = () => {
|
|||
mod.onRendererWindow(window);
|
||||
}
|
||||
|
||||
console.log(`Plugin ${pluginName} (${pluginVersion}) loaded.`);
|
||||
|
||||
return mod;
|
||||
})
|
||||
.filter(mod => Boolean(mod));
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import path from 'path';
|
||||
import * as regex from './url-regex';
|
||||
|
||||
export default function isUrlCommand(shell, data) {
|
||||
const matcher = regex[shell]; // eslint-disable-line import/namespace
|
||||
const matcher = regex[path.parse(shell).name]; // eslint-disable-line import/namespace
|
||||
if (undefined === matcher || !data) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# MIT License
|
||||
|
||||
Copyright (c) 2016 Zeit, Inc.
|
||||
Copyright (c) 2017 Zeit, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
|||
77
package.json
77
package.json
|
|
@ -1,19 +1,19 @@
|
|||
{
|
||||
"repository": "zeit/hyper",
|
||||
"scripts": {
|
||||
"start": "echo 'please run `npm run dev` in one tab and then `npm run app` in another one'",
|
||||
"start": "echo 'please run `yarn run dev` in one tab and then `yarn run app` in another one'",
|
||||
"app": "electron app",
|
||||
"dev": "webpack -w",
|
||||
"build": "cross-env NODE_ENV=production webpack",
|
||||
"lint": "eslint .",
|
||||
"test": "npm run lint",
|
||||
"test": "yarn run lint",
|
||||
"test:unit": "ava test/unit",
|
||||
"test:unit:watch": "npm run test:unit -- --watch",
|
||||
"prepush": "npm test",
|
||||
"postinstall": "install-app-deps && npm run rebuild-node-pty",
|
||||
"test:unit:watch": "yarn run test:unit -- --watch",
|
||||
"prepush": "yarn test",
|
||||
"postinstall": "electron-builder install-app-deps && yarn run rebuild-node-pty",
|
||||
"rebuild-node-pty": "electron-rebuild -f -w app/node_modules/node-pty -m app",
|
||||
"dist": "npm run build && cross-env BABEL_ENV=production babel --out-file app/dist/bundle.js --no-comments --minified app/dist/bundle.js && build",
|
||||
"clean": "npm cache clear && rm -rf node_modules && rm -rf app/node_modules && rm -rf app/dist"
|
||||
"dist": "yarn run build && cross-env BABEL_ENV=production babel --out-file app/renderer/bundle.js --no-comments --minified app/renderer/bundle.js && build",
|
||||
"clean": "node ./bin/rimraf-standalone.js node_modules && node ./bin/rimraf-standalone.js ./app/node_modules && node ./bin/rimraf-standalone.js ./app/renderer"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"plugins": [
|
||||
|
|
@ -86,27 +86,24 @@
|
|||
},
|
||||
"build": {
|
||||
"appId": "co.zeit.hyper",
|
||||
"asar": true,
|
||||
"extraResources": "./bin/yarn-standalone.js",
|
||||
"linux": {
|
||||
"target": [
|
||||
{
|
||||
"target": "deb",
|
||||
"arch": [
|
||||
"ia32",
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "AppImage",
|
||||
"arch": [
|
||||
"ia32",
|
||||
"x64"
|
||||
]
|
||||
},
|
||||
{
|
||||
"target": "rpm",
|
||||
"arch": [
|
||||
"ia32",
|
||||
"x64"
|
||||
]
|
||||
}
|
||||
|
|
@ -129,47 +126,47 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"aphrodite-simple": "0.4.1",
|
||||
"color": "0.11.4",
|
||||
"css-loader": "^0.28.4",
|
||||
"color": "2.0.0",
|
||||
"css-loader": "0.28.4",
|
||||
"hterm-umdjs": "1.1.3",
|
||||
"json-loader": "0.5.4",
|
||||
"json-loader": "0.5.7",
|
||||
"mousetrap": "1.6.1",
|
||||
"ms": "0.7.2",
|
||||
"ms": "2.0.0",
|
||||
"php-escape-shell": "1.0.0",
|
||||
"react": "15.5.4",
|
||||
"react": "15.6.1",
|
||||
"react-deep-force-update": "2.0.1",
|
||||
"react-dom": "15.5.4",
|
||||
"react-redux": "5.0.5",
|
||||
"redux": "3.6.0",
|
||||
"react-dom": "15.6.1",
|
||||
"react-redux": "5.0.6",
|
||||
"redux": "3.7.2",
|
||||
"redux-thunk": "2.2.0",
|
||||
"reselect": "3.0.1",
|
||||
"runes": "0.4.0",
|
||||
"seamless-immutable": "6.1.3",
|
||||
"semver": "5.3.0",
|
||||
"style-loader": "^0.18.2",
|
||||
"uuid": "3.0.1",
|
||||
"runes": "0.4.2",
|
||||
"seamless-immutable": "7.1.2",
|
||||
"semver": "5.4.1",
|
||||
"uuid": "3.1.0",
|
||||
"xterm": "2.8.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"asar": "0.13.0",
|
||||
"ava": "0.17.0",
|
||||
"babel-cli": "6.24.1",
|
||||
"babel-core": "6.24.1",
|
||||
"babel-loader": "7.0.0",
|
||||
"babel-preset-babili": "0.1.2",
|
||||
"ava": "0.22.0",
|
||||
"babel-cli": "6.26.0",
|
||||
"babel-core": "6.26.0",
|
||||
"babel-loader": "7.1.2",
|
||||
"babel-preset-babili": "0.1.4",
|
||||
"babel-preset-react": "6.24.1",
|
||||
"copy-webpack-plugin": "4.0.1",
|
||||
"cross-env": "5.0.0",
|
||||
"electron": "1.6.11",
|
||||
"electron-builder": "18.3.5",
|
||||
"electron-builder-squirrel-windows": "18.3.0",
|
||||
"cross-env": "5.0.5",
|
||||
"electron": "1.7.5",
|
||||
"electron-builder": "19.27.3",
|
||||
"electron-builder-squirrel-windows": "19.27.3",
|
||||
"electron-devtools-installer": "2.2.0",
|
||||
"electron-rebuild": "1.5.11",
|
||||
"eslint": "^3.19.0",
|
||||
"eslint-plugin-react": "^7.0.1",
|
||||
"husky": "0.13.4",
|
||||
"electron-rebuild": "1.6.0",
|
||||
"eslint": "3.19.0",
|
||||
"eslint-plugin-react": "7.3.0",
|
||||
"husky": "0.14.3",
|
||||
"node-gyp": "3.6.2",
|
||||
"redux-logger": "3.0.6",
|
||||
"spectron": "3.6.4",
|
||||
"webpack": "2.6.1"
|
||||
"spectron": "3.7.2",
|
||||
"style-loader": "0.18.2",
|
||||
"webpack": "3.5.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
22
readme.md
22
readme.md
|
|
@ -1,7 +1,8 @@
|
|||

|
||||
|
||||
[](https://travis-ci.org/zeit/hyper)
|
||||
[](https://ci.appveyor.com/project/appveyor-zeit/hyper/branch/master)
|
||||
[](https://circleci.com/gh/zeit/hyper)
|
||||
[](https://ci.appveyor.com/project/zeit/hyper)
|
||||
[](https://travis-ci.org/zeit/hyper)
|
||||
[](https://zeit.chat/)
|
||||
[](https://changelog.com/213)
|
||||
[](https://github.com/sindresorhus/xo)
|
||||
|
|
@ -20,17 +21,19 @@ brew cask install hyper
|
|||
```
|
||||
|
||||
If you're on windows, you can use [chocolatey](https://chocolatey.org/) to install the app by running the following command (package information can be found [here](https://chocolatey.org/packages/hyper/)):
|
||||
|
||||
```bash
|
||||
choco install hyper
|
||||
```
|
||||
Note: the version of hyper available from chocolatey may not be the latest. Consider using the direct download link, https://hyper-updates.now.sh/download/win
|
||||
|
||||
**Note:** The version available on [Homebrew Cask](https://caskroom.github.io/) or [Chocolatey](https://chocolatey.org) may not be the latest. Please consider downloading it from [here](https://hyper.is/#installation) if that's the case.
|
||||
|
||||
## Contribute
|
||||
|
||||
1. Install the dependencies
|
||||
* If you are running Linux, install `icnsutils`, `graphicsmagick`, `xz-utils` and `rpm`
|
||||
* If you are running Windows, install `windows-build-tools` with `yarn global add windows-build-tools`.
|
||||
* If you are running Linux, install `icnsutils`, `graphicsmagick`, `xz-utils`, `npm`, `rpm` and `yarn`
|
||||
* If you are running Windows, install `yarn` (If you use the MSI installer you will need to install npm), and `windows-build-tools` with `yarn global add windows-build-tools`.
|
||||
* Yarn installation instructions: https://yarnpkg.com/en/docs/install
|
||||
2. [Fork](https://help.github.com/articles/fork-a-repo/) this repository to your own GitHub account and then [clone](https://help.github.com/articles/cloning-a-repository/) it to your local device
|
||||
3. Install the dependencies: `yarn`
|
||||
4. Build the code and watch for changes: `yarn run dev`
|
||||
|
|
@ -44,7 +47,9 @@ yarn run dist
|
|||
|
||||
After that, you'll see the binary in the `./dist` folder!
|
||||
|
||||
### node-pty issues
|
||||
#### Known issues that can happen during development
|
||||
|
||||
##### Error building `node-pty`
|
||||
|
||||
If after building during development you get an alert dialog related to `node-pty` issues,
|
||||
make sure its build process is working correctly by running `yarn run rebuild-node-pty`.
|
||||
|
|
@ -52,6 +57,11 @@ make sure its build process is working correctly by running `yarn run rebuild-no
|
|||
If you're on macOS, this typically is related to Xcode issues (like not having agreed
|
||||
to the Terms of Service by running `sudo xcodebuild` after a fresh Xcode installation).
|
||||
|
||||
##### Error with `codesign` on macOS when running `yarn run dist`
|
||||
|
||||
If you have issues in the `codesign` step when running `yarn run dist` on macOS, you can temporarily disable code signing locally by setting
|
||||
`export CSC_IDENTITY_AUTO_DISCOVERY=false` for the current terminal session.
|
||||
|
||||
## Related Repositories
|
||||
|
||||
- [Art](https://github.com/zeit/art/tree/master/hyper)
|
||||
|
|
|
|||
|
|
@ -7,14 +7,17 @@ const nodeEnv = process.env.NODE_ENV || 'development';
|
|||
const isProd = nodeEnv === 'production';
|
||||
|
||||
module.exports = {
|
||||
resolve: {
|
||||
extensions: ['.js', '.jsx']
|
||||
},
|
||||
devtool: isProd ? 'hidden-source-map' : 'cheap-module-source-map',
|
||||
entry: './lib/index.js',
|
||||
output: {
|
||||
path: path.join(__dirname, 'app', 'dist'),
|
||||
path: path.join(__dirname, 'app', 'renderer'),
|
||||
filename: 'bundle.js'
|
||||
},
|
||||
module: {
|
||||
loaders: [
|
||||
rules: [
|
||||
{
|
||||
test: /\.(js|jsx)$/,
|
||||
exclude: /node_modules/,
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
|
||||
0.3.1 / 2016-07-04
|
||||
==================
|
||||
|
||||
* Added changelog for `0.3.0` release
|
||||
* Added changelog for `0.3.1` release
|
||||
|
||||
0.2.1 / 2016-07-03
|
||||
==================
|
||||
|
||||
* Added changelog for `0.2.1` release
|
||||
* Reduce caching interval
|
||||
* Rename `data.json` to `updates.json`
|
||||
|
||||
0.2.0 / 2016-07-03
|
||||
==================
|
||||
|
||||
* Added changelog for `0.2.0` release
|
||||
* Increase `line-height` of `<ul>`
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
# MIT License
|
||||
|
||||
Copyright (c) 2016 Zeit, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -287,6 +287,10 @@
|
|||
#content h3:before {
|
||||
content: '### ';
|
||||
}
|
||||
|
||||
#content h4:before {
|
||||
content: '#### ';
|
||||
}
|
||||
|
||||
#content code {
|
||||
font: 12px Menlo, "DejaVu Sans Mono", "Lucida Console", monospace;
|
||||
|
|
@ -604,37 +608,37 @@
|
|||
<td style="width: 33.333%">64-bit</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>macOS</b> (.dmg)</td>
|
||||
<td><b>macOS</b> (.app)</td>
|
||||
<td id="td-mac-os" colspan="2">
|
||||
<a href="https://hyper-latest.now.sh/latest/dmg">DOWNLOAD <img src="static/download-icon.svg"/></a>
|
||||
<a href="https://releases.hyper.is/download/mac">DOWNLOAD <img src="static/download-icon.svg"/></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Windows</b> (.exe)</td>
|
||||
<td class="soon">COMING SOON</td>
|
||||
<td id="td-win">
|
||||
<a href="https://hyper-latest.now.sh/latest/exe">DOWNLOAD <img src="static/download-icon.svg"/></a>
|
||||
<a href="https://releases.hyper.is/download/win">DOWNLOAD <img src="static/download-icon.svg"/></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Debian</b> (.deb)</td>
|
||||
<td class="soon">COMING SOON</td>
|
||||
<td id="td-debian">
|
||||
<a href="https://hyper-latest.now.sh/latest/deb">DOWNLOAD <img src="static/download-icon.svg"/></a>
|
||||
<a href="https://releases.hyper.is/download/deb">DOWNLOAD <img src="static/download-icon.svg"/></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Fedora</b> (.rpm)</td>
|
||||
<td class="soon">COMING SOON</td>
|
||||
<td id="td-fedora">
|
||||
<a href="https://hyper-latest.now.sh/latest/rpm">DOWNLOAD <img src="static/download-icon.svg"/></a>
|
||||
<a href="https://releases.hyper.is/download/rpm">DOWNLOAD <img src="static/download-icon.svg"/></a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><b>Other Linux distros</b> (.AppImage)</td>
|
||||
<td class="soon">COMING SOON</td>
|
||||
<td id="td-appimage" colspan="2">
|
||||
<a href="https://hyper-latest.now.sh/latest/AppImage">DOWNLOAD <img src="static/download-icon.svg"/></a>
|
||||
<a href="https://releases.hyper.is/download/AppImage">DOWNLOAD <img src="static/download-icon.svg"/></a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
|
@ -678,6 +682,23 @@
|
|||
<a href="https://github.com/bnb/awesome-hyper">Awesome Hyper</a>
|
||||
for a curated list of plugins and resources.</p>
|
||||
|
||||
<h2 id="keymaps"><a href="#keymaps">Keymaps</a></h2>
|
||||
|
||||
<P> All command keys can be changed. In order to change them, edit
|
||||
<code>~/.hyper.js</code> and add your desired change to <code>keymaps</code>.
|
||||
</p>
|
||||
<p> Then Hyper will change the default with your custom change.</p>
|
||||
<p> Example: <code>'window:devtools': 'Cmd+Alt+O'</code> <p>
|
||||
<pre><code>module.exports = {
|
||||
config: { /*... */ },
|
||||
|
||||
keymaps: {
|
||||
'window:devtools': 'cmd+alt+o'
|
||||
}
|
||||
|
||||
};</code></pre>
|
||||
|
||||
|
||||
<h2 id="cfg"><a href="#cfg">Configuration</a></h2>
|
||||
|
||||
<p>The <code>config</code> object seen above in <code>~/.hyper.js</code>
|
||||
|
|
@ -1200,6 +1221,9 @@
|
|||
<p>The user can hot-load and hot-reload plugins by pressing
|
||||
Command + R (refresh). Please strive to make plugins that don't
|
||||
require a complete restart of the application to work.</p>
|
||||
|
||||
<h4>Notice</h4>
|
||||
<p>Plugins affecting the `BrowserWindow` will the effect on new windows after hot-reload.</p>
|
||||
|
||||
<p>In the future we might do this automatically.</p>
|
||||
|
||||
|
|
@ -1598,27 +1622,27 @@ exports.mapTermsState = (state, map) => {
|
|||
var downloadButtonText = document.getElementById('download-button-text');
|
||||
|
||||
if (/Mac/.test(userAgent) && !/iPhone/.test(userAgent) && !/iPad/.test(userAgent)) {
|
||||
downloadButton.setAttribute('href', 'https://hyper-updates.now.sh/download/mac');
|
||||
downloadButton.setAttribute('href', 'https://releases.hyper.is/download/mac');
|
||||
downloadButtonText.innerHTML += ' for <strong>macOS</strong>';
|
||||
document.getElementById('td-mac-os').classList.add('highlighted');
|
||||
document.getElementById('mac-os-icon').classList.remove('is-hidden');
|
||||
} else if (/Windows/.test(userAgent)) {
|
||||
downloadButton.setAttribute('href', 'https://hyper-updates.now.sh/download/win');
|
||||
downloadButton.setAttribute('href', 'https://releases.hyper.is/download/win');
|
||||
downloadButtonText.innerHTML += ' for <strong>Windows</strong>';
|
||||
document.getElementById('td-win').classList.add('highlighted');
|
||||
document.getElementById('windows-icon').classList.remove('is-hidden');
|
||||
} else if (/Linux/.test(userAgent) && !/Android/.test(userAgent)) {
|
||||
document.getElementById('linux-icon').classList.remove('is-hidden');
|
||||
if (/Ubuntu/.test(userAgent)) { // needs to be improved with other debian distros
|
||||
downloadButton.setAttribute('href', 'https://hyper-updates.now.sh/download/linux_deb');
|
||||
downloadButton.setAttribute('href', 'https://releases.hyper.is/download/deb');
|
||||
downloadButtonText.innerHTML += ' for <strong>Debian</strong>';
|
||||
document.getElementById('td-debian').classList.add('highlighted');
|
||||
} else if (/Fedora/.test(userAgent)) {
|
||||
downloadButton.setAttribute('href', 'https://hyper-updates.now.sh/download/linux_rpm');
|
||||
downloadButton.setAttribute('href', 'https://releases.hyper.is/download/rpm');
|
||||
downloadButtonText.innerHTML += ' for <strong>Fedora</strong>';
|
||||
document.getElementById('td-fedora').classList.add('highlighted');
|
||||
} else {
|
||||
downloadButton.setAttribute('href', 'https://hyper-latest.now.sh/latest/AppImage');
|
||||
downloadButton.setAttribute('href', 'https://releases.hyper.is/download/AppImage');
|
||||
downloadButtonText.innerHTML += ' for <strong>Linux</strong>';
|
||||
document.getElementById('td-appimage').classList.add('highlighted');
|
||||
}
|
||||
|
|
|
|||
1196
website/package-lock.json
generated
Normal file
1196
website/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"name": "hyper-website",
|
||||
"name": "website",
|
||||
"version": "0.0.1",
|
||||
"description": "The official website for hyper.app",
|
||||
"dependencies": {
|
||||
|
|
@ -7,5 +7,8 @@
|
|||
},
|
||||
"scripts": {
|
||||
"start": "serve . -c 5"
|
||||
},
|
||||
"now": {
|
||||
"alias": "hyper.is"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue