Compare commits

..

14 Commits

Author SHA1 Message Date
Akash Nimare
d6738b7087 sentry: Init sentry-electron. 2018-04-13 17:02:57 +05:30
Priyank Patel
f70432f4e3 internal-links: open non-image links in hidden webview.
* This will make sure that the current server webview will not 
reload due to URL change.
* Add an option to allow users to download the file attachments.

Improves: #469.
2018-04-11 23:21:01 +05:30
Abhigyan Khaund
60d693700e internal-links: Open image link in webapp lightbox.
This will open the image in the webapp lightbox. It shows the same behaviour
that happens when clicking on the image preview.

Improves: #469.
2018-04-09 18:16:57 +05:30
Akash Nimare
6e7333eab6 shortcut: Add F5 shortcut for reloading the app. 2018-04-07 19:52:46 +05:30
Akash Nimare
0d8dd1cd90 internal-links: Open file attachments within the app.
Changing the behaviour of opening the attachments in the
default browser to the app. This commit enables users to
download the attachments. If the attachment type is image
then we open the same in the app though users need to
click on the go-back button.

To-do - Open the image link in a lightbox.

Improves: #469.
2018-04-04 18:02:40 +05:30
Akash Nimare
0ee3757774 shortcuts: Update string for selecting the keyboard shortcuts node. 2018-03-30 14:56:17 +05:30
Akash Nimare
811df9f381 shortcuts: Remove unused shortcuts. 2018-03-30 01:56:48 +05:30
Akash Nimare
8bd02cc7e4 shortcuts: Update toggle sidebar shortcut to CMD/CTRL+SHIFT+S. 2018-03-30 01:53:07 +05:30
Akash Nimare
9d5d221371 test: Add a test for new org link. 2018-03-28 02:36:50 +05:30
Akash Nimare
6006f1a3f8 builder: Update electron-builder to v20.8.1.
Fixes: #442.
2018-03-27 21:27:32 +05:30
Robert
4f96df4a34 linux: Add Snap support for Linux.
This adds a new build configs in package.json for electron-builder
to be able to generate a snap package for zulip. 

Fixes #443.
2018-03-26 22:27:04 +05:30
Abhigyan Khaund
a13558fa16 org-settings: Responsive UI for connected orgs in smaller window sizes.
Fixes: #456.
2018-03-26 21:09:52 +05:30
Abhigyan Khaund
a1d19a385c menu: Warning dialog box for Reset App Settings. 2018-03-26 21:05:26 +05:30
Robert
c98667236e tools: Fix reinstall script for working across all platforms.
Make reinstall script compatible with Windows. 

Fixes #440.
2018-03-24 00:04:26 +05:30
18 changed files with 228 additions and 11278 deletions

7
.gitignore vendored
View File

@@ -7,6 +7,13 @@ node_modules/
# Compiled binary build directory # Compiled binary build directory
dist/ dist/
#snap generated files
snap/parts
snap/prime
snap/snap
snap/stage
snap/*.snap
# Logs # Logs
logs logs
*.log *.log

View File

@@ -2,7 +2,7 @@
const os = require('os'); const os = require('os');
const path = require('path'); const path = require('path');
const { app, shell, BrowserWindow, Menu } = require('electron'); const { app, shell, BrowserWindow, Menu, dialog } = require('electron');
const fs = require('fs-extra'); const fs = require('fs-extra');
@@ -87,7 +87,7 @@ class AppMenu {
} }
}, { }, {
label: 'Toggle Sidebar', label: 'Toggle Sidebar',
accelerator: 'CommandOrControl+S', accelerator: 'CommandOrControl+Shift+S',
click(item, focusedWindow) { click(item, focusedWindow) {
if (focusedWindow) { if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('showSidebar'); const newValue = !ConfigUtil.getConfigItem('showSidebar');
@@ -388,20 +388,32 @@ class AppMenu {
} }
static resetAppSettings() { static resetAppSettings() {
const resetAppSettingsMessage = 'By proceeding you will be removing all connected organizations and preferences from Zulip.';
// We save App's settings/configurations in following files // We save App's settings/configurations in following files
const settingFiles = ['window-state.json', 'domain.json', 'settings.json']; const settingFiles = ['window-state.json', 'domain.json', 'settings.json'];
settingFiles.forEach(settingFileName => { dialog.showMessageBox({
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName); type: 'warning',
fs.access(getSettingFilesPath, error => { buttons: ['YES', 'NO'],
if (error) { defaultId: 0,
console.log(error); message: 'Are you sure?',
} else { detail: resetAppSettingsMessage
fs.unlink(getSettingFilesPath, () => { }, response => {
AppMenu.sendAction('clear-app-data'); if (response === 0) {
settingFiles.forEach(settingFileName => {
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName);
fs.access(getSettingFilesPath, error => {
if (error) {
console.log(error);
} else {
fs.unlink(getSettingFilesPath, () => {
AppMenu.sendAction('clear-app-data');
});
}
}); });
} });
}); }
}); });
} }

1380
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -30,12 +30,13 @@
"electron-is-dev": "0.3.0", "electron-is-dev": "0.3.0",
"electron-log": "2.2.7", "electron-log": "2.2.7",
"electron-spellchecker": "1.1.2", "electron-spellchecker": "1.1.2",
"electron-updater": "2.18.2", "electron-updater": "2.21.4",
"electron-window-state": "4.1.1", "electron-window-state": "4.1.1",
"is-online": "7.0.0", "is-online": "7.0.0",
"node-json-db": "0.7.3", "node-json-db": "0.7.3",
"request": "2.81.0", "request": "2.81.0",
"semver": "5.4.1", "semver": "5.4.1",
"@sentry/electron": "0.5.0",
"wurl": "2.5.0" "wurl": "2.5.0"
}, },
"optionalDependencies": { "optionalDependencies": {

View File

@@ -262,6 +262,11 @@ webview {
flex-direction: column; flex-direction: column;
} }
webview.download-webview {
z-index: -1;
pointer-events: none;
}
webview.onload { webview.onload {
transition: opacity 1s cubic-bezier(0.95, 0.05, 0.795, 0.035); transition: opacity 1s cubic-bezier(0.95, 0.05, 0.795, 0.035);
} }

View File

@@ -604,4 +604,20 @@ input.toggle-round:checked+label:after {
margin-right: 2px; margin-right: 2px;
width: 40%; width: 40%;
} }
} }
@media (max-width: 900px) {
.settings-card {
flex-direction: column;
align-items: center;
}
.server-info-right {
flex-direction: column;
align-items: center;
}
.action {
margin-top: 10px;
}
}

View File

@@ -0,0 +1,36 @@
const { shell } = require('electron').remote;
const LinkUtil = require('../utils/link-util');
const DomainUtil = require('../utils/domain-util');
const hiddenWebView = require('../components/hidden-webview');
function handleExternalLink(event) {
const { url } = event;
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
// Whitelist URLs which are allowed to be opened in the app
const {
isInternalUrl: isWhiteListURL,
isUploadsUrl: isUploadsURL
} = LinkUtil.isInternal(domainPrefix, url);
if (isWhiteListURL) {
event.preventDefault();
// only open the pdf, mp3, mp4 etc.. in hidden webview since opening the
// image in webview will do nothing and will not save it
// whereas the pdf will be saved to user desktop once openened in
// in the hidden webview and will not trigger webview reload
if (!LinkUtil.isImage(url) && isUploadsURL) {
hiddenWebView.loadURL(url);
return;
}
// open internal urls inside the current webview.
this.$el.loadURL(url);
} else {
event.preventDefault();
shell.openExternal(url);
}
}
module.exports = handleExternalLink;

View File

@@ -0,0 +1,9 @@
// this hidden webview will be used to open pdf url and
// save it to user's computer without triggering a reload
// when navigating to pdf url to download it.
const hiddenWebView = document.createElement('webview');
hiddenWebView.classList.add('download-webview');
hiddenWebView.src = 'about:blank';
document.querySelector('#webviews-container').appendChild(hiddenWebView);
module.exports = hiddenWebView;

View File

@@ -3,13 +3,12 @@
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const DomainUtil = require(__dirname + '/../utils/domain-util.js');
const ConfigUtil = require(__dirname + '/../utils/config-util.js'); const ConfigUtil = require(__dirname + '/../utils/config-util.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js'); const SystemUtil = require(__dirname + '/../utils/system-util.js');
const LinkUtil = require(__dirname + '/../utils/link-util.js'); const { app, dialog } = require('electron').remote;
const { shell, app, dialog } = require('electron').remote;
const BaseComponent = require(__dirname + '/../components/base.js'); const BaseComponent = require(__dirname + '/../components/base.js');
const handleExternalLink = require(__dirname + '/../components/handle-external-link.js');
const shouldSilentWebview = ConfigUtil.getConfigItem('silent'); const shouldSilentWebview = ConfigUtil.getConfigItem('silent');
class WebView extends BaseComponent { class WebView extends BaseComponent {
@@ -46,16 +45,7 @@ class WebView extends BaseComponent {
registerListeners() { registerListeners() {
this.$el.addEventListener('new-window', event => { this.$el.addEventListener('new-window', event => {
const { url } = event; handleExternalLink.call(this, event);
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
if (LinkUtil.isInternal(domainPrefix, url) || url === (domainPrefix + '/')) {
event.preventDefault();
this.$el.loadURL(url);
} else {
event.preventDefault();
shell.openExternal(url);
}
}); });
if (shouldSilentWebview) { if (shouldSilentWebview) {

View File

@@ -78,14 +78,6 @@ class ShortcutsSection extends BaseSection {
<td><kbd>${userOSKey}</kbd><kbd>A</kbd></td> <td><kbd>${userOSKey}</kbd><kbd>A</kbd></td>
<td>Select All</td> <td>Select All</td>
</tr> </tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>F</kbd></td>
<td>Find</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>G</kbd></td>
<td>Find Next</td>
</tr>
<tr> <tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>Space</kbd></td> <td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>Space</kbd></td>
<td>Emoji & Symbols</td> <td>Emoji & Symbols</td>
@@ -121,7 +113,7 @@ class ShortcutsSection extends BaseSection {
<td>Actual Size</td> <td>Actual Size</td>
</tr> </tr>
<tr> <tr>
<td><kbd>${userOSKey}</kbd><kbd>S</kbd></td> <td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td>Toggle Sidebar</td> <td>Toggle Sidebar</td>
</tr> </tr>
<tr> <tr>
@@ -256,7 +248,7 @@ class ShortcutsSection extends BaseSection {
<td>Actual Size</td> <td>Actual Size</td>
</tr> </tr>
<tr> <tr>
<td><kbd>${userOSKey}</kbd> + <kbd>S</kbd></td> <td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td>Toggle Sidebar</td> <td>Toggle Sidebar</td>
</tr> </tr>
<tr> <tr>

View File

@@ -4,6 +4,7 @@ const { ipcRenderer } = require('electron');
const SetupSpellChecker = require('./spellchecker'); const SetupSpellChecker = require('./spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js'); const ConfigUtil = require(__dirname + '/utils/config-util.js');
const LinkUtil = require(__dirname + '/utils/link-util.js');
// eslint-disable-next-line import/no-unassigned-import // eslint-disable-next-line import/no-unassigned-import
require('./notification'); require('./notification');
@@ -23,7 +24,7 @@ const shortcut = () => {
// Create the menu for the below // Create the menu for the below
const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]'); const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
// Additional check // Additional check
if (node.text.trim().toLowerCase() === 'keyboard shortcuts') { if (node.text.trim().toLowerCase() === 'keyboard shortcuts (?)') {
node.click(); node.click();
} else { } else {
// Atleast click the dropdown // Atleast click the dropdown
@@ -55,6 +56,24 @@ document.addEventListener('DOMContentLoaded', () => {
ipcRenderer.send('forward-message', 'reload-viewer'); ipcRenderer.send('forward-message', 'reload-viewer');
}); });
} }
// Open image attachment link in the lightbox instead of opening in the default browser
const { $, lightbox } = window;
$('#main_div').on('click', '.message_content p a', function (e) {
const url = $(this).attr('href');
if (LinkUtil.isImage(url)) {
const $img = $(this).parent().siblings('.message_inline_image').find('img');
// prevent the image link from opening in a new page.
e.preventDefault();
// prevent the message compose dialog from happening.
e.stopPropagation();
lightbox.open($img);
}
});
}); });
// Clean up spellchecker events after you navigate away from this page; // Clean up spellchecker events after you navigate away from this page;
@@ -63,3 +82,10 @@ window.addEventListener('beforeunload', () => {
SetupSpellChecker.unsubscribeSpellChecker(); SetupSpellChecker.unsubscribeSpellChecker();
}); });
// electron's globalShortcut can cause unexpected results
// so adding the reload shortcut in the old-school way
document.addEventListener('keydown', event => {
if (event.code === 'F5') {
ipcRenderer.send('forward-message', 'hard-reload');
}
});

View File

@@ -19,7 +19,20 @@ class LinkUtil {
const currentDomain = wurl('hostname', currentUrl); const currentDomain = wurl('hostname', currentUrl);
const newDomain = wurl('hostname', newUrl); const newDomain = wurl('hostname', newUrl);
return (currentDomain === newDomain) && newUrl.includes('/#narrow'); const sameDomainUrl = (currentDomain === newDomain || newUrl === currentUrl + '/');
const isUploadsUrl = newUrl.includes(currentUrl + '/user_uploads/');
const isInternalUrl = newUrl.includes('/#narrow') || isUploadsUrl;
return {
isInternalUrl: sameDomainUrl && isInternalUrl,
isUploadsUrl
};
}
isImage(url) {
// test for images extension as well as urls like .png?s=100
const isImageUrl = /\.(bmp|gif|jpg|jpeg|png|webp)\?*.*$/i;
return isImageUrl.test(url);
} }
} }

View File

@@ -15,6 +15,7 @@ To build and run the app from source, you'll need the following:
* [Python](https://www.python.org/downloads/release/python-2713/) * [Python](https://www.python.org/downloads/release/python-2713/)
(v2.7.x recommended) (v2.7.x recommended)
* A C++ compiler compatible with C++11 * A C++ compiler compatible with C++11
* Linux users also need [Snapcraft](https://snapcraft.io/)
* Development headers for the libXext, libXtst, and libxkbfile libraries * Development headers for the libXext, libXtst, and libxkbfile libraries
### Debian/Ubuntu and friends ### Debian/Ubuntu and friends
@@ -25,7 +26,7 @@ manager (see [here][nodesource-install] for more on the first command):
```sh ```sh
$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash - $ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
$ sudo apt install git nodejs python build-essential libxext-dev libxtst-dev libxkbfile-dev libgconf-2-4 $ sudo apt install git nodejs python build-essential snapcraft libxext-dev libxtst-dev libxkbfile-dev libgconf-2-4
``` ```
[nodesource-install]: https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions [nodesource-install]: https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions
@@ -76,7 +77,7 @@ This command will produce distributable packages or installers for the
operating system you're running on: operating system you're running on:
* on Windows, a Windows installer file * on Windows, a Windows installer file
* on macOS, a `.dmg` file * on macOS, a `.dmg` file
* on Linux, a plain `.zip` file as well as a `.deb` file and an * on Linux, a plain `.zip` file as well as a `.deb` file, `.snap` file and an
`AppImage` file. `AppImage` file.
To generate all three types, you will need all three operating To generate all three types, you will need all three operating
systems. systems.

9854
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,7 +22,7 @@
}, },
"scripts": { "scripts": {
"start": "electron app --disable-http-cache --no-electron-connect", "start": "electron app --disable-http-cache --no-electron-connect",
"reinstall": "./tools/reinstall-node-modules", "reinstall": "node ./tools/reinstall-node-modules.js",
"postinstall": "electron-builder install-app-deps", "postinstall": "electron-builder install-app-deps",
"test": "xo", "test": "xo",
"test-e2e": "gulp test-e2e", "test-e2e": "gulp test-e2e",
@@ -55,7 +55,8 @@
"target": [ "target": [
"deb", "deb",
"zip", "zip",
"AppImage" "AppImage",
"snap"
], ],
"maintainer": "Akash Nimare <svnitakash@gmail.com>" "maintainer": "Akash Nimare <svnitakash@gmail.com>"
}, },
@@ -64,6 +65,9 @@
"afterInstall": "./scripts/debian-add-repo.sh", "afterInstall": "./scripts/debian-add-repo.sh",
"afterRemove": "./scripts/debian-uninstaller.sh" "afterRemove": "./scripts/debian-uninstaller.sh"
}, },
"snap": {
"synopsis": "Zulip Desktop App"
},
"dmg": { "dmg": {
"background": "build/appdmg.png", "background": "build/appdmg.png",
"icon": "build/icon.icns", "icon": "build/icon.icns",
@@ -114,7 +118,7 @@
"cp-file": "^5.0.0", "cp-file": "^5.0.0",
"devtron": "1.4.0", "devtron": "1.4.0",
"electron": "1.8.4", "electron": "1.8.4",
"electron-builder": "19.53.6", "electron-builder": "20.8.1",
"electron-connect": "0.6.2", "electron-connect": "0.6.2",
"electron-debug": "1.4.0", "electron-debug": "1.4.0",
"google-translate-api": "2.3.0", "google-translate-api": "2.3.0",

37
snap/snapcraft.yaml Normal file
View File

@@ -0,0 +1,37 @@
name: zulip
version: 1.8.2
summary: Zulip
description: Zulip Desktop Client for Linux
confinement: strict
grade: stable
icon: ../build/icon.png
apps:
zulip:
command: env TMPDIR=$XDG_RUNTIME_DIR desktop-launch $SNAP/zulip
plugs:
- desktop
- desktop-legacy
- home
- x11
- unity7
- browser-support
- network
- gsettings
- pulseaudio
- opengl
parts:
app:
plugin: dump
stage-packages:
- libasound2
- libgconf2-4
- libnotify4
- libnspr4
- libnss3
- libpcre3
- libpulse0
- libxss1
- libxtst6
source: ../dist/linux-unpacked
after:
- desktop-gtk2

View File

@@ -0,0 +1,17 @@
const test = require('tape')
const setup = require('./setup')
// Create new org link should open in the default browser [WIP]
test('new-org-link', function (t) {
t.timeoutAfter(50e3)
setup.resetTestDataDir()
const app = setup.createApp()
setup.waitForLoad(app, t)
.then(() => app.client.windowByIndex(1)) // focus on webview
.then(() => app.client.click('#open-create-org-link')) // Click on new org link button
.then(() => setup.wait(5000))
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env node
const {exec} = require('child_process');
const path = require('path');
const isWindows = process.platform === 'win32';
const command = path.join(__dirname, `reinstall-node-modules${isWindows ? '.cmd' : ''}`);
const proc = exec(command, error => {
if (error) {
console.error(error);
}
});
proc.stdout.on('data', data => console.log(data.toString()));
proc.stderr.on('data', data => console.error(data.toString()));
proc.on('exit', code => {
process.exit(code);
});