20 changes: 11 additions & 9 deletions src/package.json
@@ -1,8 +1,8 @@
{
"name": "messengerfordesktop",
"productName": "Messenger for Desktop",
"version": "2.0.6",
"versionChannel": "stable",
"version": "2.0.7",
"versionChannel": "dev",
"description": "A simple & beautiful desktop client for Facebook Messenger.",
"wvUrl": "https://www.messenger.com/login",
"wvUrlWork": "https://work.facebook.com/chat",
Expand All @@ -11,23 +11,23 @@
"app-module-path": "2.2.0",
"babel-runtime": "6.23.0",
"colors": "1.1.2",
"debug": "2.6.1",
"debug": "2.6.2",
"del": "2.2.2",
"fs-extra-promise": "0.4.1",
"keymirror": "0.1.1",
"launchd.plist": "0.0.1",
"lodash.debounce": "4.0.8",
"mousetrap": "1.6.0",
"needle": "1.5.0",
"needle": "1.5.2",
"node-uuid": "1.4.7",
"promisify-es6": "1.0.2",
"raven": "1.1.3",
"raven-js": "3.12.0",
"raven": "1.1.4",
"raven-js": "3.12.1",
"semver": "5.3.0",
"spellchecker": "3.3.1",
"strip-ansi": "3.0.1",
"winreg": "1.2.3",
"yargs": "7.0.1"
"yargs": "7.0.2"
},
"devDependencies": {
"devtron": "1.4.0",
Expand Down Expand Up @@ -66,8 +66,9 @@
"homepage": "https://messengerfordesktop.com",
"repository": {
"type": "git",
"url": "https://github.com/Aluxian/Messenger-for-Desktop.git"
"url": "https://github.com/aluxian/Messenger-for-Desktop.git"
},
"changelogUrl": "https://github.com/aluxian/Messenger-for-Desktop/releases/tag/v%CURRENT_VERSION%",
"virtualUrl": "http://app.messengerfordesktop.com",
"piwik": {
"serverUrl": "{{& PIWIK_SERVER_URL }}",
Expand All @@ -87,5 +88,6 @@
"black": "Black",
"midnight": "Midnight",
"mosaic": "Mosaic"
}
},
"darkThemes": ["dark", "black", "midnight"]
}
2 changes: 0 additions & 2 deletions src/scripts/browser/application.js
Expand Up @@ -10,7 +10,6 @@ import AutoLauncher from 'browser/components/auto-launcher';
import TrayManager from 'browser/managers/tray-manager';

import AppListenersManager from 'browser/managers/app-listeners-manager';
import IpcListenersManager from 'browser/managers/ipc-listeners-manager';

class Application extends EventEmitter {

Expand Down Expand Up @@ -46,7 +45,6 @@ class Application extends EventEmitter {

// Listeners
new AppListenersManager(this.mainWindowManager, this.autoUpdateManager).set();
new IpcListenersManager(this.mainWindowManager).set();
}

}
Expand Down
Expand Up @@ -19,7 +19,7 @@ class DarwinNativeNotifier extends BaseNativeNotifier {
this.center = $.NSUserNotificationCenter('defaultUserNotificationCenter');

// Create a notifications delegate
this.Delegate = $.NSObject.extend(global.manifest.productName + 'NotificationDelegate');
this.Delegate = $.NSObject.extend(global.manifest.name + 'NotificationDelegate');
this.Delegate.addMethod('userNotificationCenter:didActivateNotification:',
[$.void, [this.Delegate, $.selector, $.id, $.id]], ::this.didActivateNotification);
this.Delegate.addMethod('userNotificationCenter:shouldPresentNotification:',
Expand Down
45 changes: 36 additions & 9 deletions src/scripts/browser/main.js
@@ -1,4 +1,4 @@
import {app, dialog} from 'electron';
import {app, dialog, session, screen} from 'electron';
import yargs from 'yargs';

import prefs from 'browser/utils/prefs';
Expand Down Expand Up @@ -112,7 +112,10 @@ if (options.portable) {
if (printVersionsAndExit()) return;
if (enforceSingleInstance()) return;
preReadySetup();
initAndLaunch();
initAndLaunch().catch((err) => {
log('init and launch failed');
logFatal(err);
});
startRepl();
})();

Expand Down Expand Up @@ -165,13 +168,6 @@ function enforceSingleInstance () {
return false;
}

function startRepl () {
if (options.repl) {
const repl = require('browser/utils/repl');
repl.createServer(options.replPort);
}
}

function preReadySetup () {
app.disableHardwareAcceleration(); // should be easier on the GPU
}
Expand All @@ -180,6 +176,7 @@ async function initAndLaunch () {
try {
await onAppReady();
await interceptHttp();
enableHighResResources();
} catch (err) {
logFatal(err);
return;
Expand All @@ -192,6 +189,13 @@ async function initAndLaunch () {
global.ready = true;
}

function startRepl () {
if (options.repl) {
const repl = require('browser/utils/repl');
repl.createServer(options.replPort);
}
}

async function onAppReady () {
return await new Promise((resolve, reject) => {
app.on('ready', () => {
Expand Down Expand Up @@ -224,3 +228,26 @@ async function interceptHttp () {
});
});
}

/**
* @source https://github.com/sindresorhus/caprine/pull/172
*/
function enableHighResResources () {
const scaleFactor = Math.max.apply(null, screen.getAllDisplays().map(scr => scr.scaleFactor));
if (scaleFactor === 1) {
return;
}

session.defaultSession.webRequest.onBeforeSendHeaders((details, callback) => {
let cookie = details.requestHeaders.Cookie;
if (cookie && details.method === 'GET' && details.url.startsWith('https://www.messenger.com/')) {
if (cookie.match(/(; )?dpr=\d/)) {
cookie = cookie.replace(/dpr=\d/, 'dpr=' + scaleFactor);
} else {
cookie = cookie + '; dpr=' + scaleFactor;
}
details.requestHeaders.Cookie = cookie;
}
callback({cancel: false, requestHeaders: details.requestHeaders});
});
}
1 change: 1 addition & 0 deletions src/scripts/browser/managers/app-listeners-manager.js
Expand Up @@ -45,6 +45,7 @@ class AppListenersManager extends EventEmitter {
log('has update downloaded, installing it before quitting');
event.preventDefault();
prefs.setSync('launch-quit', true);
prefs.setSync('notify-app-updated', true);
setTimeout(() => {
log('timeout over');
this.autoUpdateManager.quitAndInstall();
Expand Down
66 changes: 0 additions & 66 deletions src/scripts/browser/managers/ipc-listeners-manager.js

This file was deleted.

72 changes: 28 additions & 44 deletions src/scripts/browser/managers/main-window-manager.js
@@ -1,8 +1,7 @@
import {app, shell, BrowserWindow, Menu, nativeImage} from 'electron';
import {app, BrowserWindow, Menu, nativeImage} from 'electron';
import debounce from 'lodash.debounce';
import EventEmitter from 'events';

import urls from 'common/utils/urls';
import filePaths from 'common/utils/file-paths';
import platform from 'common/utils/platform';
import contextMenu from 'browser/menus/context';
Expand Down Expand Up @@ -43,9 +42,12 @@ class MainWindowManager extends EventEmitter {
const defaultOptions = {
title: this.initialTitle,
backgroundColor: '#ffffff',
autoHideMenuBar: prefs.get('auto-hide-menubar'),
acceptFirstMouse: prefs.get('accept-first-mouse'),
darkTheme: global.manifest.darkThemes.includes(prefs.get('theme')),
useContentSize: true,
minWidth: 458,
minHeight: 355,
minWidth: 500,
minHeight: 500,
show: false
};

Expand All @@ -64,9 +66,7 @@ class MainWindowManager extends EventEmitter {
this.window.webContents.setUserAgent(cleanUA);

// Bind webContents events to local methods
this.window.webContents.on('new-window', ::this.onNewWindow);
this.window.webContents.on('will-navigate', ::this.onWillNavigate);
this.window.webContents.on('context-menu', ::this.onContextMenu);

// Bind events to local methods
this.window.on('ready-to-show', ::this.onReadyToShow);
Expand Down Expand Up @@ -99,25 +99,6 @@ class MainWindowManager extends EventEmitter {
return bounds.x !== -32000 && bounds.y !== -32000;
}

/**
* Called when the 'new-window' event is emitted.
*/
onNewWindow (event, url) {
url = urls.skipFacebookRedirect(url);

if (urls.isDownloadUrl(url)) {
log('on new window, downloading', url);
event.preventDefault();
this.window.loadURL(url);
} else if (prefs.get('links-in-browser')) {
log('on new window, opening url externally', url);
event.preventDefault();
shell.openExternal(url);
} else {
log('on new window, opening url in-app', url);
}
}

/**
* Called when the 'will-navigate' event is emitted.
*/
Expand All @@ -131,25 +112,6 @@ class MainWindowManager extends EventEmitter {
}
}

/**
* Called when the 'context-menu' event is received.
* TODO: Facebook intercepts this so it doesn't work, but at least it won't crash
*/
onContextMenu (event, params) {
log('on context-menu');
try {
const menu = contextMenu.create(params, this.window);
if (menu) {
log('opening context menu');
setTimeout(() => {
menu.popup(this.window);
}, 50);
}
} catch (err) {
logError(err);
}
}

/**
* Called when the 'ready-to-show' event is emitted.
*/
Expand Down Expand Up @@ -267,6 +229,7 @@ class MainWindowManager extends EventEmitter {
*/
onShow () {
log('onShow');

// Enable window specific menu items
if (this.menuManager) {
this.menuManager.windowSpecificItemsEnabled(true);
Expand All @@ -278,6 +241,7 @@ class MainWindowManager extends EventEmitter {
*/
onHide () {
log('onHide');

// Disable window specific menu items
if (this.menuManager) {
this.menuManager.windowSpecificItemsEnabled(false);
Expand Down Expand Up @@ -338,6 +302,25 @@ class MainWindowManager extends EventEmitter {
this.prefixWindowTitle(count ? '(' + count + ') ' : '');
}

/**
* Called by the renderer process to open the context menu.
*/
openContextMenu (params) {
log('open context menu');
try {
params = JSON.parse(params);
const menu = contextMenu.create(params, this.window);
if (menu) {
log('opening context menu');
setTimeout(() => {
menu.popup(this.window);
}, 50);
}
} catch (err) {
logError(err);
}
}

/**
* Show and focus or create the main window.
*/
Expand Down Expand Up @@ -374,6 +357,7 @@ class MainWindowManager extends EventEmitter {
hideWindow () {
if (platform.isDarwin) {
Menu.sendActionToFirstResponder('hide:');
this.window.hide();
} else {
this.window.hide();
}
Expand Down
40 changes: 22 additions & 18 deletions src/scripts/browser/menus/context.js
Expand Up @@ -2,6 +2,7 @@ import {clipboard, Menu, MenuItem} from 'electron';
import spellChecker from 'spellchecker';

import platform from 'common/utils/platform';
import urls from 'common/utils/urls';

function create (params, browserWindow) {
const webContents = browserWindow.webContents;
Expand All @@ -12,44 +13,49 @@ function create (params, browserWindow) {
label: 'Look Up "' + params.selectionText + '"',
click: () => webContents.send('call-webview-method', 'showDefinitionForSelection')
}));

menu.append(new MenuItem({
type: 'separator'
}));
}

if (params.isEditable && params.misspelledWord) {
const corrections = spellChecker.getCorrectionsForMisspelling(params.misspelledWord);
let hasMisspellingRelatedItems = !!corrections.length;
const items = [];

// add correction suggestions
for (let i = 0; i < corrections.length && i < 5; i++) {
menu.append(new MenuItem({
items.push(new MenuItem({
label: 'Correct: ' + corrections[i],
click: () => webContents.send('call-webview-method', 'replaceMisspelling', corrections[i])
}));
}

// Hunspell doesn't remember these, so skip this item
// TODO: params.isWindows7 is always undefined
// Otherwise, offer to add the word to the dictionary
if (!platform.isLinux && !params.isWindows7) {
menu.append(new MenuItem({
items.push(new MenuItem({
label: 'Add to Dictionary',
click: () => {
webContents.send('fwd-webview', 'add-selection-to-dictionary');
webContents.send('call-webview-method', 'replaceMisspelling', params.misspelledWord);
}
}));
hasMisspellingRelatedItems = true;
}

if (hasMisspellingRelatedItems) {
// prepend separator and then add items
if (items.length) {
menu.append(new MenuItem({
type: 'separator'
}));

for (const item of items) {
menu.append(item);
}
}
}

if (params.selectionText || params.isEditable) {
if (params.isEditable) {
menu.append(new MenuItem({
type: 'separator'
}));

menu.append(new MenuItem({
label: 'Undo',
enabled: params.editFlags.canUndo,
Expand All @@ -61,7 +67,9 @@ function create (params, browserWindow) {
enabled: params.editFlags.canRedo,
click: () => webContents.send('call-webview-method', 'redo')
}));
}

if (params.selectionText || params.isEditable) {
menu.append(new MenuItem({
type: 'separator'
}));
Expand Down Expand Up @@ -89,13 +97,13 @@ function create (params, browserWindow) {
enabled: params.editFlags.canSelectAll,
click: () => webContents.send('call-webview-method', 'selectAll')
}));
}

if (params.linkURL) {
menu.append(new MenuItem({
type: 'separator'
}));
}

if (params.linkURL) {
menu.append(new MenuItem({
label: 'Copy Link Text',
enabled: !!params.linkText,
Expand All @@ -104,11 +112,7 @@ function create (params, browserWindow) {

menu.append(new MenuItem({
label: 'Copy Link Address',
click: () => clipboard.writeText(params.linkURL)
}));

menu.append(new MenuItem({
type: 'separator'
click: () => clipboard.writeText(urls.skipFacebookRedirect(params.linkURL))
}));
}

Expand Down
63 changes: 57 additions & 6 deletions src/scripts/browser/menus/expressions/expr-click.js
@@ -1,7 +1,8 @@
import {app, shell} from 'electron';
import {app, shell, dialog} from 'electron';

import * as piwik from 'browser/services/piwik';
import * as requestFilter from 'browser/utils/request-filter';
import filePaths from 'common/utils/file-paths';
import prefs from 'browser/utils/prefs';

/**
Expand Down Expand Up @@ -59,6 +60,16 @@ export function appQuit () {
};
}

/**
* Restart the app.
*/
export function restartApp () {
return function () {
app.relaunch();
app.quit();
};
}

/**
* Open the url externally, in a browser.
*/
Expand All @@ -68,6 +79,21 @@ export function openUrl (url) {
};
}

/**
* Show a mac-like About dialog.
*/
export function showCustomAboutDialog () {
return function () {
dialog.showMessageBox({
icon: filePaths.getImagePath('app_icon.png'),
title: 'About ' + global.manifest.productName,
message: global.manifest.productName + ' v' + global.manifest.version + '-' + global.manifest.versionChannel,
detail: global.manifest.copyright + '\n\n' + 'Special thanks to @sytten, @nevercast' +
', @TheHimanshu, @MichaelAquilina, @franciscoib, @levrik, and all the contributors on GitHub.'
});
};
}

/**
* Send a message directly to the webview.
*/
Expand All @@ -81,6 +107,19 @@ export function sendToWebView (channel, ...valueExprs) {
};
}

/**
* Send a message to the current BrowserWindow's WebContents.
*/
export function sendToWebContents (channel, ...valueExprs) {
return function (menuItem, browserWindow) {
if (!browserWindow) {
browserWindow = global.application.mainWindowManager.window;
}
const values = valueExprs.map((e) => e.apply(this, arguments));
browserWindow.webContents.send(channel, ...values);
};
}

/**
* Reload the browser window.
*/
Expand All @@ -89,7 +128,7 @@ export function reloadWindow () {
if (!browserWindow) {
browserWindow = global.application.mainWindowManager.window;
}
browserWindow.reload();
browserWindow.webContents.reloadIgnoringCache();
};
}

Expand Down Expand Up @@ -147,15 +186,27 @@ export function toggleDevTools () {
}

/**
* Toggle the menu bar of the window.
* Toggle the webview's dev tools panel.
*/
export function toggleWebViewDevTools () {
return function (menuItem, browserWindow) {
if (!browserWindow) {
browserWindow = global.application.mainWindowManager.window;
}
browserWindow.webContents.send('toggle-wv-dev-tools');
};
}

/**
* Whether the menu bar should hide automatically.
*/
export function toggleMenuBar () {
export function autoHideMenuBar (autoHideExpr) {
return function (menuItem, browserWindow) {
if (!browserWindow) {
browserWindow = global.application.mainWindowManager.window;
}
const newState = !browserWindow.isMenuBarVisible();
browserWindow.setMenuBarVisibility(newState);
const autoHide = autoHideExpr.apply(this, arguments);
browserWindow.setAutoHideMenuBar(autoHide);
};
}

Expand Down
49 changes: 0 additions & 49 deletions src/scripts/browser/menus/generator/app.js

This file was deleted.

4 changes: 0 additions & 4 deletions src/scripts/browser/menus/generator/index.js

This file was deleted.

5 changes: 0 additions & 5 deletions src/scripts/browser/menus/generator/misc.js

This file was deleted.

6 changes: 2 additions & 4 deletions src/scripts/browser/menus/main.js
Expand Up @@ -2,14 +2,12 @@ import {parseTemplate} from 'browser/menus/utils';

export default function () {
const template = [
'browser/menus/templates/main-app-darwin',
'browser/menus/templates/main-app-nondarwin',
'browser/menus/templates/main-app',
'browser/menus/templates/main-edit',
'browser/menus/templates/main-view',
'browser/menus/templates/main-theme',
'browser/menus/templates/main-privacy',
'browser/menus/templates/main-window-darwin',
'browser/menus/templates/main-window-nondarwin',
'browser/menus/templates/main-window',
'browser/menus/templates/main-help'
].map((module) => require(module).default);
return parseTemplate(template, null);
Expand Down
98 changes: 0 additions & 98 deletions src/scripts/browser/menus/templates/main-app-darwin.js

This file was deleted.

85 changes: 0 additions & 85 deletions src/scripts/browser/menus/templates/main-app-nondarwin.js

This file was deleted.

147 changes: 147 additions & 0 deletions src/scripts/browser/menus/templates/main-app.js
@@ -0,0 +1,147 @@
import platform from 'common/utils/platform';
import $ from 'browser/menus/expressions';

export default {
label: platform.isDarwin ? global.manifest.productName : '&App',
submenu: [{
label: 'About ' + global.manifest.productName,
click: $.showCustomAboutDialog()
}, {
type: 'checkbox',
label: 'Switch to Workplace Messenger',
click: $.all(
$.setPref('switch-workplace', $.key('checked')),
$.reloadWindow()
),
parse: $.all(
$.setLocal('checked', $.pref('switch-workplace'))
)
}, {
label: platform.isDarwin ? 'Preferences...' : 'Settings',
accelerator: 'CmdOrCtrl+,',
click: $.sendToWebView('open-preferences-modal'),
needsWindow: true
}, {
type: 'separator',
allow: !global.options.mas
}, {
id: 'cfu-check-for-update',
label: 'Check for &Update...',
allow: !global.options.mas,
click: $.cfuCheckForUpdate(true)
}, {
id: 'cfu-checking-for-update',
label: 'Checking for &Update...',
allow: !global.options.mas,
enabled: false,
visible: false
}, {
id: 'cfu-update-available',
label: 'Download &Update...',
allow: platform.isNonDarwin && (platform.isLinux || global.options.portable),
visible: false,
click: $.cfuUpdateAvailable()
}, {
id: 'cfu-update-available',
label: 'Downloading &Update...',
allow: !global.options.mas && !platform.isLinux && !global.options.portable,
enabled: false,
visible: false
}, {
id: 'cfu-update-downloaded',
label: 'Restart and Install &Update...',
allow: !global.options.mas,
visible: false,
click: $.cfuUpdateDownloaded()
}, {
label: 'Updates Release Channel',
allow: !global.options.mas,
submenu: ['Stable', 'Beta', 'Dev'].map((channelName) => ({
type: 'radio',
label: channelName,
channel: channelName.toLowerCase(),
click: $.all(
$.setPref('updates-channel', $.key('channel')),
$.resetAutoUpdaterUrl(),
$.cfuCheckForUpdate(false)
),
parse: $.all(
$.setLocal('checked', $.eq($.pref('updates-channel'), $.key('channel')))
)
}))
}, {
type: 'checkbox',
label: 'Check for Update Automatically',
allow: !global.options.mas,
click: $.all(
$.checkForUpdateAuto($.key('checked')),
$.setPref('updates-auto-check', $.key('checked'))
),
parse: $.setLocal('checked', $.pref('updates-auto-check'))
}, {
type: 'separator'
}, {
type: 'checkbox',
label: '&Launch on OS Startup',
allow: !global.options.mas && !global.options.portable,
click: $.all(
$.launchOnStartup($.key('checked')),
$.updateSibling('startup-hidden', 'enabled', $.key('checked')),
$.setPref('launch-startup', $.key('checked'))
),
parse: $.all(
$.setLocal('checked', $.pref('launch-startup')),
$.updateSibling('startup-hidden', 'enabled', $.key('checked'))
)
}, {
id: 'startup-hidden',
type: 'checkbox',
label: 'Start &Hidden on Startup',
allow: !global.options.mas && !global.options.portable,
click: $.setPref('launch-startup-hidden', $.key('checked')),
parse: $.setLocal('checked', $.pref('launch-startup-hidden'))
}, {
type: 'separator'
}, {
label: 'Restart in Debug Mode',
allow: !global.options.debug,
click: $.restartInDebugMode()
}, {
label: 'Running in Debug Mode',
allow: global.options.debug,
enabled: false
}, {
label: 'Open Debug Log...',
enabled: global.options.debug,
click: $.openDebugLog()
}, {
type: 'separator'
}, {
label: '&Quit',
accelerator: 'Ctrl+Q',
allow: platform.isNonDarwin,
click: $.appQuit()
}, {
role: 'services',
submenu: [],
allow: platform.isDarwin
}, {
type: 'separator',
allow: platform.isDarwin
}, {
role: 'hide',
allow: platform.isDarwin
}, {
role: 'hideothers',
allow: platform.isDarwin
}, {
role: 'unhide',
allow: platform.isDarwin
}, {
type: 'separator',
allow: platform.isDarwin
}, {
role: 'quit',
allow: platform.isDarwin
}]
};
6 changes: 3 additions & 3 deletions src/scripts/browser/menus/templates/main-help.js
Expand Up @@ -4,10 +4,10 @@ export default {
label: '&Help',
role: 'help',
submenu: [{
label: 'App Website',
label: 'Open App Website',
click: $.openUrl('https://messengerfordesktop.com/')
}, {
label: 'Email Us',
click: $.openUrl('mailto:hello@messengerfordesktop.com')
label: 'Send Feedback',
click: $.openUrl('https://aluxian.typeform.com/to/sr2gEc')
}]
};
7 changes: 6 additions & 1 deletion src/scripts/browser/menus/templates/main-privacy.js
Expand Up @@ -3,9 +3,14 @@ import $ from 'browser/menus/expressions';
export default {
label: 'Privacy',
submenu: [{
type: 'checkbox',
label: '&Report App Stats and Crashes',
click: $.setPref('analytics-track', $.key('checked')),
parse: $.setLocal('checked', $.pref('analytics-track'))
}, {
id: 'block-seen-typing',
type: 'checkbox',
label: 'Block Seen and Typing Indicators',
label: '&Block Seen and Typing Indicators',
click: $.all(
$.setPref('block-seen-typing', $.key('checked')),
$.blockSeenTyping($.key('checked'))
Expand Down
1 change: 0 additions & 1 deletion src/scripts/browser/menus/templates/main-theme.js
Expand Up @@ -6,7 +6,6 @@ export default {
type: 'radio',
label: global.manifest.themes[themeId],
theme: themeId,
accelerator: index < 10 ? 'CmdOrCtrl+Alt+' + index : undefined,
needsWindow: true,
click: $.all(
$.themeCss($.key('theme'), (css) => $.sendToWebView('apply-theme', $.val(css))),
Expand Down
84 changes: 48 additions & 36 deletions src/scripts/browser/menus/templates/main-view.js
Expand Up @@ -8,23 +8,23 @@ export default {
accelerator: 'CmdOrCtrl+plus',
needsWindow: true,
click: $.all(
$.setPref('zoom-level', $.sum($.pref('zoom-level'), $.val(1))),
$.sendToWebView('zoom-level', $.pref('zoom-level'))
$.setPref('zoom-level', $.sum($.pref('zoom-level'), $.val(+0.25))),
$.sendToWebContents('zoom-level', $.pref('zoom-level'))
)
}, {
label: 'Zoom Out',
accelerator: 'CmdOrCtrl+-',
needsWindow: true,
click: $.all(
$.setPref('zoom-level', $.sum($.pref('zoom-level'), $.val(-1))),
$.sendToWebView('zoom-level', $.pref('zoom-level'))
$.setPref('zoom-level', $.sum($.pref('zoom-level'), $.val(-0.25))),
$.sendToWebContents('zoom-level', $.pref('zoom-level'))
)
}, {
label: 'Reset Zoom',
accelerator: 'CmdOrCtrl+0',
needsWindow: true,
click: $.all(
$.sendToWebView('zoom-level', $.val(0)),
$.sendToWebContents('zoom-level', $.val(0)),
$.unsetPref('zoom-level')
)
}, {
Expand All @@ -34,33 +34,45 @@ export default {
role: 'togglefullscreen'
}, {
label: 'Toggle &Developer Tools',
accelerator: platform.isNonDarwin ? 'Ctrl+Shift+Alt+I' : 'Alt+Cmd+I',
needsWindow: true,
click: $.toggleDevTools()
}, {
label: 'Toggle &Menu Bar',
accelerator: 'Alt+Ctrl+B',
label: 'Toggle WebView &Dev Tools',
needsWindow: true,
allow: platform.isNonDarwin,
click: $.toggleMenuBar()
click: $.toggleWebViewDevTools()
}, {
type: 'separator'
}, {
type: 'checkbox',
label: 'Auto Hide Sidebar',
label: 'Auto Hide &Menu Bar',
accelerator: 'Alt+Ctrl+B',
needsWindow: true,
allow: platform.isNonDarwin,
click: $.all(
$.styleCss('auto-hide-sidebar', (css) =>
$.sendToWebView('apply-sidebar-auto-hide', $.key('checked'), $.val(css))
),
$.setPref('sidebar-auto-hide', $.key('checked'))
$.setPref('auto-hide-menubar', $.key('checked')),
$.autoHideMenuBar($.key('checked'))
),
parse: $.all(
$.setLocal('checked', $.pref('sidebar-auto-hide'))
$.setLocal('checked', $.pref('auto-hide-menubar'))
)
}, {
type: 'separator'
}, {
// type: 'checkbox',
// label: 'Auto Hide Sidebar',
// needsWindow: true,
// click: $.all(
// $.styleCss('auto-hide-sidebar', (css) =>
// $.sendToWebView('apply-sidebar-auto-hide', $.key('checked'), $.val(css))
// ),
// $.setPref('sidebar-auto-hide', $.key('checked'))
// ),
// parse: $.all(
// $.setLocal('checked', $.pref('sidebar-auto-hide'))
// )
// }, {
// type: 'separator'
// }, {
label: 'N&ew Conversation',
accelerator: 'CmdOrCtrl+N',
needsWindow: true,
Expand All @@ -70,25 +82,25 @@ export default {
accelerator: 'CmdOrCtrl+F',
needsWindow: true,
click: $.sendToWebView('search-chats')
// }, {
// type: 'separator'
// }, {
// label: '&Next Conversation',
// accelerator: 'CmdOrCtrl+Down',
// needsWindow: true,
// click: $.sendToWebView('switch-conversation', $.val(+1))
// }, {
// label: '&Previous Conversation',
// accelerator: 'CmdOrCtrl+Up',
// needsWindow: true,
// click: $.sendToWebView('switch-conversation', $.val(-1))
// }, {
// label: 'Switch to Conversation',
// submenu: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((index) => ({
// label: 'Conversation ' + index,
// accelerator: 'CmdOrCtrl+' + (index % 10),
// needsWindow: true,
// click: $.sendToWebView('switch-conversation', $.val(1000 + index))
// }))
}, {
type: 'separator'
}, {
label: '&Next Conversation',
accelerator: 'CmdOrCtrl+Down',
needsWindow: true,
click: $.sendToWebView('switch-conversation-next')
}, {
label: '&Previous Conversation',
accelerator: 'CmdOrCtrl+Up',
needsWindow: true,
click: $.sendToWebView('switch-conversation-previous')
}, {
label: 'Switch to Conversation',
submenu: [1, 2, 3, 4, 5, 6, 7, 8, 9].map((num) => ({
label: 'Conversation ' + num,
accelerator: 'CmdOrCtrl+' + num,
needsWindow: true,
click: $.sendToWebView('switch-conversation-num', $.val(num))
}))
}]
};
61 changes: 0 additions & 61 deletions src/scripts/browser/menus/templates/main-window-nondarwin.js

This file was deleted.

Expand Up @@ -3,52 +3,75 @@ import $ from 'browser/menus/expressions';

export default {
label: 'Window',
allow: platform.isDarwin,
role: 'window',
submenu: [{
label: 'Reload',
accelerator: 'Cmd+R',
label: '&Reload',
accelerator: 'CmdOrCtrl+R',
needsWindow: true,
click: $.reloadWindow()
}, {
label: 'Reset',
accelerator: 'Cmd+Alt+R',
label: 'Re&set',
accelerator: 'CmdOrCtrl+Alt+R',
needsWindow: true,
click: $.resetWindow()
}, {
type: 'separator'
}, {
type: 'checkbox',
label: 'Float on Top',
accelerator: 'Cmd+Alt+T',
label: '&Float on Top',
accelerator: 'CmdOrCtrl+Alt+T',
needsWindow: true,
click: $.floatOnTop($.key('checked'))
}, {
type: 'checkbox',
label: 'Close with Escape Key',
label: 'Close with &Escape Key',
click: $.setPref('close-with-esc', $.key('checked')),
parse: $.setLocal('checked', $.pref('close-with-esc'))
}, {
type: 'checkbox',
label: 'Open Links in Browser',
label: 'Open Links in &Browser',
click: $.setPref('links-in-browser', $.key('checked')),
parse: $.setLocal('checked', $.pref('links-in-browser'))
}, {
type: 'checkbox',
label: 'Notifications Badge in Dock',
label: '&Notifications Badge in ' + (platform.isWindows ? 'Taskbar' : 'Dock'),
needsWindow: true,
click: $.all(
$.setPref('show-notifications-badge', $.key('checked')),
$.hideDockBadge($.key('checked'))
platform.isWindows ? $.hideTaskbarBadge($.key('checked')) : $.hideDockBadge($.key('checked'))
),
parse: $.all(
$.setLocal('checked', $.pref('show-notifications-badge'))
)
}, {
type: 'checkbox',
label: 'Accept First &Click',
needsWindow: true,
click: $.all(
$.setPref('accept-first-mouse', $.key('checked')),
$.restartApp()
),
parse: $.all(
$.setLocal('checked', $.pref('accept-first-mouse'))
)
}, {
type: 'separator'
}, {
type: 'checkbox',
label: 'Show in &Tray',
allow: platform.isNonDarwin,
click: $.all(
$.showInTray($.key('checked')),
$.setPref('show-tray', $.key('checked'))
),
parse: $.all(
$.setLocal('checked', $.pref('show-tray')),
)
}, {
id: 'show-tray',
type: 'checkbox',
label: 'Show in Menu Bar',
allow: platform.isDarwin,
click: $.all(
$.showInTray($.key('checked')),
$.updateSibling('show-dock', 'enabled', $.key('checked')),
Expand All @@ -60,12 +83,13 @@ export default {
),
parse: $.all(
$.setLocal('checked', $.pref('show-tray')),
$.updateSibling('show-dock', 'enabled', $.key('checked'))
$.setLocal('enabled', $.pref('show-dock'))
)
}, {
id: 'show-dock',
type: 'checkbox',
label: 'Show in Dock',
allow: platform.isDarwin,
click: $.all(
$.showInDock($.key('checked')),
$.updateSibling('show-tray', 'enabled', $.key('checked')),
Expand All @@ -77,16 +101,20 @@ export default {
),
parse: $.all(
$.setLocal('checked', $.pref('show-dock')),
$.updateSibling('show-tray', 'enabled', $.key('checked')),
$.setLocal('enabled', $.pref('show-tray')),
$.showInDock($.key('checked'))
)
}, {
type: 'separator'
type: 'separator',
allow: platform.isDarwin
}, {
role: 'minimize'
role: 'minimize',
allow: platform.isDarwin
}, {
role: 'zoom'
role: 'zoom',
allow: platform.isDarwin
}, {
role: 'close'
role: 'close',
allow: platform.isDarwin
}]
};
10 changes: 8 additions & 2 deletions src/scripts/browser/menus/templates/tray.js
Expand Up @@ -11,7 +11,6 @@ export default [{
type: 'checkbox',
label: 'Show in Menu Bar',
allow: platform.isDarwin,
checked: true,
click: $.all(
$.showInTray($.key('checked')),
$.updateSibling('show-dock', 'enabled', $.key('checked')),
Expand All @@ -20,13 +19,16 @@ export default [{
$.updateSibling('show-dock', 'enabled', $.val(checked))
)),
$.setPref('show-tray', $.key('checked'))
),
parse: $.all(
$.setLocal('checked', $.pref('show-tray')),
$.setLocal('enabled', $.pref('show-dock'))
)
}, {
id: 'show-dock',
type: 'checkbox',
label: 'Show in Dock',
allow: platform.isDarwin,
checked: true,
click: $.all(
$.showInDock($.key('checked')),
$.updateSibling('show-tray', 'enabled', $.key('checked')),
Expand All @@ -35,6 +37,10 @@ export default [{
$.updateSibling('show-tray', 'enabled', $.val(checked))
)),
$.setPref('show-dock', $.key('checked'))
),
parse: $.all(
$.setLocal('checked', $.pref('show-dock')),
$.setLocal('enabled', $.pref('show-tray'))
)
}, {
type: 'separator',
Expand Down
3 changes: 3 additions & 0 deletions src/scripts/browser/utils/prefs-defaults.js
Expand Up @@ -15,9 +15,11 @@ const defaults = {
'block-seen-typing': false,
'close-with-esc': false,
'quit-behaviour-taught': false,
'notify-app-updated': false,
'show-notifications-badge': true,
'show-tray': platform.isWindows,
'show-dock': true,
'auto-hide-menubar': false,
'sidebar-auto-hide': false,
'spell-checker-check': false,
'spell-checker-auto-correct': false,
Expand All @@ -30,6 +32,7 @@ const defaults = {
height: 610
},
'window-full-screen': false,
'accept-first-mouse': false,
'zoom-level': 0
};

Expand Down
30 changes: 30 additions & 0 deletions src/scripts/common/utils/graphics.js
@@ -0,0 +1,30 @@
function createBadgeDataUrl (text) {
const canvas = document.createElement('canvas');
canvas.height = 140;
canvas.width = 140;

const context = canvas.getContext('2d');
context.fillStyle = 'red';
context.beginPath();
context.ellipse(70, 70, 70, 70, 0, 0, 2 * Math.PI);
context.fill();
context.textAlign = 'center';
context.fillStyle = 'white';

if (text.length > 2) {
context.font = 'bold 65px "Segoe UI", sans-serif';
context.fillText('' + text, 70, 95);
} else if (text.length > 1) {
context.font = 'bold 85px "Segoe UI", sans-serif';
context.fillText('' + text, 70, 100);
} else {
context.font = 'bold 100px "Segoe UI", sans-serif';
context.fillText('' + text, 70, 105);
}

return canvas.toDataURL();
}

export default {
createBadgeDataUrl
};
18 changes: 11 additions & 7 deletions src/scripts/renderer/components/keymap.js
@@ -1,31 +1,35 @@
import {ipcRenderer} from 'electron';
import {remote} from 'electron';
import Mousetrap from 'mousetrap';

import prefs from 'common/utils/prefs';
import webView from 'renderer/webview';

log('binding keyboard shortcuts');

function bindSwitchConversation (keys, delta) {
function bindSwitchConversation (keys, direction) {
Mousetrap.bind(keys, function () {
log('conversation', delta);
webView.send('switch-conversation', delta);
log(direction, 'conversation');
if (direction === 'next') {
webView.send('switch-conversation-next');
} else {
webView.send('switch-conversation-previous');
}
return false;
});
}

// Previous chat
bindSwitchConversation(['ctrl+shift+tab'], -1);
bindSwitchConversation(['ctrl+shift+tab'], 'previous');

// Next chat
bindSwitchConversation(['ctrl+tab'], +1);
bindSwitchConversation(['ctrl+tab'], 'next');

// Close with Esc
Mousetrap.bind('esc', function () {
const enabled = prefs.get('close-with-esc');
log('close with esc shortcut, enabled:', enabled);
if (enabled) {
ipcRenderer.send('close-window');
remote.getGlobal('application').mainWindowManager.window.close();
}
return enabled;
});
172 changes: 80 additions & 92 deletions src/scripts/renderer/preload/events.js
@@ -1,13 +1,7 @@
import {webFrame, ipcRenderer} from 'electron';
import {webFrame, ipcRenderer, shell} from 'electron';
import {getDictionaryPath} from 'common/utils/spellchecker';
import SpellChecker from 'spellchecker';

// Set zoom level
ipcRenderer.on('zoom-level', function (event, zoomLevel) {
log('zoom level', zoomLevel);
webFrame.setZoomLevel(zoomLevel);
});

// Remove the top banner ad
ipcRenderer.on('remove-top-banner', function (event) {
log('removing top banner ad');
Expand All @@ -25,6 +19,27 @@ ipcRenderer.on('remove-top-banner', function (event) {
}
});

// Show an 'app updated' notification
ipcRenderer.on('notify-app-updated', function (event) {
log('notifying app updated');

// Display the notification
const notif = new window.Notification(global.manifest.productName, {
body: 'App updated to v' + global.manifest.version + '. Click to see changes.',
tag: 'notify-app-updated',
canReply: false
});

// handle clicks
notif.onclick = () => {
setTimeout(() => {
const changelogUrl = global.manifest.changelogUrl
.replace(/%CURRENT_VERSION%/g, global.manifest.version);
shell.openExternal(changelogUrl);
}, 300);
};
});

// Set spell checker
ipcRenderer.on('spell-checker', function (event, enabled, autoCorrect, langCode) {
const chromiumLangCode = langCode.replace('_', '-');
Expand Down Expand Up @@ -87,7 +102,7 @@ ipcRenderer.on('add-selection-to-dictionary', function () {

// Simulate a click on the 'New chat' button
ipcRenderer.on('new-conversation', function () {
const newChatButton = document.querySelector('a[href="/new"]');
const newChatButton = document.querySelector('._30yy[href="/new"]');
if (newChatButton) {
newChatButton.click();
}
Expand All @@ -99,106 +114,79 @@ ipcRenderer.on('new-conversation', function () {

// Focus the 'Search or start a new chat' input field
ipcRenderer.on('search-chats', function () {
const inputSearch = document.querySelector('[role="combobox"]');
const inputSearch = document.querySelector('._58al');
if (inputSearch) {
inputSearch.focus();
}
});

/**
* Dispatch a click event on the given item.
*/
function dispatchClick (item) {
// TODO broken
item.dispatchEvent(new window.MouseEvent('mousedown', {
view: window,
bubbles: true,
cancelable: false
}));
}

// Switch to next/previous conversation
ipcRenderer.on('switch-conversation', function (event, indexDelta) {
function getChatList () {
const chatListElem = document.querySelectorAll('[aria-label~="Conversation"][aria-label~="list"] > li');
if (chatListElem && chatListElem.length) {
return Array.from(chatListElem).sort(function (a, b) {
return parseInt(b.style.zIndex, 10) - parseInt(a.style.zIndex, 10);
});
}
}
// Switch to the next conversation
ipcRenderer.on('switch-conversation-next', function (event) {
log('switching to the next conversation');
const index = getNextConversationIndex(true);
log('index =', index);
selectConversation(index);
});

function navigateConversation (delta) {
const chatList = getChatList();
if (!chatList) {
return;
}

let found = false;
for (let [i, item] of chatList.entries()) {
const active = isItemActive(item);
if (active) {
const nextIndex = getDeltaIndex(i, delta, chatList);
if (nextIndex !== -1) {
makeActive(chatList[nextIndex]);
}
found = true;
break;
}
}
// Switch to the previous conversation
ipcRenderer.on('switch-conversation-previous', function (event) {
log('switching to the previous conversation');
const index = getNextConversationIndex(false);
log('index =', index);
selectConversation(index);
});

if (!found) {
if (delta > 0) {
makeActive(chatList[0]);
} else {
makeActive(chatList[chatList.length - 1]);
}
}
}
// Switch to a particular conversation
ipcRenderer.on('switch-conversation-num', function (event, num) {
log('switching to conversation num =', num);
const index = num - 1;
selectConversation(index);
});

function navigateConversationIndex (delta) {
const chatList = getChatList();
if (!chatList) {
return;
}
// Conversation navigation helpers
// @source https://github.com/sindresorhus/caprine/blob/master/browser.js
function selectConversation (index) {
log('select conversation index =', index);
const list = document.querySelector('div[role="navigation"] > div > ul');
list.children[index].firstChild.firstChild.click();
}
function getNextConversationIndex (ascending) {
const list = document.querySelector('div[role="navigation"] > div > ul');
const selected = document.querySelector('._5l-3._1ht1._1ht2');

if (delta < 0) {
delta = 0;
}
// none selected, return first or last
if (!selected) {
return ascending ? 0 : list.length - 1;
}

if (delta >= chatList.length) {
delta = chatList.length - 1;
}
// get selected + delta
const index = Array.from(list.children).indexOf(selected) + (ascending ? +1 : -1);

makeActive(chatList[delta]);
// keep index in bounds
if (index < 0) {
return 0;
}

function getDeltaIndex (index, delta, chatList) {
let deltaIndex = index + delta;
if (deltaIndex < 0) {
deltaIndex = -1;
}
if (deltaIndex >= chatList.length) {
deltaIndex = -1;
}
return deltaIndex;
if (index >= list.length) {
return list.length - 1;
}

function isItemActive (item) {
return item && !!item.getAttribute('aria-relevant');
}
return index;
}

// TODO broken
function makeActive (item) {
const chat = item.querySelector('.chat');
if (chat) {
dispatchClick(chat);
}
// Show the 'Settings' modal
// @source https://github.com/sindresorhus/caprine/blob/master/browser.js
ipcRenderer.on('open-preferences-modal', function (event, num) {
log('opening Settings modal');

// Click on the 'cog' icon
const cogBtn = document.querySelector('._30yy._2fug._p');
if (cogBtn) {
cogBtn.click();
}

if (indexDelta > 1000) {
navigateConversationIndex(indexDelta - 1000 - 1);
} else {
navigateConversation(indexDelta);
// Click on 'Settings'
const nodes = document.querySelectorAll('._54nq._2i-c._558b._2n_z li:first-child a');
if (nodes && nodes.length) {
nodes[nodes.length - 1].click();
}
});
1 change: 1 addition & 0 deletions src/scripts/renderer/preload/index.js
Expand Up @@ -3,4 +3,5 @@ const initPath = require('path').join(appPath, 'scripts', 'renderer', 'init.js')
require(initPath).inject('webview');

require('renderer/preload/events');
require('renderer/preload/listeners');
require('renderer/preload/notification');
12 changes: 12 additions & 0 deletions src/scripts/renderer/preload/listeners.js
@@ -0,0 +1,12 @@
import {remote} from 'electron';

import platform from 'common/utils/platform';

// Forward context menu opens
remote.getCurrentWebContents().on('context-menu', function (event, params) {
params.isWindows7 = platform.isWindows7;
params = JSON.stringify(params);
log('sending context menu', event, params);
remote.getGlobal('application').mainWindowManager.openContextMenu(params);
event.preventDefault();
});
4 changes: 2 additions & 2 deletions src/scripts/renderer/preload/notification.js
Expand Up @@ -25,7 +25,7 @@ window.Notification = (function (Html5Notification) {

log('showing native notification');
const nativeOptions = Object.assign({}, options, {
canReply: true,
canReply: options.canReply !== false,
title
});

Expand All @@ -37,7 +37,7 @@ window.Notification = (function (Html5Notification) {
if (result.__data) {
nativeNotifier.removeNotification(result.__data.identifier);
} else {
logError(new Error('tried to close notification with falsy __data'));
logFatal(new Error('tried to close notification with falsy __data'));
}
};

Expand Down
19 changes: 19 additions & 0 deletions src/scripts/renderer/webview/events.js
Expand Up @@ -3,6 +3,14 @@ import {ipcRenderer} from 'electron';
import * as piwik from 'renderer/services/piwik';
import webView from 'renderer/webview';

/**
* Change the webview's zoom level.
*/
ipcRenderer.on('zoom-level', function (event, zoomLevel) {
log('setting webview zoom level', zoomLevel);
webView.setZoomLevel(zoomLevel);
});

/**
* Forward a message to the webview.
*/
Expand Down Expand Up @@ -30,6 +38,17 @@ ipcRenderer.on('call-webview-method', function (event, method, ...args) {
}
});

/**
* Toggle the dev tools panel of the webview.
*/
ipcRenderer.on('toggle-wv-dev-tools', function (event) {
if (webView.isDevToolsOpened()) {
webView.closeDevTools();
} else {
webView.openDevTools();
}
});

/**
* Track an analytics event.
*/
Expand Down
95 changes: 56 additions & 39 deletions src/scripts/renderer/webview/listeners.js
@@ -1,69 +1,77 @@
import {ipcRenderer} from 'electron';
import {shell, remote} from 'electron';

import webView from 'renderer/webview';
import platform from 'common/utils/platform';
import graphics from 'common/utils/graphics';
import files from 'common/utils/files';
import prefs from 'common/utils/prefs';

function createBadgeDataUrl (text) {
const canvas = document.createElement('canvas');
canvas.height = 140;
canvas.width = 140;

const context = canvas.getContext('2d');
context.fillStyle = 'red';
context.beginPath();
context.ellipse(70, 70, 70, 70, 0, 0, 2 * Math.PI);
context.fill();
context.textAlign = 'center';
context.fillStyle = 'white';

if (text.length > 2) {
context.font = 'bold 65px "Segoe UI", sans-serif';
context.fillText('' + text, 70, 95);
} else if (text.length > 1) {
context.font = 'bold 85px "Segoe UI", sans-serif';
context.fillText('' + text, 70, 100);
} else {
context.font = 'bold 100px "Segoe UI", sans-serif';
context.fillText('' + text, 70, 105);
}

return canvas.toDataURL();
}
import urls from 'common/utils/urls';

// Log console messages
webView.addEventListener('console-message', function (event) {
const msg = event.message.replace(/%c/g, '');
const fwNormal = 'font-weight: normal;';
const fwBold = 'font-weight: bold;';
console.log('WV: ' + msg, fwBold, fwNormal);
console.log('WV: ' + msg);
log('WV:', msg);
});

// Listen for title changes to update the badge
let _delayedRemoveBadge = null;
webView.addEventListener('page-title-updated', function () {
log('webview page-title-updated');
const matches = /\(([\d]+)\)/.exec(webView.getTitle());
const parsed = parseInt(matches && matches[1], 10);
const count = isNaN(parsed) || !parsed ? '' : '' + parsed;
let badgeDataUrl = null;

if (platform.isWindows && count) {
badgeDataUrl = createBadgeDataUrl(count);
badgeDataUrl = graphics.createBadgeDataUrl(count);
}

log('sending notif-count', count, !!badgeDataUrl || null);
ipcRenderer.send('notif-count', count, badgeDataUrl);
log('notifying window of notif-count', count, !!badgeDataUrl || null);
clearTimeout(_delayedRemoveBadge);

// clear badge either instantly or after delay
_delayedRemoveBadge = setTimeout(() => {
remote.getGlobal('application').mainWindowManager.notifCountChanged(count, badgeDataUrl);
}, count ? 0 : 1500);
});

// Handle url clicks
webView.addEventListener('new-window', function (event) {
log('sending open-url', event.frameName, event.url);
ipcRenderer.send('open-url', event.url, event.options);
log('webview new-window', JSON.stringify(event));
const url = urls.skipFacebookRedirect(event.url);
event.preventDefault();

// download url
if (urls.isDownloadUrl(url)) {
log('on webview new-window, downloading', url);
webView.getWebContents().loadURL(url);
return;
}

// open it externally (if preference is set)
if (prefs.get('links-in-browser')) {
log('on webview new-window, externally', url);
shell.openExternal(url);
return;
}

// otherwise open it in a new app window (unless it's an audio/video call)
if (event.frameName !== 'Video Call' || event.url !== 'about:blank') {
const options = {
title: event.frameName || global.manifest.productName,
darkTheme: global.manifest.darkThemes.includes(prefs.get('theme'))
};
log('on webview new-window, new window', url, options);
const newWindow = new remote.BrowserWindow(options);
newWindow.loadURL(url);
event.newGuest = newWindow;
}
});

// Listen for dom-ready
webView.addEventListener('dom-ready', function () {
log('dom-ready');
log('webview dom-ready');

// Open dev tools when debugging
const autoLaunchDevTools = window.localStorage.autoLaunchDevTools;
Expand Down Expand Up @@ -94,11 +102,11 @@ webView.addEventListener('dom-ready', function () {
.catch(logError);
}

// Restore the default zoom level
// Restore the zoom level
const zoomLevel = prefs.get('zoom-level');
if (zoomLevel) {
log('restoring zoom level', zoomLevel);
webView.send('zoom-level', zoomLevel);
webView.setZoomLevel(zoomLevel);
}

// Restore spell checker and auto correct
Expand All @@ -109,10 +117,18 @@ webView.addEventListener('dom-ready', function () {
log('restoring spell checker', spellCheckerCheck, 'auto correct', autoCorrect, 'lang code', langCode);
webView.send('spell-checker', spellCheckerCheck, autoCorrect, langCode);
}

// Show an 'app updated' notification
if (prefs.get('notify-app-updated')) {
webView.send('notify-app-updated');
prefs.set('notify-app-updated', false);
}
});

// Listen for did-finish-load
webView.addEventListener('did-finish-load', function () {
log('webview did-finish-load');

// Remove top banner
webView.send('remove-top-banner');

Expand All @@ -126,6 +142,7 @@ webView.addEventListener('did-finish-load', function () {

// Animate the splash screen into view
document.addEventListener('DOMContentLoaded', function () {
log('document DOMContentLoaded');
const loadingSplashDiv = document.querySelector('.loader');
loadingSplashDiv.style.opacity = 1;
});
Expand Down
59 changes: 55 additions & 4 deletions src/themes/midnight.css
@@ -1,5 +1,5 @@
/* @source https://github.com/sindresorhus/caprine/blob/master/dark-mode.css */
/* Last update: 2017-02-23 */
/* Last update: 2017-03-14 */

body {
color: rgba(255, 255, 255, 0.7);
Expand Down Expand Up @@ -92,7 +92,7 @@ body {
color: rgba(255, 255, 255, 0.7) !important;
}

/* messages list: user info */
/* Messages list: user info */
._1n-e {
color: rgba(255, 255, 255, 0.4);
}
Expand Down Expand Up @@ -141,7 +141,7 @@ body {

/* Contact list: person container */
._1qt4 {
border-top: solid 1px rgba(255, 255, 255, 0.05);
border-top: solid 1px rgba(255, 255, 255, 0.05) !important;
}

/* contact list: person name */
Expand All @@ -164,6 +164,11 @@ body {
color: rgba(0, 132, 255, 0.7)
}

/* Contact list: unread conversation message subtitle */
._1ht3 ._1htf {
color: rgba(0, 132, 255, 0.7)
}

/* contact list: search results */
._5t4c,
._5t4c ._5l37 {
Expand Down Expand Up @@ -236,7 +241,7 @@ body {

/* Right sidebar: people list (add people) */
._4rph ._4rpj {
border-top: solid 1px rgba(255, 255, 255, 0.1);
border-top: solid 1px rgba(255, 255, 255, 0.1) !important;
}

/* Right sidebar: people list item (name) */
Expand Down Expand Up @@ -292,6 +297,41 @@ body {
filter: invert();
}

/* Poll */
/* Border */
._3b4t {
border-color: rgba(255, 255, 255, 0.09);
}
/* "Poll" header text */
._4qba {
color: rgba(255, 255, 255, 0.7);
}
/* Voting options text */
._1mr_,
._1mq_ ._1mq- {
color: rgba(255, 255, 255, 0.4);
}
/* Check box colors */
._2m5p[aria-checked="false"][role="checkbox"] {
border: 1px solid rgba(255, 255, 255, .10);
}
/* New option text box */
._58al {
color: white;
}
/* Poll results bar graph colors */
._3b4h {
background-color: rgba(255, 255, 255, 0.09);
}
/* Poll results profile pictures */
._4mnq {
border-color: #192633;
}
._34n6 {
color: rgba(255, 255, 255, 0.4);
background-color: rgba(255, 255, 255, 0.09);
}

/* Dialogs */
._53ij,
._4eby,
Expand Down Expand Up @@ -334,6 +374,11 @@ body {
color: rgba(255, 255, 255, 0.7);
}

/* Delete popover */
._hw2 ._53ij {
background-color: rgba(255, 255, 255, 0.1) !important;
}

/* Login tile and names */
._5hy4,
._3403 {
Expand All @@ -350,3 +395,9 @@ body {
button {
background: #192633 !important;
}

/* Fix the Sticker button */
._4rv6 {
filter: invert() !important;
opacity: 0.6 !important;
}
63 changes: 51 additions & 12 deletions tasks/pack.coffee
Expand Up @@ -9,6 +9,8 @@ del = require 'del'

gulp = require 'gulp'
zip = require 'gulp-zip'
tar = require 'gulp-tar'
gzip = require 'gulp-gzip'

utils = require './utils'
{applyPromise, applySpawn, applyIf, updateManifest, platform} = require './utils'
Expand Down Expand Up @@ -279,6 +281,45 @@ gulp.task 'pack:darwin64:zip', ['build:darwin64'], (done) ->
applySpawn 'fpm', fpmArgs
], done

# Create tar packages for linux32 and linux64
[32, 64].forEach (arch) ->
gulp.task 'pack:linux' + arch + ':tar', ['build:linux' + arch, 'clean:dist:linux' + arch], (done) ->
async.series [
# Update package.json
(callback) ->
jsonPath = './build/linux' + arch + '/opt/' + manifest.name + '/resources/app/package.json'
updateManifest jsonPath, (manifest) ->
manifest.portable = true
manifest.distrib = 'linux' + arch + ':tar'
manifest.buildNum = process.env.CIRCLE_BUILD_NUM
manifest.dev = false
, callback

# Remove the dev modules
applyIf args.prod, applySpawn 'npm', ['prune', '--production'],
cwd: './build/linux' + arch + '/opt/' + manifest.name + '/resources/app'

# Deduplicate dependencies
applyIf args.prod, applySpawn 'npm', ['dedupe'],
cwd: './build/linux' + arch + '/opt/' + manifest.name + '/resources/app'

# Compress the source files into an asar archive
async.apply asar.createPackage,
'./build/linux' + arch + '/opt/' + manifest.name + '/resources/app',
'./build/linux' + arch + '/opt/' + manifest.name + '/resources/app.asar'

# Remove leftovers
applyPromise del, './build/linux' + arch + '/opt/' + manifest.name + '/resources/app'

# Archive the files
(callback) ->
gulp.src './build/linux' + arch + '/opt/' + manifest.name + '/**/*'
.pipe tar(manifest.name + '-' + manifest.version + '-linux' + arch + '.tar.gz')
.pipe gzip()
.pipe gulp.dest './dist'
.on 'end', callback
], done

# Create the win32 installer; only works on Windows
gulp.task 'pack:win32:installer', ['build:win32', 'clean:dist:win32'], (done) ->
if process.platform isnt 'win32'
Expand Down Expand Up @@ -435,17 +476,21 @@ gulp.task 'pack:win32:nsis', ['build:win32', 'clean:dist:win32'], (done) ->
(callback) ->
properties =
'version-string':
ProductName: manifest.productName
CompanyName: manifest.authorName
FileDescription: manifest.productName
LegalCopyright: manifest.copyright
OriginalFilename: manifest.productName + '.exe'
ProductName: manifest.name
CompanyName: manifest.name
FileDescription: manifest.name
LegalCopyright: manifest.name
'file-version': manifest.version
'product-version': manifest.version

exePath = './dist/' + manifest.name + '-' + manifest.version + '-win32-nsis.exe'
logMessage = 'rcedit ' + exePath + ' properties'
rcedit exePath, properties, utils.log callback, logMessage, JSON.stringify(properties)
fakeCallback = (err) ->
if err
console.log 'rcedit failed'
console.error err
callback()
rcedit exePath, properties, utils.log fakeCallback, logMessage, JSON.stringify(properties)

# Sign the exe
(callback) ->
Expand Down Expand Up @@ -518,9 +563,3 @@ gulp.task 'pack:win32:portable', ['build:win32', 'clean:dist:win32'], (done) ->
.pipe gulp.dest './dist'
.on 'end', callback
], done

# Pack for the current platform by default
if process.platform is 'win32'
gulp.task 'pack', ['pack:' + platform() + ':installer']
else
gulp.task 'pack', ['pack:' + platform()]
111 changes: 0 additions & 111 deletions tasks/publish.coffee
Expand Up @@ -140,114 +140,3 @@ gulp.task 'publish:github', ->
mainManifest.bintray.artifactsRepoName + '/staging/' + dist + '/'
console.log 'Upload finished: ' + artifactsUrl if args.verbose
done()

# Upload AUR artifacts to Bintray
gulp.task 'publish:bintray:aur', ->
if not process.env.BINTRAY_API_KEY
return console.warn 'BINTRAY_API_KEY env var not set.'

fileNameShort = manifest.name + '-' + manifest.version + '-linux-amd64.deb'
fileNameLong = path.resolve './dist/', fileNameShort

host = 'https://api.bintray.com'
subject = mainManifest.bintray.subject
aurRepoName = mainManifest.bintray.aurRepoName

opts =
url: host + '/content/' + subject + '/' + aurRepoName + '/dist/' + fileNameShort
auth:
user: subject
pass: process.env.BINTRAY_API_KEY
headers:
'X-Bintray-Package': manifest.name
'X-Bintray-Version': manifest.version
'X-Bintray-Publish': 1
'X-Bintray-Override': 1

console.log 'Uploading', fileNameLong if args.verbose
fs.createReadStream fileNameLong
.pipe request.put opts, (err, res, body) ->
if err
console.log err
else
console.log body if args.verbose
artifactsUrl = 'https://dl.bintray.com/' + mainManifest.bintray.subject + '/' + aurRepoName + '/dist/'
console.log 'Upload finished: ' + artifactsUrl if args.verbose

# Publish AUR package
gulp.task 'publish:aur', ['publish:bintray:aur'], (done) ->
manifest.linux.name = manifest.name
manifest.linux.buildNum = process.env.CIRCLE_BUILD_NUM or manifest.buildNum
manifest.linux.productName = manifest.productName
manifest.linux.description = manifest.description
manifest.linux.homepage = manifest.homepage
manifest.linux.license = manifest.license
manifest.linux.version = manifest.version

manifest.linux.archlinux_depends = [
'desktop-file-utils'
'gconf'
'gtk2'
'gvfs'
'hicolor-icon-theme'
'libgudev'
'libgcrypt'
'libnotify'
'libxtst'
'nss'
'python'
'xdg-utils'
'libcap'
]

manifest.linux.archlinux_optdepends = [
'hunspell: spell check'
]

manifest.linux.gpgpub = mainManifest.bintray.gpgpub
manifest.linux.source_url = 'https://dl.bintray.com/' + mainManifest.bintray.subject + '/' +
mainManifest.bintray.aurRepoName + '/dist/' + manifest.name + '-' + manifest.version + '-linux-amd64.deb'

async.series [
# Calculate md5sum
(callback) ->
fileNameShort = manifest.name + '-' + manifest.version + '-linux-amd64.deb'
fileNameLong = path.resolve './dist/', fileNameShort
md5sum = crypto.createHash 'md5'
fs.createReadStream fileNameLong
.on 'data', (d) -> md5sum.update d
.on 'end', ->
manifest.linux.md5sum = md5sum.digest 'hex'
callback()

# Remove existing files
(callback) ->
cmd = 'rm'
args = ['-rf', './build/resources/aur']
applySpawn(cmd, args)(utils.log callback, cmd, args...)

# Clone the repo
(callback) ->
cmd = 'git'
args = ['clone', mainManifest.repository.aur, './build/resources/aur']
applySpawn(cmd, args)(utils.log callback, cmd, args...)

# Move the files
(callback) ->
gulp.src './resources/aur/**/*', {dot: true}
.pipe mustache manifest.linux
.pipe gulp.dest './build/resources/aur'
.on 'end', callback

# Rename .install file
async.apply fs.rename, './build/resources/aur/app.install', './build/resources/aur/' + manifest.name + '.install'

# Git: add files
applySpawn 'git', ['add', '.'], {cwd: './build/resources/aur/'}

# Git: commit
applySpawn 'git', ['commit', '-m', '[CI] v' + manifest.version], {cwd: './build/resources/aur/'}

# Git: push
applySpawn 'git', ['push'], {cwd: './build/resources/aur/'}
], done
2 changes: 1 addition & 1 deletion tasks/restart.coffee
Expand Up @@ -47,6 +47,6 @@ manifest = require '../src/package.json'
return

console.log 're-spawning app' if args.verbose
applySpawn(runnablePath, [], {stdio: 'inherit'})()
applySpawn(runnablePath, ['--debug'], {stdio: 'inherit'})()
done null
applySpawn(killCmd, killArgs)(cb)
2 changes: 1 addition & 1 deletion tasks/start.coffee
Expand Up @@ -16,7 +16,7 @@ manifest = require '../src/package.json'
# Start the app without any building
gulp.task 'start:' + dist, ->
console.log 'starting app' if args.verbose
applySpawn(runnablePath, [], {stdio: 'inherit'})()
applySpawn(runnablePath, ['--debug'], {stdio: 'inherit'})()

# Start for the current platform by default
gulp.task 'start', ['start:' + platform()]
2 changes: 1 addition & 1 deletion tasks/watch.coffee
Expand Up @@ -15,7 +15,7 @@ args = require './args'
gulp.task 'watch:' + dist, ['build:' + dist], ->
# Launch the app
console.log 'initial spawn' if args.verbose
applySpawn(runnablePath, [], {stdio: 'inherit'})()
applySpawn(runnablePath, ['--debug'], {stdio: 'inherit'})()

# Watch files
gulp.watch './src/styles/**/*', ['compile:' + dist + ':styles']
Expand Down