Compare commits

...

25 Commits

Author SHA1 Message Date
Akash Nimare
dbf66e5585 Update snap version. 2018-04-19 21:49:02 +05:30
Akash Nimare
329e27142b snap: Fix snap config. 2018-04-19 21:46:31 +05:30
Akash Nimare
9f756cad3e snap: Update snap icon path. 2018-04-19 20:58:48 +05:30
Akash Nimare
6db6b7c482 reconnect-util: Do not throw error message on server page while reconnecting. 2018-04-18 22:00:30 +05:30
Akash Nimare
09c45e75e8 Update package-lock files. 2018-04-18 17:51:22 +05:30
Akash Nimare
120b80cf65 release: 🎉 v2.0.0. 2018-04-17 15:51:32 +05:30
Abhigyan Khaund
22f705960d sidebar: Add scrollbar for list of organizations on overflow. 2018-04-16 19:38:26 +05:30
Priyank Patel
ca8ce1deaa report-issue: Add report issue UX using send-feedback electron element.
This uses @electron-elements/send-feedback package to easily implement
UX for reporting issues.
2018-04-16 11:34:11 +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
Akash Nimare
b80c3d007b server-page: Responsive tweaks for divider. 2018-03-23 12:18:44 +05:30
Akash Nimare
70fb198a0b release: 🎉 new-release v1.9.0. 2018-03-23 03:59:56 +05:30
Abhigyan Khaund
7276bfeaa7 org-settings-page: UI fixes and tweaks for the org-settings page.
Fixes:
* Move "Disconnect" button to be at the far right.
* Ellipsis text-overflow for server-url.
* Show server url as title tooltip on hover.
* Remove settings-card's right transition on hover.

Improves: #456.
2018-03-23 03:45:50 +05:30
Akash Nimare
abd71330ba setting-page: Minor UI fixes for add new server page.
Minor fixes - 
* Update link to create a new organization.
* Update placeholder text for demo zulip server.
* Remove border from connect button.
* Add overflow for smaller window.
* Fix a typo in background color.
* Use buttons for server actions.
* Make new server page responsive.
* Focus buttons on tab.
* Update styling of button.
* Make add new server page responsive.

Fixes: #457.
2018-03-23 01:36:43 +05:30
24 changed files with 1680 additions and 548 deletions

7
.gitignore vendored
View File

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

View File

@@ -1,8 +1,7 @@
'use strict';
const os = require('os');
const path = require('path');
const { app, shell, BrowserWindow, Menu } = require('electron');
const { app, shell, BrowserWindow, Menu, dialog } = require('electron');
const fs = require('fs-extra');
@@ -87,7 +86,7 @@ class AppMenu {
}
}, {
label: 'Toggle Sidebar',
accelerator: 'CommandOrControl+S',
accelerator: 'CommandOrControl+Shift+S',
click(item, focusedWindow) {
if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('showSidebar');
@@ -139,13 +138,11 @@ class AppMenu {
}, {
label: 'Report an Issue...',
click() {
const body = `
<!-- Please succinctly describe your issue and steps to reproduce it. -->
-
${app.getName()} ${app.getVersion()}
Electron ${process.versions.electron}
${process.platform} ${process.arch} ${os.release()}`;
shell.openExternal(`https://github.com/zulip/zulip-electron/issues/new?body=${encodeURIComponent(body)}`);
// the goal is to notify the main.html BrowserWindow
// which may not be the focused window.
BrowserWindow.getAllWindows().forEach(window => {
window.webContents.send('open-feedback-modal');
});
}
}];
}
@@ -388,20 +385,32 @@ class AppMenu {
}
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
const settingFiles = ['window-state.json', 'domain.json', 'settings.json'];
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');
dialog.showMessageBox({
type: 'warning',
buttons: ['YES', 'NO'],
defaultId: 0,
message: 'Are you sure?',
detail: resetAppSettingsMessage
}, response => {
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');
});
}
});
}
});
});
}
});
}

150
app/package-lock.json generated
View File

@@ -1,9 +1,22 @@
{
"name": "zulip",
"version": "1.8.1",
"version": "2.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@electron-elements/send-feedback": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@electron-elements/send-feedback/-/send-feedback-1.0.7.tgz",
"integrity": "sha512-3VS5JlAkZi0Ik2xHPdO0ZT3VvtLH7fqY8UXKHiR4BJxJPdoqlPM9Z0YT0uQxSz/xfBjcfCRV1DlnuJ/S3J99ow==",
"requires": {
"@electron-elements/utils": "1.0.3"
}
},
"@electron-elements/utils": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@electron-elements/utils/-/utils-1.0.3.tgz",
"integrity": "sha512-SMIKfu0UrmslHVKDQhOJDE2OeynWnuznkZlhgB5X2V2h+0t00Pq+ekusxBAAXIdZSdnLPlMcH3zZ6TfDq6mbPA=="
},
"@paulcbetts/cld": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@paulcbetts/cld/-/cld-2.4.6.tgz",
@@ -52,9 +65,9 @@
"integrity": "sha1-u4evVoytA0pOSMS9r2Bno6JwExc="
},
"argparse": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz",
"integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=",
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "1.0.3"
}
@@ -151,6 +164,27 @@
"concat-map": "0.0.1"
}
},
"builder-util-runtime": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-4.2.0.tgz",
"integrity": "sha512-cROCExnJOJvRD58HHcnrrgyRAoDHGZT0hKox0op7vTuuuRC/1JKMXvSR+Hxy7KWy/aEmKu0HfSqMd4znDEqQsA==",
"requires": {
"bluebird-lst": "1.0.5",
"debug": "3.1.0",
"fs-extra-p": "4.5.2",
"sax": "1.2.4"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
}
}
},
"cacheable-request": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-2.1.4.tgz",
@@ -347,62 +381,25 @@
}
},
"electron-updater": {
"version": "2.18.2",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-2.18.2.tgz",
"integrity": "sha512-Kl7zK2E5R/EBYZapDUxWEHDE/ybD+Gw9gheqIy4uVSeKWjkkQkit/QpPOK8LaQwbXD5vDawp6lrB7Z0Yoacd2A==",
"version": "2.21.4",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-2.21.4.tgz",
"integrity": "sha512-x6QSbyxgGR3szIOBtFoCJH0TfgB55AWHaXmilNgorfvpnCdEMQEATxEzLOW0JCzzcB5y3vBrawvmMUEdXwutmA==",
"requires": {
"bluebird-lst": "1.0.5",
"builder-util-runtime": "4.0.5",
"builder-util-runtime": "4.2.0",
"electron-is-dev": "0.3.0",
"fs-extra-p": "4.5.0",
"js-yaml": "3.10.0",
"fs-extra-p": "4.5.2",
"js-yaml": "3.11.0",
"lazy-val": "1.0.3",
"lodash.isequal": "4.5.0",
"semver": "5.4.1"
"semver": "5.5.0",
"source-map-support": "0.5.4"
},
"dependencies": {
"builder-util-runtime": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-4.0.5.tgz",
"integrity": "sha512-NT8AxWH6miZQHnZzaTVjVp1uc6C/mWlxi6GQXKpd4CwyTQd3rT7+poOGrcOhtIiHYCL9VEbRsVfxUAPPsgqJdg==",
"requires": {
"bluebird-lst": "1.0.5",
"debug": "3.1.0",
"fs-extra-p": "4.5.0",
"sax": "1.2.4"
}
},
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"fs-extra": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
"integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
"requires": {
"graceful-fs": "4.1.11",
"jsonfile": "4.0.0",
"universalify": "0.1.1"
}
},
"fs-extra-p": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.0.tgz",
"integrity": "sha512-V/sdZmV+Yx3+nfXmjRTdBP4mVWCt7hZ0+ZOv+IZo+6fdkBxafaGsI7mYeNv/J3rWyz+mIToCFQORFSwt1bZw8Q==",
"requires": {
"bluebird-lst": "1.0.5",
"fs-extra": "5.0.0"
}
},
"lazy-val": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg=="
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
}
}
},
@@ -476,6 +473,25 @@
"readable-stream": "2.3.4"
}
},
"fs-extra": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
"integrity": "sha512-66Pm4RYbjzdyeuqudYqhFiNBbCIuI9kgRqLPSHIlXHidW8NIQtVdkM1yeZ4lXwuhbTETv3EUGMNHAAw6hiundQ==",
"requires": {
"graceful-fs": "4.1.11",
"jsonfile": "4.0.0",
"universalify": "0.1.1"
}
},
"fs-extra-p": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.5.2.tgz",
"integrity": "sha512-ZYqFpBdy9w7PsK+vB30j+TnHOyWHm/CJbUq1qqoE8tb71m6qgk5Wa7gp3MYQdlGFxb9vfznF+yD4jcl8l+y91A==",
"requires": {
"bluebird-lst": "1.0.5",
"fs-extra": "5.0.0"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -706,11 +722,11 @@
}
},
"js-yaml": {
"version": "3.10.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.10.0.tgz",
"integrity": "sha512-O2v52ffjLa9VeM43J4XocZE//WT9N0IiwDa3KSHH7Tu8CtH+1qM8SIZvnsTh6v+4yFy5KUY3BHUVwjpfAWsjIA==",
"version": "3.11.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.11.0.tgz",
"integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==",
"requires": {
"argparse": "1.0.9",
"argparse": "1.0.10",
"esprima": "4.0.0"
}
},
@@ -791,6 +807,11 @@
"json-buffer": "3.0.0"
}
},
"lazy-val": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg=="
},
"lodash.assign": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.assign/-/lodash.assign-4.2.0.tgz",
@@ -1186,7 +1207,7 @@
"semver": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz",
"integrity": "sha1-4FnAnYVx8FQII3M0M1BdOi8AsY4="
"integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg=="
},
"sntp": {
"version": "1.0.9",
@@ -1204,6 +1225,19 @@
"is-plain-obj": "1.1.0"
}
},
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"source-map-support": {
"version": "0.5.4",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz",
"integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==",
"requires": {
"source-map": "0.6.1"
}
},
"spawn-rx": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.12.tgz",

View File

@@ -1,7 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "1.8.2",
"version": "2.0.0",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
@@ -26,11 +26,12 @@
"InstantMessaging"
],
"dependencies": {
"@electron-elements/send-feedback": "1.0.7",
"auto-launch": "5.0.1",
"electron-is-dev": "0.3.0",
"electron-log": "2.2.7",
"electron-spellchecker": "1.1.2",
"electron-updater": "2.18.2",
"electron-updater": "2.21.4",
"electron-window-state": "4.1.1",
"is-online": "7.0.0",
"node-json-db": "0.7.3",

View File

@@ -45,6 +45,28 @@ body {
transition: all 0.6s ease-out;
}
#view-controls-container {
height: calc(100% - 156px);
overflow-y: hidden;
}
#view-controls-container:hover {
overflow-y: overlay;
}
#view-controls-container::-webkit-scrollbar {
width: 4px;
}
#view-controls-container::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
}
#view-controls-container::-webkit-scrollbar-thumb {
background-color: darkgrey;
outline: 1px solid slategrey;
}
@font-face {
font-family: 'Material Icons';
font-style: normal;
@@ -262,6 +284,11 @@ webview {
flex-direction: column;
}
webview.download-webview {
z-index: -1;
pointer-events: none;
}
webview.onload {
transition: opacity 1s cubic-bezier(0.95, 0.05, 0.795, 0.035);
}
@@ -403,3 +430,26 @@ webview.focus {
opacity: 1;
}
}
send-feedback {
width: 60%;
height: 85%;
}
#feedback-modal {
display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(68, 67, 67, 0.81);
align-items: center;
justify-content: center;
z-index: 2;
transition: all 1s ease-out;
}
#feedback-modal.show {
display: flex;
}

View File

@@ -129,6 +129,7 @@ td:nth-child(odd) {
content: '';
}
/* We don't want to show this in nav item since we have the + button for adding an Organization */
#nav-AddServer {
@@ -198,7 +199,11 @@ img.server-info-icon {
.server-info-right {
margin-top: 4px;
min-width: 55%;
width: 55%;
display: flex;
align-items: baseline;
justify-content: space-between;
flex-grow: 1;
}
.server-info-row {
@@ -214,7 +219,9 @@ img.server-info-icon {
}
.server-url {
min-width: 60%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.server-info-alias {
@@ -304,7 +311,6 @@ img.server-info-icon {
}
.settings-card:hover {
border-left: 8px solid #bcbcbc;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 0px 0px rgba(0, 0, 0, 0.12);
}
@@ -319,9 +325,10 @@ img.server-info-icon {
border: rgba(239, 83, 80, 0.5) solid 1px;
}
.red:hover{
.red:hover {
color: #e63431;
border: rgba(239, 83, 80, 0.7) solid 1px;;
border: rgba(239, 83, 80, 0.7) solid 1px;
;
}
.blue {
@@ -477,6 +484,7 @@ input.toggle-round:checked+label:after {
height: 100%;
/* background: rgba(61, 64, 67, 15); */
background: linear-gradient(35deg, #003b52, #45b59b);
overflow: auto;
}
@@ -499,15 +507,14 @@ input.toggle-round:checked+label:after {
.divider {
margin-bottom: 30px;
margin-right: 10px;
margin-top: 30px;
color: #7d878a;
}
.divider hr {
margin-left: auto;
margin-right: auto;
width: 45%;
margin-left: 8px;
margin-right: 8px;
width: 44%;
}
.left {
@@ -523,24 +530,31 @@ input.toggle-round:checked+label:after {
text-align: center;
align-items: center;
padding-top: 13px;
margin-left: -5px;
}
.server-center span {
.server-center button {
font-weight: bold;
font-size: 1.1rem;
padding: 10px;
margin: auto;
align-items: center;
text-align: center;
}
.server-center .blue {
color: #fff;
background: #4EBFAC;
border-color: none;
border: none;
width: 98%;
height: 46px;
border-radius: 3px;
cursor: pointer;
}
.server-center .blue:hover {
border-right: 2px solid #309085;
border-bottom: 2px solid #309085;
.server-center button:hover {
background: #329588;
}
.server-center button:focus {
background: #329588;
}
@@ -564,10 +578,46 @@ input.toggle-round:checked+label:after {
padding: 40px;
min-width: 300px;
}
.server-center .blue {
margin-right: 1px;
.server-center button {
margin-right: -12px;
width: 100%;
}
.divider {
margin-right: -8px;
}
.divider hr {
margin-left: 6px;
margin-right: 6px;
width: 43%;
}
#new-server-container {
padding-left: 0px;
}
}
@media (max-width: 600px) {
.divider {
margin-left: 4%;
}
.divider hr {
margin-left: 2px;
margin-right: 2px;
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 fs = require('fs');
const DomainUtil = require(__dirname + '/../utils/domain-util.js');
const ConfigUtil = require(__dirname + '/../utils/config-util.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js');
const LinkUtil = require(__dirname + '/../utils/link-util.js');
const { shell, app, dialog } = require('electron').remote;
const { app, dialog } = require('electron').remote;
const BaseComponent = require(__dirname + '/../components/base.js');
const handleExternalLink = require(__dirname + '/../components/handle-external-link.js');
const shouldSilentWebview = ConfigUtil.getConfigItem('silent');
class WebView extends BaseComponent {
@@ -46,16 +45,7 @@ class WebView extends BaseComponent {
registerListeners() {
this.$el.addEventListener('new-window', event => {
const { url } = 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);
}
handleExternalLink.call(this, event);
});
if (shouldSilentWebview) {

View File

@@ -0,0 +1,62 @@
const { app } = require('electron').remote;
const path = require('path');
const fs = require('fs');
const SendFeedback = require('@electron-elements/send-feedback');
// make the button color match zulip app's theme
SendFeedback.customStyles = `
button:hover, button:focus {
border-color: #4EBFAC;
color: #4EBFAC;
}
button:active {
background-color: #f1f1f1;
color: #4EBFAC;
}
button {
background-color: #4EBFAC;
border-color: #4EBFAC;
}
`;
customElements.define('send-feedback', SendFeedback);
const sendFeedback = document.querySelector('send-feedback');
const feedbackHolder = sendFeedback.parentElement;
// customize the fields of custom elements
sendFeedback.title = 'Report Issue';
sendFeedback.titleLabel = 'Issue title:';
sendFeedback.titlePlaceholder = 'Enter issue title';
sendFeedback.textareaLabel = 'Describe the issue:';
sendFeedback.textareaPlaceholder = 'Succinctly describe your issue and steps to reproduce it...';
sendFeedback.buttonLabel = 'Report Issue';
sendFeedback.loaderSuccessText = '';
sendFeedback.useReporter('emailReporter', {
email: 'akash@zulipchat.com'
});
feedbackHolder.addEventListener('click', e => {
// only remove the class if the grey out faded
// part is clicked and not the feedback element itself
if (e.target === e.currentTarget) {
feedbackHolder.classList.remove('show');
}
});
sendFeedback.addEventListener('feedback-submitted', () => {
setTimeout(() => {
feedbackHolder.classList.remove('show');
}, 1000);
});
const dataDir = app.getPath('userData');
const logsDir = path.join(dataDir, '/Logs');
sendFeedback.logs.push(...fs.readdirSync(logsDir).map(file => path.join(logsDir, file)));
module.exports = {
feedbackHolder,
sendFeedback
};

View File

@@ -12,6 +12,7 @@ const ServerTab = require(__dirname + '/js/components/server-tab.js');
const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
const ConfigUtil = require(__dirname + '/js/utils/config-util.js');
const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js');
const { feedbackHolder } = require(__dirname + '/js/feedback.js');
class ServerManagerView {
constructor() {
@@ -175,7 +176,7 @@ class ServerManagerView {
});
});
this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip);
this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip, true);
this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip);
this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip);
this.sidebarHoverEvent(this.$backButton, this.$backTooltip);
@@ -187,9 +188,17 @@ class ServerManagerView {
return currentIndex;
}
sidebarHoverEvent(SidebarButton, SidebarTooltip) {
sidebarHoverEvent(SidebarButton, SidebarTooltip, addServer = false) {
SidebarButton.addEventListener('mouseover', () => {
SidebarTooltip.removeAttribute('style');
// To handle position of add server tooltip due to scrolling of list of organizations
// This could not be handled using CSS, hence the top of the tooltip is made same
// as that of its parent element.
// This needs to handled only for the add server tooltip and not others.
if (addServer) {
const { top } = SidebarButton.getBoundingClientRect();
SidebarTooltip.style.top = top + 'px';
}
});
SidebarButton.addEventListener('mouseout', () => {
SidebarTooltip.style.display = 'none';
@@ -199,6 +208,11 @@ class ServerManagerView {
onHover(index, serverName) {
this.$serverIconTooltip[index].innerHTML = serverName;
this.$serverIconTooltip[index].removeAttribute('style');
// To handle position of servers' tooltip due to scrolling of list of organizations
// This could not be handled using CSS, hence the top of the tooltip is made same
// as that of its parent element.
const { top } = this.$serverIconTooltip[index].parentElement.getBoundingClientRect();
this.$serverIconTooltip[index].style.top = top + 'px';
}
onHoverOut(index) {
@@ -496,6 +510,10 @@ class ServerManagerView {
}
ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount));
});
ipcRenderer.on('open-feedback-modal', () => {
feedbackHolder.classList.add('show');
});
}
}

View File

@@ -15,11 +15,11 @@ class NewServerForm extends BaseComponent {
<div class="server-input-container">
<div class="title">Organization URL</div>
<div class="add-server-info-row">
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or chat.your-organization.com"/>
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/>
</div>
<div class="server-center">
<div class="action blue server-save-action">
<span id="connect">Connect</span>
<div class="server-save-action">
<button id="connect">Connect</button>
</div>
</div>
<div class="server-center">
@@ -28,8 +28,8 @@ class NewServerForm extends BaseComponent {
</div>
</div>
<div class="server-center">
<div class="action blue server-save-action">
<span id="open-create-org-link">Create a new organization</span>
<div class="server-save-action">
<button id="open-create-org-link">Create a new organization</button>
</div>
</div>
</div>
@@ -44,7 +44,6 @@ class NewServerForm extends BaseComponent {
initForm() {
this.$newServerForm = this.generateNodeFromTemplate(this.template());
this.$saveServerButton = this.$newServerForm.getElementsByClassName('server-save-action')[0];
this.props.$root.innerHTML = '';
this.props.$root.appendChild(this.$newServerForm);
@@ -64,7 +63,7 @@ class NewServerForm extends BaseComponent {
}
openCreateNewOrgExternalLink() {
const link = 'https://zulipchat.com/beta/';
const link = 'https://zulipchat.com/new/';
const externalCreateNewOrgEl = document.getElementById('open-create-org-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);

View File

@@ -23,7 +23,7 @@ class ServerInfoForm extends BaseComponent {
</div>
<div class="server-info-right">
<div class="server-info-row server-url">
<span class="server-url-info">${this.props.server.url}</span>
<span class="server-url-info" title="${this.props.server.url}">${this.props.server.url}</span>
</div>
<div class="server-info-row">
<div class="action red server-delete-action">

View File

@@ -78,14 +78,6 @@ class ShortcutsSection extends BaseSection {
<td><kbd>${userOSKey}</kbd><kbd>A</kbd></td>
<td>Select All</td>
</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>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>Space</kbd></td>
<td>Emoji & Symbols</td>
@@ -121,7 +113,7 @@ class ShortcutsSection extends BaseSection {
<td>Actual Size</td>
</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>
</tr>
<tr>
@@ -256,7 +248,7 @@ class ShortcutsSection extends BaseSection {
<td>Actual Size</td>
</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>
</tr>
<tr>

View File

@@ -4,6 +4,7 @@ const { ipcRenderer } = require('electron');
const SetupSpellChecker = require('./spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const LinkUtil = require(__dirname + '/utils/link-util.js');
// eslint-disable-next-line import/no-unassigned-import
require('./notification');
@@ -23,7 +24,7 @@ const shortcut = () => {
// Create the menu for the below
const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
// Additional check
if (node.text.trim().toLowerCase() === 'keyboard shortcuts') {
if (node.text.trim().toLowerCase() === 'keyboard shortcuts (?)') {
node.click();
} else {
// Atleast click the dropdown
@@ -55,6 +56,24 @@ document.addEventListener('DOMContentLoaded', () => {
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;
@@ -63,3 +82,10 @@ window.addEventListener('beforeunload', () => {
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 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

@@ -13,12 +13,12 @@ class ReconnectUtil {
pollInternetAndReload() {
const pollInterval = setInterval(() => {
this._checkAndReload()
.then(status => {
if (status) {
this.alreadyReloaded = true;
clearInterval(pollInterval);
}
});
.then(status => {
if (status) {
this.alreadyReloaded = true;
clearInterval(pollInterval);
}
});
}, 1500);
}
@@ -26,22 +26,24 @@ class ReconnectUtil {
return new Promise(resolve => {
if (!this.alreadyReloaded) { // eslint-disable-line no-negated-condition
isOnline()
.then(online => {
if (online) {
if (!this.alreadyReloaded) {
this.serverManagerView.reloadView();
.then(online => {
if (online) {
if (!this.alreadyReloaded) {
this.serverManagerView.reloadView();
}
console.log('You\'re back online.');
return resolve(true);
}
console.log('You\'re back online.');
return resolve(true);
}
console.log('There is no internet connection, try checking network cables, modem and router.');
const errMsgHolder = document.querySelector('#description');
errMsgHolder.innerHTML = `
console.log('There is no internet connection, try checking network cables, modem and router.');
const errMsgHolder = document.querySelector('#description');
if (errMsgHolder) {
errMsgHolder.innerHTML = `
<div>You internet connection does't seem to work properly!</div>
</div>Verify that it works and then click try again.</div>`;
return resolve(false);
});
}
return resolve(false);
});
} else {
return resolve(true);
}

View File

@@ -42,6 +42,10 @@
<div id="webviews-container"></div>
</div>
</div>
<div id="feedback-modal">
<send-feedback></send-feedback>
</div>
</body>
<script src="js/main.js"></script>
<script>require('./js/shared/preventdrag.js')</script>

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/)
(v2.7.x recommended)
* A C++ compiler compatible with C++11
* Linux users also need [Snapcraft](https://snapcraft.io/)
* Development headers for the libXext, libXtst, and libxkbfile libraries
### Debian/Ubuntu and friends
@@ -25,7 +26,7 @@ manager (see [here][nodesource-install] for more on the first command):
```sh
$ 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
@@ -76,7 +77,7 @@ This command will produce distributable packages or installers for the
operating system you're running on:
* on Windows, a Windows installer 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.
To generate all three types, you will need all three operating
systems.

1533
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "1.8.2",
"version": "2.0.0",
"main": "./app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
@@ -22,7 +22,7 @@
},
"scripts": {
"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",
"test": "xo",
"test-e2e": "gulp test-e2e",
@@ -55,7 +55,8 @@
"target": [
"deb",
"zip",
"AppImage"
"AppImage",
"snap"
],
"maintainer": "Akash Nimare <svnitakash@gmail.com>"
},
@@ -64,6 +65,9 @@
"afterInstall": "./scripts/debian-add-repo.sh",
"afterRemove": "./scripts/debian-uninstaller.sh"
},
"snap": {
"synopsis": "Zulip Desktop App"
},
"dmg": {
"background": "build/appdmg.png",
"icon": "build/icon.icns",
@@ -114,7 +118,7 @@
"cp-file": "^5.0.0",
"devtron": "1.4.0",
"electron": "1.8.4",
"electron-builder": "19.53.6",
"electron-builder": "20.8.1",
"electron-connect": "0.6.2",
"electron-debug": "1.4.0",
"google-translate-api": "2.3.0",

37
snap/snapcraft.yaml Normal file
View File

@@ -0,0 +1,37 @@
name: zulip
version: 2.0.0
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);
});