mirror of
https://github.com/zulip/zulip-desktop.git
synced 2025-10-26 17:43:36 +00:00
Compare commits
53 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b34bc696e | ||
|
|
0e3c6bceeb | ||
|
|
c2e138c16a | ||
|
|
8d1b027b0e | ||
|
|
7bb8d78973 | ||
|
|
a7c7791bf8 | ||
|
|
5d120b4416 | ||
|
|
ceaf13dee2 | ||
|
|
f81381dfec | ||
|
|
dbe89cdd09 | ||
|
|
14c59bdae1 | ||
|
|
0ac3e3f6d3 | ||
|
|
d69c1339e6 | ||
|
|
fe56a20334 | ||
|
|
cfc97c9b73 | ||
|
|
2e70b515da | ||
|
|
51e414a508 | ||
|
|
8e7a9bf230 | ||
|
|
6493ddb8ec | ||
|
|
31edbe0d67 | ||
|
|
9980fee785 | ||
|
|
ff9986ec6b | ||
|
|
f3423d394c | ||
|
|
a1da199627 | ||
|
|
537fbe8f9e | ||
|
|
3fccb33fca | ||
|
|
5638590c8b | ||
|
|
29ed00981d | ||
|
|
d7638c0b95 | ||
|
|
7fadbe877b | ||
|
|
32a21889fb | ||
|
|
c4a961f9da | ||
|
|
ceaa898570 | ||
|
|
73fe17041d | ||
|
|
9f756cad3e | ||
|
|
6db6b7c482 | ||
|
|
09c45e75e8 | ||
|
|
120b80cf65 | ||
|
|
22f705960d | ||
|
|
ca8ce1deaa | ||
|
|
f70432f4e3 | ||
|
|
60d693700e | ||
|
|
6e7333eab6 | ||
|
|
0d8dd1cd90 | ||
|
|
0ee3757774 | ||
|
|
811df9f381 | ||
|
|
8bd02cc7e4 | ||
|
|
9d5d221371 | ||
|
|
6006f1a3f8 | ||
|
|
4f96df4a34 | ||
|
|
a13558fa16 | ||
|
|
a1d19a385c | ||
|
|
c98667236e |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -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
|
||||
|
||||
@@ -15,7 +15,7 @@ addons:
|
||||
|
||||
language: node_js
|
||||
node_js:
|
||||
- '6'
|
||||
- '8'
|
||||
|
||||
before_install:
|
||||
- ./scripts/travis-xvfb.sh
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use strict';
|
||||
const { app, dialog } = require('electron');
|
||||
const { app, dialog, shell } = require('electron');
|
||||
const { autoUpdater } = require('electron-updater');
|
||||
const isDev = require('electron-is-dev');
|
||||
|
||||
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
|
||||
|
||||
function appUpdater() {
|
||||
function appUpdater(updateFromMenu = false) {
|
||||
// Don't initiate auto-updates in development
|
||||
if (isDev) {
|
||||
return;
|
||||
@@ -17,6 +17,8 @@ function appUpdater() {
|
||||
return;
|
||||
}
|
||||
|
||||
let updateAvailable = false;
|
||||
|
||||
// Create Logs directory
|
||||
const LogsDir = `${app.getPath('userData')}/Logs`;
|
||||
|
||||
@@ -28,7 +30,58 @@ function appUpdater() {
|
||||
autoUpdater.logger = log;
|
||||
|
||||
// Handle auto updates for beta/pre releases
|
||||
autoUpdater.allowPrerelease = ConfigUtil.getConfigItem('betaUpdate') || false;
|
||||
const isBetaUpdate = ConfigUtil.getConfigItem('betaUpdate');
|
||||
|
||||
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
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
|
||||
@@ -7,7 +7,7 @@ const crashHandler = () => {
|
||||
productName: 'zulip-electron',
|
||||
companyName: 'Kandra Labs, Inc.',
|
||||
submitURL: 'https://zulip-sentry.herokuapp.com/crashreport',
|
||||
autoSubmit: true
|
||||
uploadToServer: true
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
@@ -131,6 +131,9 @@ function createMainWindow() {
|
||||
// Decrease load on GPU (experimental)
|
||||
app.disableHardwareAcceleration();
|
||||
|
||||
// Temporary fix for Electron render colors differently
|
||||
app.commandLine.appendSwitch('force-color-profile', 'srgb');
|
||||
|
||||
// eslint-disable-next-line max-params
|
||||
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
|
||||
event.preventDefault();
|
||||
@@ -161,7 +164,9 @@ app.on('ready', () => {
|
||||
|
||||
page.once('did-frame-finish-load', () => {
|
||||
// Initate auto-updates on MacOS and Windows
|
||||
appUpdater();
|
||||
if (ConfigUtil.getConfigItem('autoUpdate')) {
|
||||
appUpdater();
|
||||
}
|
||||
crashHandler();
|
||||
});
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
'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');
|
||||
const { appUpdater } = require('./autoupdater');
|
||||
|
||||
const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js');
|
||||
const DNDUtil = require(__dirname + '/../renderer/js/utils/dnd-util.js');
|
||||
|
||||
const appName = app.getName();
|
||||
|
||||
@@ -87,7 +88,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 +140,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');
|
||||
});
|
||||
}
|
||||
}];
|
||||
}
|
||||
@@ -197,7 +196,12 @@ class AppMenu {
|
||||
AppMenu.sendAction('open-about');
|
||||
}
|
||||
}
|
||||
}, {
|
||||
}, {
|
||||
label: `Check for Update`,
|
||||
click() {
|
||||
AppMenu.checkForUpdate();
|
||||
}
|
||||
}, {
|
||||
type: 'separator'
|
||||
}, {
|
||||
label: 'Desktop App Settings',
|
||||
@@ -217,6 +221,13 @@ class AppMenu {
|
||||
}
|
||||
}, {
|
||||
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',
|
||||
accelerator: 'Command+Shift+D',
|
||||
@@ -297,7 +308,12 @@ class AppMenu {
|
||||
AppMenu.sendAction('open-about');
|
||||
}
|
||||
}
|
||||
}, {
|
||||
}, {
|
||||
label: `Check for Update`,
|
||||
click() {
|
||||
AppMenu.checkForUpdate();
|
||||
}
|
||||
}, {
|
||||
type: 'separator'
|
||||
}, {
|
||||
label: 'Desktop App Settings',
|
||||
@@ -319,6 +335,13 @@ class AppMenu {
|
||||
}
|
||||
}, {
|
||||
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',
|
||||
accelerator: 'Ctrl+Shift+D',
|
||||
@@ -387,21 +410,36 @@ class AppMenu {
|
||||
win.webContents.send(action, ...params);
|
||||
}
|
||||
|
||||
static checkForUpdate() {
|
||||
appUpdater(true);
|
||||
}
|
||||
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');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
903
app/package-lock.json
generated
903
app/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "zulip",
|
||||
"productName": "Zulip",
|
||||
"version": "1.9.0",
|
||||
"version": "2.3.1",
|
||||
"description": "Zulip Desktop App",
|
||||
"license": "Apache-2.0",
|
||||
"copyright": "Kandra Labs, Inc.",
|
||||
@@ -26,19 +26,21 @@
|
||||
"InstantMessaging"
|
||||
],
|
||||
"dependencies": {
|
||||
"auto-launch": "5.0.1",
|
||||
"@electron-elements/send-feedback": "1.0.7",
|
||||
"escape-html": "1.0.3",
|
||||
"auto-launch": "5.0.5",
|
||||
"electron-is-dev": "0.3.0",
|
||||
"electron-log": "2.2.7",
|
||||
"electron-log": "2.2.14",
|
||||
"electron-spellchecker": "1.1.2",
|
||||
"electron-updater": "2.18.2",
|
||||
"electron-updater": "2.21.10",
|
||||
"electron-window-state": "4.1.1",
|
||||
"is-online": "7.0.0",
|
||||
"node-json-db": "0.7.3",
|
||||
"request": "2.81.0",
|
||||
"request": "2.85.0",
|
||||
"semver": "5.4.1",
|
||||
"wurl": "2.5.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"node-mac-notifier": "0.0.13"
|
||||
"node-mac-notifier": "0.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,9 +13,6 @@ body {
|
||||
#content {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
background: #fff url(../img/ic_loading.gif) no-repeat;
|
||||
background-size: 60px 60px;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.toggle-sidebar {
|
||||
@@ -45,6 +42,28 @@ body {
|
||||
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-family: 'Material Icons';
|
||||
font-style: normal;
|
||||
@@ -251,6 +270,25 @@ body {
|
||||
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 {
|
||||
/* transition: opacity 0.3s ease-in; */
|
||||
flex-grow: 1;
|
||||
@@ -282,6 +320,7 @@ webview.focus {
|
||||
|
||||
/* Tooltip styling */
|
||||
|
||||
#dnd-tooltip,
|
||||
#back-tooltip,
|
||||
#reload-tooltip,
|
||||
#setting-tooltip {
|
||||
@@ -299,6 +338,7 @@ webview.focus {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
#dnd-tooltip:after,
|
||||
#back-tooltip:after,
|
||||
#reload-tooltip:after,
|
||||
#setting-tooltip:after {
|
||||
@@ -403,3 +443,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;
|
||||
}
|
||||
|
||||
@@ -557,6 +557,22 @@ input.toggle-round:checked+label:after {
|
||||
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 */
|
||||
|
||||
@@ -604,4 +620,20 @@ input.toggle-round:checked+label:after {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
35
app/renderer/js/components/handle-external-link.js
Normal file
35
app/renderer/js/components/handle-external-link.js
Normal file
@@ -0,0 +1,35 @@
|
||||
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;
|
||||
@@ -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 {
|
||||
@@ -19,9 +18,10 @@ class WebView extends BaseComponent {
|
||||
this.props = props;
|
||||
|
||||
this.zoomFactor = 1.0;
|
||||
this.loading = false;
|
||||
this.loading = true;
|
||||
this.badgeCount = 0;
|
||||
this.customCSS = ConfigUtil.getConfigItem('customCSS');
|
||||
this.$webviewsContainer = document.querySelector('#webviews-container').classList;
|
||||
}
|
||||
|
||||
template() {
|
||||
@@ -46,16 +46,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) {
|
||||
@@ -96,6 +87,7 @@ class WebView extends BaseComponent {
|
||||
if (this.props.role === 'server') {
|
||||
this.$el.classList.add('onload');
|
||||
}
|
||||
this.loading = false;
|
||||
this.show();
|
||||
});
|
||||
|
||||
@@ -129,6 +121,13 @@ class WebView extends BaseComponent {
|
||||
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.add('active');
|
||||
setTimeout(() => {
|
||||
@@ -137,7 +136,6 @@ class WebView extends BaseComponent {
|
||||
}
|
||||
}, 1000);
|
||||
this.focus();
|
||||
this.loading = false;
|
||||
this.props.onTitleChange();
|
||||
// Injecting preload css in webview to override some css rules
|
||||
this.$el.insertCSS(fs.readFileSync(path.join(__dirname, '/../../css/preload.css'), 'utf8'));
|
||||
@@ -230,6 +228,9 @@ class WebView extends BaseComponent {
|
||||
|
||||
reload() {
|
||||
this.hide();
|
||||
// Shows the loading indicator till the webview is reloaded
|
||||
this.$webviewsContainer.remove('loaded');
|
||||
this.loading = true;
|
||||
this.$el.reload();
|
||||
}
|
||||
|
||||
|
||||
62
app/renderer/js/feedback.js
Normal file
62
app/renderer/js/feedback.js
Normal 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
|
||||
};
|
||||
@@ -11,7 +11,9 @@ const WebView = require(__dirname + '/js/components/webview.js');
|
||||
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 DNDUtil = require(__dirname + '/js/utils/dnd-util.js');
|
||||
const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js');
|
||||
const { feedbackHolder } = require(__dirname + '/js/feedback.js');
|
||||
|
||||
class ServerManagerView {
|
||||
constructor() {
|
||||
@@ -23,12 +25,14 @@ class ServerManagerView {
|
||||
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
|
||||
this.$webviewsContainer = document.getElementById('webviews-container');
|
||||
this.$backButton = $actionsContainer.querySelector('#back-action');
|
||||
this.$dndButton = $actionsContainer.querySelector('#dnd-action');
|
||||
|
||||
this.$addServerTooltip = document.getElementById('add-server-tooltip');
|
||||
this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip');
|
||||
this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip');
|
||||
this.$serverIconTooltip = document.getElementsByClassName('server-tooltip');
|
||||
this.$backTooltip = $actionsContainer.querySelector('#back-tooltip');
|
||||
this.$dndTooltip = $actionsContainer.querySelector('#dnd-tooltip');
|
||||
|
||||
this.$sidebar = document.getElementById('sidebar');
|
||||
|
||||
@@ -85,9 +89,15 @@ class ServerManagerView {
|
||||
startMinimized: false,
|
||||
enableSpellchecker: true,
|
||||
showNotification: true,
|
||||
autoUpdate: true,
|
||||
betaUpdate: false,
|
||||
silent: false,
|
||||
lastActiveTab: 0
|
||||
lastActiveTab: 0,
|
||||
dnd: false,
|
||||
dndPreviousSettings: {
|
||||
showNotification: true,
|
||||
silent: false
|
||||
}
|
||||
};
|
||||
|
||||
// Platform specific settings
|
||||
@@ -95,6 +105,7 @@ class ServerManagerView {
|
||||
if (process.platform === 'win32') {
|
||||
// Only available on Windows
|
||||
settingOptions.flashTaskbarOnMessage = true;
|
||||
settingOptions.dndPreviousSettings.flashTaskbarOnMessage = true;
|
||||
}
|
||||
|
||||
for (const i in settingOptions) {
|
||||
@@ -155,6 +166,11 @@ class ServerManagerView {
|
||||
}
|
||||
|
||||
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.tabs[this.activeTabIndex].webview.reload();
|
||||
});
|
||||
@@ -175,10 +191,16 @@ 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);
|
||||
this.sidebarHoverEvent(this.$dndButton, this.$dndTooltip);
|
||||
}
|
||||
|
||||
initDNDButton() {
|
||||
const dnd = ConfigUtil.getConfigItem('dnd', false);
|
||||
this.toggleDNDButton(dnd);
|
||||
}
|
||||
|
||||
getTabIndex() {
|
||||
@@ -187,9 +209,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';
|
||||
@@ -197,8 +227,13 @@ class ServerManagerView {
|
||||
}
|
||||
|
||||
onHover(index, serverName) {
|
||||
this.$serverIconTooltip[index].innerHTML = serverName;
|
||||
this.$serverIconTooltip[index].innerText = 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) {
|
||||
@@ -238,6 +273,9 @@ class ServerManagerView {
|
||||
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]);
|
||||
}
|
||||
|
||||
@@ -324,6 +362,15 @@ 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) {
|
||||
@@ -343,6 +390,9 @@ class ServerManagerView {
|
||||
}
|
||||
|
||||
destroyView() {
|
||||
// Show loading indicator
|
||||
this.$webviewsContainer.classList.remove('loaded');
|
||||
|
||||
// Clear global variables
|
||||
this.activeTabIndex = -1;
|
||||
this.tabs = [];
|
||||
@@ -390,6 +440,12 @@ 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() {
|
||||
const webviewListeners = {
|
||||
'webview-reload': 'reload',
|
||||
@@ -496,6 +552,10 @@ class ServerManagerView {
|
||||
}
|
||||
ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount));
|
||||
});
|
||||
|
||||
ipcRenderer.on('open-feedback-modal', () => {
|
||||
feedbackHolder.classList.add('show');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -48,7 +48,11 @@ class GeneralSection extends BaseSection {
|
||||
</div>
|
||||
</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-description">Get beta updates</div>
|
||||
<div class="setting-control"></div>
|
||||
@@ -104,7 +108,8 @@ class GeneralSection extends BaseSection {
|
||||
this.updateTrayOption();
|
||||
this.updateBadgeOption();
|
||||
this.updateSilentOption();
|
||||
this.updateUpdateOption();
|
||||
this.autoUpdateOption();
|
||||
this.betaUpdateOption();
|
||||
this.updateSidebarOption();
|
||||
this.updateStartAtLoginOption();
|
||||
this.updateResetDataOption();
|
||||
@@ -160,14 +165,26 @@ class GeneralSection extends BaseSection {
|
||||
});
|
||||
}
|
||||
|
||||
updateUpdateOption() {
|
||||
autoUpdateOption() {
|
||||
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({
|
||||
$element: document.querySelector('#betaupdate-option .setting-control'),
|
||||
value: ConfigUtil.getConfigItem('betaUpdate', false),
|
||||
clickHandler: () => {
|
||||
const newValue = !ConfigUtil.getConfigItem('betaUpdate');
|
||||
ConfigUtil.setConfigItem('betaUpdate', newValue);
|
||||
this.updateUpdateOption();
|
||||
this.betaUpdateOption();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -97,6 +97,15 @@ class PreferenceView extends BaseComponent {
|
||||
ipcRenderer.on('toggletray', (event, 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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const BaseSection = require(__dirname + '/base-section.js');
|
||||
const shell = require('electron').shell;
|
||||
|
||||
class ShortcutsSection extends BaseSection {
|
||||
constructor(props) {
|
||||
@@ -23,6 +24,10 @@ class ShortcutsSection extends BaseSection {
|
||||
<tr>
|
||||
<td><kbd>${userOSKey}</kbd><kbd>K</kbd></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>
|
||||
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>D</kbd></td>
|
||||
@@ -78,14 +83,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 +118,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>
|
||||
@@ -163,6 +160,7 @@ class ShortcutsSection extends BaseSection {
|
||||
</table>
|
||||
<div class="setting-control"></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>
|
||||
`;
|
||||
}
|
||||
@@ -182,6 +180,10 @@ class ShortcutsSection extends BaseSection {
|
||||
<tr>
|
||||
<td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></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>
|
||||
<td><kbd>${userOSKey}</kbd> + <kbd>L</kbd></td>
|
||||
@@ -256,7 +258,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>
|
||||
@@ -298,13 +300,22 @@ class ShortcutsSection extends BaseSection {
|
||||
</table>
|
||||
<div class="setting-control"></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>
|
||||
`;
|
||||
}
|
||||
|
||||
openHotkeysExternalLink() {
|
||||
const link = 'https://zulipchat.com/help/keyboard-shortcuts';
|
||||
const externalCreateNewOrgEl = document.getElementById('open-hotkeys-link');
|
||||
externalCreateNewOrgEl.addEventListener('click', () => {
|
||||
shell.openExternal(link);
|
||||
});
|
||||
}
|
||||
init() {
|
||||
this.props.$root.innerHTML = (process.platform === 'darwin') ?
|
||||
this.templateMac() : this.templateWinLin();
|
||||
this.openHotkeysExternalLink();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -98,7 +98,7 @@ const renderNativeImage = function (arg) {
|
||||
return Promise.resolve()
|
||||
.then(() => renderCanvas(arg))
|
||||
.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));
|
||||
});
|
||||
};
|
||||
|
||||
41
app/renderer/js/utils/dnd-util.js
Normal file
41
app/renderer/js/utils/dnd-util.js
Normal file
@@ -0,0 +1,41 @@
|
||||
'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
|
||||
};
|
||||
@@ -5,6 +5,8 @@ const fs = require('fs');
|
||||
const path = require('path');
|
||||
const JsonDB = require('node-json-db');
|
||||
const request = require('request');
|
||||
const escape = require('escape-html');
|
||||
|
||||
const Logger = require('./logger-util');
|
||||
|
||||
const logger = new Logger({
|
||||
@@ -188,7 +190,7 @@ class DomainUtil {
|
||||
// Following check handles both the cases
|
||||
icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon,
|
||||
url: data.realm_uri,
|
||||
alias: data.realm_name
|
||||
alias: escape(data.realm_name)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<i class="material-icons md-48">refresh</i>
|
||||
<span id="reload-tooltip" style="display:none">Reload</span>
|
||||
@@ -42,7 +46,11 @@
|
||||
<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>
|
||||
</html>
|
||||
</html>
|
||||
|
||||
@@ -6,9 +6,9 @@ os: Previous Visual Studio 2015
|
||||
|
||||
cache:
|
||||
- node_modules
|
||||
|
||||
|
||||
install:
|
||||
- ps: Install-Product node 6 x64
|
||||
- ps: Install-Product node 8 x64
|
||||
- git reset --hard HEAD
|
||||
- npm install npm -g
|
||||
- node --version
|
||||
@@ -21,4 +21,4 @@ build: off
|
||||
|
||||
test_script:
|
||||
- npm run test
|
||||
- npm run test-e2e
|
||||
# - npm run test-e2e
|
||||
|
||||
BIN
build/appdmg.png
BIN
build/appdmg.png
Binary file not shown.
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 12 KiB |
@@ -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.
|
||||
|
||||
8478
package-lock.json
generated
8478
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
36
package.json
36
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "zulip",
|
||||
"productName": "Zulip",
|
||||
"version": "1.9.0",
|
||||
"version": "2.3.1",
|
||||
"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",
|
||||
@@ -46,7 +46,8 @@
|
||||
],
|
||||
"copyright": "©2017 Kandra Labs, Inc.",
|
||||
"mac": {
|
||||
"category": "public.app-category.productivity"
|
||||
"category": "public.app-category.productivity",
|
||||
"artifactName": "${productName}-${version}-${arch}.${ext}"
|
||||
},
|
||||
"linux": {
|
||||
"category": "Chat;GNOME;GTK;Network;InstantMessaging",
|
||||
@@ -55,32 +56,41 @@
|
||||
"target": [
|
||||
"deb",
|
||||
"zip",
|
||||
"AppImage"
|
||||
"AppImage",
|
||||
"snap"
|
||||
],
|
||||
"maintainer": "Akash Nimare <svnitakash@gmail.com>"
|
||||
"maintainer": "Akash Nimare <svnitakash@gmail.com>",
|
||||
"artifactName": "${productName}-${version}-${arch}.${ext}"
|
||||
},
|
||||
"deb": {
|
||||
"synopsis": "Zulip Desktop App",
|
||||
"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",
|
||||
"iconSize": 128,
|
||||
"iconSize": 100,
|
||||
"contents": [
|
||||
{
|
||||
"x": 380,
|
||||
"y": 240,
|
||||
"y": 280,
|
||||
"type": "link",
|
||||
"path": "/Applications"
|
||||
},
|
||||
{
|
||||
"x": 122,
|
||||
"y": 240,
|
||||
"x": 110,
|
||||
"y": 280,
|
||||
"type": "file"
|
||||
}
|
||||
]
|
||||
],
|
||||
"window": {
|
||||
"width": 500,
|
||||
"height": 500
|
||||
}
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
@@ -113,12 +123,12 @@
|
||||
"assert": "1.4.1",
|
||||
"cp-file": "^5.0.0",
|
||||
"devtron": "1.4.0",
|
||||
"electron": "1.8.4",
|
||||
"electron-builder": "19.53.6",
|
||||
"electron": "2.0.1",
|
||||
"electron-builder": "20.13.4",
|
||||
"electron-connect": "0.6.2",
|
||||
"electron-debug": "1.4.0",
|
||||
"google-translate-api": "2.3.0",
|
||||
"gulp": "3.9.1",
|
||||
"gulp": "^4.0.0",
|
||||
"gulp-tape": "0.0.9",
|
||||
"is-ci": "^1.0.10",
|
||||
"nodemon": "^1.14.11",
|
||||
|
||||
@@ -15,6 +15,6 @@ fi
|
||||
|
||||
npm run test
|
||||
|
||||
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
npm run test-e2e
|
||||
fi
|
||||
# if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
|
||||
# npm run test-e2e
|
||||
# fi
|
||||
|
||||
BIN
snap/gui/icon.png
Normal file
BIN
snap/gui/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
37
snap/snapcraft.yaml
Normal file
37
snap/snapcraft.yaml
Normal file
@@ -0,0 +1,37 @@
|
||||
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
|
||||
17
tests/test-new-organization.js
Normal file
17
tests/test-new-organization.js
Normal 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'))
|
||||
})
|
||||
|
||||
18
tools/reinstall-node-modules.js
Normal file
18
tools/reinstall-node-modules.js
Normal 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);
|
||||
});
|
||||
Reference in New Issue
Block a user