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:
CHaBou 2017-09-03 22:04:03 +02:00
commit 944f9d7e89
No known key found for this signature in database
GPG key ID: EF8D073B729A0B33
52 changed files with 129442 additions and 2306 deletions

View file

@ -1,6 +1,8 @@
build build
app/dist app/renderer
app/static app/static
app/bin
app/node_modules
assets assets
website website
node_modules bin

1
.gitignore vendored
View file

@ -1,5 +1,6 @@
# build output # build output
dist dist
app/renderer
# dependencies # dependencies
node_modules node_modules

1
.npmrc
View file

@ -1 +0,0 @@
save-exact=true

View file

@ -5,16 +5,16 @@ language: node_js
matrix: matrix:
include: include:
- os: osx
node_js: 7.4
- os: linux - os: linux
node_js: 7.4 node_js: 8.4.0
env: CC=clang CXX=clang++ npm_config_clang=1 env: CC=clang CXX=clang++ npm_config_clang=1
compiler: clang compiler: clang
addons: addons:
apt: apt:
packages: packages:
- gcc-multilib
- g++-multilib
- libgnome-keyring-dev - libgnome-keyring-dev
- icnsutils - icnsutils
- graphicsmagick - graphicsmagick
@ -24,11 +24,10 @@ addons:
- snapd - snapd
before_install: 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" == "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 - npm install -g yarn
- 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 cache: yarn
install: install:
- yarn - yarn

1
.yarnrc Normal file
View file

@ -0,0 +1 @@
save-prefix false

View file

@ -1 +0,0 @@
save-exact=true

View file

@ -1,15 +1,15 @@
// Packages
const {autoUpdater} = require('electron'); const {autoUpdater} = require('electron');
const ms = require('ms'); const ms = require('ms');
const retry = require('async-retry');
// Utilities
const notify = require('./notify'); // eslint-disable-line no-unused-vars const notify = require('./notify'); // eslint-disable-line no-unused-vars
const {version} = require('./package'); 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; let isInit = false;
function init() { function init() {
@ -17,7 +17,28 @@ function init() {
console.error('Error fetching updates', msg + ' (' + err.stack + ')'); 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(() => { setTimeout(() => {
autoUpdater.checkForUpdates(); autoUpdater.checkForUpdates();

View file

@ -1,4 +1,4 @@
const gaze = require('gaze'); const chokidar = require('chokidar');
const notify = require('./notify'); const notify = require('./notify');
const _import = require('./config/import'); const _import = require('./config/import');
const _openConfig = require('./config/open'); const _openConfig = require('./config/open');
@ -10,20 +10,23 @@ const watchers = [];
// https://github.com/zeit/hyper/pull/1772 // https://github.com/zeit/hyper/pull/1772
const watchCfg = process.platform === 'win32' ? {interval: 2000} : {}; const watchCfg = process.platform === 'win32' ? {interval: 2000} : {};
let cfg = {}; let cfg = {};
let _watcher;
const _watch = function () { const _watch = function () {
gaze(cfgPath, watchCfg, function (err) { if (_watcher) {
if (err) { return _watcher;
throw err; }
}
this.on('changed', () => { _watcher = chokidar.watch(cfgPath, watchCfg);
cfg = _import();
notify('Configuration updated', 'Hyper configuration reloaded!'); _watcher.on('change', () => {
watchers.forEach(fn => fn()); cfg = _import();
}); notify('Configuration updated', 'Hyper configuration reloaded!');
this.on('error', () => { watchers.forEach(fn => fn());
// Ignore file watching errors });
});
_watcher.on('error', error => {
console.error('error watching config', error);
}); });
}; };

View file

@ -4,6 +4,10 @@
module.exports = { module.exports = {
config: { 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 // default font size in pixels for all tabs
fontSize: 12, fontSize: 12,
@ -116,5 +120,10 @@ module.exports = {
// in development, you can create a directory under // in development, you can create a directory under
// `~/.hyper_plugins/local/` and include it here // `~/.hyper_plugins/local/` and include it here
// to load it and avoid it being `npm install`ed // to load it and avoid it being `npm install`ed
localPlugins: [] localPlugins: [],
keymaps: {
// Example
// 'window:devtools': 'cmd+alt+o',
}
}; };

View file

@ -1,5 +1,6 @@
const {writeFileSync, readFileSync} = require('fs'); 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 _init = require('./init');
const _keymaps = require('./keymaps'); const _keymaps = require('./keymaps');
@ -15,13 +16,17 @@ const _write = function (path, data) {
}; };
const _importConf = function () { const _importConf = function () {
// init plugin directories if not present
mkdirpSync(plugs.base);
mkdirpSync(plugs.local);
try { try {
const _defaultCfg = readFileSync(defaultCfg, 'utf8'); const _defaultCfg = readFileSync(defaultCfg, 'utf8');
try { try {
const _cfgPath = readFileSync(cfgPath, 'utf8'); const _cfgPath = readFileSync(cfgPath, 'utf8');
return {userCfg: _cfgPath, defaultCfg: _defaultCfg}; return {userCfg: _cfgPath, defaultCfg: _defaultCfg};
} catch (err) { } catch (err) {
_write(cfgPath, defaultCfg); _write(cfgPath, _defaultCfg);
return {userCfg: {}, defaultCfg: _defaultCfg}; return {userCfg: {}, defaultCfg: _defaultCfg};
} }
} catch (err) { } catch (err) {

View file

@ -16,7 +16,6 @@ const _syntaxValidation = function (cfg) {
} catch (err) { } catch (err) {
notify(`Error loading config: ${err.name}, see DevTools for more info`); notify(`Error loading config: ${err.name}, see DevTools for more info`);
console.error('Error loading config:', err); console.error('Error loading config:', err);
return;
} }
}; };
@ -36,6 +35,9 @@ const _init = function (cfg) {
notify('Error reading configuration: `config` key is missing'); notify('Error reading configuration: `config` key is missing');
return _extractDefault(cfg.defaultCfg); 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 _cfg;
} }
return _extractDefault(cfg.defaultCfg); return _extractDefault(cfg.defaultCfg);

View file

@ -4,7 +4,56 @@ const {cfgPath} = require('./paths');
module.exports = () => Promise.resolve(shell.openItem(cfgPath)); module.exports = () => Promise.resolve(shell.openItem(cfgPath));
if (process.platform === 'win32') { 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. // This mimics shell.openItem, true if it worked, false if not.
const openNotepad = file => new Promise(resolve => { const openNotepad = file => new Promise(resolve => {
@ -13,21 +62,16 @@ if (process.platform === 'win32') {
}); });
}); });
// Windows opens .js files with WScript.exe by default module.exports = () => hasDefaultSet()
// If the user hasn't set up an editor for .js files, we fallback to notepad. .then(yes => {
const canOpenNative = () => new Promise((resolve, reject) => { if (yes) {
exec('ftype JSFile', (error, stdout) => { return shell.openItem(cfgPath);
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);
} }
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));
} }

View file

@ -15,6 +15,14 @@ const devDir = resolve(__dirname, '../..');
const devCfg = join(devDir, cfgFile); const devCfg = join(devDir, cfgFile);
const defaultCfg = resolve(__dirname, defaultCfgFile); 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 icon = resolve(__dirname, '../static/icon.png');
const keymapPath = resolve(__dirname, '../keymaps'); const keymapPath = resolve(__dirname, '../keymaps');
@ -44,5 +52,5 @@ if (isDev) {
} }
module.exports = { module.exports = {
cfgDir, cfgPath, cfgFile, defaultCfg, icon, defaultPlatformKeyPath cfgDir, cfgPath, cfgFile, defaultCfg, icon, defaultPlatformKeyPath, plugs, yarn
}; };

View file

@ -31,7 +31,7 @@
<div id="mount"></div> <div id="mount"></div>
<script>start = performance.now();</script> <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> <script>console.log('total init time', performance.now() - start);</script>
</body> </body>
</html> </html>

View file

@ -9,6 +9,19 @@ if (['--help', '-v', '--version'].includes(process.argv[1])) {
process.exit(); 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 // handle startup squirrel events
if (process.platform === 'win32') { if (process.platform === 'win32') {
// eslint-disable-next-line import/order // eslint-disable-next-line import/order
@ -18,50 +31,36 @@ if (process.platform === 'win32') {
case '--squirrel-install': case '--squirrel-install':
case '--squirrel-updated': case '--squirrel-updated':
systemContextMenu.add(() => { systemContextMenu.add(() => {
// eslint-disable-next-line curly, unicorn/no-process-exit checkSquirrel();
if (require('electron-squirrel-startup')) process.exit();
}); });
break; break;
case '--squirrel-uninstall': case '--squirrel-uninstall':
systemContextMenu.remove(() => { systemContextMenu.remove(() => {
// eslint-disable-next-line curly, unicorn/no-process-exit checkSquirrel();
if (require('electron-squirrel-startup')) process.exit();
}); });
break; break;
default: default:
// eslint-disable-next-line curly, unicorn/no-process-exit checkSquirrel();
if (require('electron-squirrel-startup')) process.exit();
} }
} }
// Native // Native
const {resolve, isAbsolute} = require('path'); const {resolve} = require('path');
const {homedir} = require('os');
// Packages // Packages
const {parse: parseUrl} = require('url'); const {app, BrowserWindow, Menu} = require('electron');
const {app, BrowserWindow, shell, Menu} = require('electron');
const {gitDescribe} = require('git-describe'); const {gitDescribe} = require('git-describe');
const uuid = require('uuid');
const fileUriToPath = require('file-uri-to-path');
const isDev = require('electron-is-dev'); 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 AppMenu = require('./menus/menu');
const createRPC = require('./rpc');
const notify = require('./notify');
const fetchNotifications = require('./notifications');
const config = require('./config'); const config = require('./config');
app.commandLine.appendSwitch('js-flags', '--harmony-async-await');
// set up config // set up config
config.setup(); config.setup();
const plugins = require('./plugins'); const plugins = require('./plugins');
const Session = require('./session');
const Window = require('./ui/window');
const windowSet = new Set([]); const windowSet = new Set([]);
@ -103,7 +102,7 @@ console.log('electron will open', url);
app.on('ready', () => installDevExtensions(isDev).then(() => { app.on('ready', () => installDevExtensions(isDev).then(() => {
function createWindow(fn, options = {}) { function createWindow(fn, options = {}) {
let cfg = plugins.getDecoratedConfig(); const cfg = plugins.getDecoratedConfig();
const winSet = config.getWin(); const winSet = config.getWin();
let [startX, startY] = winSet.position; let [startX, startY] = winSet.position;
@ -139,239 +138,23 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
} }
} }
const browserDefaults = { const hwin = new Window({width, height, x: startX, y: startY}, cfg, fn);
width, windowSet.add(hwin);
height, hwin.loadURL(url);
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();
// the window can be closed by the browser process itself // the window can be closed by the browser process itself
win.on('close', () => { hwin.on('close', () => {
config.winRecord(win); hwin.clean();
windowSet.delete(win); windowSet.delete(hwin);
rpc.destroy();
deleteSessions();
cfgUnsubscribe();
pluginsUnsubscribe();
}); });
// Same deal as above, grabbing the window titlebar when the window hwin.on('closed', () => {
// 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', () => {
if (process.platform !== 'darwin' && windowSet.size === 0) { if (process.platform !== 'darwin' && windowSet.size === 0) {
app.quit(); app.quit();
} }
}); });
return hwin;
} }
// when opening create a new window // when opening create a new window
@ -393,8 +176,9 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
const menu = plugins.decorateMenu( const menu = plugins.decorateMenu(
AppMenu(createWindow, () => { AppMenu(createWindow, () => {
plugins.updatePlugins({force: true}); plugins.updatePlugins({force: true});
}) },
); plugins.getLoadedPluginVersions
));
// If we're on Mac make a Dock Menu // If we're on Mac make a Dock Menu
if (process.platform === 'darwin') { if (process.platform === 'darwin') {
@ -424,10 +208,6 @@ app.on('ready', () => installDevExtensions(isDev).then(() => {
console.error('Error while loading devtools extensions', err); console.error('Error while loading devtools extensions', err);
})); }));
function initSession(opts, fn) {
fn(uuid.v4(), new Session(opts));
}
app.on('open-file', (event, path) => { app.on('open-file', (event, path) => {
const lastWindow = app.getLastFocusedWindow(); const lastWindow = app.getLastFocusedWindow();
const callback = win => win.rpc.emit('open file', {path}); const callback = win => win.rpc.emit('open file', {path});

View file

@ -18,7 +18,7 @@
"pane:prev": "ctrl+pagedown", "pane:prev": "ctrl+pagedown",
"pane:splitVertical": "ctrl+shift+d", "pane:splitVertical": "ctrl+shift+d",
"pane:splitHorizontal": "ctrl+shift+e", "pane:splitHorizontal": "ctrl+shift+e",
"pane:close": "ctrl+shift+w", "pane:close": "ctrl+alt+w",
"editor:undo": "ctrl+shift+z", "editor:undo": "ctrl+shift+z",
"editor:redo": "ctrl+shift+y", "editor:redo": "ctrl+shift+y",
"editor:cut": "ctrl+shift+x", "editor:cut": "ctrl+shift+x",

View file

@ -18,7 +18,7 @@
"pane:prev": "ctrl+pagedown", "pane:prev": "ctrl+pagedown",
"pane:splitVertical": "ctrl+shift+d", "pane:splitVertical": "ctrl+shift+d",
"pane:splitHorizontal": "ctrl+shift+e", "pane:splitHorizontal": "ctrl+shift+e",
"pane:close": "ctrl+shift+w", "pane:close": "ctrl+alt+w",
"editor:undo": "ctrl+shift+z", "editor:undo": "ctrl+shift+z",
"editor:redo": "ctrl+shift+y", "editor:redo": "ctrl+shift+y",
"editor:cut": "ctrl+shift+x", "editor:cut": "ctrl+shift+x",

View file

@ -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 viewMenu = require('./menus/view');
const shellMenu = require('./menus/shell'); const shellMenu = require('./menus/shell');
const editMenu = require('./menus/edit'); const editMenu = require('./menus/edit');
@ -9,16 +12,41 @@ const windowMenu = require('./menus/window');
const helpMenu = require('./menus/help'); const helpMenu = require('./menus/help');
const darwinMenu = require('./menus/darwin'); const darwinMenu = require('./menus/darwin');
module.exports = (createWindow, updatePlugins) => { const appName = app.getName();
const commands = getKeymaps().commands; 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 = [ const menu = [
...(process.platform === 'darwin' ? [darwinMenu(commands)] : []), ...(process.platform === 'darwin' ? [darwinMenu(commands, showAbout)] : []),
shellMenu(commands, createWindow), shellMenu(commands, createWindow),
editMenu(commands), editMenu(commands),
viewMenu(commands), viewMenu(commands),
pluginsMenu(commands, updatePlugins), pluginsMenu(commands, updatePlugins),
windowMenu(commands), windowMenu(commands),
helpMenu(commands) helpMenu(commands, showAbout)
]; ];
return menu; return menu;

View file

@ -3,12 +3,15 @@
const {app} = require('electron'); const {app} = require('electron');
const {openConfig} = require('../../config'); const {openConfig} = require('../../config');
module.exports = function (commands) { module.exports = function (commands, showAbout) {
return { return {
label: `${app.getName()}`, label: `${app.getName()}`,
submenu: [ submenu: [
{ {
role: 'about' label: 'About Hyper',
click() {
showAbout();
}
}, },
{ {
type: 'separator' type: 'separator'

View file

@ -1,8 +1,7 @@
const os = require('os'); const os = require('os');
const {app, shell, dialog} = require('electron'); const {app, shell} = require('electron');
const {icon} = require('../../config/paths');
module.exports = function () { module.exports = function (commands, showAbout) {
const submenu = [ const submenu = [
{ {
label: `${app.getName()} Website`, label: `${app.getName()} Website`,
@ -31,13 +30,7 @@ module.exports = function () {
{ {
role: 'about', role: 'about',
click() { click() {
dialog.showMessageBox({ showAbout();
title: `About ${app.getName()}`,
message: `${app.getName()} ${app.getVersion()}`,
detail: 'Created by Guillermo Rauch',
icon,
buttons: []
});
} }
} }
); );

View file

@ -2,7 +2,7 @@
"name": "hyper", "name": "hyper",
"productName": "Hyper", "productName": "Hyper",
"description": "A terminal built on web technologies", "description": "A terminal built on web technologies",
"version": "1.3.3", "version": "1.4.3",
"license": "MIT", "license": "MIT",
"author": { "author": {
"name": "Zeit, Inc.", "name": "Zeit, Inc.",
@ -11,22 +11,24 @@
"repository": "zeit/hyper", "repository": "zeit/hyper",
"xo": false, "xo": false,
"dependencies": { "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", "convert-css-color-name-to-hex": "0.1.1",
"default-shell": "1.0.1", "default-shell": "1.0.1",
"electron-config": "0.2.1", "electron-config": "1.0.0",
"electron-is-dev": "0.1.1", "electron-is-dev": "0.3.0",
"electron-squirrel-startup": "1.0.0", "electron-squirrel-startup": "1.0.0",
"file-uri-to-path": "0.0.2", "file-uri-to-path": "1.0.0",
"gaze": "1.1.2", "git-describe": "4.0.2",
"git-describe": "3.0.2",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"ms": "0.7.1", "ms": "2.0.0",
"node-fetch": "1.6.3", "node-fetch": "1.7.2",
"node-pty": "0.6.6", "node-pty": "0.7.0",
"semver": "5.3.0", "queue": "4.4.0",
"shell-env": "0.2.0", "semver": "5.4.1",
"uuid": "3.0.0", "shell-env": "0.3.0",
"winreg": "1.2.2" "uuid": "3.1.0",
"winreg": "1.2.4"
} }
} }

View file

@ -1,43 +1,21 @@
const {exec} = require('child_process'); const {app, dialog} = require('electron');
const {resolve, basename} = require('path'); const {resolve, basename} = require('path');
const {writeFileSync} = require('fs'); const {writeFileSync} = require('fs');
const {app, dialog} = require('electron');
const {sync: mkdirpSync} = require('mkdirp');
const Config = require('electron-config'); const Config = require('electron-config');
const ms = require('ms'); const ms = require('ms');
const shellEnv = require('shell-env');
const config = require('./config'); const config = require('./config');
const notify = require('./notify'); const notify = require('./notify');
const _keys = require('./config/keymaps'); const _keys = require('./config/keymaps');
const {availableExtensions} = require('./plugins/extensions');
const {install} = require('./plugins/install');
const {plugs} = require('./config/paths');
// local storage // local storage
const cache = new Config(); const cache = new Config();
// modules path const path = plugs.base;
const path = resolve(config.getConfigDir(), '.hyper_plugins'); const localPath = plugs.local;
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);
// caches // caches
let plugins = config.getPlugins(); let plugins = config.getPlugins();
@ -79,17 +57,10 @@ function updatePlugins({force = false} = {}) {
if (err) { if (err) {
console.error(err.stack); console.error(err.stack);
if (/not a recognized/.test(err.message) || /command not found/.test(err.message)) { notify(
notify( 'Error updating plugins.',
'Error updating plugins.', err.message
'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.'
);
}
} else { } else {
// flag successful plugin update // flag successful plugin update
cache.set('hyper.plugins', id_); cache.set('hyper.plugins', id_);
@ -161,6 +132,10 @@ function clearCache() {
exports.updatePlugins = updatePlugins; exports.updatePlugins = updatePlugins;
exports.getLoadedPluginVersions = function () {
return modules.map(mod => ({name: mod._name, version: mod._version}));
};
// we schedule the initial plugins update // we schedule the initial plugins update
// a bit after the user launches the terminal // a bit after the user launches the terminal
// to prevent slowness // to prevent slowness
@ -223,49 +198,6 @@ function toDependencies(plugins) {
return obj; 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) { exports.subscribe = function (fn) {
watchers.push(fn); watchers.push(fn);
return () => { return () => {
@ -309,6 +241,13 @@ function requirePlugins() {
// populate the name for internal errors here // 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;
} catch (err) {
console.warn(`No package.json found in ${path}`);
}
console.log(`Plugin ${mod._name} (${mod._version}) loaded.`);
return mod; return mod;
} catch (err) { } catch (err) {

18
app/plugins/extensions.js Normal file
View 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
View 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
View 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;
}
};

View file

@ -1,3 +1,4 @@
// Packages
const Color = require('color'); const Color = require('color');
// returns a background color that's in hex // 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…) // input can be any css value (rgb, hsl, string…)
module.exports = bgColor => { module.exports = bgColor => {
const color = Color(bgColor); const color = Color(bgColor);
if (color.alpha() === 1) { if (color.alpha() === 1) {
return color.hexString(); return color.hex().toString();
} }
// http://stackoverflow.com/a/11019879/1202488 // http://stackoverflow.com/a/11019879/1202488
const alphaHex = Math.round(color.alpha() * 255).toString(16); const alphaHex = Math.round(color.alpha() * 255).toString(16);
return '#' + alphaHex + color.hexString().substr(1); return '#' + alphaHex + color.hex().toString().substr(1);
}; };

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,6 @@
environment: environment:
matrix: matrix:
- platform: x64 - platform: x64
GH_TOKEN:
secure: tEHrAaRFMrUzagNJsnU+6Esvaw/FdUXZKWz7a690VAy6zzEThB0lThHLFVKZrQrP
image: Visual Studio 2015 image: Visual Studio 2015
@ -12,12 +10,15 @@ init:
- yarn config set msvs_version 2015 # we need this to build `pty.js` - yarn config set msvs_version 2015 # we need this to build `pty.js`
install: install:
- ps: Install-Product node 6 x64 - ps: Install-Product node 8 x64
- set CI=true - set CI=true
- yarn - yarn
build: off build: off
matrix:
fast_finish: true
shallow_clone: true shallow_clone: true
test_script: test_script:

3716
bin/rimraf-standalone.js Normal file

File diff suppressed because it is too large Load diff

121409
bin/yarn-standalone.js Normal file

File diff suppressed because one or more lines are too long

31
circle.yml Normal file
View 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

View file

@ -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) { return function (dispatch, getState) {
dispatch({ dispatch({
type: SESSION_USER_DATA, type: SESSION_USER_DATA,
@ -148,7 +148,8 @@ export function sendSessionData(uid, data) {
effect() { effect() {
// If no uid is passed, data is sent to the active session. // If no uid is passed, data is sent to the active session.
const targetUid = uid || getState().sessions.activeUid; const targetUid = uid || getState().sessions.activeUid;
rpc.emit('data', {uid: targetUid, data});
rpc.emit('data', {uid: targetUid, data, escaped});
} }
}); });
}; };

View file

@ -106,8 +106,8 @@ export default class Header extends Component {
const {hambMenu, winCtrls} = this.getWindowHeaderConfig(); const {hambMenu, winCtrls} = this.getWindowHeaderConfig();
const left = winCtrls === 'left'; const left = winCtrls === 'left';
const maxButtonHref = this.props.maximized ? const maxButtonHref = this.props.maximized ?
'./dist/assets/icons.svg#restore-window' : './renderer/assets/icons.svg#restore-window' :
'./dist/assets/icons.svg#maximize-window'; './renderer/assets/icons.svg#maximize-window';
return (<header return (<header
className={css('header', isMac && 'headerRounded')} className={css('header', isMac && 'headerRounded')}
@ -126,7 +126,7 @@ export default class Header extends Component {
className={css('shape', (left && 'hamburgerMenuRight') || 'hamburgerMenuLeft')} className={css('shape', (left && 'hamburgerMenuRight') || 'hamburgerMenuLeft')}
onClick={this.handleHamburgerMenuClick} onClick={this.handleHamburgerMenuClick}
> >
<use xlinkHref="./dist/assets/icons.svg#hamburger-menu"/> <use xlinkHref="./renderer/assets/icons.svg#hamburger-menu"/>
</svg> </svg>
} }
<span className={css('appTitle')}>{title}</span> <span className={css('appTitle')}>{title}</span>
@ -137,7 +137,7 @@ export default class Header extends Component {
className={css('shape', left && 'minimizeWindowLeft')} className={css('shape', left && 'minimizeWindowLeft')}
onClick={this.handleMinimizeClick} onClick={this.handleMinimizeClick}
> >
<use xlinkHref="./dist/assets/icons.svg#minimize-window"/> <use xlinkHref="./renderer/assets/icons.svg#minimize-window"/>
</svg> </svg>
<svg <svg
className={css('shape', left && 'maximizeWindowLeft')} className={css('shape', left && 'maximizeWindowLeft')}
@ -149,7 +149,7 @@ export default class Header extends Component {
className={css('shape', 'closeWindow', left && 'closeWindowLeft')} className={css('shape', 'closeWindow', left && 'closeWindowLeft')}
onClick={this.handleCloseClick} onClick={this.handleCloseClick}
> >
<use xlinkHref="./dist/assets/icons.svg#close-window"/> <use xlinkHref="./renderer/assets/icons.svg#close-window"/>
</svg> </svg>
</div> </div>
} }

View file

@ -72,8 +72,8 @@ export default class Notification extends Component {
const {backgroundColor} = this.props; const {backgroundColor} = this.props;
const opacity = this.state.dismissing ? 0 : 1; const opacity = this.state.dismissing ? 0 : 1;
return (<div return (<div
style={{opacity, backgroundColor}}
ref={this.onElement} ref={this.onElement}
style={{opacity, backgroundColor}}
className={css('indicator')} className={css('indicator')}
> >
{ this.props.customChildrenBefore } { this.props.customChildrenBefore }

View file

@ -47,12 +47,17 @@ export default class SplitPane extends Component {
onDrag(ev) { onDrag(ev) {
let {sizes} = this.props; let {sizes} = this.props;
let sizes_; let sizes_;
if (sizes) { if (sizes) {
sizes_ = [].concat(sizes); sizes_ = [].concat(sizes);
} else { } else {
const total = this.props.children.length; 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 i = this.paneIndex;
const pos = ev[this.d3]; const pos = ev[this.d3];
const d = Math.abs(this.dragPanePosition - pos) / this.panesSize; const d = Math.abs(this.dragPanePosition - pos) / this.panesSize;

View file

@ -82,7 +82,7 @@ export default class Tab extends Component {
onClick={this.props.onClose} onClick={this.props.onClose}
> >
<svg className={css('shape')}> <svg className={css('shape')}>
<use xlinkHref="./dist/assets/icons.svg#close-tab"/> <use xlinkHref="./renderer/assets/icons.svg#close-tab"/>
</svg> </svg>
</i> </i>
{ this.props.customChildren } { this.props.customChildren }

View file

@ -65,20 +65,22 @@ class Hyper extends Component {
const borderWidth = isMac_ ? '' : const borderWidth = isMac_ ? '' :
`${maximized ? '0' : '1'}px`; `${maximized ? '0' : '1'}px`;
return (<div> return (
<div <div>
style={{fontFamily: uiFontFamily, borderColor, borderWidth}} <div
className={css('main', isMac_ && 'mainRounded')} style={{fontFamily: uiFontFamily, borderColor, borderWidth}}
className={css('main', isMac_ && 'mainRounded')}
> >
<HeaderContainer/> <HeaderContainer/>
<TermsContainer ref_={this.onTermsRef}/> <TermsContainer ref_={this.onTermsRef}/>
{ this.props.customInnerChildren } { this.props.customInnerChildren }
</div> </div>
<NotificationsContainer/> <NotificationsContainer/>
<style dangerouslySetInnerHTML={{__html: customCSS}}/> <style dangerouslySetInnerHTML={{__html: customCSS}}/>
{ this.props.customChildren } { this.props.customChildren }
</div>); </div>
);
} }
styles() { styles() {

View file

@ -72,7 +72,7 @@ lib.wc.strWidth = function (str) {
if (width < 0) { if (width < 0) {
return -1; return -1;
} }
rv += width * ((codePoint <= 0xffff) ? 1 : 2); rv += width * ((codePoint <= 0xFFFF) ? 1 : 2);
} }
if (shouldCache) { if (shouldCache) {
cache[str] = rv; cache[str] = rv;
@ -256,7 +256,9 @@ hterm.Keyboard.prototype.onKeyDown_ = function (e) {
]; ];
if (!clearBlacklist.includes(e.code.toLowerCase()) && if (!clearBlacklist.includes(e.code.toLowerCase()) &&
!clearBlacklist.includes(e.key.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 // 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++) { for (let i = 0; i < arg.length; i++) {
arg[i] = convert(arg[i]); arg[i] = convert(arg[i]);
} }

View file

@ -70,8 +70,8 @@ rpc.on('session data', d => {
} }
}); });
rpc.on('session data send', ({uid, data}) => { rpc.on('session data send', ({uid, data, escaped}) => {
store_.dispatch(sessionActions.sendSessionData(uid, data)); store_.dispatch(sessionActions.sendSessionData(uid, data, escaped));
}); });
rpc.on('session exit', ({uid}) => { rpc.on('session exit', ({uid}) => {

View file

@ -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 = () => { const loadModules = () => {
console.log('(re)loading renderer plugins'); console.log('(re)loading renderer plugins');
@ -112,6 +124,7 @@ const loadModules = () => {
.map(path => { .map(path => {
let mod; let mod;
const pluginName = getPluginName(path); const pluginName = getPluginName(path);
const pluginVersion = getPluginVersion(path);
// window.require allows us to ensure this doesn't get // window.require allows us to ensure this doesn't get
// in the way of our build // in the way of our build
@ -126,6 +139,7 @@ const loadModules = () => {
for (const i in mod) { for (const i in mod) {
if ({}.hasOwnProperty.call(mod, i)) { if ({}.hasOwnProperty.call(mod, i)) {
mod[i]._pluginName = pluginName; mod[i]._pluginName = pluginName;
mod[i]._pluginVersion = pluginVersion;
} }
} }
@ -209,6 +223,8 @@ const loadModules = () => {
mod.onRendererWindow(window); mod.onRendererWindow(window);
} }
console.log(`Plugin ${pluginName} (${pluginVersion}) loaded.`);
return mod; return mod;
}) })
.filter(mod => Boolean(mod)); .filter(mod => Boolean(mod));

View file

@ -1,7 +1,8 @@
import path from 'path';
import * as regex from './url-regex'; import * as regex from './url-regex';
export default function isUrlCommand(shell, data) { 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) { if (undefined === matcher || !data) {
return null; return null;
} }

View file

@ -1,6 +1,6 @@
# MIT License # 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 Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -1,19 +1,19 @@
{ {
"repository": "zeit/hyper", "repository": "zeit/hyper",
"scripts": { "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", "app": "electron app",
"dev": "webpack -w", "dev": "webpack -w",
"build": "cross-env NODE_ENV=production webpack", "build": "cross-env NODE_ENV=production webpack",
"lint": "eslint .", "lint": "eslint .",
"test": "npm run lint", "test": "yarn run lint",
"test:unit": "ava test/unit", "test:unit": "ava test/unit",
"test:unit:watch": "npm run test:unit -- --watch", "test:unit:watch": "yarn run test:unit -- --watch",
"prepush": "npm test", "prepush": "yarn test",
"postinstall": "install-app-deps && npm run rebuild-node-pty", "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", "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", "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": "npm cache clear && rm -rf node_modules && rm -rf app/node_modules && rm -rf app/dist" "clean": "node ./bin/rimraf-standalone.js node_modules && node ./bin/rimraf-standalone.js ./app/node_modules && node ./bin/rimraf-standalone.js ./app/renderer"
}, },
"eslintConfig": { "eslintConfig": {
"plugins": [ "plugins": [
@ -86,27 +86,24 @@
}, },
"build": { "build": {
"appId": "co.zeit.hyper", "appId": "co.zeit.hyper",
"asar": true, "extraResources": "./bin/yarn-standalone.js",
"linux": { "linux": {
"target": [ "target": [
{ {
"target": "deb", "target": "deb",
"arch": [ "arch": [
"ia32",
"x64" "x64"
] ]
}, },
{ {
"target": "AppImage", "target": "AppImage",
"arch": [ "arch": [
"ia32",
"x64" "x64"
] ]
}, },
{ {
"target": "rpm", "target": "rpm",
"arch": [ "arch": [
"ia32",
"x64" "x64"
] ]
} }
@ -129,47 +126,47 @@
}, },
"dependencies": { "dependencies": {
"aphrodite-simple": "0.4.1", "aphrodite-simple": "0.4.1",
"color": "0.11.4", "color": "2.0.0",
"css-loader": "^0.28.4", "css-loader": "0.28.4",
"hterm-umdjs": "1.1.3", "hterm-umdjs": "1.1.3",
"json-loader": "0.5.4", "json-loader": "0.5.7",
"mousetrap": "1.6.1", "mousetrap": "1.6.1",
"ms": "0.7.2", "ms": "2.0.0",
"php-escape-shell": "1.0.0", "php-escape-shell": "1.0.0",
"react": "15.5.4", "react": "15.6.1",
"react-deep-force-update": "2.0.1", "react-deep-force-update": "2.0.1",
"react-dom": "15.5.4", "react-dom": "15.6.1",
"react-redux": "5.0.5", "react-redux": "5.0.6",
"redux": "3.6.0", "redux": "3.7.2",
"redux-thunk": "2.2.0", "redux-thunk": "2.2.0",
"reselect": "3.0.1", "reselect": "3.0.1",
"runes": "0.4.0", "runes": "0.4.2",
"seamless-immutable": "6.1.3", "seamless-immutable": "7.1.2",
"semver": "5.3.0", "semver": "5.4.1",
"style-loader": "^0.18.2", "uuid": "3.1.0",
"uuid": "3.0.1",
"xterm": "2.8.1" "xterm": "2.8.1"
}, },
"devDependencies": { "devDependencies": {
"asar": "0.13.0", "ava": "0.22.0",
"ava": "0.17.0", "babel-cli": "6.26.0",
"babel-cli": "6.24.1", "babel-core": "6.26.0",
"babel-core": "6.24.1", "babel-loader": "7.1.2",
"babel-loader": "7.0.0", "babel-preset-babili": "0.1.4",
"babel-preset-babili": "0.1.2",
"babel-preset-react": "6.24.1", "babel-preset-react": "6.24.1",
"copy-webpack-plugin": "4.0.1", "copy-webpack-plugin": "4.0.1",
"cross-env": "5.0.0", "cross-env": "5.0.5",
"electron": "1.6.11", "electron": "1.7.5",
"electron-builder": "18.3.5", "electron-builder": "19.27.3",
"electron-builder-squirrel-windows": "18.3.0", "electron-builder-squirrel-windows": "19.27.3",
"electron-devtools-installer": "2.2.0", "electron-devtools-installer": "2.2.0",
"electron-rebuild": "1.5.11", "electron-rebuild": "1.6.0",
"eslint": "^3.19.0", "eslint": "3.19.0",
"eslint-plugin-react": "^7.0.1", "eslint-plugin-react": "7.3.0",
"husky": "0.13.4", "husky": "0.14.3",
"node-gyp": "3.6.2",
"redux-logger": "3.0.6", "redux-logger": "3.0.6",
"spectron": "3.6.4", "spectron": "3.7.2",
"webpack": "2.6.1" "style-loader": "0.18.2",
"webpack": "3.5.5"
} }
} }

View file

@ -1,7 +1,8 @@
![](https://github.com/zeit/art/blob/525bd1bb39d97dd3b91c976106a6d5cc5766b678/hyper/repo-banner.png) ![](https://github.com/zeit/art/blob/525bd1bb39d97dd3b91c976106a6d5cc5766b678/hyper/repo-banner.png)
[![Build Status](https://travis-ci.org/zeit/hyper.svg?branch=master)](https://travis-ci.org/zeit/hyper) [![macOS CI Status](https://circleci.com/gh/zeit/hyper.svg?style=shield)](https://circleci.com/gh/zeit/hyper)
[![Build status](https://ci.appveyor.com/api/projects/status/txg5qb0x35h0h65p/branch/master?svg=true)](https://ci.appveyor.com/project/appveyor-zeit/hyper/branch/master) [![Windows CI status](https://ci.appveyor.com/api/projects/status/kqvb4oa772an58sc?svg=true)](https://ci.appveyor.com/project/zeit/hyper)
[![Linux CI status](https://travis-ci.org/zeit/hyper.svg?branch=master)](https://travis-ci.org/zeit/hyper)
[![Slack Channel](http://zeit-slackin.now.sh/badge.svg)](https://zeit.chat/) [![Slack Channel](http://zeit-slackin.now.sh/badge.svg)](https://zeit.chat/)
[![Changelog #213](https://img.shields.io/badge/changelog-%23213-lightgrey.svg)](https://changelog.com/213) [![Changelog #213](https://img.shields.io/badge/changelog-%23213-lightgrey.svg)](https://changelog.com/213)
[![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo) [![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](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/)): 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 ```bash
choco install hyper 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 ## Contribute
1. Install the dependencies 1. Install the dependencies
* If you are running Linux, install `icnsutils`, `graphicsmagick`, `xz-utils` and `rpm` * If you are running Linux, install `icnsutils`, `graphicsmagick`, `xz-utils`, `npm`, `rpm` and `yarn`
* If you are running Windows, install `windows-build-tools` with `yarn global add windows-build-tools`. * 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 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` 3. Install the dependencies: `yarn`
4. Build the code and watch for changes: `yarn run dev` 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! 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, 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`. 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 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). 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 ## Related Repositories
- [Art](https://github.com/zeit/art/tree/master/hyper) - [Art](https://github.com/zeit/art/tree/master/hyper)

View file

@ -7,14 +7,17 @@ const nodeEnv = process.env.NODE_ENV || 'development';
const isProd = nodeEnv === 'production'; const isProd = nodeEnv === 'production';
module.exports = { module.exports = {
resolve: {
extensions: ['.js', '.jsx']
},
devtool: isProd ? 'hidden-source-map' : 'cheap-module-source-map', devtool: isProd ? 'hidden-source-map' : 'cheap-module-source-map',
entry: './lib/index.js', entry: './lib/index.js',
output: { output: {
path: path.join(__dirname, 'app', 'dist'), path: path.join(__dirname, 'app', 'renderer'),
filename: 'bundle.js' filename: 'bundle.js'
}, },
module: { module: {
loaders: [ rules: [
{ {
test: /\.(js|jsx)$/, test: /\.(js|jsx)$/,
exclude: /node_modules/, exclude: /node_modules/,

View file

@ -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>`

View file

@ -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.

View file

@ -288,6 +288,10 @@
content: '### '; content: '### ';
} }
#content h4:before {
content: '#### ';
}
#content code { #content code {
font: 12px Menlo, "DejaVu Sans Mono", "Lucida Console", monospace; font: 12px Menlo, "DejaVu Sans Mono", "Lucida Console", monospace;
} }
@ -604,37 +608,37 @@
<td style="width: 33.333%">64-bit</td> <td style="width: 33.333%">64-bit</td>
</tr> </tr>
<tr> <tr>
<td><b>macOS</b> (.dmg)</td> <td><b>macOS</b> (.app)</td>
<td id="td-mac-os" colspan="2"> <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> </td>
</tr> </tr>
<tr> <tr>
<td><b>Windows</b> (.exe)</td> <td><b>Windows</b> (.exe)</td>
<td class="soon">COMING SOON</td> <td class="soon">COMING SOON</td>
<td id="td-win"> <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> </td>
</tr> </tr>
<tr> <tr>
<td><b>Debian</b> (.deb)</td> <td><b>Debian</b> (.deb)</td>
<td class="soon">COMING SOON</td> <td class="soon">COMING SOON</td>
<td id="td-debian"> <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> </td>
</tr> </tr>
<tr> <tr>
<td><b>Fedora</b> (.rpm)</td> <td><b>Fedora</b> (.rpm)</td>
<td class="soon">COMING SOON</td> <td class="soon">COMING SOON</td>
<td id="td-fedora"> <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> </td>
</tr> </tr>
<tr> <tr>
<td><b>Other Linux distros</b> (.AppImage)</td> <td><b>Other Linux distros</b> (.AppImage)</td>
<td class="soon">COMING SOON</td> <td class="soon">COMING SOON</td>
<td id="td-appimage" colspan="2"> <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> </td>
</tr> </tr>
</table> </table>
@ -678,6 +682,23 @@
<a href="https://github.com/bnb/awesome-hyper">Awesome Hyper</a> <a href="https://github.com/bnb/awesome-hyper">Awesome Hyper</a>
for a curated list of plugins and resources.</p> 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> <h2 id="cfg"><a href="#cfg">Configuration</a></h2>
<p>The <code>config</code> object seen above in <code>~/.hyper.js</code> <p>The <code>config</code> object seen above in <code>~/.hyper.js</code>
@ -1201,6 +1222,9 @@
Command + R (refresh). Please strive to make plugins that don't Command + R (refresh). Please strive to make plugins that don't
require a complete restart of the application to work.</p> 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> <p>In the future we might do this automatically.</p>
<p>When developing, you can add your plugin to <p>When developing, you can add your plugin to
@ -1598,27 +1622,27 @@ exports.mapTermsState = (state, map) => {
var downloadButtonText = document.getElementById('download-button-text'); var downloadButtonText = document.getElementById('download-button-text');
if (/Mac/.test(userAgent) && !/iPhone/.test(userAgent) && !/iPad/.test(userAgent)) { 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>'; downloadButtonText.innerHTML += ' for <strong>macOS</strong>';
document.getElementById('td-mac-os').classList.add('highlighted'); document.getElementById('td-mac-os').classList.add('highlighted');
document.getElementById('mac-os-icon').classList.remove('is-hidden'); document.getElementById('mac-os-icon').classList.remove('is-hidden');
} else if (/Windows/.test(userAgent)) { } 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>'; downloadButtonText.innerHTML += ' for <strong>Windows</strong>';
document.getElementById('td-win').classList.add('highlighted'); document.getElementById('td-win').classList.add('highlighted');
document.getElementById('windows-icon').classList.remove('is-hidden'); document.getElementById('windows-icon').classList.remove('is-hidden');
} else if (/Linux/.test(userAgent) && !/Android/.test(userAgent)) { } else if (/Linux/.test(userAgent) && !/Android/.test(userAgent)) {
document.getElementById('linux-icon').classList.remove('is-hidden'); document.getElementById('linux-icon').classList.remove('is-hidden');
if (/Ubuntu/.test(userAgent)) { // needs to be improved with other debian distros 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>'; downloadButtonText.innerHTML += ' for <strong>Debian</strong>';
document.getElementById('td-debian').classList.add('highlighted'); document.getElementById('td-debian').classList.add('highlighted');
} else if (/Fedora/.test(userAgent)) { } 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>'; downloadButtonText.innerHTML += ' for <strong>Fedora</strong>';
document.getElementById('td-fedora').classList.add('highlighted'); document.getElementById('td-fedora').classList.add('highlighted');
} else { } 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>'; downloadButtonText.innerHTML += ' for <strong>Linux</strong>';
document.getElementById('td-appimage').classList.add('highlighted'); document.getElementById('td-appimage').classList.add('highlighted');
} }

1196
website/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,5 @@
{ {
"name": "hyper-website", "name": "website",
"version": "0.0.1", "version": "0.0.1",
"description": "The official website for hyper.app", "description": "The official website for hyper.app",
"dependencies": { "dependencies": {
@ -7,5 +7,8 @@
}, },
"scripts": { "scripts": {
"start": "serve . -c 5" "start": "serve . -c 5"
},
"now": {
"alias": "hyper.is"
} }
} }

2942
yarn.lock

File diff suppressed because it is too large Load diff