Compare commits

..

1 Commits

Author SHA1 Message Date
Akash Nimare
340da70868 design: Testing new design. 2018-03-07 20:47:44 +05:30
47 changed files with 4945 additions and 5745 deletions

7
.gitignore vendored
View File

@@ -7,13 +7,6 @@ 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

@@ -15,7 +15,7 @@ addons:
language: node_js language: node_js
node_js: node_js:
- '8' - '6'
before_install: before_install:
- ./scripts/travis-xvfb.sh - ./scripts/travis-xvfb.sh

View File

@@ -1,11 +1,11 @@
'use strict'; 'use strict';
const { app, dialog, shell } = require('electron'); const { app, dialog } = require('electron');
const { autoUpdater } = require('electron-updater'); const { autoUpdater } = require('electron-updater');
const isDev = require('electron-is-dev'); const isDev = require('electron-is-dev');
const ConfigUtil = require('./../renderer/js/utils/config-util.js'); const ConfigUtil = require('./../renderer/js/utils/config-util.js');
function appUpdater(updateFromMenu = false) { function appUpdater() {
// Don't initiate auto-updates in development // Don't initiate auto-updates in development
if (isDev) { if (isDev) {
return; return;
@@ -17,8 +17,6 @@ function appUpdater(updateFromMenu = false) {
return; return;
} }
let updateAvailable = false;
// Create Logs directory // Create Logs directory
const LogsDir = `${app.getPath('userData')}/Logs`; const LogsDir = `${app.getPath('userData')}/Logs`;
@@ -30,58 +28,7 @@ function appUpdater(updateFromMenu = false) {
autoUpdater.logger = log; autoUpdater.logger = log;
// Handle auto updates for beta/pre releases // Handle auto updates for beta/pre releases
const isBetaUpdate = ConfigUtil.getConfigItem('betaUpdate'); autoUpdater.allowPrerelease = ConfigUtil.getConfigItem('betaUpdate') || false;
autoUpdater.allowPrerelease = isBetaUpdate || false;
const eventsListenerRemove = ['update-available', 'update-not-available'];
autoUpdater.on('update-available', info => {
if (updateFromMenu) {
dialog.showMessageBox({
message: `A new version ${info.version}, of Zulip Desktop is available`,
detail: 'The update will be downloaded in the background. You will be notified when it is ready to be installed.'
});
updateAvailable = true;
// This is to prevent removal of 'update-downloaded' and 'error' event listener.
eventsListenerRemove.forEach(event => {
autoUpdater.removeAllListeners(event);
});
}
});
autoUpdater.on('update-not-available', () => {
if (updateFromMenu) {
dialog.showMessageBox({
message: 'No updates available',
detail: `You are running the latest version of Zulip Desktop.\nVersion: ${app.getVersion()}`
});
// Remove all autoUpdator listeners so that next time autoUpdator is manually called these
// listeners don't trigger multiple times.
autoUpdater.removeAllListeners();
}
});
autoUpdater.on('error', error => {
if (updateFromMenu) {
const messageText = (updateAvailable) ? ('Unable to download the updates') : ('Unable to check for updates');
dialog.showMessageBox({
type: 'error',
buttons: ['Manual Download', 'Cancel'],
message: messageText,
detail: (error).toString() + `\n\nThe latest version of Zulip Desktop is available at -\nhttps://zulipchat.com/apps/.\n
Current Version: ${app.getVersion()}`
}, response => {
if (response === 0) {
shell.openExternal('https://zulipchat.com/apps/');
}
});
// Remove all autoUpdator listeners so that next time autoUpdator is manually called these
// listeners don't trigger multiple times.
autoUpdater.removeAllListeners();
}
});
// Ask the user if update is available // Ask the user if update is available
// eslint-disable-next-line no-unused-vars // eslint-disable-next-line no-unused-vars

View File

@@ -7,7 +7,7 @@ const crashHandler = () => {
productName: 'zulip-electron', productName: 'zulip-electron',
companyName: 'Kandra Labs, Inc.', companyName: 'Kandra Labs, Inc.',
submitURL: 'https://zulip-sentry.herokuapp.com/crashreport', submitURL: 'https://zulip-sentry.herokuapp.com/crashreport',
uploadToServer: true autoSubmit: true
}); });
}; };

View File

@@ -52,8 +52,8 @@ const iconPath = () => {
function createMainWindow() { function createMainWindow() {
// Load the previous state with fallback to defaults // Load the previous state with fallback to defaults
const mainWindowState = windowStateKeeper({ const mainWindowState = windowStateKeeper({
defaultWidth: 1100, defaultWidth: 1000,
defaultHeight: 720 defaultHeight: 600
}); });
// Let's keep the window position global so that we can access it in other process // Let's keep the window position global so that we can access it in other process
@@ -71,6 +71,7 @@ function createMainWindow() {
minHeight: 400, minHeight: 400,
webPreferences: { webPreferences: {
plugins: true, plugins: true,
allowDisplayingInsecureContent: true,
nodeIntegration: true nodeIntegration: true
}, },
show: false show: false
@@ -131,9 +132,6 @@ function createMainWindow() {
// Decrease load on GPU (experimental) // Decrease load on GPU (experimental)
app.disableHardwareAcceleration(); app.disableHardwareAcceleration();
// Temporary fix for Electron render colors differently
app.commandLine.appendSwitch('force-color-profile', 'srgb');
// eslint-disable-next-line max-params // eslint-disable-next-line max-params
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => { app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
event.preventDefault(); event.preventDefault();
@@ -164,9 +162,7 @@ app.on('ready', () => {
page.once('did-frame-finish-load', () => { page.once('did-frame-finish-load', () => {
// Initate auto-updates on MacOS and Windows // Initate auto-updates on MacOS and Windows
if (ConfigUtil.getConfigItem('autoUpdate')) {
appUpdater(); appUpdater();
}
crashHandler(); crashHandler();
}); });

View File

@@ -1,13 +1,12 @@
'use strict'; 'use strict';
const os = require('os');
const path = require('path'); const path = require('path');
const { app, shell, BrowserWindow, Menu, dialog } = require('electron'); const { app, shell, BrowserWindow, Menu } = require('electron');
const fs = require('fs-extra'); const fs = require('fs-extra');
const { appUpdater } = require('./autoupdater');
const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js'); const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js');
const DNDUtil = require(__dirname + '/../renderer/js/utils/dnd-util.js');
const appName = app.getName(); const appName = app.getName();
@@ -88,7 +87,7 @@ class AppMenu {
} }
}, { }, {
label: 'Toggle Sidebar', label: 'Toggle Sidebar',
accelerator: 'CommandOrControl+Shift+S', accelerator: 'CommandOrControl+S',
click(item, focusedWindow) { click(item, focusedWindow) {
if (focusedWindow) { if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('showSidebar'); const newValue = !ConfigUtil.getConfigItem('showSidebar');
@@ -140,11 +139,13 @@ class AppMenu {
}, { }, {
label: 'Report an Issue...', label: 'Report an Issue...',
click() { click() {
// the goal is to notify the main.html BrowserWindow const body = `
// which may not be the focused window. <!-- Please succinctly describe your issue and steps to reproduce it. -->
BrowserWindow.getAllWindows().forEach(window => { -
window.webContents.send('open-feedback-modal'); ${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)}`);
} }
}]; }];
} }
@@ -196,11 +197,6 @@ class AppMenu {
AppMenu.sendAction('open-about'); AppMenu.sendAction('open-about');
} }
} }
}, {
label: `Check for Update`,
click() {
AppMenu.checkForUpdate();
}
}, { }, {
type: 'separator' type: 'separator'
}, { }, {
@@ -221,13 +217,6 @@ class AppMenu {
} }
}, { }, {
type: 'separator' type: 'separator'
}, {
label: 'Toggle Do Not Disturb',
accelerator: 'Command+Shift+M',
click() {
const dndUtil = DNDUtil.toggle();
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
}
}, { }, {
label: 'Reset App Settings', label: 'Reset App Settings',
accelerator: 'Command+Shift+D', accelerator: 'Command+Shift+D',
@@ -308,11 +297,6 @@ class AppMenu {
AppMenu.sendAction('open-about'); AppMenu.sendAction('open-about');
} }
} }
}, {
label: `Check for Update`,
click() {
AppMenu.checkForUpdate();
}
}, { }, {
type: 'separator' type: 'separator'
}, { }, {
@@ -335,13 +319,6 @@ class AppMenu {
} }
}, { }, {
type: 'separator' type: 'separator'
}, {
label: 'Toggle Do Not Disturb',
accelerator: 'Ctrl+Shift+M',
click() {
const dndUtil = DNDUtil.toggle();
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
}
}, { }, {
label: 'Reset App Settings', label: 'Reset App Settings',
accelerator: 'Ctrl+Shift+D', accelerator: 'Ctrl+Shift+D',
@@ -410,23 +387,10 @@ class AppMenu {
win.webContents.send(action, ...params); win.webContents.send(action, ...params);
} }
static checkForUpdate() {
appUpdater(true);
}
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'];
dialog.showMessageBox({
type: 'warning',
buttons: ['YES', 'NO'],
defaultId: 0,
message: 'Are you sure?',
detail: resetAppSettingsMessage
}, response => {
if (response === 0) {
settingFiles.forEach(settingFileName => { settingFiles.forEach(settingFileName => {
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName); const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName);
fs.access(getSettingFilesPath, error => { fs.access(getSettingFilesPath, error => {
@@ -440,8 +404,6 @@ class AppMenu {
}); });
}); });
} }
});
}
setMenu(props) { setMenu(props) {
const tpl = process.platform === 'darwin' ? this.getDarwinTpl(props) : this.getOtherTpl(props); const tpl = process.platform === 'darwin' ? this.getDarwinTpl(props) : this.getOtherTpl(props);

893
app/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "zulip", "name": "zulip",
"productName": "Zulip", "productName": "Zulip",
"version": "2.3.1", "version": "1.8.2",
"description": "Zulip Desktop App", "description": "Zulip Desktop App",
"license": "Apache-2.0", "license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.", "copyright": "Kandra Labs, Inc.",
@@ -26,21 +26,19 @@
"InstantMessaging" "InstantMessaging"
], ],
"dependencies": { "dependencies": {
"@electron-elements/send-feedback": "1.0.7", "auto-launch": "5.0.1",
"escape-html": "1.0.3",
"auto-launch": "5.0.5",
"electron-is-dev": "0.3.0", "electron-is-dev": "0.3.0",
"electron-log": "2.2.14", "electron-log": "2.2.7",
"electron-spellchecker": "1.1.2", "electron-spellchecker": "1.1.2",
"electron-updater": "2.21.10", "electron-updater": "2.18.2",
"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.85.0", "request": "2.81.0",
"semver": "5.4.1", "semver": "5.4.1",
"wurl": "2.5.0" "wurl": "2.5.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"node-mac-notifier": "0.1.0" "node-mac-notifier": "0.0.13"
} }
} }

View File

@@ -1,23 +1,19 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<link rel="stylesheet" href="css/about.css"> <link rel="stylesheet" href="css/about.css">
</head> </head>
<body> <body>
<div class="about"> <div class="about">
<img class="logo" src="../resources/zulip.png" /> <img class="logo" src="../resources/zulip.png" />
<p class="detail" id="version">v?.?.?</p> <p class="detail" id="version">v?.?.?</p>
<div class="maintenance-info"> <div class="maintenance-info">
<p class="detail maintainer"> <p class="detail maintainer">
Maintained by Maintained by <a onclick="linkInBrowser('website')">Zulip</a>
<a onclick="linkInBrowser('website')">Zulip</a>
</p> </p>
<p class="detail license"> <p class="detail license">
Available under the Available under the <a onclick="linkInBrowser('license')">Apache 2.0 License</a>
<a onclick="linkInBrowser('license')">Apache 2.0 License</a>
</p> </p>
<a class="bug" onclick="linkInBrowser('bug')" href="#">Found bug?</a> <a class="bug" onclick="linkInBrowser('bug')" href="#">Found bug?</a>
</div> </div>
@@ -45,6 +41,5 @@
shell.openExternal(url); shell.openExternal(url);
} }
</script> </script>
<script>require('./js/shared/preventdrag.js')</script>
</body> </body>
</html> </html>

View File

@@ -13,6 +13,9 @@ body {
#content { #content {
display: flex; display: flex;
height: 100%; height: 100%;
background: #fff url(../img/ic_loading.gif) no-repeat;
background-size: 60px 60px;
background-position: center;
} }
.toggle-sidebar { .toggle-sidebar {
@@ -25,7 +28,7 @@ body {
-webkit-app-region: drag; -webkit-app-region: drag;
overflow: hidden; overflow: hidden;
transition: all 0.5s ease; transition: all 0.5s ease;
z-index: 2; z-index: 1;
} }
.toggle-sidebar div { .toggle-sidebar div {
@@ -42,28 +45,6 @@ body {
transition: all 0.6s ease-out; transition: all 0.6s ease-out;
} }
#view-controls-container {
height: calc(100% - 208px);
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-face {
font-family: 'Material Icons'; font-family: 'Material Icons';
font-style: normal; font-style: normal;
@@ -140,14 +121,11 @@ body {
} }
.action-button.active { .action-button.active {
/* background-color: rgba(255, 255, 255, 0.25); */ background-color: rgba(255, 255, 255, 0.25);
background-color: #efefef;
opacity: 0.9;
padding-right: 14px;
} }
.action-button.active i { .action-button.active i {
color: #1c262b; color: #eee;
} }
.tab:first-child { .tab:first-child {
@@ -270,63 +248,37 @@ body {
width: 100%; width: 100%;
} }
/*Pseudo element for loading indicator*/
#webviews-container::before {
content: "";
position: absolute;
z-index: 1;
background: #fff url(../img/ic_loading.gif) no-repeat;
background-size: 60px 60px;
background-position: center;
width: 100%;
height: 100%;
}
/*When the active webview is loaded*/
#webviews-container.loaded::before {
opacity: 0;
z-index: -1;
visibility: hidden;
}
webview { webview {
opacity: 1;
/* transition: opacity 0.3s ease-in; */ /* transition: opacity 0.3s ease-in; */
flex-grow: 1; flex-grow: 1;
position: absolute;
width: 100%;
height: 100%;
flex-grow: 1;
display: flex;
flex-direction: column;
} }
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);
} }
webview.active {
opacity: 1;
z-index: 1;
visibility: visible;
}
webview.disabled { webview.disabled {
flex: 0 1;
height: 0;
width: 0;
opacity: 0; opacity: 0;
transition: opacity 0.3s ease-out;
} }
webview.focus { webview:focus {
outline: 0px solid transparent; outline: 0px solid transparent;
} }
/* Tooltip styling */ /* Tooltip styling */
#dnd-tooltip,
#back-tooltip, #back-tooltip,
#reload-tooltip, #reload-tooltip,
#setting-tooltip { #setting-tooltip {
font-family: sans-serif; font-family: sans-serif;
background: #222c31; background: #222c31;
margin-left: 48px; margin-left: 45px;
padding: 6px 8px; padding: 6px 8px;
position: absolute; position: absolute;
margin-top: 0px; margin-top: 0px;
@@ -338,7 +290,6 @@ webview.focus {
font-size: 14px; font-size: 14px;
} }
#dnd-tooltip:after,
#back-tooltip:after, #back-tooltip:after,
#reload-tooltip:after, #reload-tooltip:after,
#setting-tooltip:after { #setting-tooltip:after {
@@ -399,8 +350,6 @@ webview.focus {
height: 100%; height: 100%;
width: 100%; width: 100%;
position: relative; position: relative;
flex-grow: 1;
flex-basis: 0px;
} }
.hidden { .hidden {
@@ -443,26 +392,3 @@ webview.focus {
opacity: 1; 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

@@ -95,13 +95,11 @@ td:nth-child(odd) {
} }
#sidebar { #sidebar {
width: 150px; width: 80px;
min-width: 100px; padding: 30px;
padding: 30px 30px 30px 35px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
font-size: 16px; font-size: 16px;
background: #f2f2f2;
} }
#nav-container { #nav-container {
@@ -115,7 +113,7 @@ td:nth-child(odd) {
} }
.nav.active { .nav.active {
color: #4ebfac; color: #464e5a;
cursor: default; cursor: default;
position: relative; position: relative;
} }
@@ -123,19 +121,12 @@ td:nth-child(odd) {
.nav.active::before { .nav.active::before {
background: #464e5a; background: #464e5a;
width: 3px; width: 3px;
height: 18px; height: 16px;
position: absolute; position: absolute;
left: -8px; left: -8px;
content: ''; content: '';
} }
/* We don't want to show this in nav item since we have the + button for adding an Organization */
#nav-AddServer {
display: none;
}
#settings-header { #settings-header {
font-size: 22px; font-size: 22px;
color: #222c31; color: #222c31;
@@ -151,12 +142,12 @@ td:nth-child(odd) {
} }
#new-server-container { #new-server-container {
padding-left: 42px; opacity: 1;
padding-top: 25px; transition: opacity 0.3s;
margin-right: 16px;
} }
.title { .title {
padding: 4px 0 6px 0;
font-weight: 500; font-weight: 500;
color: #222c31; color: #222c31;
} }
@@ -168,16 +159,6 @@ td:nth-child(odd) {
padding: 4px 0 6px 0; padding: 4px 0 6px 0;
} }
.add-server-info-row {
display: flex;
margin: 8px 0 0 0;
}
.add-server-info-right {
flex-grow: 1;
margin-top: 10px;
}
.sub-title { .sub-title {
padding: 4px 0 6px 0; padding: 4px 0 6px 0;
font-weight: bold; font-weight: bold;
@@ -188,40 +169,20 @@ img.server-info-icon {
width: 36px; width: 36px;
height: 36px; height: 36px;
padding: 4px; padding: 4px;
cursor: pointer;
vertical-align: middle;
} }
.server-info-left { .server-info-left {
margin: 4px 20px 0 0; margin: 10px 20px 0 0;
min-width: 40%;
} }
.server-info-right { .server-info-right {
margin-top: 4px;
width: 55%;
display: flex;
align-items: baseline;
justify-content: space-between;
flex-grow: 1; flex-grow: 1;
margin-right: 10px;
} }
.server-info-row { .server-info-row {
display: inline-block; display: flex;
margin: 5px 0 0 0; margin: 8px 0 0 0;
}
.server-info-left .server-info-row {
display: inline-flex;
align-items: inherit;
vertical-align: -2px;
position: relative;
}
.server-url {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
} }
.server-info-alias { .server-info-alias {
@@ -229,10 +190,6 @@ img.server-info-icon {
cursor: pointer; cursor: pointer;
} }
.server-url-info {
margin-right: 10px;
}
.setting-input-key { .setting-input-key {
font-size: 14px; font-size: 14px;
height: 27px; height: 27px;
@@ -246,16 +203,18 @@ img.server-info-icon {
.setting-input-value { .setting-input-value {
flex-grow: 1; flex-grow: 1;
font-size: 14px; font-size: 14px;
height: 22px;
border-radius: 3px; border-radius: 3px;
padding: 13px; padding: 7px;
border: #ededed 2px solid; border: #ededed 2px solid;
outline-width: 0; outline-width: 0;
background: transparent; background: transparent;
max-width: 450px; max-width: 500px;
} }
.setting-input-value:focus { .setting-input-value:focus {
border: #4EBFAC 2px solid; border: #7cb980 2px solid;
border-radius: 3px;
} }
.setting-block { .setting-block {
@@ -311,7 +270,8 @@ img.server-info-icon {
} }
.settings-card:hover { .settings-card:hover {
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 0px 0px rgba(0, 0, 0, 0.12); /* 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); */
} }
.hidden { .hidden {
@@ -320,15 +280,11 @@ img.server-info-icon {
} }
.red { .red {
color: #ef5350; color: #ffffff;
padding: 8px; background: #ef5350;
border: rgba(239, 83, 80, 0.5) solid 1px; padding: 3px;
} padding-right: 10px;
padding-left: 10px;
.red:hover {
color: #e63431;
border: rgba(239, 83, 80, 0.7) solid 1px;
;
} }
.blue { .blue {
@@ -358,8 +314,8 @@ img.server-info-icon {
} }
i.open-tab-button { i.open-tab-button {
padding-left: 2px; padding: 0 5px;
font-size: 19px; font-size: 18px;
cursor: pointer; cursor: pointer;
} }
@@ -412,10 +368,17 @@ i.open-tab-button {
} }
#open-create-org-link { #open-create-org-link {
color: #666;
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
} }
#open-create-org-link:hover {
color: #005580;
;
text-decoration: underline;
}
.toggle { .toggle {
position: absolute; position: absolute;
margin-left: -9999px; margin-left: -9999px;
@@ -470,110 +433,6 @@ input.toggle-round:checked+label:after {
} }
/* Add new server modal */
.add-server-modal {
display: block;
position: fixed;
z-index: 1;
padding-top: 15vh;
left: 0;
top: 0;
margin: auto;
width: 100%;
height: 100%;
/* background: rgba(61, 64, 67, 15); */
background: linear-gradient(35deg, #003b52, #45b59b);
overflow: auto;
}
/* Modal Content */
.modal-container {
background-color: #f4f7f8;
margin: auto;
padding: 57px;
border: #dae1e3 1px solid;
width: 550px;
height: 370px;
border-radius: 4px;
}
.add-server-modal .page-title {
text-align: center;
font-size: 1.6rem;
}
.divider {
margin-bottom: 30px;
margin-top: 30px;
color: #7d878a;
}
.divider hr {
margin-left: 8px;
margin-right: 8px;
width: 44%;
}
.left {
float: left;
}
.right {
float: right;
}
.server-center {
width: 100%;
text-align: center;
align-items: center;
padding-top: 13px;
margin-left: -5px;
}
.server-center button {
font-weight: bold;
font-size: 1.1rem;
margin: auto;
align-items: center;
text-align: center;
color: #fff;
background: #4EBFAC;
border-color: none;
border: none;
width: 98%;
height: 46px;
border-radius: 3px;
cursor: pointer;
}
.server-center button:hover {
background: #329588;
}
.server-center button:focus {
background: #329588;
}
.tip {
background-color: hsl(46,63%,95%);
border: 1px solid hsl(46,63%,84%);
border-radius: 4px;
}
.md-14 {
font-size: 14px;
vertical-align: middle;
padding-right: 6px;
}
#open-hotkeys-link {
text-decoration: underline;
cursor: pointer;
}
/* responsive grid */ /* responsive grid */
@media (max-width: 650px) { @media (max-width: 650px) {
@@ -587,53 +446,3 @@ input.toggle-round:checked+label:after {
display: none; display: none;
} }
} }
@media (max-width: 720px) {
.modal-container {
width: 60vw;
padding: 40px;
min-width: 300px;
}
.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

@@ -1,35 +0,0 @@
const { shell } = require('electron').remote;
const LinkUtil = require('../utils/link-util');
const DomainUtil = require('../utils/domain-util');
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();
// download txt, pdf, mp3, mp4 etc.. by using downloadURL in the
// main process which allows the user to save the files to their desktop
// and not trigger webview reload while image in webview will
// do nothing and will not save it
if (!LinkUtil.isImage(url) && isUploadsURL) {
this.$el.downloadURL(url);
return;
}
// open internal urls inside the current webview.
this.$el.loadURL(url);
} else {
event.preventDefault();
shell.openExternal(url);
}
}
module.exports = handleExternalLink;

View File

@@ -3,12 +3,13 @@
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 { app, dialog } = require('electron').remote; const LinkUtil = require(__dirname + '/../utils/link-util.js');
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 {
@@ -18,10 +19,9 @@ class WebView extends BaseComponent {
this.props = props; this.props = props;
this.zoomFactor = 1.0; this.zoomFactor = 1.0;
this.loading = true; this.loading = false;
this.badgeCount = 0; this.badgeCount = 0;
this.customCSS = ConfigUtil.getConfigItem('customCSS'); this.customCSS = ConfigUtil.getConfigItem('customCSS');
this.$webviewsContainer = document.querySelector('#webviews-container').classList;
} }
template() { template() {
@@ -46,7 +46,16 @@ class WebView extends BaseComponent {
registerListeners() { registerListeners() {
this.$el.addEventListener('new-window', event => { this.$el.addEventListener('new-window', event => {
handleExternalLink.call(this, 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);
}
}); });
if (shouldSilentWebview) { if (shouldSilentWebview) {
@@ -87,7 +96,6 @@ class WebView extends BaseComponent {
if (this.props.role === 'server') { if (this.props.role === 'server') {
this.$el.classList.add('onload'); this.$el.classList.add('onload');
} }
this.loading = false;
this.show(); this.show();
}); });
@@ -121,21 +129,14 @@ class WebView extends BaseComponent {
return; return;
} }
// To show or hide the loading indicator in the the active tab
if (this.loading) {
this.$webviewsContainer.remove('loaded');
} else {
this.$webviewsContainer.add('loaded');
}
this.$el.classList.remove('disabled'); this.$el.classList.remove('disabled');
this.$el.classList.add('active');
setTimeout(() => { setTimeout(() => {
if (this.props.role === 'server') { if (this.props.role === 'server') {
this.$el.classList.remove('onload'); this.$el.classList.remove('onload');
} }
}, 1000); }, 1000);
this.focus(); this.focus();
this.loading = false;
this.props.onTitleChange(); this.props.onTitleChange();
// Injecting preload css in webview to override some css rules // Injecting preload css in webview to override some css rules
this.$el.insertCSS(fs.readFileSync(path.join(__dirname, '/../../css/preload.css'), 'utf8')); this.$el.insertCSS(fs.readFileSync(path.join(__dirname, '/../../css/preload.css'), 'utf8'));
@@ -167,7 +168,6 @@ class WebView extends BaseComponent {
hide() { hide() {
this.$el.classList.add('disabled'); this.$el.classList.add('disabled');
this.$el.classList.remove('active');
} }
load() { load() {
@@ -228,9 +228,6 @@ class WebView extends BaseComponent {
reload() { reload() {
this.hide(); this.hide();
// Shows the loading indicator till the webview is reloaded
this.$webviewsContainer.remove('loaded');
this.loading = true;
this.$el.reload(); this.$el.reload();
} }

View File

@@ -1,62 +0,0 @@
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

@@ -11,9 +11,7 @@ const WebView = require(__dirname + '/js/components/webview.js');
const ServerTab = require(__dirname + '/js/components/server-tab.js'); const ServerTab = require(__dirname + '/js/components/server-tab.js');
const FunctionalTab = require(__dirname + '/js/components/functional-tab.js'); const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
const ConfigUtil = require(__dirname + '/js/utils/config-util.js'); const ConfigUtil = require(__dirname + '/js/utils/config-util.js');
const DNDUtil = require(__dirname + '/js/utils/dnd-util.js');
const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js'); const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js');
const { feedbackHolder } = require(__dirname + '/js/feedback.js');
class ServerManagerView { class ServerManagerView {
constructor() { constructor() {
@@ -25,14 +23,12 @@ class ServerManagerView {
this.$settingsButton = $actionsContainer.querySelector('#settings-action'); this.$settingsButton = $actionsContainer.querySelector('#settings-action');
this.$webviewsContainer = document.getElementById('webviews-container'); this.$webviewsContainer = document.getElementById('webviews-container');
this.$backButton = $actionsContainer.querySelector('#back-action'); this.$backButton = $actionsContainer.querySelector('#back-action');
this.$dndButton = $actionsContainer.querySelector('#dnd-action');
this.$addServerTooltip = document.getElementById('add-server-tooltip'); this.$addServerTooltip = document.getElementById('add-server-tooltip');
this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip'); this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip');
this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip'); this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip');
this.$serverIconTooltip = document.getElementsByClassName('server-tooltip'); this.$serverIconTooltip = document.getElementsByClassName('server-tooltip');
this.$backTooltip = $actionsContainer.querySelector('#back-tooltip'); this.$backTooltip = $actionsContainer.querySelector('#back-tooltip');
this.$dndTooltip = $actionsContainer.querySelector('#dnd-tooltip');
this.$sidebar = document.getElementById('sidebar'); this.$sidebar = document.getElementById('sidebar');
@@ -89,15 +85,9 @@ class ServerManagerView {
startMinimized: false, startMinimized: false,
enableSpellchecker: true, enableSpellchecker: true,
showNotification: true, showNotification: true,
autoUpdate: true,
betaUpdate: false, betaUpdate: false,
silent: false, silent: false,
lastActiveTab: 0, lastActiveTab: 0
dnd: false,
dndPreviousSettings: {
showNotification: true,
silent: false
}
}; };
// Platform specific settings // Platform specific settings
@@ -105,7 +95,6 @@ class ServerManagerView {
if (process.platform === 'win32') { if (process.platform === 'win32') {
// Only available on Windows // Only available on Windows
settingOptions.flashTaskbarOnMessage = true; settingOptions.flashTaskbarOnMessage = true;
settingOptions.dndPreviousSettings.flashTaskbarOnMessage = true;
} }
for (const i in settingOptions) { for (const i in settingOptions) {
@@ -133,7 +122,7 @@ class ServerManagerView {
// Remove focus from the settings icon at sidebar bottom // Remove focus from the settings icon at sidebar bottom
this.$settingsButton.classList.remove('active'); this.$settingsButton.classList.remove('active');
} else { } else {
this.openSettings('AddServer'); this.openSettings('Servers');
} }
} }
@@ -166,16 +155,11 @@ class ServerManagerView {
} }
initActions() { initActions() {
this.initDNDButton();
this.$dndButton.addEventListener('click', () => {
const dndUtil = DNDUtil.toggle();
ipcRenderer.send('forward-message', 'toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
});
this.$reloadButton.addEventListener('click', () => { this.$reloadButton.addEventListener('click', () => {
this.tabs[this.activeTabIndex].webview.reload(); this.tabs[this.activeTabIndex].webview.reload();
}); });
this.$addServerButton.addEventListener('click', () => { this.$addServerButton.addEventListener('click', () => {
this.openSettings('AddServer'); this.openSettings('Servers');
}); });
this.$settingsButton.addEventListener('click', () => { this.$settingsButton.addEventListener('click', () => {
this.openSettings('General'); this.openSettings('General');
@@ -191,16 +175,10 @@ class ServerManagerView {
}); });
}); });
this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip, true); this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip);
this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip); this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip);
this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip); this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip);
this.sidebarHoverEvent(this.$backButton, this.$backTooltip); this.sidebarHoverEvent(this.$backButton, this.$backTooltip);
this.sidebarHoverEvent(this.$dndButton, this.$dndTooltip);
}
initDNDButton() {
const dnd = ConfigUtil.getConfigItem('dnd', false);
this.toggleDNDButton(dnd);
} }
getTabIndex() { getTabIndex() {
@@ -209,17 +187,9 @@ class ServerManagerView {
return currentIndex; return currentIndex;
} }
sidebarHoverEvent(SidebarButton, SidebarTooltip, addServer = false) { sidebarHoverEvent(SidebarButton, SidebarTooltip) {
SidebarButton.addEventListener('mouseover', () => { SidebarButton.addEventListener('mouseover', () => {
SidebarTooltip.removeAttribute('style'); 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', () => { SidebarButton.addEventListener('mouseout', () => {
SidebarTooltip.style.display = 'none'; SidebarTooltip.style.display = 'none';
@@ -227,13 +197,8 @@ class ServerManagerView {
} }
onHover(index, serverName) { onHover(index, serverName) {
this.$serverIconTooltip[index].innerText = serverName; this.$serverIconTooltip[index].innerHTML = serverName;
this.$serverIconTooltip[index].removeAttribute('style'); 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) { onHoverOut(index) {
@@ -273,9 +238,6 @@ class ServerManagerView {
preload: false preload: false
}) })
})); }));
// To show loading indicator the first time a functional tab is opened, indicator is
// closed when the functional tab DOM is ready, handled in webview.js
this.$webviewsContainer.classList.remove('loaded');
this.activateTab(this.functionalTabs[tabProps.name]); this.activateTab(this.functionalTabs[tabProps.name]);
} }
@@ -306,10 +268,10 @@ class ServerManagerView {
} }
activateLastTab(index) { activateLastTab(index) {
// Open all the tabs in background, also activate the tab based on the index // Open last active tab
this.activateTab(index);
// Save last active tab
ConfigUtil.setConfigItem('lastActiveTab', index); ConfigUtil.setConfigItem('lastActiveTab', index);
// Open all the tabs in background
this.activateTab(index);
} }
activateTab(index, hideOldTab = true) { activateTab(index, hideOldTab = true) {
@@ -349,7 +311,7 @@ class ServerManagerView {
webContents.send('toggle-sidebar', state); webContents.send('toggle-sidebar', state);
}); });
ipcRenderer.on('toggle-silent', (event, state) => { ipcRenderer.on('toogle-silent', (event, state) => {
const webviews = document.querySelectorAll('webview'); const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => { webviews.forEach(webview => {
try { try {
@@ -362,15 +324,6 @@ class ServerManagerView {
} }
}); });
}); });
ipcRenderer.on('toggle-dnd', (event, state, newSettings) => {
this.toggleDNDButton(state);
ipcRenderer.send('forward-message', 'toggle-silent', newSettings.silent);
const selector = 'webview:not([class*=disabled])';
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggle-dnd', state, newSettings);
});
} }
destroyTab(name, index) { destroyTab(name, index) {
@@ -390,9 +343,6 @@ class ServerManagerView {
} }
destroyView() { destroyView() {
// Show loading indicator
this.$webviewsContainer.classList.remove('loaded');
// Clear global variables // Clear global variables
this.activeTabIndex = -1; this.activeTabIndex = -1;
this.tabs = []; this.tabs = [];
@@ -440,12 +390,6 @@ class ServerManagerView {
} }
} }
// Toggles the dnd button icon.
toggleDNDButton(alert) {
this.$dndTooltip.textContent = (alert ? 'Turn Off' : 'Enable') + ' Do Not Disturb';
this.$dndButton.querySelector('i').textContent = alert ? 'notifications_off' : 'notifications';
}
registerIpcs() { registerIpcs() {
const webviewListeners = { const webviewListeners = {
'webview-reload': 'reload', 'webview-reload': 'reload',
@@ -552,10 +496,6 @@ class ServerManagerView {
} }
ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount)); ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount));
}); });
ipcRenderer.on('open-feedback-modal', () => {
feedbackHolder.classList.add('show');
});
} }
} }

View File

@@ -3,8 +3,9 @@
const BaseSection = require(__dirname + '/base-section.js'); const BaseSection = require(__dirname + '/base-section.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js'); const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const ServerInfoForm = require(__dirname + '/server-info-form.js'); const ServerInfoForm = require(__dirname + '/server-info-form.js');
const CreateOrganziation = require(__dirname + '/create-new-org.js');
class ConnectedOrgSection extends BaseSection { class AddedSection extends BaseSection {
constructor(props) { constructor(props) {
super(); super();
this.props = props; this.props = props;
@@ -13,9 +14,10 @@ class ConnectedOrgSection extends BaseSection {
template() { template() {
return ` return `
<div class="settings-pane" id="server-settings-pane"> <div class="settings-pane" id="server-settings-pane">
<div class="page-title">Connected organizations</div> <div id="new-server-container"></div>
<div class="title" id="existing-servers">All the connected orgnizations will appear here.</div> <div class="title" id="existing-servers"></div>
<div id="server-info-container"></div> <div id="server-info-container"></div>
<div id="create-organization-container"></div>
</div> </div>
`; `;
} }
@@ -32,9 +34,12 @@ class ConnectedOrgSection extends BaseSection {
this.$serverInfoContainer = document.getElementById('server-info-container'); this.$serverInfoContainer = document.getElementById('server-info-container');
this.$existingServers = document.getElementById('existing-servers'); this.$existingServers = document.getElementById('existing-servers');
const noServerText = 'All the connected orgnizations will appear here'; this.$serverInfoContainer.innerHTML = servers.length ? '' : '';
// Show noServerText if no servers are there otherwise hide it // Show Existing servers if servers are there otherwise hide it
this.$existingServers.innerHTML = servers.length === 0 ? noServerText : ''; this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Connected organizations';
this.$createOrganizationContainer = document.getElementById('create-organization-container');
this.initCreateNewOrganization();
for (let i = 0; i < servers.length; i++) { for (let i = 0; i < servers.length; i++) {
new ServerInfoForm({ new ServerInfoForm({
@@ -46,6 +51,11 @@ class ConnectedOrgSection extends BaseSection {
} }
} }
initCreateNewOrganization() {
new CreateOrganziation({
$root: this.$createOrganizationContainer
}).init();
}
} }
module.exports = ConnectedOrgSection; module.exports = AddedSection;

View File

@@ -0,0 +1,37 @@
'use strict';
const BaseComponent = require(__dirname + '/../../components/base.js');
const shell = require('electron').shell;
class CreateOrganziation extends BaseComponent {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="setting-row">
<div class="setting-description">
<span id="open-create-org-link">Or create a new organization on zulipchat.com<i class="material-icons open-tab-button">open_in_new</i></span>
</div>
<div class="setting-control"></div>
</div>
`;
}
init() {
this.props.$root.innerHTML = this.template();
this.openCreateNewOrgExternalLink();
}
openCreateNewOrgExternalLink() {
const link = 'https://zulipchat.com/beta/';
const externalCreateNewOrgEl = document.getElementById('open-create-org-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
}
module.exports = CreateOrganziation;

View File

@@ -49,10 +49,6 @@ class GeneralSection extends BaseSection {
</div> </div>
<div class="title">App Updates</div> <div class="title">App Updates</div>
<div class="settings-card"> <div class="settings-card">
<div class="setting-row" id="autoupdate-option">
<div class="setting-description">Enable auto updates</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="betaupdate-option"> <div class="setting-row" id="betaupdate-option">
<div class="setting-description">Get beta updates</div> <div class="setting-description">Get beta updates</div>
<div class="setting-control"></div> <div class="setting-control"></div>
@@ -108,8 +104,7 @@ class GeneralSection extends BaseSection {
this.updateTrayOption(); this.updateTrayOption();
this.updateBadgeOption(); this.updateBadgeOption();
this.updateSilentOption(); this.updateSilentOption();
this.autoUpdateOption(); this.updateUpdateOption();
this.betaUpdateOption();
this.updateSidebarOption(); this.updateSidebarOption();
this.updateStartAtLoginOption(); this.updateStartAtLoginOption();
this.updateResetDataOption(); this.updateResetDataOption();
@@ -165,26 +160,14 @@ class GeneralSection extends BaseSection {
}); });
} }
autoUpdateOption() { updateUpdateOption() {
this.generateSettingOption({
$element: document.querySelector('#autoupdate-option .setting-control'),
value: ConfigUtil.getConfigItem('autoUpdate', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('autoUpdate');
ConfigUtil.setConfigItem('autoUpdate', newValue);
this.autoUpdateOption();
}
});
}
betaUpdateOption() {
this.generateSettingOption({ this.generateSettingOption({
$element: document.querySelector('#betaupdate-option .setting-control'), $element: document.querySelector('#betaupdate-option .setting-control'),
value: ConfigUtil.getConfigItem('betaUpdate', false), value: ConfigUtil.getConfigItem('betaUpdate', false),
clickHandler: () => { clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('betaUpdate'); const newValue = !ConfigUtil.getConfigItem('betaUpdate');
ConfigUtil.setConfigItem('betaUpdate', newValue); ConfigUtil.setConfigItem('betaUpdate', newValue);
this.betaUpdateOption(); this.updateUpdateOption();
} }
}); });
} }
@@ -197,7 +180,7 @@ class GeneralSection extends BaseSection {
const newValue = !ConfigUtil.getConfigItem('silent', true); const newValue = !ConfigUtil.getConfigItem('silent', true);
ConfigUtil.setConfigItem('silent', newValue); ConfigUtil.setConfigItem('silent', newValue);
this.updateSilentOption(); this.updateSilentOption();
currentBrowserWindow.send('toggle-silent', newValue); currentBrowserWindow.send('toogle-silent', newValue);
} }
}); });
} }

View File

@@ -8,7 +8,7 @@ class PreferenceNav extends BaseComponent {
this.props = props; this.props = props;
this.navItems = ['General', 'Network', 'AddServer', 'Organizations', 'Shortcuts']; this.navItems = ['General', 'Network', 'Servers', 'Added-servers', 'Shortcuts'];
this.init(); this.init();
} }

View File

@@ -2,7 +2,6 @@
const BaseComponent = require(__dirname + '/../../components/base.js'); const BaseComponent = require(__dirname + '/../../components/base.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js'); const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const shell = require('electron').shell;
class NewServerForm extends BaseComponent { class NewServerForm extends BaseComponent {
constructor(props) { constructor(props) {
@@ -12,24 +11,25 @@ class NewServerForm extends BaseComponent {
template() { template() {
return ` return `
<div class="server-input-container"> <div class="settings-card">
<div class="server-info-right">
<div class="title">Organization URL</div> <div class="title">Organization URL</div>
<div class="add-server-info-row"> <div class="server-info-row">
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/> <input class="setting-input-value" autofocus placeholder="example.zulipchat.com or chat.example.com"/>
</div>
<div class="server-info-row">
<div class="action blue server-save-action">
<span>Next</span>
</div> </div>
<div class="server-center">
<div class="server-save-action">
<button id="connect">Connect</button>
</div> </div>
</div> </div>
<div class="server-center">
<div class="divider"> <div class="divider">
<hr class="left"/>OR<hr class="right" /> <hr class="left"/>OR<hr class="right" />
</div> </div>
</div>
<div class="server-center"> <div class="server-info-row">
<div class="server-save-action"> <div class="action blue server-create-action">
<button id="open-create-org-link">Create a new organization</button> <span>Create a new organization</span>
</div> </div>
</div> </div>
</div> </div>
@@ -51,25 +51,17 @@ class NewServerForm extends BaseComponent {
} }
submitFormHandler() { submitFormHandler() {
this.$saveServerButton.children[0].innerHTML = 'Connecting...'; this.$saveServerButton.children[0].innerHTML = 'Adding...';
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => { DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
DomainUtil.addDomain(serverConf).then(() => { DomainUtil.addDomain(serverConf).then(() => {
this.props.onChange(this.props.index); this.props.onChange(this.props.index);
}); });
}, errorMessage => { }, errorMessage => {
this.$saveServerButton.children[0].innerHTML = 'Connect'; this.$saveServerButton.children[0].innerHTML = 'Next';
alert(errorMessage); alert(errorMessage);
}); });
} }
openCreateNewOrgExternalLink() {
const link = 'https://zulipchat.com/new/';
const externalCreateNewOrgEl = document.getElementById('open-create-org-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
initActions() { initActions() {
this.$saveServerButton.addEventListener('click', () => { this.$saveServerButton.addEventListener('click', () => {
this.submitFormHandler(); this.submitFormHandler();
@@ -81,8 +73,6 @@ class NewServerForm extends BaseComponent {
this.submitFormHandler(); this.submitFormHandler();
} }
}); });
// open create new org link in default browser
this.openCreateNewOrgExternalLink();
} }
} }

View File

@@ -7,8 +7,8 @@ const Nav = require(__dirname + '/js/pages/preference/nav.js');
const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js'); const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js');
const GeneralSection = require(__dirname + '/js/pages/preference/general-section.js'); const GeneralSection = require(__dirname + '/js/pages/preference/general-section.js');
const NetworkSection = require(__dirname + '/js/pages/preference/network-section.js'); const NetworkSection = require(__dirname + '/js/pages/preference/network-section.js');
const ConnectedOrgSection = require(__dirname + '/js/pages/preference/connected-org-section.js');
const ShortcutsSection = require(__dirname + '/js/pages/preference/shortcuts-section.js'); const ShortcutsSection = require(__dirname + '/js/pages/preference/shortcuts-section.js');
const AddedSection = require(__dirname + '/js/pages/preference/added-server-section.js');
class PreferenceView extends BaseComponent { class PreferenceView extends BaseComponent {
constructor() { constructor() {
@@ -40,7 +40,7 @@ class PreferenceView extends BaseComponent {
handleNavigation(navItem) { handleNavigation(navItem) {
this.nav.select(navItem); this.nav.select(navItem);
switch (navItem) { switch (navItem) {
case 'AddServer': { case 'Servers': {
this.section = new ServersSection({ this.section = new ServersSection({
$root: this.$settingsContainer $root: this.$settingsContainer
}); });
@@ -52,14 +52,14 @@ class PreferenceView extends BaseComponent {
}); });
break; break;
} }
case 'Organizations': { case 'Network': {
this.section = new ConnectedOrgSection({ this.section = new NetworkSection({
$root: this.$settingsContainer $root: this.$settingsContainer
}); });
break; break;
} }
case 'Network': { case 'Added-servers': {
this.section = new NetworkSection({ this.section = new AddedSection({
$root: this.$settingsContainer $root: this.$settingsContainer
}); });
break; break;
@@ -97,15 +97,6 @@ class PreferenceView extends BaseComponent {
ipcRenderer.on('toggletray', (event, state) => { ipcRenderer.on('toggletray', (event, state) => {
this.handleToggle('tray-option', state); this.handleToggle('tray-option', state);
}); });
ipcRenderer.on('toggle-dnd', (event, state, newSettings) => {
this.handleToggle('show-notification-option', newSettings.showNotification);
this.handleToggle('silent-option', newSettings.silent);
if (process.platform === 'win32') {
this.handleToggle('flash-taskbar-option', newSettings.flashTaskbarOnMessage);
}
});
} }
} }

View File

@@ -16,18 +16,19 @@ class ServerInfoForm extends BaseComponent {
<div class="settings-card"> <div class="settings-card">
<div class="server-info-left"> <div class="server-info-left">
<img class="server-info-icon" src="${this.props.server.icon}"/> <img class="server-info-icon" src="${this.props.server.icon}"/>
</div>
<div class="server-info-right">
<div class="server-info-row"> <div class="server-info-row">
<span class="server-info-alias">${this.props.server.alias}</span> <span class="server-info-alias">${this.props.server.alias}</span>
<i class="material-icons open-tab-button">open_in_new</i> <i class="material-icons open-tab-button">open_in_new</i>
</div> </div>
</div> <div class="server-info-row">
<div class="server-info-right"> <input class="setting-input-value" disabled value="${this.props.server.url}"/>
<div class="server-info-row server-url">
<span class="server-url-info" title="${this.props.server.url}">${this.props.server.url}</span>
</div> </div>
<div class="server-info-row"> <div class="server-info-row">
<div class="action red server-delete-action"> <div class="action red server-delete-action">
<span>Disconnect</span> <i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
</div> </div>
</div> </div>
</div> </div>
@@ -43,7 +44,6 @@ class ServerInfoForm extends BaseComponent {
initForm() { initForm() {
this.$serverInfoForm = this.generateNodeFromTemplate(this.template()); this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
this.$serverInfoAlias = this.$serverInfoForm.getElementsByClassName('server-info-alias')[0]; this.$serverInfoAlias = this.$serverInfoForm.getElementsByClassName('server-info-alias')[0];
this.$serverIcon = this.$serverInfoForm.getElementsByClassName('server-info-icon')[0];
this.$deleteServerButton = this.$serverInfoForm.getElementsByClassName('server-delete-action')[0]; this.$deleteServerButton = this.$serverInfoForm.getElementsByClassName('server-delete-action')[0];
this.$openServerButton = this.$serverInfoForm.getElementsByClassName('open-tab-button')[0]; this.$openServerButton = this.$serverInfoForm.getElementsByClassName('open-tab-button')[0];
this.props.$root.appendChild(this.$serverInfoForm); this.props.$root.appendChild(this.$serverInfoForm);
@@ -71,12 +71,7 @@ class ServerInfoForm extends BaseComponent {
this.$serverInfoAlias.addEventListener('click', () => { this.$serverInfoAlias.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index); ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
}); });
this.$serverIcon.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
});
} }
} }
module.exports = ServerInfoForm; module.exports = ServerInfoForm;

View File

@@ -11,12 +11,14 @@ class ServersSection extends BaseSection {
template() { template() {
return ` return `
<div class="add-server-modal"> <div id="myModal" class="modal">
<div class="modal-container"> <div class="modal-content">
<div class="settings-pane" id="server-settings-pane"> <div class="settings-pane" id="server-settings-pane">
<div class="page-title">Add a Zulip organization</div> <div class="page-title">Add a Zulip organization</div>
<div id="new-server-container"></div> <div id="new-server-container"></div>
</div> </div>
</div> </div>
</div> </div>
`; `;
@@ -30,9 +32,16 @@ class ServersSection extends BaseSection {
this.props.$root.innerHTML = ''; this.props.$root.innerHTML = '';
this.props.$root.innerHTML = this.template(); this.props.$root.innerHTML = this.template();
this.$serverInfoContainer = document.getElementById('server-info-container');
this.$existingServers = document.getElementById('existing-servers');
this.$newServerContainer = document.getElementById('new-server-container'); this.$newServerContainer = document.getElementById('new-server-container');
this.$newServerButton = document.getElementById('new-server-action');
this.initNewServerForm(); this.initNewServerForm();
this.$createOrganizationContainer = document.getElementById('create-organization-container');
} }
initNewServerForm() { initNewServerForm() {

View File

@@ -1,7 +1,6 @@
'use strict'; 'use strict';
const BaseSection = require(__dirname + '/base-section.js'); const BaseSection = require(__dirname + '/base-section.js');
const shell = require('electron').shell;
class ShortcutsSection extends BaseSection { class ShortcutsSection extends BaseSection {
constructor(props) { constructor(props) {
@@ -24,10 +23,6 @@ class ShortcutsSection extends BaseSection {
<tr> <tr>
<td><kbd>${userOSKey}</kbd><kbd>K</kbd></td> <td><kbd>${userOSKey}</kbd><kbd>K</kbd></td>
<td>Keyboard Shortcuts</td> <td>Keyboard Shortcuts</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>Toggle Do Not Disturb</td>
</tr> </tr>
<tr> <tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>D</kbd></td> <td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>D</kbd></td>
@@ -83,6 +78,14 @@ 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>
@@ -118,7 +121,7 @@ class ShortcutsSection extends BaseSection {
<td>Actual Size</td> <td>Actual Size</td>
</tr> </tr>
<tr> <tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td> <td><kbd>${userOSKey}</kbd><kbd>S</kbd></td>
<td>Toggle Sidebar</td> <td>Toggle Sidebar</td>
</tr> </tr>
<tr> <tr>
@@ -160,7 +163,6 @@ class ShortcutsSection extends BaseSection {
</table> </table>
<div class="setting-control"></div> <div class="setting-control"></div>
</div> </div>
<div class="settings-card tip"><b><i class="material-icons md-14">settings</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link">keyboard shortcuts</span>.</div>
</div> </div>
`; `;
} }
@@ -180,10 +182,6 @@ class ShortcutsSection extends BaseSection {
<tr> <tr>
<td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></td> <td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></td>
<td>Keyboard Shortcuts</td> <td>Keyboard Shortcuts</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>Toggle Do Not Disturb</td>
</tr> </tr>
<tr> <tr>
<td><kbd>${userOSKey}</kbd> + <kbd>L</kbd></td> <td><kbd>${userOSKey}</kbd> + <kbd>L</kbd></td>
@@ -258,7 +256,7 @@ class ShortcutsSection extends BaseSection {
<td>Actual Size</td> <td>Actual Size</td>
</tr> </tr>
<tr> <tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td> <td><kbd>${userOSKey}</kbd> + <kbd>S</kbd></td>
<td>Toggle Sidebar</td> <td>Toggle Sidebar</td>
</tr> </tr>
<tr> <tr>
@@ -300,22 +298,13 @@ class ShortcutsSection extends BaseSection {
</table> </table>
<div class="setting-control"></div> <div class="setting-control"></div>
</div> </div>
<div class="tip"><b><i class="material-icons md-14">lightbulb_outline</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link">keyboard shortcuts</span>.</div>
</div> </div>
`; `;
} }
openHotkeysExternalLink() {
const link = 'https://zulipchat.com/help/keyboard-shortcuts';
const externalCreateNewOrgEl = document.getElementById('open-hotkeys-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
init() { init() {
this.props.$root.innerHTML = (process.platform === 'darwin') ? this.props.$root.innerHTML = (process.platform === 'darwin') ?
this.templateMac() : this.templateWinLin(); this.templateMac() : this.templateWinLin();
this.openHotkeysExternalLink();
} }
} }

View File

@@ -4,14 +4,10 @@ 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');
// Prevent drag and drop event in main process which prevents remote code executaion
require(__dirname + '/shared/preventdrag.js');
const logout = () => { const logout = () => {
// Create the menu for the below // Create the menu for the below
document.querySelector('.dropdown-toggle').click(); document.querySelector('.dropdown-toggle').click();
@@ -24,7 +20,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
@@ -56,24 +52,6 @@ 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;
@@ -82,10 +60,3 @@ 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

@@ -1,17 +0,0 @@
'use strict';
// This is a security fix. Following function prevents drag and drop event in the app
// so that attackers can't execute any remote code within the app
// It doesn't affect the compose box so that users can still
// use drag and drop event to share files etc
const preventDragAndDrop = () => {
const preventEvents = ['dragover', 'drop'];
preventEvents.forEach(dragEvents => {
document.addEventListener(dragEvents, event => {
event.preventDefault();
});
});
};
preventDragAndDrop();

View File

@@ -98,7 +98,7 @@ const renderNativeImage = function (arg) {
return Promise.resolve() return Promise.resolve()
.then(() => renderCanvas(arg)) .then(() => renderCanvas(arg))
.then(canvas => { .then(canvas => {
const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPNG(); const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPng();
return Promise.resolve(nativeImage.createFromBuffer(pngData, config.pixelRatio)); return Promise.resolve(nativeImage.createFromBuffer(pngData, config.pixelRatio));
}); });
}; };

View File

@@ -1,41 +0,0 @@
'use strict';
const ConfigUtil = require(__dirname + '/config-util.js');
function toggle() {
const dnd = !ConfigUtil.getConfigItem('dnd', false);
const dndSettingList = ['showNotification', 'silent'];
if (process.platform === 'win32') {
dndSettingList.push('flashTaskbarOnMessage');
}
let newSettings;
if (dnd) {
const oldSettings = {};
newSettings = {};
// Iterate through the dndSettingList.
for (const settingName of dndSettingList) {
// Store the current value of setting.
oldSettings[settingName] = ConfigUtil.getConfigItem(settingName);
// New value of setting.
newSettings[settingName] = (settingName === 'silent');
}
// Store old value in oldSettings.
ConfigUtil.setConfigItem('dndPreviousSettings', oldSettings);
} else {
newSettings = ConfigUtil.getConfigItem('dndPreviousSettings');
}
for (const settingName of dndSettingList) {
ConfigUtil.setConfigItem(settingName, newSettings[settingName]);
}
ConfigUtil.setConfigItem('dnd', dnd);
return {dnd, newSettings};
}
module.exports = {
toggle
};

View File

@@ -5,8 +5,6 @@ const fs = require('fs');
const path = require('path'); const path = require('path');
const JsonDB = require('node-json-db'); const JsonDB = require('node-json-db');
const request = require('request'); const request = require('request');
const escape = require('escape-html');
const Logger = require('./logger-util'); const Logger = require('./logger-util');
const logger = new Logger({ const logger = new Logger({
@@ -190,7 +188,7 @@ class DomainUtil {
// Following check handles both the cases // Following check handles both the cases
icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon, icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon,
url: data.realm_uri, url: data.realm_uri,
alias: escape(data.realm_name) alias: data.realm_name
}); });
} }
} else { } else {

View File

@@ -19,20 +19,7 @@ class LinkUtil {
const currentDomain = wurl('hostname', currentUrl); const currentDomain = wurl('hostname', currentUrl);
const newDomain = wurl('hostname', newUrl); const newDomain = wurl('hostname', newUrl);
const sameDomainUrl = (currentDomain === newDomain || newUrl === currentUrl + '/'); return (currentDomain === newDomain) && newUrl.includes('/#narrow');
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

@@ -37,11 +37,9 @@ class ReconnectUtil {
console.log('There is no internet connection, try checking network cables, modem and router.'); console.log('There is no internet connection, try checking network cables, modem and router.');
const errMsgHolder = document.querySelector('#description'); const errMsgHolder = document.querySelector('#description');
if (errMsgHolder) {
errMsgHolder.innerHTML = ` errMsgHolder.innerHTML = `
<div>You internet connection does't seem to work properly!</div> <div>You internet connection does't seem to work properly!</div>
</div>Verify that it works and then click try again.</div>`; </div>Verify that it works and then click try again.</div>`;
}
return resolve(false); return resolve(false);
}); });
} else { } else {

View File

@@ -24,10 +24,6 @@
</div> </div>
</div> </div>
<div id="actions-container"> <div id="actions-container">
<div class="action-button" id="dnd-action">
<i class="material-icons md-48">notifications</i>
<span id="dnd-tooltip" style="display:none">Do Not Disturb</span>
</div>
<div class="action-button" id="reload-action"> <div class="action-button" id="reload-action">
<i class="material-icons md-48">refresh</i> <i class="material-icons md-48">refresh</i>
<span id="reload-tooltip" style="display:none">Reload</span> <span id="reload-tooltip" style="display:none">Reload</span>
@@ -46,11 +42,6 @@
<div id="webviews-container"></div> <div id="webviews-container"></div>
</div> </div>
</div> </div>
<div id="feedback-modal">
<send-feedback></send-feedback>
</div>
</body> </body>
<script src="js/main.js"></script> <script src="js/main.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html> </html>

View File

@@ -18,5 +18,4 @@
</div> </div>
</body> </body>
<script src="js/pages/network.js"></script> <script src="js/pages/network.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html> </html>

View File

@@ -1,11 +1,157 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en" class="responsive desktop"> <html lang="en" class="responsive desktop">
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width"> <meta name="viewport" content="width=device-width">
<title>Zulip - Settings</title> <title>Zulip - Settings</title>
<link rel="stylesheet" href="css/preference.css" type="text/css" media="screen"> <link rel="stylesheet" href="css/preference.css" type="text/css" media="screen">
<style>
/* The Modal (background) */
.modal {
display: block;
/* Hidden by default */
position: fixed;
/* Stay in place */
z-index: 1;
/* Sit on top */
padding-top: 100px;
/* Location of the box */
left: 0;
top: 0;
width: 100%;
/* Full width */
height: 100%;
/* Full height */
overflow: auto;
/* Enable scroll if needed */
/* background-color: rgb(0, 0, 0); */
/* Fallback color */
/* background-color: rgba(0, 0, 0, 0.4); */
background: rgba(61, 64, 67, 15);
/* Black w/ opacity */
}
/* Modal Content */
.modal-content {
background-color: #fefefe;
margin: auto;
padding: 57px;
border: #dae1e3 1px solid;
width: 600px;
/* margin-bottom: 113px; */
height: 344px;
border-radius: 4px;
}
/* The Close Button */
.close {
color: #aaaaaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: #000;
text-decoration: none;
cursor: pointer;
}
#server-settings-pane {
margin-left: 18%;
}
.settings-card.server-info-right:hover {
box-shadow: none !important;
border: none !important;
}
.server-save-action {
padding: 11px;
padding-left: 20px;
width: 292px;
border-radius: 4px;
margin-top: 16px;
}
.divider {
/* width: 250px;
text-align: center;
margin-left: 39px;
margin-top: 48px; */
width: 335px;
text-align: center;
margin-left: 0px;
margin-top: 48px;
color: #7d878a;
}
.divider hr {
margin-left: auto;
margin-right: auto;
width: 42%;
}
.left {
float: left;
}
.right {
float: right;
}
.server-create-action {
width: 314px;
margin-top: 40px;
padding: 11px;
border-radius: 4px;
}
.server-create-action span {
margin-left: 60px;
font-size: 15px;
}
.server-save-action span {
margin-left: 130px;
}
.server-save-action {
width: 306px;
}
.server-info-row {
display: block;
}
.setting-input-value {
width: 319px;
}
.page-title {
color: #222c31;
margin-left: 65px;
font-size: 22px;
font-weight: 100;
margin-bottom: 20px;
padding: 0;
}
.title {
color: #7d878a;
}
</style>
</head> </head>
<body> <body>
<div id="content"> <div id="content">
<div id="sidebar"></div> <div id="sidebar"></div>
@@ -13,5 +159,5 @@
</div> </div>
</body> </body>
<script src="js/pages/preference/preference.js"></script> <script src="js/pages/preference/preference.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html> </html>

View File

@@ -8,7 +8,7 @@ cache:
- node_modules - node_modules
install: install:
- ps: Install-Product node 8 x64 - ps: Install-Product node 6 x64
- git reset --hard HEAD - git reset --hard HEAD
- npm install npm -g - npm install npm -g
- node --version - node --version
@@ -21,4 +21,4 @@ build: off
test_script: test_script:
- npm run test - npm run test
# - npm run test-e2e - npm run test-e2e

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -15,7 +15,6 @@ 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
@@ -26,7 +25,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 snapcraft libxext-dev libxtst-dev libxkbfile-dev libgconf-2-4 $ sudo apt install git nodejs python build-essential libxext-dev libxtst-dev libxkbfile-dev
``` ```
[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
@@ -77,7 +76,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, `.snap` file and an * on Linux, a plain `.zip` file as well as a `.deb` 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.

8202
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "zulip", "name": "zulip",
"productName": "Zulip", "productName": "Zulip",
"version": "2.3.1", "version": "1.8.2",
"main": "./app/main", "main": "./app/main",
"description": "Zulip Desktop App", "description": "Zulip Desktop App",
"license": "Apache-2.0", "license": "Apache-2.0",
@@ -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": "node ./tools/reinstall-node-modules.js", "reinstall": "./tools/reinstall-node-modules",
"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",
@@ -46,8 +46,7 @@
], ],
"copyright": "©2017 Kandra Labs, Inc.", "copyright": "©2017 Kandra Labs, Inc.",
"mac": { "mac": {
"category": "public.app-category.productivity", "category": "public.app-category.productivity"
"artifactName": "${productName}-${version}-${arch}.${ext}"
}, },
"linux": { "linux": {
"category": "Chat;GNOME;GTK;Network;InstantMessaging", "category": "Chat;GNOME;GTK;Network;InstantMessaging",
@@ -56,41 +55,32 @@
"target": [ "target": [
"deb", "deb",
"zip", "zip",
"AppImage", "AppImage"
"snap"
], ],
"maintainer": "Akash Nimare <svnitakash@gmail.com>", "maintainer": "Akash Nimare <svnitakash@gmail.com>"
"artifactName": "${productName}-${version}-${arch}.${ext}"
}, },
"deb": { "deb": {
"synopsis": "Zulip Desktop App", "synopsis": "Zulip Desktop App",
"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",
"iconSize": 100, "iconSize": 128,
"contents": [ "contents": [
{ {
"x": 380, "x": 380,
"y": 280, "y": 240,
"type": "link", "type": "link",
"path": "/Applications" "path": "/Applications"
}, },
{ {
"x": 110, "x": 122,
"y": 280, "y": 240,
"type": "file" "type": "file"
} }
], ]
"window": {
"width": 500,
"height": 500
}
}, },
"win": { "win": {
"target": [ "target": [
@@ -123,17 +113,17 @@
"assert": "1.4.1", "assert": "1.4.1",
"cp-file": "^5.0.0", "cp-file": "^5.0.0",
"devtron": "1.4.0", "devtron": "1.4.0",
"electron": "2.0.1", "electron": "1.8.2",
"electron-builder": "20.13.4", "electron-builder": "19.53.6",
"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",
"gulp": "^4.0.0", "gulp": "3.9.1",
"gulp-tape": "0.0.9", "gulp-tape": "0.0.9",
"is-ci": "^1.0.10", "is-ci": "^1.0.10",
"nodemon": "^1.14.11", "nodemon": "^1.14.11",
"pre-commit": "1.2.2", "pre-commit": "1.2.2",
"spectron": "3.8.0", "spectron": "3.7.2",
"tap-colorize": "^1.2.0", "tap-colorize": "^1.2.0",
"tape": "^4.8.0", "tape": "^4.8.0",
"xo": "0.18.2" "xo": "0.18.2"

View File

@@ -2,10 +2,6 @@
# This script runs when user install the debian package # This script runs when user install the debian package
# Link to the binary
ln -sf '/opt/${productFilename}/${executable}' '/usr/local/bin/${executable}';
echo 'Successfully added /opt/${productFilename}/${executable} to /usr/local/bin/${executable}'
# Install apt repository source list if it does not exist # Install apt repository source list if it does not exist
if ! grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep zulip.list; then if ! grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep zulip.list; then
sudo apt-key adv --keyserver pool.sks-keyservers.net --recv 69AD12704E71A4803DCA3A682424BE5AE9BD10D9 sudo apt-key adv --keyserver pool.sks-keyservers.net --recv 69AD12704E71A4803DCA3A682424BE5AE9BD10D9

View File

@@ -29,7 +29,3 @@ appDirectory=/home/$getSudoUser/.config/Zulip/;
if [ -d $appDirectory ]; then if [ -d $appDirectory ]; then
sudo rm -rf $appDirectory; sudo rm -rf $appDirectory;
fi fi
# Delete the link to the binary
echo 'Removing binary link'
sudo rm -f '/usr/local/bin/${executable}';

View File

@@ -15,6 +15,6 @@ fi
npm run test npm run test
# if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
# npm run test-e2e npm run test-e2e
# fi fi

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,37 +0,0 @@
name: zulip
version: 2.0.0
summary: Zulip Desktop Client for Linux
description: Zulip combines the immediacy of Slack with an email threading model. With Zulip, you can catch up on important conversations while ignoring irrelevant ones.
confinement: strict
grade: stable
icon: snap/gui/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

@@ -7,7 +7,7 @@ test('app runs', function (t) {
const app = setup.createApp() const app = setup.createApp()
setup.waitForLoad(app, t) setup.waitForLoad(app, t)
.then(() => app.client.windowByIndex(1)) // focus on webview .then(() => app.client.windowByIndex(1)) // focus on webview
.then(() => app.client.waitForExist('//*[@id="connect"]')) // id of the connect button .then(() => app.client.waitForExist('//*[@id="new-server-container"]/div/div/div[2]/input'))
.then(() => setup.endTest(app, t), .then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error')) (err) => setup.endTest(app, t, err || 'error'))
}) })

View File

@@ -1,17 +0,0 @@
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

@@ -1,18 +0,0 @@
#!/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);
});