Compare commits

...

176 Commits

Author SHA1 Message Date
Akash Nimare
dbf66e5585 Update snap version. 2018-04-19 21:49:02 +05:30
Akash Nimare
329e27142b snap: Fix snap config. 2018-04-19 21:46:31 +05:30
Akash Nimare
9f756cad3e snap: Update snap icon path. 2018-04-19 20:58:48 +05:30
Akash Nimare
6db6b7c482 reconnect-util: Do not throw error message on server page while reconnecting. 2018-04-18 22:00:30 +05:30
Akash Nimare
09c45e75e8 Update package-lock files. 2018-04-18 17:51:22 +05:30
Akash Nimare
120b80cf65 release: 🎉 v2.0.0. 2018-04-17 15:51:32 +05:30
Abhigyan Khaund
22f705960d sidebar: Add scrollbar for list of organizations on overflow. 2018-04-16 19:38:26 +05:30
Priyank Patel
ca8ce1deaa report-issue: Add report issue UX using send-feedback electron element.
This uses @electron-elements/send-feedback package to easily implement
UX for reporting issues.
2018-04-16 11:34:11 +05:30
Priyank Patel
f70432f4e3 internal-links: open non-image links in hidden webview.
* This will make sure that the current server webview will not 
reload due to URL change.
* Add an option to allow users to download the file attachments.

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

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

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

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

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

Fixes #440.
2018-03-24 00:04:26 +05:30
Akash Nimare
b80c3d007b server-page: Responsive tweaks for divider. 2018-03-23 12:18:44 +05:30
Akash Nimare
70fb198a0b release: 🎉 new-release v1.9.0. 2018-03-23 03:59:56 +05:30
Abhigyan Khaund
7276bfeaa7 org-settings-page: UI fixes and tweaks for the org-settings page.
Fixes:
* Move "Disconnect" button to be at the far right.
* Ellipsis text-overflow for server-url.
* Show server url as title tooltip on hover.
* Remove settings-card's right transition on hover.

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

Fixes: #457.
2018-03-23 01:36:43 +05:30
Akash Nimare
e776222d6b browser-window: Prevent drag and drop events.
This stops a remote code execution via drag and drop event in
the main/renderer process.

Fixes #453.
2018-03-22 23:23:18 +05:30
Akash Nimare
30b05571e7 electron: Upgrade to v1.8.4.
This upgrade includes some webview security fixes.
https://www.electronjs.org/blog/webview-fix
2018-03-22 11:14:32 +05:30
Akash Nimare
0a155c63e7 security: Do not allow running insecure contents in app. 2018-03-22 10:57:35 +05:30
Akash Nimare
13c750ac6c tests: Update Spectron to latest. 2018-03-20 20:54:32 +05:30
Akash Nimare
48799f75d1 tests: Fix failing renderer tests. 2018-03-20 20:53:10 +05:30
Akash Nimare
29f4e702ad Merge branch 'master' of github.com:zulip/zulip-electron 2018-03-19 20:45:54 +05:30
Akash Nimare
3eb4cf4f64 Ubuntu: Add Zulip link to the binrary.
Zulip can't be run from the terminal because we were not
linking the binary to the bin. The electron-builder used to support
this by default but we as we added the afterInstall
script it got removed.

More info -
https://github.com/electron-userland/electron-builder/issues/2682,
https://github.com/electron-userland/electron-builder/issues/2689

Fixes #444.
2018-03-19 20:38:38 +05:30
Abhigyan Khaund
1a97d8a5b0 design: Improve connected organization design.
This PR updates the connected organization page.
Changes - 
* All the server details are now inline. 
* Change "Delete" to "Disconnect". 
* Club org logo and name together on the right, and URL and "Disconnect" to the left.
* Link to organization logo on the server tab.
 
Improves #340.
2018-03-19 17:10:42 +05:30
Akash Nimare
2f96ec6199 settings: Fix a typo in silent setting option. 2018-03-17 03:49:43 +05:30
Akash Nimare
52de465457 settings: Fix app settings overriding issue. (#449)
This fixes an issue which was caused by saving the last active tab
before a functional tab activates. The bug was introduced in f409bb0449.
It was unnoticed from v1.5.0 to the latest v1.8.2.

The bug causes a serious issue where the app can't change the app settings and all settings were
getting overridden when a user switches back to setting page.

Fixes #448.
2018-03-17 03:06:41 +05:30
Ganesha Danu Enastika
e3039cf5a9 tools: Add missing Ubuntu dependancy. 2018-03-14 23:43:07 +05:30
Akash Nimare
6c120269eb design: Redesign Add new server page. (#446)
Changes - 
* New setting nav item for connected organizations.
* All new page for adding a new server.
* Minor UI changes in setting page.
* Update default height, width of the main window.
* Fix webview showing outline.
* Hide Server nav item in setting page.

Fixes #340.
2018-03-14 23:41:12 +05:30
Akash Nimare
b31fc6b66d setting-page: Better looking nav items. 2018-03-10 23:14:42 +05:30
Akash Nimare
435e5f086e webview: Fix webview flickering. (#439)
Rewrote styling for the webview so that it takes the whole window
when the app loads up.

Fixes #249.
2018-03-09 14:35:13 +05:30
Akash Nimare
603ad7dfcd setting-page: Make setting navs better. 2018-03-07 21:00:30 +05:30
Akash Nimare
a47a0e3e90 server-page: Improve add new server page.
WIP, #340.
2018-03-06 17:45:00 +05:30
Priyank P
8e04920f54 il8n: Add locale helper script. (#437)
This script automatically builds locales if the locale-template is changed.
2018-03-06 05:08:24 +05:30
Abhigyan Khaund
4a7b84e483 menu: Add "What's new" in help submenu. 2018-03-06 04:49:51 +05:30
Akash Nimare
ffd2ee36fa left-sidebar : Fix position of left-sidebar on toggel. 2018-03-01 21:36:13 +05:30
Akash Nimare
c11fa93642 left-sidebar: Fix tooltip not visible on hover.
Regression from c11089027c.
2018-03-01 21:12:07 +05:30
Akash Nimare
a90dc0c82f Custom css: Add a setting option for custom css.
This PR adds an option to inject custom CSS.
Fixes - #432.
2018-03-01 18:52:53 +05:30
Akash Nimare
028bc02d0a Downgrade node to 6.
Looks like the app can't be packaged on node > 8. This must be an issue with electron-builder.
Downgrading until the fix comes.
2018-03-01 04:37:00 +05:30
Akash Nimare
d673d5b76c 🎉 v1.8.2. 2018-02-26 19:18:23 +05:30
Akash Nimare
2bf88aa912 webview: Remove loading indicator once webview is loaded. 2018-02-26 18:36:25 +05:30
Akash Nimare
c9f179a2ae node: Enforce LTS node v8.0.0. 2018-02-26 18:28:16 +05:30
Akash Nimare
c11089027c left-sidebar: Align action container properly. 2018-02-26 18:09:42 +05:30
Abhigyan Khaund
5c45ab7b66 fix: New organization link overlapping existing servers.
Fixes: #427.
2018-02-26 17:28:59 +05:30
Akash Nimare
6205ca0aca Update shortcut for opening webapp keyboard shortcuts. 2018-02-24 01:27:28 +05:30
Abhigyan Khaund
b83e2dd428 settings-tab: Do not create a sidebar tab rather highlight bottom settings icon. (#422)
Creating the new functional tab for setting tab was unnecessary as we already have a sticky setting icon at the bottom. This PR adds a functionality to highlight that settings icon instead of creating the new one.

Fixes: #418.
2018-02-23 19:03:30 +05:30
Akash Nimare
fd421a62d2 webview: Improve flickering of webview on first load. 2018-02-23 02:03:18 +05:30
Abhigyan Khaund
fa85241c79 design: Only show the back button when needed.
Fixes: #421.
2018-02-23 00:53:23 +05:30
Akash Nimare
b163c237b6 Do not auto-reload app when system comes back from sleep.
Improves #412, #312.
2018-02-22 20:55:55 +05:30
Abhigyan Khaund
5351ee10fa auto-updates: Show a notification when a new update available [Linux].
[Linux] - This PR adds a feature to notify the user whenever a new update is available.

Fixes: #398.
2018-02-22 16:55:45 +05:30
Priyank P
d48b6ae80d reconnect: Check wheather internet is working before reloading. (#415)
When the online event is triggered check whether the internet is actually working or not.
Commonly on windows, it turns out that internet takes couple of seconds to boot up after
connecting to the internet or in some cases, this might be they have to sign in to internet service
portal in order to access the internet.
2018-02-21 16:56:01 +05:30
Akash Nimare
2d07d40c92 activetab: Fix broken last active tab. (#420)
This fixes an issue where the server tabs were not loading
according to the last active tab. This used to load last server
no matter what the last used server is.

Fixes - #416.
2018-02-18 17:52:26 +05:30
Akash Nimare
55ae71c00c travis: Fix travis not failing on linting errors. 2018-02-15 19:58:08 +05:30
cPhost
0c18bb5226 menu.js: Fix linting error. 2018-02-15 19:55:37 +05:30
Abhigyan Khaund
6d213c86a8 design: Add back button in left-sidebar.
Fixes: #208.
2018-02-14 14:01:35 +05:30
Akash Nimare
0c1cd96ed7 Remove transition property from checkbox input.
That delay can cause the setting button to not change/switch properly.
2018-02-13 17:23:39 +05:30
Priyank P
d77b1b5960 electron-connect: Disable electron-connect when it's run from npm start.
Improves gulp dev script.
2018-02-13 00:07:44 +05:30
Akash Nimare
2c6c0c8c8d Update electron to v1.8.2. 2018-02-11 22:36:32 +05:30
Akash Nimare
84bf59d32f Update package-lock.json files. 2018-02-11 22:08:50 +05:30
Priyank P
256c352fb9 windows: Add tools support with batch scripts.
Add tools support with batch scripts.

Fixes - #397.
2018-02-09 18:19:09 +05:30
Akash Nimare
da28589c17 menu: Add menu-item to reveal app logs in file manager #405. 2018-02-03 02:04:36 +05:30
Akash Nimare
c452ee2ef6 menu: Improve help menu. 2018-02-03 01:37:13 +05:30
Abhigyan Khaund
3582aa4694 menu: Rename Settings to Desktop App Settings. 2018-02-02 17:07:12 +05:30
akashnimare
7f7eee2455 setting-page: design improvemnets. 2018-01-31 21:36:39 +05:30
akashnimare
c716b8f233 notification: Refactor code for bot mention in reply. 2018-01-31 01:34:46 +05:30
Abhigyan Khaund
4a40c75127 notifications: Adds bots mention support to reply option. (#395)
Fixes: #391.
2018-01-31 01:26:11 +05:30
Balaji
239631a2b6 tools: Add scripts for review Pull Requests. (#399)
* tools: Add script to fetch-pull-request.

Improves #397.

* tools: Add script to fetch-rebase-pull-request.

Improves #397.
2018-01-30 17:11:10 +05:30
Abhigyan Khaund
89d1344e2f preference page: Add a Loading indication for new server button. (#401)
Change the text of "Add" button to "Adding..." when a user clicks on Add button for adding new server.

Fixes: #396.
2018-01-29 23:53:10 +05:30
akashnimare
1948ba2cc3 menu: Fix Zoom In shortcut.
This is a temporary fix. Ideally, 'CmdOrCtrl+Plus' works on all
the platforms but because of https://github.com/electron/electron/issues/6731
it converts accelerator to 'Ctrl+Shift+Plus'.
2018-01-27 19:56:27 +05:30
Abhigyan Khaund
b8da7dd6ee gulp-dev: Add nodeman to dev script and fix renderer reload on changes.
Fixes: #368.
2018-01-23 00:29:15 +05:30
akashnimare
4a0efb7301 Code refactoring. 2018-01-23 00:05:15 +05:30
Priyank P
aedd95259d preference: Only toggle the state if element is present.
Fixes #393.
2018-01-22 22:38:01 +05:30
Priyank P
c8d7a79877 design: Improve preference design. 2018-01-17 18:34:24 +05:30
akashnimare
6e6db42b54 v1.8.1 2018-01-17 02:16:04 +05:30
akashnimare
db79284fbb fix: Don't import appId from package file #386. 2018-01-17 01:54:20 +05:30
akashnimare
2434f06655 release: 🎉 new-release v1.8.0. 2018-01-15 20:59:28 +05:30
akashnimare
1d611d3382 setting-page: Update placeholder for adding new server page.
Improves #340.
2018-01-15 20:55:51 +05:30
akashnimare
a746194e9e help-menu: Add a space between app name and version. 2018-01-15 18:45:45 +05:30
akashnimare
7cc13f7a26 Update dependencies.
Updated:
electron - 1.7.10
electron-builder - 19.53.6
electron-updater - 2.18.2
2018-01-15 17:46:33 +05:30
Akash Nimare
6a9bb152a0 Update features section. 2018-01-14 01:11:18 +05:30
Priyank P
8b6dcd355f notification: Add reply option to notifications for macOS.
This PR adds reply option to notifications of macOS using
`node-mac-notifier` and then post the reply for to the webapp.
It also fixes an issue that even though the app is focused the server that sent
the notification did not focus. And it also adds parsing for mentioning. This also
refactors code for notification.

Fixes: #284, #381.
2018-01-14 00:04:34 +05:30
Priyank P
91742a5770 silent: Reflect changes in webview for silent option. (#380)
* silent: Reflect changes in webview for mute/silent option.

This silent the webview incase silent option is toggled, and
by default silent the webview when its create if needed.

Fixes: #380.
2018-01-11 18:46:39 +05:30
akashnimare
fb74251a2c performance: Disable hardware acceleration to decrease the load on GPU.
Adding this experimental electron api to see if it makes any difference
in performance.

Improves #213.
2018-01-08 02:10:43 +05:30
akashnimare
a920720f91 setting-page: Improve add new server page #340. 2018-01-07 15:10:21 +05:30
Priyank P
aa8e99b7a6 domain-util: Fix checkDomain, so it checks all error codes. (#370)
This fixes an issue where if server send non 404 error code such
as 403 forbidden we marked them as Zulip server even though they are
not, now it checks for 400 error range.
2018-01-07 00:16:52 +05:30
akashnimare
e23f8aaa58 shortcut: Rename Zoom In keyboard shortcut. 2018-01-06 16:30:59 +05:30
Akash Nimare
5c3208d44c settings: Add a setting option to start the app in background. (#366)
Fixes #314.
2017-12-29 12:37:53 +05:30
akashnimare
c0b57bbe2b settings: Set default value of flashtaskbar setting[Windows]. 2017-12-28 00:29:16 +05:30
Priyank P
afe4e8901b github templates: Add pull request template and update issue template. (#365) 2017-12-27 23:15:02 +05:30
Priyank P
231e7fd9c2 preferences page: Reflect changes in the preference page. (#362)
This updated the setting page if the sidebar was toggled using a shortcut.
This also updates the setting page if the tray was toggled using menu.

Fixes: #304.
2017-12-27 21:06:32 +05:30
Akash Nimare
a0d898a5b7 Merge pull request #363 from cPhost/logger
logger-util: Code refactoring and better logs design.
2017-12-27 20:45:13 +05:30
cPhost
1abf62555c logger-util: code refactoring and better logs design.
This imporves logging and refactors most of the code.
This also renames console-util to logger-util.
2017-12-26 22:03:05 -05:00
Akash Nimare
6befcbaa8f Merge pull request #361 from cPhost/focus-app
notifications: Focus app when a notification is triggered.
2017-12-27 01:08:32 +05:30
cPhost
e56a01049b notifications: Focus app when a notification is triggered.
This PR adds a feature of showing app window when the notification are clicked
Fixes: #358
2017-12-26 14:30:33 -05:00
akashnimare
72cb8459ff code refactoring. 2017-12-26 01:02:42 +05:30
Akash Nimare
0b83b22206 Merge pull request #353 from cPhost/fix-errors
default-util: Fix log dir errors.
2017-12-26 00:21:35 +05:30
Akash Nimare
267d25e5c4 Merge pull request #357 from cPhost/img-fix
Add default icon if the server image is not available.
2017-12-25 23:43:35 +05:30
cPhost
8401f8f5ce server icon: Load default icon if org icon is not avalible. 2017-12-25 12:17:45 -05:00
cPhost
c4a7264f34 console: Fix errors where Logs dir can't be created. 2017-12-25 09:50:52 -05:00
akashnimare
9d081ecd5a Reload full app on system hibernation.
Improves #312.
2017-12-20 02:33:08 +05:30
akashnimare
dc6582fa82 Logs: Unused Logs dir. 2017-12-19 03:42:42 +05:30
akashnimare
3b412672c6 Logs: Fix an issue where Logs dir don't get init properly. 2017-12-19 03:40:24 +05:30
Akash Nimare
04083bfa81 Merge pull request #352 from zulip/cPhost-handle-domainjson
Handle corrupted config files.
Improves #310.
2017-12-19 02:33:39 +05:30
akashnimare
562e82d2f1 test: Create logs dir on app startup. 2017-12-19 01:22:50 +05:30
cPhost
3b014e0715 settings util: delete settings.json file in case it is corrupted. 2017-12-17 15:17:08 -05:00
cPhost
13178ebc8f domain util: delete domain.json file in case it is corrupted. 2017-12-17 15:16:55 -05:00
cPhost
08693bf105 console: require app form remote if required 2017-12-17 15:00:04 -05:00
akashnimare
d7a0b63d62 test: fix failing e2e test on travis 2017-12-13 20:05:07 +05:30
akashnimare
a193ecf229 design: update new server page #340. 2017-12-13 18:54:31 +05:30
akashnimare
31f04754a4 design: fixed positioning of create-new-org link 2017-12-11 16:08:23 +05:30
akashnimare
ae7374475f design: Update UI for server section #340 2017-12-11 15:42:50 +05:30
akashnimare
7697d5d698 electron: update electron to v1.7.9 #213 2017-12-11 15:09:56 +05:30
akashnimare
d3e1b5de45 update app configs 2017-12-06 19:46:24 +05:30
Akash Nimare
9efa6191f7 Merge pull request #349 from zulip/improve-debian-installer
linux: Improve debian installer
2017-12-06 18:17:57 +05:30
akashnimare
d86797d2fc debian: replace bash shebang with '#!/usr/bin/env bash" for portability.
According to the debian config rules '#!/usr/bin/env bash' violates
the quality standard which isn't allowed.

More info - https://github.com/Microsoft/vscode/issues/35638
2017-12-05 23:58:26 +05:30
akashnimare
23eef7edb0 debian: remove app settings on uninstall 2017-12-05 22:15:18 +05:30
akashnimare
a1d5a35ccf debian: Remove config files when user uninstalls app 2017-12-05 21:01:26 +05:30
akashnimare
62e8dfe180 Rename afterRemove script to debian uninstaller 2017-12-05 20:54:39 +05:30
Akash Nimare
8322054984 Merge pull request #347 from cPhost/deb
debian-installer: add apt repo for debian
2017-12-05 18:42:15 +05:30
Akash Nimare
13ae6f07e9 Add comment for adding apt repo 2017-12-05 18:41:06 +05:30
cPhost
dab92be54c debian installer: remove the apt repo after unistallation 2017-12-05 07:29:45 -05:00
cPhost
fe9c66d8c2 debian-installer: add apt repo for debian 2017-12-05 07:29:34 -05:00
akashnimare
43b4d511dc init default settings properly fixes #348 2017-12-04 17:19:25 +05:30
akashnimare
aa5a47ad53 Make app window thinner fixes #332 2017-12-04 15:19:43 +05:30
Akash Nimare
ce27f92900 Merge pull request #346 from cPhost/about-page
about-page: add links for zulip-electron and license.
2017-12-04 14:54:13 +05:30
cPhost
7be051bb6e about-page: center everything, add links for zulip-electron, license 2017-12-03 18:02:34 -05:00
Akash Nimare
c2a01adabe Merge pull request #345 from cPhost/logger
logger: add console helper to log for both file and console
2017-12-04 01:13:07 +05:30
cPhost
770926e6eb logger: add console helper to log for both file and console 2017-12-02 11:58:28 -05:00
Akash Nimare
10ef627f59 Add notifications troubleshooting 2017-12-01 20:43:52 +05:30
akashnimare
4adba0f4b4 Update electron-builder to v19.46.4 2017-11-24 17:00:03 +05:30
akashnimare
ed590c26e3 Added whitelist zulip server 2017-11-23 22:38:28 +05:30
akashnimare
6d10291a87 🎉 v1.7.0 2017-11-22 20:10:59 +05:30
akashnimare
3fe3a3da85 Update electron to v1.6.15 2017-11-22 16:21:39 +05:30
akashnimare
ba64438a99 Improve Add Server page for new users [WIP] #340 2017-11-22 14:39:39 +05:30
Akash Nimare
bcc27894c4 Merge pull request #337 from SimplyAhmazing/setup-e2e-tests
setup e2e tests
2017-11-22 13:28:31 +05:30
Akash Nimare
0dd0f593d1 Merge pull request #338 from sroy8091/issue_#336
Better error message on invalid server fixes #336
2017-11-21 21:08:08 +05:30
Sumit Roy
f5e9342f78 fail to connect message #336 2017-11-21 11:31:42 +05:30
simplyahmazing
083ccdf229 remove unused imports 2017-11-20 11:14:22 -05:00
simplyahmazing
1261786db2 remove unused dependencies 2017-11-20 11:06:05 -05:00
simplyahmazing
7f567f55c3 setup e2e tests 2017-11-19 16:48:21 -05:00
akashnimare
dcd2abca6e v1.6.0-beta 2017-11-16 00:32:32 +05:30
Akash Nimare
2fb9efb981 Merge pull request #335 from zulip/electron-shortcut-fix
Remove electron-localshortcut completely
2017-11-15 16:56:42 +05:30
akashnimare
7245b6a110 Move electron-debug to devDependencies 2017-11-15 04:59:59 +05:30
akashnimare
bcb8ffb55f Remove electron-LocalShortcut completely
This commit removes the usage of "electron-localshortcut" completely.
Now, we rely on only menu accelerators for shortcuts. It's risky to register a local shortcuts in the app window
either using electronLocalShortcut or globalShortcut as the registered shortcuts could interfare with
OS global shortcuts which is very frustrating. This fixes #317 once and for all.
2017-11-15 03:39:51 +05:30
akashnimare
77094596a5 Use electron-debug in development only WIP #317
electron-debug hijacks the CMD/CTRL+R and reloads the whole app,
whereas we only need to reload the current server. Removed those commands from
electronLocalShortcuts as well as they are already registered in menu items.
2017-11-15 03:07:36 +05:30
Akash Nimare
06ad44bdd7 Merge pull request #333 from zulip/appimage-autoupdates
Add AppImage autoupdates fixes #333
2017-11-11 20:12:24 +05:30
akashnimare
e719ba139c Update electron-builder to latest version 2017-11-11 20:10:53 +05:30
akashnimare
9853e9226c Better tray icon for retina display, fixes #330 2017-11-08 12:26:52 +05:30
akashnimare
f2c76b5ca3 Enable auto-update on Linux (AppImage) 2017-11-04 01:31:45 +05:30
Akash Nimare
e6dbff995b Merge pull request #327 from zulip/taskbar-setting-option
Setting option for Windows taskbar flash fixes #299
2017-11-03 00:09:58 +05:30
akashnimare
4578d4a5f7 typo in setting 2017-11-03 00:06:34 +05:30
akashnimare
4b895a2312 Don't show flash taskbar setting on Linux/macOS 2017-11-02 23:54:38 +05:30
akashnimare
53c0428a3a Add setting to control Windows taskbar flashing #299 2017-11-02 20:58:35 +05:30
akashnimare
0a1866abb5 Show Detailed error message on invaild Zulip server #325 2017-11-01 17:59:00 +05:30
Akash Nimare
ce862a4890 Merge pull request #326 from zulip/remove-python-version-file
Remove python version file

Fixes failing tests on Travis Linux
2017-10-31 20:55:17 +05:30
simplyahmazing
1b1ad2cd61 remove .python-version file from repo 2017-10-28 20:06:56 -04:00
simplyahmazing
ead7a06308 ignore .python-version files 2017-10-28 20:06:26 -04:00
akashnimare
6659dd5097 Update electron-builder & updater to latest 2017-10-26 11:54:55 +05:30
akashnimare
ed1f0f6d5b Update electron-builder & updater to latest 2017-10-24 18:38:00 +05:30
akashnimare
79acf8a6e1 Add option to remove app settings
Menu item "Reset app settings" now remove all the configurations/settings files related to app.
Previously it used to remove only window-state.json. This helps a bit in #310.
2017-10-24 18:29:35 +05:30
akashnimare
8e0033f03e Handle certificate issuer error
Fixes, #316
2017-10-21 01:47:16 +05:30
Akash Nimare
9144c2630d Merge pull request #322 from zulip/spellchecker-osx-fix
Spellchecker Improvements
2017-10-20 23:52:24 +05:30
akashnimare
fae05fc3b1 Initialize default app settings
Settings are initialized only when user clicks on General/Server/Network section settings
In case, user doesn't visit these section, those values set to be null automatically.
This fix makes sure the default settings are correctly set to either true or false.
2017-10-18 21:38:48 +05:30
akashnimare
73603a4fd2 Add settings to disable/enable spellchecker 2017-10-18 21:36:01 +05:30
akashnimare
a498ffc7d6 Update spellchecker to v1.1.2 2017-10-18 04:17:51 +05:30
akashnimare
7afcf13401 Re-write and improve spellchecker class
Rewrote the Spellchecker class so that we can have better control
over the context menu and spellchecker.
2017-10-18 04:14:02 +05:30
akashnimare
89a292559d Set English as default language for spellchecker on Linux/Windows 2017-10-17 21:57:51 +05:30
akashnimare
be14517caf Set server language for spellchecker on macOS
Ideally spellchecker should detect the language, but on macOS, it fails to auto-detect the lanugage user is typing in
that's why we need to mention it explicitly. We set this language with the help of the default language of the server.
2017-10-17 00:29:50 +05:30
101 changed files with 15357 additions and 627 deletions

8
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,8 @@
---
<!-- Please Include: -->
- **Operating System**:
- [ ] Windows
- [ ] Linux/Ubutnu
- [ ] macOS
- **Clear steps to reproduce the issue**:
- **Relevant error messages and/or screenshots**:

16
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@@ -0,0 +1,16 @@
---
<!--
Remove the fields that are not appropriate
Please include:
-->
**What's this PR do?**
**Any background context you want to provide?**
**Screenshots?**
**You have tested this PR on:**
- [ ] Windows
- [ ] Linux/Ubuntu
- [ ] macOS

14
.gitignore vendored
View File

@@ -7,6 +7,13 @@ node_modules/
# Compiled binary build directory
dist/
#snap generated files
snap/parts
snap/prime
snap/snap
snap/stage
snap/*.snap
# Logs
logs
*.log
@@ -23,4 +30,9 @@ yarn-error.log*
# miscellaneous
.idea
config.gypi
config.gypi
# Test generated files
tests/package.json
.python-version

View File

@@ -1 +0,0 @@
2.7.9

View File

@@ -18,8 +18,9 @@ node_js:
- '6'
before_install:
- npm install -g gulp
- npm install
- ./scripts/travis-xvfb.sh
- npm install -g gulp
- npm install
cache:
directories:
@@ -33,4 +34,4 @@ notifications:
urls:
- https://zulip.org/zulipbot/travis
on_success: always
on_failure: always
on_failure: always

View File

@@ -1,7 +0,0 @@
---
Please include:
- `Operating System`
- `Clear steps to reproduce the issue`
- `Relevant error messages and/or screenshots`

View File

@@ -12,19 +12,12 @@ Please see [installation guide](https://zulipchat.com/help/desktop-app-install-g
# Features
* Sign in to multiple teams
* Native desktop Notifications
* SpellChecker
* Desktop Notifications with inline reply support
* Multilanguage SpellChecker
* OSX/Win/Linux installers
* Automatic Updates (macOS/Windows)
* Automatic Updates (macOS/Windows/Linux)
* Keyboard shortcuts
Description | Keys
-----------------------| -----------------------
Default shortcuts | <kbd>Cmd/Ctrl</kbd> <kbd>k</kbd>
Manage Zulip Servers | <kbd>Cmd/Ctrl</kbd> <kbd>,</kbd>
Back | <kbd>Cmd/Ctrl</kbd> <kbd>[</kbd>
Forward | <kbd>Cmd/Ctrl</kbd> <kbd>]</kbd>
# Development
Please see our [development guide](./development.md) to get started and run app locally.

View File

@@ -1,5 +1,4 @@
'use strict';
const fs = require('fs');
const { app, dialog } = require('electron');
const { autoUpdater } = require('electron-updater');
const isDev = require('electron-is-dev');
@@ -7,19 +6,20 @@ const isDev = require('electron-is-dev');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
function appUpdater() {
// Don't initiate auto-updates in development and on Linux system
// since autoUpdater doesn't work on Linux
if (isDev || process.platform === 'linux') {
// Don't initiate auto-updates in development
if (isDev) {
return;
}
if (process.platform === 'linux' && !process.env.APPIMAGE) {
const { linuxUpdateNotification } = require('./linuxupdater');
linuxUpdateNotification();
return;
}
// Create Logs directory
const LogsDir = `${app.getPath('userData')}/Logs`;
if (!fs.existsSync(LogsDir)) {
fs.mkdirSync(LogsDir);
}
// Log whats happening
const log = require('electron-log');

View File

@@ -1,8 +1,8 @@
'use strict';
const path = require('path');
const electron = require('electron');
const electronLocalshortcut = require('electron-localshortcut');
const windowStateKeeper = require('electron-window-state');
const isDev = require('electron-is-dev');
const appMenu = require('./menu');
const { appUpdater } = require('./autoupdater');
const { crashHandler } = require('./crash-reporter');
@@ -12,9 +12,13 @@ const { setAutoLaunch } = require('./startup');
const { app, ipcMain } = electron;
const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
// Adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')();
// in development mode
if (isDev) {
require('electron-debug')();
}
// Prevent window being garbage collected
let mainWindow;
@@ -48,8 +52,8 @@ const iconPath = () => {
function createMainWindow() {
// Load the previous state with fallback to defaults
const mainWindowState = windowStateKeeper({
defaultWidth: 1000,
defaultHeight: 600
defaultWidth: 1100,
defaultHeight: 720
});
// Let's keep the window position global so that we can access it in other process
@@ -63,11 +67,10 @@ function createMainWindow() {
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
minWidth: 600,
minHeight: 500,
minWidth: 300,
minHeight: 400,
webPreferences: {
plugins: true,
allowDisplayingInsecureContent: true,
nodeIntegration: true
},
show: false
@@ -78,7 +81,11 @@ function createMainWindow() {
});
win.once('ready-to-show', () => {
win.show();
if (ConfigUtil.getConfigItem('startMinimized')) {
win.minimize();
} else {
win.show();
}
});
win.loadURL(mainURL);
@@ -94,9 +101,6 @@ function createMainWindow() {
win.hide();
}
}
// Unregister all the shortcuts so that they don't interfare with other apps
electronLocalshortcut.unregisterAll(mainWindow);
});
win.setTitle('Zulip');
@@ -124,21 +128,8 @@ function createMainWindow() {
return win;
}
function registerLocalShortcuts(page) {
// Somehow, reload action cannot be overwritten by the menu item
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
page.send('reload-current-viewer');
});
// Also adding these shortcuts because some users might want to use it instead of CMD/Left-Right
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
page.send('back');
});
electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => {
page.send('forward');
});
}
// Decrease load on GPU (experimental)
app.disableHardwareAcceleration();
// eslint-disable-next-line max-params
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
@@ -146,11 +137,6 @@ app.on('certificate-error', (event, webContents, url, error, certificate, callba
callback(true);
});
app.on('window-all-closed', () => {
// Unregister all the shortcuts so that they don't interfare with other apps
electronLocalshortcut.unregisterAll(mainWindow);
});
app.on('activate', () => {
if (!mainWindow) {
mainWindow = createMainWindow();
@@ -165,10 +151,12 @@ app.on('ready', () => {
const page = mainWindow.webContents;
registerLocalShortcuts(page);
page.on('dom-ready', () => {
mainWindow.show();
if (ConfigUtil.getConfigItem('startMinimized')) {
mainWindow.minimize();
} else {
mainWindow.show();
}
});
page.once('did-frame-finish-load', () => {
@@ -177,9 +165,11 @@ app.on('ready', () => {
crashHandler();
});
electron.powerMonitor.on('resume', () => {
page.send('reload-viewer');
});
// Temporarily remove this event
// electron.powerMonitor.on('resume', () => {
// mainWindow.reload();
// page.send('destroytray');
// });
ipcMain.on('focus-app', () => {
mainWindow.show();
@@ -231,31 +221,11 @@ app.on('ready', () => {
appMenu.setMenu(props);
});
ipcMain.on('register-server-tab-shortcut', (event, index) => {
electronLocalshortcut.register(mainWindow, `CommandOrControl+${index}`, () => {
// Array index == Shown index - 1
page.send('switch-server-tab', index - 1);
});
});
ipcMain.on('local-shortcuts', (event, enable) => {
if (enable) {
registerLocalShortcuts(page);
} else {
electronLocalshortcut.unregisterAll(mainWindow);
}
});
ipcMain.on('toggleAutoLauncher', (event, AutoLaunchValue) => {
setAutoLaunch(AutoLaunchValue);
});
});
app.on('will-quit', () => {
// Unregister all the shortcuts so that they don't interfare with other apps
electronLocalshortcut.unregisterAll(mainWindow);
});
app.on('before-quit', () => {
isQuitting = true;
});

42
app/main/linuxupdater.js Normal file
View File

@@ -0,0 +1,42 @@
const { app } = require('electron');
const { Notification } = require('electron');
const request = require('request');
const semver = require('semver');
const ConfigUtil = require('../renderer/js/utils/config-util');
const LinuxUpdateUtil = require('../renderer/js/utils/linux-update-util');
function linuxUpdateNotification() {
let url = 'https://api.github.com/repos/zulip/zulip-electron/releases';
url = ConfigUtil.getConfigItem('betaUpdate') ? url : url + '/latest';
const options = {
url,
headers: {'User-Agent': 'request'}
};
request(options, (error, response, body) => {
if (error) {
console.log('Error:', error);
return;
}
if (response.statusCode < 400) {
const data = JSON.parse(body);
const latestVersion = ConfigUtil.getConfigItem('betaUpdate') ? data[0].tag_name : data.tag_name;
if (semver.gt(latestVersion, app.getVersion())) {
const notified = LinuxUpdateUtil.getUpdateItem(latestVersion);
if (notified === null) {
new Notification({title: 'Zulip Update', body: 'A new version ' + latestVersion + ' is available. Please update using your package manager.'}).show();
LinuxUpdateUtil.setUpdateItem(latestVersion, true);
}
}
} else {
console.log('Status:', response.statusCode);
}
});
}
module.exports = {
linuxUpdateNotification
};

View File

@@ -1,8 +1,7 @@
'use strict';
const os = require('os');
const path = require('path');
const { app, shell, BrowserWindow, Menu } = require('electron');
const { app, shell, BrowserWindow, Menu, dialog } = require('electron');
const fs = require('fs-extra');
@@ -54,7 +53,7 @@ class AppMenu {
role: 'togglefullscreen'
}, {
label: 'Zoom In',
accelerator: 'CommandOrControl+=',
accelerator: process.platform === 'darwin' ? 'Command+Plus' : 'Control+=',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomIn');
@@ -87,7 +86,7 @@ class AppMenu {
}
}, {
label: 'Toggle Sidebar',
accelerator: 'CommandOrControl+S',
accelerator: 'CommandOrControl+Shift+S',
click(item, focusedWindow) {
if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('showSidebar');
@@ -115,26 +114,37 @@ class AppMenu {
}
getHelpSubmenu() {
return [{
label: `${appName} Website`,
click() {
shell.openExternal('https://zulipchat.com/help/');
}
}, {
label: `${appName + 'Desktop'} - ${app.getVersion()}`,
enabled: false
}, {
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)}`);
}
}];
return [
{
label: `${appName + ' Desktop-'} v${app.getVersion()}`,
enabled: false
},
{
label: `What's New...`,
click() {
shell.openExternal(`https://github.com/zulip/zulip-electron/releases/tag/v${app.getVersion()}`);
}
},
{
label: `${appName} Help`,
click() {
shell.openExternal('https://zulipchat.com/help/');
}
}, {
label: 'Show App Logs',
click() {
shell.openItem(app.getPath('userData'));
}
}, {
label: 'Report an Issue...',
click() {
// 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');
});
}
}];
}
getWindowSubmenu(tabs, activeTabIndex) {
@@ -150,6 +160,11 @@ class AppMenu {
type: 'separator'
});
for (let i = 0; i < tabs.length; i++) {
// Do not add functional tab settings to list of windows in menu bar
if (tabs[i].props.role === 'function' && tabs[i].webview.props.name === 'Settings') {
continue;
}
initialSubmenu.push({
label: tabs[i].webview.props.name,
accelerator: tabs[i].props.role === 'function' ? '' : `${ShortcutKey} + ${tabs[i].props.index + 1}`,
@@ -159,7 +174,7 @@ class AppMenu {
AppMenu.sendAction('switch-server-tab', tabs[i].props.index);
}
},
type: 'radio'
type: 'checkbox'
});
}
}
@@ -182,7 +197,7 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: 'Settings',
label: 'Desktop App Settings',
accelerator: 'Cmd+,',
click(item, focusedWindow) {
if (focusedWindow) {
@@ -191,7 +206,7 @@ class AppMenu {
}
}, {
label: 'Keyboard Shortcuts',
accelerator: 'Cmd+K',
accelerator: 'Cmd+Shift+K',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
@@ -282,7 +297,7 @@ class AppMenu {
}, {
type: 'separator'
}, {
label: 'Settings',
label: 'Desktop App Settings',
accelerator: 'Ctrl+,',
click(item, focusedWindow) {
if (focusedWindow) {
@@ -293,7 +308,7 @@ class AppMenu {
type: 'separator'
}, {
label: 'Keyboard Shortcuts',
accelerator: 'Ctrl+K',
accelerator: 'Ctrl+Shift+K',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
@@ -370,10 +385,32 @@ class AppMenu {
}
static resetAppSettings() {
const getAppPath = path.join(app.getPath('appData'), appName, 'window-state.json');
const resetAppSettingsMessage = 'By proceeding you will be removing all connected organizations and preferences from Zulip.';
fs.unlink(getAppPath, () => {
setTimeout(() => AppMenu.sendAction('clear-app-data'), 1000);
// We save App's settings/configurations in following files
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 => {
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');
});
}
});
});
}
});
}

1414
app/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,14 +1,13 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "1.5.0",
"version": "2.0.0",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"email": "<svnitakash@gmail.com>",
"copyright": "©2017 Kandra Labs, Inc.",
"copyright": "Kandra Labs, Inc.",
"author": {
"name": "Kandra Labs, Inc.",
"email": "svnitakash@gmail.com"
"email": "support@zulipchat.com"
},
"repository": {
"type": "git",
@@ -27,16 +26,20 @@
"InstantMessaging"
],
"dependencies": {
"electron-debug": "1.4.0",
"@electron-elements/send-feedback": "1.0.7",
"auto-launch": "5.0.1",
"electron-is-dev": "0.3.0",
"electron-localshortcut": "2.0.2",
"electron-log": "2.2.7",
"electron-spellchecker": "1.2.0",
"electron-updater": "2.8.9",
"electron-spellchecker": "1.1.2",
"electron-updater": "2.21.4",
"electron-window-state": "4.1.1",
"is-online": "7.0.0",
"node-json-db": "0.7.3",
"request": "2.81.0",
"wurl": "2.5.0",
"electron-window-state": "4.1.1",
"auto-launch": "5.0.1"
"semver": "5.4.1",
"wurl": "2.5.0"
},
"optionalDependencies": {
"node-mac-notifier": "0.0.13"
}
}

View File

@@ -1,33 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/about.css">
</head>
<body>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/about.css">
</head>
<body>
<div class="about">
<img class="logo" src="../resources/zulip.png" />
<p class="detail" id="version">version ?.?.?</p>
<p class="detail" id="version">v?.?.?</p>
<div class="maintenance-info">
<p class="detail maintainer">Maintained by Zulip</p>
<p class="detail license">Available under the Apache License</p>
<a class="bug" onclick="linkInBrowser()" href="#">Found bug?</a>
<p class="detail maintainer">
Maintained by
<a onclick="linkInBrowser('website')">Zulip</a>
</p>
<p class="detail license">
Available under the
<a onclick="linkInBrowser('license')">Apache 2.0 License</a>
</p>
<a class="bug" onclick="linkInBrowser('bug')" href="#">Found bug?</a>
</div>
</div>
<script>
const app = require('electron').remote.app;
const version_tag = document.getElementById('version');
version_tag.innerHTML = 'version ' + app.getVersion();
const { app } = require('electron').remote;
const { shell } = require('electron');
const version_tag = document.querySelector('#version');
version_tag.innerHTML = 'v' + app.getVersion();
function linkInBrowser(event) {
const shell = require('electron').shell;
const url = "https://github.com/zulip/zulip-electron/issues/new?body=Please describe your issue and steps to reproduce it."
shell.openExternal(url);
}
function linkInBrowser(type) {
let url;
switch (type) {
case 'website':
url = "https://zulipchat.com";
break;
case 'license':
url = "https://github.com/zulip/zulip-electron/blob/master/LICENSE";
break;
default:
url = 'https://github.com/zulip/zulip-electron/issues/new?body=' +
'%3C!--Please%20describe%20your%20issue%20and%20steps%20to%20reproduce%20it.--%3E';
}
shell.openExternal(url);
}
</script>
</body>
<script>require('./js/shared/preventdrag.js')</script>
</body>
</html>

View File

@@ -1,21 +1,23 @@
body {
background: #fafafa;
font-family: menu, "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-webkit-font-smoothing: subpixel-antialiased;
}
.logo {
display: block;
margin: 0 auto;
margin: -40px auto;
}
#version {
color: #aaa;
font-size: 0.9em;
color: #444343;
font-size: 1.3em;
padding-top: 40px;
}
.about {
margin-top: 50px;
margin: 25vh auto;
height: 25vh;
text-align: center;
}
@@ -42,9 +44,9 @@ body {
}
.maintenance-info {
cursor: pointer;
position: absolute;
width: 100%;
bottom: 20px;
left: 0px;
color: #444;
}
@@ -52,7 +54,6 @@ body {
.maintenance-info p {
margin: 0;
font-size: 1em;
width: 100%;
}
@@ -71,3 +72,11 @@ body {
.maintenance-info .bug:hover {
background-color: #32a692;
}
p.detail a {
color: #355f4c;
}
p.detail a:hover {
text-decoration: underline;
}

View File

@@ -13,7 +13,7 @@ body {
#content {
display: flex;
height: 100%;
background: #eee url(../img/ic_loading.gif) no-repeat;
background: #fff url(../img/ic_loading.gif) no-repeat;
background-size: 60px 60px;
background-position: center;
}
@@ -28,6 +28,7 @@ body {
-webkit-app-region: drag;
overflow: hidden;
transition: all 0.5s ease;
z-index: 2;
}
.toggle-sidebar div {
@@ -44,6 +45,28 @@ body {
transition: all 0.6s ease-out;
}
#view-controls-container {
height: calc(100% - 156px);
overflow-y: hidden;
}
#view-controls-container:hover {
overflow-y: overlay;
}
#view-controls-container::-webkit-scrollbar {
width: 4px;
}
#view-controls-container::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
}
#view-controls-container::-webkit-scrollbar-thumb {
background-color: darkgrey;
outline: 1px solid slategrey;
}
@font-face {
font-family: 'Material Icons';
font-style: normal;
@@ -81,11 +104,17 @@ body {
text-rendering: optimizeLegibility;
}
#actions-container {
display: flex;
flex-direction: column;
position: fixed;
bottom: 0;
}
.action-button {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
padding: 12px;
}
.action-button:hover {
@@ -101,6 +130,29 @@ body {
color: #98a9b3;
}
.action-button.disable {
opacity: 0.6;
}
.action-button.disable:hover {
cursor: not-allowed;
}
.action-button.disable:hover i {
color: #6c8592;
}
.action-button.active {
/* background-color: rgba(255, 255, 255, 0.25); */
background-color: #efefef;
opacity: 0.9;
padding-right: 14px;
}
.action-button.active i {
color: #1c262b;
}
.tab:first-child {
margin-top: 8px;
}
@@ -222,35 +274,47 @@ body {
}
webview {
opacity: 1;
transition: opacity 0.3s ease-in;
/* transition: opacity 0.3s ease-in; */
flex-grow: 1;
position: absolute;
width: 100%;
height: 100%;
flex-grow: 1;
display: flex;
flex-direction: column;
}
webview.download-webview {
z-index: -1;
pointer-events: none;
}
webview.onload {
transition: opacity 1s cubic-bezier(0.95, 0.05, 0.795, 0.035);
}
webview.disabled {
flex: 0 1;
height: 0;
width: 0;
opacity: 0;
transition: opacity 0.3s ease-out;
webview.active {
opacity: 1;
z-index: 1;
visibility: visible;
}
webview:focus {
webview.disabled {
opacity: 0;
}
webview.focus {
outline: 0px solid transparent;
}
/* Tooltip styling */
#back-tooltip,
#reload-tooltip,
#setting-tooltip {
font-family: sans-serif;
background: #222c31;
margin-left: 68px;
margin-left: 48px;
padding: 6px 8px;
position: absolute;
margin-top: 0px;
@@ -262,6 +326,7 @@ webview:focus {
font-size: 14px;
}
#back-tooltip:after,
#reload-tooltip:after,
#setting-tooltip:after {
content: " ";
@@ -321,6 +386,8 @@ webview:focus {
height: 100%;
width: 100%;
position: relative;
flex-grow: 1;
flex-basis: 0px;
}
.hidden {
@@ -362,4 +429,27 @@ webview:focus {
overflow: hidden;
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

@@ -5,79 +5,87 @@ body {
cursor: default;
user-select: none;
font-family: menu, "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
-webkit-font-smoothing: antialiased;
font-size: 14px;
color: #333;
background: #efefef;
}
kbd {
padding: 0.3em 0.8em;
display: inline-block;
border: 1px solid #ccc;
border-radius: 3px;
font-size: 15px;
font-family: Courier New, Courier, monospace;
background-color: #383430;
color: #ededed;
display: inline-block;
margin: 0 0.1em;
font-weight: bold;
white-space: nowrap;
background-color: #f7f7f7;
color: #333;
margin: 0 0.1em;
padding: 0.3em 0.8em;
text-shadow: 0 1px 0 #fff;
line-height: 1.4;
}
table, th, td {
table,
th,
td {
border-collapse: collapse;
color: #383430;
}
table {
table {
width: 100%;
margin-top: 18px;
margin-bottom: 18px;
}
}
table tr:nth-child(even) { background-color: #f7eee6; }
table tr:nth-child(even) {
background-color: #fafafa;
}
table tr:nth-child(odd) { background-color: #fff8ef; }
table tr:nth-child(odd) {
background-color: #fff;
}
td { padding: 5px; }
td {
padding: 5px;
}
td:nth-child(odd) {
text-align: right;
width: 50%;
text-align: right;
width: 50%;
}
@font-face {
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'),
local('MaterialIcons-Regular'),
url(../fonts/MaterialIcons-Regular.ttf) format('truetype');
font-family: 'Material Icons';
font-style: normal;
font-weight: 400;
src: local('Material Icons'), local('MaterialIcons-Regular'), url(../fonts/MaterialIcons-Regular.ttf) format('truetype');
}
@font-face {
font-family: 'Montserrat';
src: url(../fonts/Montserrat-Regular.ttf) format('truetype');
font-family: 'Montserrat';
src: url(../fonts/Montserrat-Regular.ttf) format('truetype');
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
/* Preferred icon size */
font-size: 24px;
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
/* Preferred icon size */
font-size: 24px;
display: inline-block;
line-height: 1;
text-transform: none;
letter-spacing: normal;
word-wrap: normal;
white-space: nowrap;
direction: ltr;
/* Support for all WebKit browsers. */
-webkit-font-smoothing: antialiased;
/* Support for Safari and Chrome. */
text-rendering: optimizeLegibility;
}
#content {
@@ -87,11 +95,13 @@ td:nth-child(odd) {
}
#sidebar {
width: 80px;
padding: 30px;
width: 150px;
min-width: 100px;
padding: 30px 30px 30px 35px;
display: flex;
flex-direction: column;
font-size: 16px;
background: #f2f2f2;
}
#nav-container {
@@ -105,7 +115,7 @@ td:nth-child(odd) {
}
.nav.active {
color: #464e5a;
color: #4ebfac;
cursor: default;
position: relative;
}
@@ -113,12 +123,19 @@ td:nth-child(odd) {
.nav.active::before {
background: #464e5a;
width: 3px;
height: 16px;
height: 18px;
position: absolute;
left: -8px;
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 {
font-size: 22px;
color: #222c31;
@@ -134,15 +151,31 @@ td:nth-child(odd) {
}
#new-server-container {
opacity: 1;
transition: opacity 0.3s;
padding-left: 42px;
padding-top: 25px;
margin-right: 16px;
}
.title {
padding: 4px 0 6px 0;
font-weight: bold;
font-weight: 500;
color: #222c31;
text-transform: uppercase;
}
.page-title {
color: #222c31;
font-size: 15px;
font-weight: bold;
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 {
@@ -155,20 +188,40 @@ img.server-info-icon {
width: 36px;
height: 36px;
padding: 4px;
cursor: pointer;
vertical-align: middle;
}
.server-info-left {
margin: 10px 20px 0 0;
margin: 4px 20px 0 0;
min-width: 40%;
}
.server-info-right {
margin-top: 4px;
width: 55%;
display: flex;
align-items: baseline;
justify-content: space-between;
flex-grow: 1;
margin-right: 10px;
}
.server-info-row {
display: flex;
margin: 8px 0 0 0;
display: inline-block;
margin: 5px 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 {
@@ -176,6 +229,10 @@ img.server-info-icon {
cursor: pointer;
}
.server-url-info {
margin-right: 10px;
}
.setting-input-key {
font-size: 14px;
height: 27px;
@@ -189,16 +246,16 @@ img.server-info-icon {
.setting-input-value {
flex-grow: 1;
font-size: 14px;
height: 24px;
border: none;
border-bottom: #ededed 1px solid;
border-radius: 3px;
padding: 13px;
border: #ededed 2px solid;
outline-width: 0;
background: transparent;
max-width: 500px;
max-width: 450px;
}
.setting-input-value:focus {
border-bottom: #7cb980 1px solid;
border: #4EBFAC 2px solid;
}
.setting-block {
@@ -224,7 +281,7 @@ img.server-info-icon {
.action i {
margin-right: 5px;
font-size: 18px;
line-height: 27px;
line-height: 26px;
}
.settings-pane {
@@ -250,7 +307,11 @@ img.server-info-icon {
margin: 10px 0 20px 0;
background: #fff;
width: 70%;
border-left: 8px solid #bcbcbc;
transition: all 0.2s;
}
.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);
}
.hidden {
@@ -259,11 +320,15 @@ img.server-info-icon {
}
.red {
color: #ffffff;
background: #ef5350;
padding: 3px;
padding-right: 10px;
padding-left: 10px;
color: #ef5350;
padding: 8px;
border: rgba(239, 83, 80, 0.5) solid 1px;
}
.red:hover {
color: #e63431;
border: rgba(239, 83, 80, 0.7) solid 1px;
;
}
.blue {
@@ -293,12 +358,13 @@ img.server-info-icon {
}
i.open-tab-button {
padding: 0 5px;
font-size: 18px;
padding-left: 2px;
font-size: 19px;
cursor: pointer;
}
.reset-data-button {
.reset-data-button,
.custom-css-button {
display: inline-block;
border: none;
padding: 10px;
@@ -309,81 +375,249 @@ i.open-tab-button {
text-decoration: none;
}
.css-delete-action {
margin-bottom: 10px;
}
.reset-data-button:hover {
background-color: #3c9f8d;
color: #fff;
}
.selected-css-path {
background: #eeeeee;
padding: 10px;
margin-top: 10px;
margin-right: 10px;
display: flex;
width: 90%;
justify-content: space-between;
}
#remove-custom-css {
align-items: flex-end;
}
#server-info-container {
min-height: calc(100% - 235px);
min-height: calc(100% - 260px);
}
#create-organization-container {
font-size: 1.15em;
margin-bottom: 15px;
font-size: 1.15em;
}
#create-organization-container i {
position: relative;
top: 3px;
position: relative;
top: 3px;
}
#open-create-org-link {
color: #666;
cursor: pointer;
text-decoration: none;
}
#open-create-org-link:hover {
color: #005580;;
text-decoration: underline;
cursor: pointer;
text-decoration: none;
}
.toggle {
position: absolute;
margin-left: -9999px;
visibility: hidden;
}
.toggle + label {
display: block;
position: relative;
cursor: pointer;
outline: none;
user-select: none;
position: absolute;
margin-left: -9999px;
visibility: hidden;
}
input.toggle-round + label {
padding: 2px;
width: 50px;
height: 25px;
background-color: #dddddd;
border-radius: 25px;
.toggle+label {
display: block;
position: relative;
cursor: pointer;
outline: none;
user-select: none;
}
input.toggle-round + label:before,
input.toggle-round + label:after {
display: block;
position: absolute;
top: 2px;
left: 2px;
bottom: 2px;
content: "";
input.toggle-round+label {
padding: 2px;
width: 50px;
height: 25px;
background-color: #dddddd;
border-radius: 25px;
}
input.toggle-round + label:before {
right: 2px;
background-color: #f1f1f1;
border-radius: 25px;
transition: background 0.4s;
input.toggle-round+label:before,
input.toggle-round+label:after {
display: block;
position: absolute;
top: 2px;
left: 2px;
bottom: 2px;
content: "";
}
input.toggle-round + label:after {
width: 25px;
height: 25px;
background-color: #fff;
border-radius: 100%;
transition: margin 0.4s;
input.toggle-round+label:before {
right: 2px;
background-color: #f1f1f1;
border-radius: 25px;
}
input.toggle-round:checked + label:before {
background-color: #4EBFAC;
input.toggle-round+label:after {
width: 25px;
height: 25px;
background-color: #fff;
border-radius: 100%;
}
input.toggle-round:checked+label:before {
background-color: #4EBFAC;
}
input.toggle-round:checked+label:after {
margin-left: 25px;
}
/* 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;
}
/* responsive grid */
@media (max-width: 650px) {
.selected-css-path {
margin-right: 15px;
}
#css-delete-action {
margin-left: 10px;
}
#css-delete-action span {
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;
}
}
input.toggle-round:checked + label:after {
margin-left: 25px;
}

View File

@@ -4,7 +4,7 @@ const Tab = require(__dirname + '/../components/tab.js');
class FunctionalTab extends Tab {
template() {
return `<div class="tab functional-tab">
return `<div class="tab functional-tab" data-tab-id="${this.props.tabIndex}">
<div class="server-tab-badge close-button">
<i class="material-icons">close</i>
</div>
@@ -16,10 +16,11 @@ class FunctionalTab extends Tab {
init() {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$el);
this.$closeButton = this.$el.getElementsByClassName('server-tab-badge')[0];
this.registerListeners();
if (this.props.name !== 'Settings') {
this.props.$root.appendChild(this.$el);
this.$closeButton = this.$el.getElementsByClassName('server-tab-badge')[0];
this.registerListeners();
}
}
registerListeners() {

View File

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

View File

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

View File

@@ -7,7 +7,7 @@ const {ipcRenderer} = require('electron');
class ServerTab extends Tab {
template() {
return `<div class="tab">
return `<div class="tab" data-tab-id="${this.props.tabIndex}">
<div class="server-tooltip" style="display:none"></div>
<div class="server-tab-badge"></div>
<div class="server-tab">
@@ -50,7 +50,8 @@ class ServerTab extends Tab {
shortcutText = `Ctrl+${shownIndex}`;
}
ipcRenderer.send('register-server-tab-shortcut', shownIndex);
// Array index == Shown index - 1
ipcRenderer.send('switch-server-tab', shownIndex - 1);
return shortcutText;
}

View File

@@ -3,13 +3,14 @@
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 } = 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 {
constructor(props) {
super();
@@ -19,11 +20,13 @@ class WebView extends BaseComponent {
this.zoomFactor = 1.0;
this.loading = false;
this.badgeCount = 0;
this.customCSS = ConfigUtil.getConfigItem('customCSS');
}
template() {
return `<webview
class="disabled"
data-tab-id="${this.props.tabIndex}"
src="${this.props.url}"
${this.props.nodeIntegration ? 'nodeIntegration' : ''}
disablewebsecurity
@@ -42,24 +45,33 @@ 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) {
this.$el.addEventListener('dom-ready', () => {
this.$el.setAudioMuted(true);
});
}
this.$el.addEventListener('page-title-updated', event => {
const { title } = event;
this.badgeCount = this.getBadgeCount(title);
this.props.onTitleChange();
});
this.$el.addEventListener('did-navigate-in-page', event => {
const isSettingPage = event.url.includes('renderer/preference.html');
if (isSettingPage) {
return;
}
this.canGoBackButton();
});
this.$el.addEventListener('did-navigate', () => {
this.canGoBackButton();
});
this.$el.addEventListener('page-favicon-updated', event => {
const { favicons } = event;
// This returns a string of favicons URL. If there is a PM counts in unread messages then the URL would be like
@@ -108,6 +120,7 @@ class WebView extends BaseComponent {
}
this.$el.classList.remove('disabled');
this.$el.classList.add('active');
setTimeout(() => {
if (this.props.role === 'server') {
this.$el.classList.remove('onload');
@@ -118,6 +131,21 @@ class WebView extends BaseComponent {
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'));
// get customCSS again from config util to avoid warning user again
this.customCSS = ConfigUtil.getConfigItem('customCSS');
if (this.customCSS) {
if (!fs.existsSync(this.customCSS)) {
this.customCSS = null;
ConfigUtil.setConfigItem('customCSS', null);
const errMsg = 'The custom css previously set is deleted!';
dialog.showErrorBox('custom css file deleted!', errMsg);
return;
}
this.$el.insertCSS(fs.readFileSync(path.resolve(__dirname, this.customCSS), 'utf8'));
}
}
focus() {
@@ -131,6 +159,7 @@ class WebView extends BaseComponent {
hide() {
this.$el.classList.add('disabled');
this.$el.classList.remove('active');
}
load() {
@@ -174,6 +203,15 @@ class WebView extends BaseComponent {
}
}
canGoBackButton() {
const $backButton = document.querySelector('#actions-container #back-action');
if (this.$el.canGoBack()) {
$backButton.classList.remove('disable');
} else {
$backButton.classList.add('disable');
}
}
forward() {
if (this.$el.canGoForward()) {
this.$el.goForward();

View File

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

View File

@@ -1,15 +1,18 @@
'use strict';
require(__dirname + '/js/tray.js');
const { ipcRenderer, remote } = require('electron');
const isDev = require('electron-is-dev');
const { session } = remote;
require(__dirname + '/js/tray.js');
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
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 ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js');
const { feedbackHolder } = require(__dirname + '/js/feedback.js');
class ServerManagerView {
constructor() {
@@ -20,11 +23,13 @@ class ServerManagerView {
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
this.$webviewsContainer = document.getElementById('webviews-container');
this.$backButton = $actionsContainer.querySelector('#back-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.$sidebar = document.getElementById('sidebar');
@@ -35,6 +40,7 @@ class ServerManagerView {
this.activeTabIndex = -1;
this.tabs = [];
this.functionalTabs = {};
this.tabIndex = 0;
}
init() {
@@ -43,6 +49,7 @@ class ServerManagerView {
this.initTabs();
this.initActions();
this.registerIpcs();
this.initDefaultSettings();
});
}
@@ -65,6 +72,39 @@ class ServerManagerView {
});
}
// Settings are initialized only when user clicks on General/Server/Network section settings
// In case, user doesn't visit these section, those values set to be null automatically
// This will make sure the default settings are correctly set to either true or false
initDefaultSettings() {
// Default settings which should be respected
const settingOptions = {
trayIcon: true,
useProxy: false,
showSidebar: true,
badgeOption: true,
startAtLogin: false,
startMinimized: false,
enableSpellchecker: true,
showNotification: true,
betaUpdate: false,
silent: false,
lastActiveTab: 0
};
// Platform specific settings
if (process.platform === 'win32') {
// Only available on Windows
settingOptions.flashTaskbarOnMessage = true;
}
for (const i in settingOptions) {
if (ConfigUtil.getConfigItem(i) === null) {
ConfigUtil.setConfigItem(i, settingOptions[i]);
}
}
}
initSidebar() {
const showSidebar = ConfigUtil.getConfigItem('showSidebar', true);
this.toggleSidebar(showSidebar);
@@ -80,24 +120,28 @@ class ServerManagerView {
}
// Open last active tab
this.activateTab(ConfigUtil.getConfigItem('lastActiveTab'));
// Remove focus from the settings icon at sidebar bottom
this.$settingsButton.classList.remove('active');
} else {
this.openSettings('Servers');
this.openSettings('AddServer');
}
ipcRenderer.send('local-shortcuts', true);
}
initServer(server, index) {
const tabIndex = this.getTabIndex();
this.tabs.push(new ServerTab({
role: 'server',
icon: server.icon,
$root: this.$tabsContainer,
onClick: this.activateLastTab.bind(this, index),
index,
tabIndex,
onHover: this.onHover.bind(this, index, server.alias),
onHoverOut: this.onHoverOut.bind(this, index),
webview: new WebView({
$root: this.$webviewsContainer,
index,
tabIndex,
url: server.url,
name: server.alias,
isActive: () => {
@@ -116,20 +160,45 @@ class ServerManagerView {
this.tabs[this.activeTabIndex].webview.reload();
});
this.$addServerButton.addEventListener('click', () => {
this.openSettings('Servers');
this.openSettings('AddServer');
});
this.$settingsButton.addEventListener('click', () => {
this.openSettings('General');
});
this.$backButton.addEventListener('click', () => {
this.tabs[this.activeTabIndex].webview.back();
});
this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip);
const $serverImgs = document.querySelectorAll('.server-icons');
$serverImgs.forEach($serverImg => {
$serverImg.addEventListener('error', () => {
$serverImg.src = 'img/icon.png';
});
});
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);
}
sidebarHoverEvent(SidebarButton, SidebarTooltip) {
getTabIndex() {
const currentIndex = this.tabIndex;
this.tabIndex++;
return currentIndex;
}
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';
@@ -139,6 +208,11 @@ class ServerManagerView {
onHover(index, serverName) {
this.$serverIconTooltip[index].innerHTML = serverName;
this.$serverIconTooltip[index].removeAttribute('style');
// To handle position of servers' tooltip due to scrolling of list of organizations
// This could not be handled using CSS, hence the top of the tooltip is made same
// as that of its parent element.
const { top } = this.$serverIconTooltip[index].parentElement.getBoundingClientRect();
this.$serverIconTooltip[index].style.top = top + 'px';
}
onHoverOut(index) {
@@ -153,16 +227,20 @@ class ServerManagerView {
this.functionalTabs[tabProps.name] = this.tabs.length;
const tabIndex = this.getTabIndex();
this.tabs.push(new FunctionalTab({
role: 'function',
materialIcon: tabProps.materialIcon,
name: tabProps.name,
$root: this.$tabsContainer,
index: this.functionalTabs[tabProps.name],
tabIndex,
onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]),
onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]),
webview: new WebView({
$root: this.$webviewsContainer,
index: this.functionalTabs[tabProps.name],
tabIndex,
url: tabProps.url,
name: tabProps.name,
isActive: () => {
@@ -174,7 +252,6 @@ class ServerManagerView {
preload: false
})
}));
this.activateTab(this.functionalTabs[tabProps.name]);
}
@@ -184,6 +261,7 @@ class ServerManagerView {
materialIcon: 'settings',
url: `file://${__dirname}/preference.html#${nav}`
});
this.$settingsButton.classList.add('active');
this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav);
}
@@ -204,10 +282,10 @@ class ServerManagerView {
}
activateLastTab(index) {
// Open last active tab
ConfigUtil.setConfigItem('lastActiveTab', index);
// Open all the tabs in background
// Open all the tabs in background, also activate the tab based on the index
this.activateTab(index);
// Save last active tab
ConfigUtil.setConfigItem('lastActiveTab', index);
}
activateTab(index, hideOldTab = true) {
@@ -219,10 +297,19 @@ class ServerManagerView {
if (this.activeTabIndex === index) {
return;
} else if (hideOldTab) {
// If old tab is functional tab Settings, remove focus from the settings icon at sidebar bottom
if (this.tabs[this.activeTabIndex].props.role === 'function' && this.tabs[this.activeTabIndex].props.name === 'Settings') {
this.$settingsButton.classList.remove('active');
}
this.tabs[this.activeTabIndex].deactivate();
}
}
try {
this.tabs[index].webview.canGoBackButton();
} catch (err) {
}
this.activeTabIndex = index;
this.tabs[index].activate();
@@ -230,6 +317,27 @@ class ServerManagerView {
tabs: this.tabs,
activeTabIndex: this.activeTabIndex
});
ipcRenderer.on('toggle-sidebar', (event, state) => {
const selector = 'webview:not([class*=disabled])';
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggle-sidebar', state);
});
ipcRenderer.on('toggle-silent', (event, state) => {
const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => {
try {
webview.setAudioMuted(state);
} catch (err) {
// webview is not ready yet
webview.addEventListener('dom-ready', () => {
webview.isAudioMuted();
});
}
});
});
}
destroyTab(name, index) {
@@ -257,9 +365,6 @@ class ServerManagerView {
// Clear DOM elements
this.$tabsContainer.innerHTML = '';
this.$webviewsContainer.innerHTML = '';
// Destroy shortcuts
ipcRenderer.send('local-shortcuts', false);
}
reloadView() {
@@ -341,7 +446,7 @@ class ServerManagerView {
});
ipcRenderer.on('switch-server-tab', (event, index) => {
this.activateTab(index);
this.activateLastTab(index);
});
ipcRenderer.on('reload-proxy', (event, showAlert) => {
@@ -365,6 +470,18 @@ class ServerManagerView {
this.$fullscreenPopup.classList.remove('show');
});
ipcRenderer.on('focus-webview-with-id', (event, webviewId) => {
const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => {
const currentId = webview.getWebContents().id;
const tabId = webview.getAttribute('data-tab-id');
const concurrentTab = document.querySelector(`div[data-tab-id="${tabId}"]`);
if (currentId === webviewId) {
concurrentTab.click();
}
});
});
ipcRenderer.on('render-taskbar-icon', (event, messageCount) => {
// Create a canvas from unread messagecounts
function createOverlayIcon(messageCount) {
@@ -393,14 +510,33 @@ class ServerManagerView {
}
ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount));
});
ipcRenderer.on('open-feedback-modal', () => {
feedbackHolder.classList.add('show');
});
}
}
window.onload = () => {
const serverManagerView = new ServerManagerView();
const reconnectUtil = new ReconnectUtil(serverManagerView);
serverManagerView.init();
window.addEventListener('online', () => {
serverManagerView.reloadView();
reconnectUtil.pollInternetAndReload();
});
window.addEventListener('offline', () => {
reconnectUtil.clearState();
console.log('No internet connection, you are offline.');
});
// only start electron-connect (auto reload on change) when its ran
// from `npm run dev` or `gulp dev` and not from `npm start` when
// app is started `npm start` main process's proces.argv will have
// `--no-electron-connect`
const mainProcessArgv = remote.getGlobal('process').argv;
if (isDev && !mainProcessArgv.includes('--no-electron-connect')) {
const electronConnect = require('electron-connect');
electronConnect.client.create();
}
};

View File

@@ -1,30 +0,0 @@
'use strict';
const { remote } = require('electron');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const app = remote.app;
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
app.setAppUserModelId('org.zulip.zulip-electron');
const NativeNotification = window.Notification;
class baseNotification extends NativeNotification {
constructor(title, opts) {
opts.silent = ConfigUtil.getConfigItem('silent') || false;
super(title, opts);
}
static requestPermission() {
return; // eslint-disable-line no-useless-return
}
// Override default Notification permission
static get permission() {
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
}
}
window.Notification = baseNotification;

View File

@@ -0,0 +1,100 @@
'use strict';
const { ipcRenderer } = require('electron');
const url = require('url');
const MacNotifier = require('node-mac-notifier');
const ConfigUtil = require('../utils/config-util');
const {
appId, customReply, focusCurrentServer, parseReply, setupReply
} = require('./helpers');
let replyHandler;
let clickHandler;
class DarwinNotification {
constructor(title, opts) {
const silent = ConfigUtil.getConfigItem('silent') || false;
const { host, protocol } = location;
const { icon } = opts;
const profilePic = url.resolve(`${protocol}//${host}`, icon);
this.tag = opts.tag;
const notification = new MacNotifier(title, Object.assign(opts, {
bundleId: appId,
canReply: true,
silent,
icon: profilePic
}));
notification.addEventListener('click', () => {
// focus to the server who sent the
// notification if not focused already
if (clickHandler) {
clickHandler();
}
focusCurrentServer();
ipcRenderer.send('focus-app');
});
notification.addEventListener('reply', this.notificationHandler);
}
static requestPermission() {
return; // eslint-disable-line no-useless-return
}
// Override default Notification permission
static get permission() {
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
}
set onreply(handler) {
replyHandler = handler;
}
get onreply() {
return replyHandler;
}
set onclick(handler) {
clickHandler = handler;
}
get onclick() {
return clickHandler;
}
// not something that is common or
// used by zulip server but added to be
// future proff.
addEventListener(event, handler) {
if (event === 'click') {
clickHandler = handler;
}
if (event === 'reply') {
replyHandler = handler;
}
}
notificationHandler({ response }) {
response = parseReply(response);
focusCurrentServer();
setupReply(this.tag);
if (replyHandler) {
replyHandler(response);
return;
}
customReply(response);
}
// method specific to notification api
// used by zulip
close() {
return; // eslint-disable-line no-useless-return
}
}
module.exports = DarwinNotification;

View File

@@ -0,0 +1,31 @@
'use strict';
const { ipcRenderer } = require('electron');
const ConfigUtil = require('../utils/config-util');
const { focusCurrentServer } = require('./helpers');
const NativeNotification = window.Notification;
class BaseNotification extends NativeNotification {
constructor(title, opts) {
opts.silent = true;
super(title, opts);
this.addEventListener('click', () => {
// focus to the server who sent the
// notification if not focused already
focusCurrentServer();
ipcRenderer.send('focus-app');
});
}
static requestPermission() {
return; // eslint-disable-line no-useless-return
}
// Override default Notification permission
static get permission() {
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
}
}
module.exports = BaseNotification;

View File

@@ -0,0 +1,149 @@
const { remote } = require('electron');
// Do not change this
const appId = 'org.zulip.zulip-electron';
const botsList = [];
let botsListLoaded = false;
// this function load list of bots from the server
// sync=True for a synchronous getJSON request
// in case botsList isn't already completely loaded when required in parseRely
function loadBots(sync = false) {
const { $ } = window;
botsList.length = 0;
if (sync) {
$.ajaxSetup({async: false});
}
$.getJSON('/json/users')
.done(data => {
const members = data.members;
members.forEach(membersRow => {
if (membersRow.is_bot) {
const bot = `@${membersRow.full_name}`;
const mention = `@**${bot.replace(/^@/, '')}**`;
botsList.push([bot, mention]);
}
});
botsListLoaded = true;
})
.fail(error => {
console.log('Request failed: ', error.responseText);
console.log('Request status: ', error.statusText);
});
if (sync) {
$.ajaxSetup({async: true});
}
}
function checkElements(...elements) {
let status = true;
elements.forEach(element => {
if (element === null || element === undefined) {
status = false;
}
});
return status;
}
function customReply(reply) {
// server does not support notification reply yet.
const buttonSelector = '.messagebox #send_controls button[type=submit]';
const messageboxSelector = '.selected_message .messagebox .messagebox-border .messagebox-content';
const textarea = document.querySelector('#compose-textarea');
const messagebox = document.querySelector(messageboxSelector);
const sendButton = document.querySelector(buttonSelector);
// sanity check for old server versions
const elementsExists = checkElements(textarea, messagebox, sendButton);
if (!elementsExists) {
return;
}
textarea.value = reply;
messagebox.click();
sendButton.click();
}
const currentWindow = remote.getCurrentWindow();
const webContents = remote.getCurrentWebContents();
const webContentsId = webContents.id;
// this function will focus the server that sent
// the notification. Main function implemented in main.js
function focusCurrentServer() {
currentWindow.send('focus-webview-with-id', webContentsId);
}
// this function parses the reply from to notification
// making it easier to reply from notification eg
// @username in reply will be converted to @**username**
// #stream in reply will be converted to #**stream**
// bot mentions are not yet supported
function parseReply(reply) {
const usersDiv = document.querySelectorAll('#user_presences li');
const streamHolder = document.querySelectorAll('#stream_filters li');
const users = [];
const streams = [];
usersDiv.forEach(userRow => {
const anchor = userRow.querySelector('span a');
if (anchor !== null) {
const user = `@${anchor.textContent.trim()}`;
const mention = `@**${user.replace(/^@/, '')}**`;
users.push([user, mention]);
}
});
streamHolder.forEach(stream => {
const streamAnchor = stream.querySelector('div a');
if (streamAnchor !== null) {
const streamName = `#${streamAnchor.textContent.trim()}`;
const streamMention = `#**${streamName.replace(/^#/, '')}**`;
streams.push([streamName, streamMention]);
}
});
users.forEach(([user, mention]) => {
if (reply.includes(user)) {
const regex = new RegExp(user, 'g');
reply = reply.replace(regex, mention);
}
});
streams.forEach(([stream, streamMention]) => {
const regex = new RegExp(stream, 'g');
reply = reply.replace(regex, streamMention);
});
// If botsList isn't completely loaded yet, make a synchronous getJSON request for list
if (botsListLoaded === false) {
loadBots(true);
}
// Iterate for every bot name and replace in reply
// @botname with @**botname**
botsList.forEach(([bot, mention]) => {
if (reply.includes(bot)) {
const regex = new RegExp(bot, 'g');
reply = reply.replace(regex, mention);
}
});
reply = reply.replace(/\\n/, '\n');
return reply;
}
function setupReply(id) {
const { narrow } = window;
narrow.by_subject(id, { trigger: 'notification' });
}
module.exports = {
appId,
checkElements,
customReply,
parseReply,
setupReply,
focusCurrentServer,
loadBots
};

View File

@@ -0,0 +1,27 @@
'use strict';
const {
remote: { app }
} = require('electron');
const DefaultNotification = require('./default-notification');
const { appId, loadBots } = require('./helpers');
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
app.setAppUserModelId(appId);
window.Notification = DefaultNotification;
if (process.platform === 'darwin') {
const DarwinNotification = require('./darwin-notifications');
window.Notification = DarwinNotification;
}
window.addEventListener('load', () => {
// Call this function only when user is logged in
// eslint-disable-next-line no-undef, camelcase
if (page_params.realm_uri) {
loadBots();
}
});

View File

@@ -45,7 +45,7 @@ class BadgeSettings {
updateOverlayIcon(messageCount, mainWindow) {
if (!mainWindow.isFocused()) {
mainWindow.flashFrame(true);
mainWindow.flashFrame(ConfigUtil.getConfigItem('flashTaskbarOnMessage'));
}
if (messageCount === 0) {
mainWindow.setOverlayIcon(null, '');

View File

@@ -0,0 +1,51 @@
'use strict';
const BaseSection = require(__dirname + '/base-section.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const ServerInfoForm = require(__dirname + '/server-info-form.js');
class ConnectedOrgSection extends BaseSection {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="settings-pane" id="server-settings-pane">
<div class="page-title">Connected organizations</div>
<div class="title" id="existing-servers">All the connected orgnizations will appear here.</div>
<div id="server-info-container"></div>
</div>
`;
}
init() {
this.initServers();
}
initServers() {
this.props.$root.innerHTML = '';
const servers = DomainUtil.getDomains();
this.props.$root.innerHTML = this.template();
this.$serverInfoContainer = document.getElementById('server-info-container');
this.$existingServers = document.getElementById('existing-servers');
const noServerText = 'All the connected orgnizations will appear here';
// Show noServerText if no servers are there otherwise hide it
this.$existingServers.innerHTML = servers.length === 0 ? noServerText : '';
for (let i = 0; i < servers.length; i++) {
new ServerInfoForm({
$root: this.$serverInfoContainer,
server: servers[i],
index: i,
onChange: this.reloadApp
}).init();
}
}
}
module.exports = ConnectedOrgSection;

View File

@@ -1,37 +0,0 @@
'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">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

@@ -1,11 +1,10 @@
'use strict';
const path = require('path');
const { ipcRenderer } = require('electron');
const { app, dialog } = require('electron').remote;
const { ipcRenderer, remote } = require('electron');
const fs = require('fs-extra');
const { app, dialog } = remote;
const currentBrowserWindow = remote.getCurrentWindow();
const BaseSection = require(__dirname + '/base-section.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
@@ -29,9 +28,13 @@ class GeneralSection extends BaseSection {
<div class="setting-control"></div>
</div>
<div class="setting-row" id="badge-option">
<div class="setting-description">Show app unread badge</div>
<div class="setting-control"></div>
</div>
<div class="setting-description">Show app unread badge</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="flash-taskbar-option" style= "display:${process.platform === 'win32' ? '' : 'none'}">
<div class="setting-description">Flash taskbar on new message</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Desktop Notification</div>
<div class="settings-card">
@@ -57,6 +60,32 @@ class GeneralSection extends BaseSection {
<div class="setting-description">Start app at login</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="start-minimize-option">
<div class="setting-description">Always start minimized</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="enable-spellchecker-option">
<div class="setting-description">Enable Spellchecker (requires restart)</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Add custom CSS</div>
<div class="settings-card">
<div class="setting-row" id="add-custom-css">
<div class="setting-description">
This will inject the selected css stylesheet in all the added accounts
</div>
<button class="custom-css-button blue">Add</button>
</div>
<div class="setting-row" id="remove-custom-css">
<div class="setting-description">
<div class="selected-css-path" id="custom-css-path">${ConfigUtil.getConfigItem('customCSS')}</div>
</div>
<div class="action red" id="css-delete-action">
<i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
</div>
</div>
</div>
<div class="title">Reset Application Data</div>
<div class="settings-card">
@@ -74,12 +103,23 @@ class GeneralSection extends BaseSection {
this.props.$root.innerHTML = this.template();
this.updateTrayOption();
this.updateBadgeOption();
this.updateUpdateOption();
this.updateSilentOption();
this.updateUpdateOption();
this.updateSidebarOption();
this.updateStartAtLoginOption();
this.updateResetDataOption();
this.showDesktopNotification();
this.enableSpellchecker();
this.minimizeOnStart();
this.addCustomCSS();
this.showCustomCSSPath();
this.removeCustomCSS();
// Platform specific settings
// Flashing taskbar on Windows
if (process.platform === 'win32') {
this.updateFlashTaskbar();
}
}
updateTrayOption() {
@@ -108,6 +148,18 @@ class GeneralSection extends BaseSection {
});
}
updateFlashTaskbar() {
this.generateSettingOption({
$element: document.querySelector('#flash-taskbar-option .setting-control'),
value: ConfigUtil.getConfigItem('flashTaskbarOnMessage', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('flashTaskbarOnMessage');
ConfigUtil.setConfigItem('flashTaskbarOnMessage', newValue);
this.updateFlashTaskbar();
}
});
}
updateUpdateOption() {
this.generateSettingOption({
$element: document.querySelector('#betaupdate-option .setting-control'),
@@ -128,6 +180,7 @@ class GeneralSection extends BaseSection {
const newValue = !ConfigUtil.getConfigItem('silent', true);
ConfigUtil.setConfigItem('silent', newValue);
this.updateSilentOption();
currentBrowserWindow.send('toggle-silent', newValue);
}
});
}
@@ -170,6 +223,18 @@ class GeneralSection extends BaseSection {
});
}
enableSpellchecker() {
this.generateSettingOption({
$element: document.querySelector('#enable-spellchecker-option .setting-control'),
value: ConfigUtil.getConfigItem('enableSpellchecker', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('enableSpellchecker');
ConfigUtil.setConfigItem('enableSpellchecker', newValue);
this.enableSpellchecker();
}
});
}
clearAppDataDialog() {
const clearAppDataMessage = 'By clicking proceed you will be removing all added accounts and preferences from Zulip. When the application restarts, it will be as if you are starting Zulip for the first time.';
const getAppPath = path.join(app.getPath('appData'), app.getName());
@@ -188,6 +253,22 @@ class GeneralSection extends BaseSection {
});
}
customCssDialog() {
const showDialogOptions = {
title: 'Select file',
defaultId: 1,
properties: ['openFile'],
filters: [{ name: 'CSS file', extensions: ['css'] }]
};
dialog.showOpenDialog(showDialogOptions, selectedFile => {
if (selectedFile) {
ConfigUtil.setConfigItem('customCSS', selectedFile[0]);
ipcRenderer.send('forward-message', 'hard-reload');
}
});
}
updateResetDataOption() {
const resetDataButton = document.querySelector('#resetdata-option .reset-data-button');
resetDataButton.addEventListener('click', () => {
@@ -195,6 +276,40 @@ class GeneralSection extends BaseSection {
});
}
minimizeOnStart() {
this.generateSettingOption({
$element: document.querySelector('#start-minimize-option .setting-control'),
value: ConfigUtil.getConfigItem('startMinimized', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('startMinimized');
ConfigUtil.setConfigItem('startMinimized', newValue);
this.minimizeOnStart();
}
});
}
addCustomCSS() {
const customCSSButton = document.querySelector('#add-custom-css .custom-css-button');
customCSSButton.addEventListener('click', () => {
this.customCssDialog();
});
}
showCustomCSSPath() {
if (!ConfigUtil.getConfigItem('customCSS')) {
const cssPATH = document.getElementById('remove-custom-css');
cssPATH.style.display = 'none';
}
}
removeCustomCSS() {
const removeCSSButton = document.getElementById('css-delete-action');
removeCSSButton.addEventListener('click', () => {
ConfigUtil.setConfigItem('customCSS');
ipcRenderer.send('forward-message', 'hard-reload');
});
}
}
module.exports = GeneralSection;

View File

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

View File

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

View File

@@ -1,12 +1,13 @@
'use strict';
const BaseComponent = require(__dirname + '/js/components/base.js');
const {ipcRenderer} = require('electron');
const { ipcRenderer } = require('electron');
const Nav = require(__dirname + '/js/pages/preference/nav.js');
const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js');
const GeneralSection = require(__dirname + '/js/pages/preference/general-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');
class PreferenceView extends BaseComponent {
@@ -39,7 +40,7 @@ class PreferenceView extends BaseComponent {
handleNavigation(navItem) {
this.nav.select(navItem);
switch (navItem) {
case 'Servers': {
case 'AddServer': {
this.section = new ServersSection({
$root: this.$settingsContainer
});
@@ -51,6 +52,12 @@ class PreferenceView extends BaseComponent {
});
break;
}
case 'Organizations': {
this.section = new ConnectedOrgSection({
$root: this.$settingsContainer
});
break;
}
case 'Network': {
this.section = new NetworkSection({
$root: this.$settingsContainer
@@ -69,10 +76,27 @@ class PreferenceView extends BaseComponent {
window.location.hash = `#${navItem}`;
}
// Handle toggling and reflect changes in preference page
handleToggle(elementName, state) {
const inputSelector = `#${elementName} .action .switch input`;
const input = document.querySelector(inputSelector);
if (input) {
input.checked = state;
}
}
registerIpcs() {
ipcRenderer.on('switch-settings-nav', (event, navItem) => {
this.handleNavigation(navItem);
});
ipcRenderer.on('toggle-sidebar', (event, state) => {
this.handleToggle('sidebar-option', state);
});
ipcRenderer.on('toggletray', (event, state) => {
this.handleToggle('tray-option', state);
});
}
}

View File

@@ -1,6 +1,6 @@
'use strict';
const {dialog} = require('electron').remote;
const {ipcRenderer} = require('electron');
const { dialog } = require('electron').remote;
const { ipcRenderer } = require('electron');
const BaseComponent = require(__dirname + '/../../components/base.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
@@ -16,19 +16,18 @@ class ServerInfoForm extends BaseComponent {
<div class="settings-card">
<div class="server-info-left">
<img class="server-info-icon" src="${this.props.server.icon}"/>
</div>
<div class="server-info-right">
<div class="server-info-row">
<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 class="server-info-row">
<input class="setting-input-value" disabled value="${this.props.server.url}"/>
</div>
<div class="server-info-right">
<div class="server-info-row server-url">
<span class="server-url-info" title="${this.props.server.url}">${this.props.server.url}</span>
</div>
<div class="server-info-row">
<div class="action red server-delete-action">
<i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
<span>Disconnect</span>
</div>
</div>
</div>
@@ -44,6 +43,7 @@ class ServerInfoForm extends BaseComponent {
initForm() {
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
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.$openServerButton = this.$serverInfoForm.getElementsByClassName('open-tab-button')[0];
this.props.$root.appendChild(this.$serverInfoForm);
@@ -55,7 +55,7 @@ class ServerInfoForm extends BaseComponent {
type: 'warning',
buttons: ['YES', 'NO'],
defaultId: 0,
message: 'Are you sure you want to delete this server?'
message: 'Are you sure you want to disconnect this organization?'
}, response => {
if (response === 0) {
DomainUtil.removeDomain(this.props.index);
@@ -71,7 +71,12 @@ class ServerInfoForm extends BaseComponent {
this.$serverInfoAlias.addEventListener('click', () => {
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;

View File

@@ -1,10 +1,7 @@
'use strict';
const BaseSection = require(__dirname + '/base-section.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const ServerInfoForm = require(__dirname + '/server-info-form.js');
const NewServerForm = require(__dirname + '/new-server-form.js');
const CreateOrganziation = require(__dirname + '/create-new-org.js');
class ServersSection extends BaseSection {
constructor(props) {
@@ -14,13 +11,14 @@ class ServersSection extends BaseSection {
template() {
return `
<div class="settings-pane" id="server-settings-pane">
<div class="title">Add Server</div>
<div id="new-server-container"></div>
<div class="title" id="existing-servers"></div>
<div id="server-info-container"></div>
<div id="create-organization-container"></div>
<div class="add-server-modal">
<div class="modal-container">
<div class="settings-pane" id="server-settings-pane">
<div class="page-title">Add a Zulip organization</div>
<div id="new-server-container"></div>
</div>
</div>
</div>
`;
}
@@ -31,35 +29,10 @@ class ServersSection extends BaseSection {
initServers() {
this.props.$root.innerHTML = '';
const servers = DomainUtil.getDomains();
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.$newServerButton = document.getElementById('new-server-action');
this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!';
// Show Existing servers if servers are there otherwise hide it
this.$existingServers.innerHTML = servers.length === 0 ? '' : 'Existing Servers';
this.initNewServerForm();
this.$createOrganizationContainer = document.getElementById('create-organization-container');
this.initCreateNewOrganization();
for (let i = 0; i < servers.length; i++) {
new ServerInfoForm({
$root: this.$serverInfoContainer,
server: servers[i],
index: i,
onChange: this.reloadApp
}).init();
}
}
initCreateNewOrganization() {
new CreateOrganziation({
$root: this.$createOrganizationContainer
}).init();
}
initNewServerForm() {

View File

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

View File

@@ -1,10 +1,17 @@
'use strict';
const { ipcRenderer } = require('electron');
const { spellChecker } = require('./spellchecker');
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');
// Prevent drag and drop event in main process which prevents remote code executaion
require(__dirname + '/shared/preventdrag.js');
const logout = () => {
// Create the menu for the below
document.querySelector('.dropdown-toggle').click();
@@ -17,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
@@ -32,8 +39,15 @@ process.once('loaded', () => {
// To prevent failing this script on linux we need to load it after the document loaded
document.addEventListener('DOMContentLoaded', () => {
// Init spellchecker
spellChecker();
// Get the default language of the server
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef, camelcase
if (serverLanguage) {
// Set spellcheker language
ConfigUtil.setConfigItem('spellcheckerLanguage', serverLanguage);
// Init spellchecker
SetupSpellChecker.init();
}
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
@@ -42,4 +56,36 @@ 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;
// otherwise, you may experience errors
window.addEventListener('beforeunload', () => {
SetupSpellChecker.unsubscribeSpellChecker();
});
// electron's globalShortcut can cause unexpected results
// so adding the reload shortcut in the old-school way
document.addEventListener('keydown', event => {
if (event.code === 'F5') {
ipcRenderer.send('forward-message', 'hard-reload');
}
});

View File

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

@@ -1,29 +1,56 @@
'use strict';
const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker');
const { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } = require('electron-spellchecker');
function spellChecker() {
// Implement spellcheck using electron api
window.spellCheckHandler = new SpellCheckHandler();
window.spellCheckHandler.attachToInput();
const ConfigUtil = require(__dirname + '/utils/config-util.js');
// Start off as US English
window.spellCheckHandler.switchLanguage('en-US');
class SetupSpellChecker {
init() {
if (ConfigUtil.getConfigItem('enableSpellchecker')) {
this.enableSpellChecker();
}
this.enableContextMenu();
}
const contextMenuBuilder = new ContextMenuBuilder(window.spellCheckHandler);
const contextMenuListener = new ContextMenuListener(info => {
contextMenuBuilder.showPopupMenu(info);
});
enableSpellChecker() {
try {
this.SpellCheckHandler = new SpellCheckHandler();
} catch (err) {
console.log(err);
}
}
// Clean up events after you navigate away from this page;
// otherwise, you may experience errors
window.addEventListener('beforeunload', () => {
// eslint-disable-next-line no-undef
spellCheckHandler.unsubscribe();
contextMenuListener.unsubscribe();
});
enableContextMenu() {
if (this.SpellCheckHandler) {
this.SpellCheckHandler.attachToInput();
const userLanguage = ConfigUtil.getConfigItem('spellcheckerLanguage');
// eslint-disable-next-line no-unused-expressions
process.platform === 'darwin' ?
// On macOS, spellchecker fails to auto-detect the lanugage user is typing in
// that's why we need to mention it explicitly
this.SpellCheckHandler.switchLanguage(userLanguage) :
// On Linux and Windows, spellchecker can automatically detects the language the user is typing in
// and silently switches on the fly; thus we can start off as US English
this.SpellCheckHandler.switchLanguage('en-US');
}
const contextMenuBuilder = new ContextMenuBuilder(this.SpellCheckHandler);
this.contextMenuListener = new ContextMenuListener(info => {
contextMenuBuilder.showPopupMenu(info);
});
}
unsubscribeSpellChecker() {
// eslint-disable-next-line no-undef
if (this.SpellCheckHandler) {
this.SpellCheckHandler.unsubscribe();
}
if (this.contextMenuListener) {
this.contextMenuListener.unsubscribe();
}
}
}
module.exports = {
spellChecker
};
module.exports = new SetupSpellChecker();

View File

@@ -197,13 +197,16 @@ ipcRenderer.on('tray', (event, arg) => {
});
function toggleTray() {
let state;
if (window.tray) {
state = false;
window.tray.destroy();
if (window.tray.isDestroyed()) {
window.tray = null;
}
ConfigUtil.setConfigItem('trayIcon', false);
} else {
state = true;
createTray();
if (process.platform === 'linux' || process.platform === 'win32') {
renderNativeImage(unread).then(image => {
@@ -213,6 +216,10 @@ function toggleTray() {
}
ConfigUtil.setConfigItem('trayIcon', true);
}
const selector = 'webview:not([class*=disabled])';
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggletray', state);
}
ipcRenderer.on('toggletray', toggleTray);

View File

@@ -1,16 +1,29 @@
'use strict';
const fs = require('fs');
const path = require('path');
const process = require('process');
const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
const logger = new Logger({
file: 'config-util.log',
timestamp: true
});
let instance = null;
let dialog = null;
let app = null;
/* To make the util runnable in both main and renderer process */
if (process.type === 'renderer') {
app = require('electron').remote.app;
const remote = require('electron').remote;
dialog = remote.dialog;
app = remote.app;
} else {
app = require('electron').app;
const electron = require('electron');
dialog = electron.dialog;
app = electron.app;
}
class ConfigUtil {
@@ -47,7 +60,22 @@ class ConfigUtil {
}
reloadDB() {
this.db = new JsonDB(app.getPath('userData') + '/settings.json', true, true);
const settingsJsonPath = path.join(app.getPath('userData'), '/settings.json');
try {
const file = fs.readFileSync(settingsJsonPath, 'utf8');
JSON.parse(file);
} catch (err) {
if (fs.existsSync(settingsJsonPath)) {
fs.unlinkSync(settingsJsonPath);
dialog.showErrorBox(
'Error saving settings',
'We encountered error while saving current settings.'
);
logger.error('Error while JSON parsing settings.json: ');
logger.error(err);
}
}
this.db = new JsonDB(settingsJsonPath, true, true);
}
}

View File

@@ -0,0 +1,31 @@
const fs = require('fs');
let app = null;
let setupCompleted = false;
if (process.type === 'renderer') {
app = require('electron').remote.app;
} else {
app = require('electron').app;
}
const zulipDir = app.getPath('userData');
const logDir = `${zulipDir}/Logs/`;
const initSetUp = () => {
// if it is the first time the app is running
// create zulip dir in userData folder to
// avoid errors
if (!setupCompleted) {
if (!fs.existsSync(zulipDir)) {
fs.mkdirSync(zulipDir);
}
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir);
}
setupCompleted = true;
}
};
module.exports = {
initSetUp
};

View File

@@ -5,6 +5,12 @@ const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const request = require('request');
const Logger = require('./logger-util');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
let instance = null;
@@ -93,8 +99,7 @@ class DomainUtil {
checkDomain(domain, silent = false) {
if (!silent && this.duplicateDomain(domain)) {
// Do not check duplicate in silent mode
alert('This server has been added.');
return;
return Promise.reject('This server has been added.');
}
domain = this.formatUrl(domain);
@@ -110,17 +115,28 @@ class DomainUtil {
return new Promise((resolve, reject) => {
request(checkDomain, (error, response) => {
const certsError =
['Error: self signed certificate',
'Error: unable to verify the first certificate'
[
'Error: self signed certificate',
'Error: unable to verify the first certificate',
'Error: unable to get local issuer certificate'
];
if (!error && response.statusCode !== 404) {
// If the domain contains following strings we just bypass the server
const whitelistDomains = [
'zulipdev.org'
];
// make sure that error is a error or string not undefined
// so validation does not throw error.
error = error || '';
if (!error && response.statusCode < 400) {
// Correct
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
}, () => {
resolve(serverConf);
});
} else if (certsError.indexOf(error.toString()) >= 0) {
} else if (domain.indexOf(whitelistDomains) >= 0 || certsError.indexOf(error.toString()) >= 0) {
if (silent) {
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
@@ -152,7 +168,9 @@ class DomainUtil {
});
}
} else {
reject('Not a valid Zulip server');
const invalidZulipServerError = `${domain} does not appear to be a valid Zulip server. Make sure that \
\n(1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings \n (3) its a zulip server`;
reject(invalidZulipServerError);
}
});
});
@@ -217,7 +235,23 @@ class DomainUtil {
}
reloadDB() {
this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
const domainJsonPath = path.join(app.getPath('userData'), '/domain.json');
try {
const file = fs.readFileSync(domainJsonPath, 'utf8');
JSON.parse(file);
} catch (err) {
if (fs.existsSync(domainJsonPath)) {
fs.unlinkSync(domainJsonPath);
dialog.showErrorBox(
'Error saving new organization',
'There seems to be error while saving new organization, ' +
'you may have to re-add your previous organizations back.'
);
logger.error('Error while JSON parsing domain.json: ');
logger.error(err);
}
}
this.db = new JsonDB(domainJsonPath, true, true);
}
generateFilePath(url) {

View File

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

View File

@@ -0,0 +1,74 @@
'use strict';
const fs = require('fs');
const path = require('path');
const process = require('process');
const remote =
process.type === 'renderer' ? require('electron').remote : require('electron');
const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
const logger = new Logger({
file: 'linux-update-util.log',
timestamp: true
});
/* To make the util runnable in both main and renderer process */
const { dialog, app } = remote;
let instance = null;
class LinuxUpdateUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.reloadDB();
return instance;
}
getUpdateItem(key, defaultValue = null) {
this.reloadDB();
const value = this.db.getData('/')[key];
if (value === undefined) {
this.setUpdateItem(key, defaultValue);
return defaultValue;
} else {
return value;
}
}
setUpdateItem(key, value) {
this.db.push(`/${key}`, value, true);
this.reloadDB();
}
removeUpdateItem(key) {
this.db.delete(`/${key}`);
this.reloadDB();
}
reloadDB() {
const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/updates.json');
try {
const file = fs.readFileSync(linuxUpdateJsonPath, 'utf8');
JSON.parse(file);
} catch (err) {
if (fs.existsSync(linuxUpdateJsonPath)) {
fs.unlinkSync(linuxUpdateJsonPath);
dialog.showErrorBox(
'Error saving update notifications.',
'We encountered error while saving update notifications.'
);
logger.error('Error while JSON parsing updates.json: ');
logger.error(err);
}
}
this.db = new JsonDB(linuxUpdateJsonPath, true, true);
}
}
module.exports = new LinuxUpdateUtil();

View File

@@ -0,0 +1,87 @@
const NodeConsole = require('console').Console;
const fs = require('fs');
const isDev = require('electron-is-dev');
const { initSetUp } = require('./default-util');
initSetUp();
let app = null;
if (process.type === 'renderer') {
app = require('electron').remote.app;
} else {
app = require('electron').app;
}
const browserConsole = console;
const logDir = `${app.getPath('userData')}/Logs`;
class Logger {
constructor(opts = {}) {
let {
timestamp = true,
file = 'console.log',
level = true,
logInDevMode = false
} = opts;
file = `${logDir}/${file}`;
if (timestamp === true) {
timestamp = this.getTimestamp;
}
const fileStream = fs.createWriteStream(file, { flags: 'a' });
const nodeConsole = new NodeConsole(fileStream);
this.nodeConsole = nodeConsole;
this.timestamp = timestamp;
this.level = level;
this.logInDevMode = logInDevMode;
this.setUpConsole();
}
_log(type, ...args) {
const {
nodeConsole, timestamp, level, logInDevMode
} = this;
let nodeConsoleLog;
/* eslint-disable no-fallthrough */
switch (true) {
case typeof timestamp === 'function':
args.unshift(timestamp() + ' |\t');
case (level !== false):
args.unshift(type.toUpperCase() + ' |');
case isDev || logInDevMode:
nodeConsoleLog = nodeConsole[type] || nodeConsole.log;
nodeConsoleLog.apply(null, args);
default: break;
}
/* eslint-enable no-fallthrough */
browserConsole[type].apply(null, args);
}
setUpConsole() {
for (const type in browserConsole) {
this.setupConsoleMethod(type);
}
}
setupConsoleMethod(type) {
this[type] = (...args) => {
this._log(type, ...args);
};
}
getTimestamp() {
const date = new Date();
const timestamp =
`${date.getMonth()}/${date.getDate()} ` +
`${date.getMinutes()}:${date.getSeconds()}`;
return timestamp;
}
}
module.exports = Logger;

View File

@@ -0,0 +1,54 @@
const isOnline = require('is-online');
class ReconnectUtil {
constructor(serverManagerView) {
this.serverManagerView = serverManagerView;
this.alreadyReloaded = false;
}
clearState() {
this.alreadyReloaded = false;
}
pollInternetAndReload() {
const pollInterval = setInterval(() => {
this._checkAndReload()
.then(status => {
if (status) {
this.alreadyReloaded = true;
clearInterval(pollInterval);
}
});
}, 1500);
}
_checkAndReload() {
return new Promise(resolve => {
if (!this.alreadyReloaded) { // eslint-disable-line no-negated-condition
isOnline()
.then(online => {
if (online) {
if (!this.alreadyReloaded) {
this.serverManagerView.reloadView();
}
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');
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);
});
} else {
return resolve(true);
}
});
}
}
module.exports = ReconnectUtil;

View File

@@ -28,6 +28,10 @@
<i class="material-icons md-48">refresh</i>
<span id="reload-tooltip" style="display:none">Reload</span>
</div>
<div class="action-button disable" id="back-action">
<i class="material-icons md-48">arrow_back</i>
<span id="back-tooltip" style="display:none">Go Back</span>
</div>
<div class="action-button" id="settings-action">
<i class="material-icons md-48">settings</i>
<span id="setting-tooltip" style="display:none">Settings</span>
@@ -38,7 +42,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>

View File

@@ -17,5 +17,6 @@
<div id="reconnect">Try now</div>
</div>
</body>
<script src="js/pages/network.js"></script>
<script src="js/pages/network.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html>

View File

@@ -12,5 +12,6 @@
<div id="settings-container"></div>
</div>
</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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

39
app/translations/bg.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "За Зулип",
"Actual Size": "Реален размер",
"Back": "обратно",
"Close": "Близо",
"Copy": "копие",
"Cut": "Разрез",
"Delete": "Изтрий",
"Desktop App Settings": "Настройки за настолни приложения",
"Edit": "редактиране",
"File": "досие",
"Forward": "напред",
"Hard Reload": "Трудно презареждане",
"Help": "Помогне",
"History": "история",
"Keyboard Shortcuts": "Комбинация от клавиши",
"Log Out": "Излез от профила си",
"Minimize": "Минимизиране",
"Paste": "паста",
"Paste and Match Style": "Поставяне и съвпадение на стила",
"Quit": "напускам",
"Redo": "ремонтирам",
"Reload": "Презареди",
"Report an issue...": "Подаване на сигнал за проблем ...",
"Reset App Settings": "Нулирайте настройките на приложението",
"Select All": "Избери всички",
"Show App Logs": "Показване на регистрационните файлове на приложения",
"Toggle DevTools for Active Tab": "Превключете DevTools за активен раздел",
"Toggle DevTools for Zulip App": "Превключете DevTools за Zulip App",
"Toggle Full Screen": "Превключване на цял екран",
"Toggle Sidebar": "Превключване на страничната лента",
"Toggle Tray Icon": "Превключване на иконата на тавата",
"Undo": "премахвам",
"View": "изглед",
"Window": "прозорец",
"Zoom In": "Увеличавам",
"Zoom Out": "Отдалечавам",
"Zulip Help": "Помощ за Zulip"
}

39
app/translations/cs.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "O společnosti Zulip",
"Actual Size": "Aktuální velikost",
"Back": "Zadní",
"Close": "Zavřít",
"Copy": "kopírovat",
"Cut": "Střih",
"Delete": "Vymazat",
"Desktop App Settings": "Nastavení aplikace Desktop",
"Edit": "Upravit",
"File": "Soubor",
"Forward": "Vpřed",
"Hard Reload": "Hard Reload",
"Help": "Pomoc",
"History": "Dějiny",
"Keyboard Shortcuts": "Klávesové zkratky",
"Log Out": "Odhlásit se",
"Minimize": "Minimalizujte",
"Paste": "Vložit",
"Paste and Match Style": "Vložit a přizpůsobit styl",
"Quit": "Přestat",
"Redo": "Předělat",
"Reload": "Znovu načíst",
"Report an issue...": "Nahlásit problém...",
"Reset App Settings": "Obnovit nastavení aplikace",
"Select All": "Vybrat vše",
"Show App Logs": "Zobrazit protokoly aplikace",
"Toggle DevTools for Active Tab": "Přepínač DevTools pro aktivní kartu",
"Toggle DevTools for Zulip App": "Přepnout nástroj DevTools pro aplikaci Zulip App",
"Toggle Full Screen": "Přepnout na celou obrazovku",
"Toggle Sidebar": "Přepnout postranní panel",
"Toggle Tray Icon": "Přepnout ikonu zásobníku",
"Undo": "vrátit",
"View": "Pohled",
"Window": "Okno",
"Zoom In": "Přiblížit",
"Zoom Out": "Oddálit",
"Zulip Help": "Zulip Nápověda"
}

39
app/translations/de.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "Über Zulip",
"Actual Size": "Tatsächliche Größe",
"Back": "Zurück",
"Close": "Schließen",
"Copy": "Kopieren",
"Cut": "Schnitt",
"Delete": "Löschen",
"Desktop App Settings": "Desktop App Einstellungen",
"Edit": "Bearbeiten",
"File": "Datei",
"Forward": "Nach vorne",
"Hard Reload": "Hard Reload",
"Help": "Hilfe",
"History": "Geschichte",
"Keyboard Shortcuts": "Tastatürkürzel",
"Log Out": "Ausloggen",
"Minimize": "Minimieren",
"Paste": "Einfügen",
"Paste and Match Style": "Einfügen und Anpassen von Stilen",
"Quit": "Verlassen",
"Redo": "Wiederholen",
"Reload": "Neu laden",
"Report an issue...": "Ein Problem melden...",
"Reset App Settings": "App-Einstellungen zurücksetzen",
"Select All": "Wählen Sie Alle",
"Show App Logs": "App-Logs anzeigen",
"Toggle DevTools for Active Tab": "Schalten Sie DevTools für Active Tab ein",
"Toggle DevTools for Zulip App": "Umschalt DevTools für Zulip App",
"Toggle Full Screen": "Vollbild umschalten",
"Toggle Sidebar": "Toggle Seitenleiste",
"Toggle Tray Icon": "Toggle Tray-Symbol",
"Undo": "Rückgängig machen",
"View": "Aussicht",
"Window": "Fenster",
"Zoom In": "Hineinzoomen",
"Zoom Out": "Rauszoomen",
"Zulip Help": "Zulip Hilfe"
}

39
app/translations/en.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "About Zulip",
"Actual Size": "Actual Size",
"Back": "Back",
"Close": "Close",
"Copy": "Copy",
"Cut": "Cut",
"Delete": "Delete",
"Desktop App Settings": "Desktop App Settings",
"Edit": "Edit",
"File": "File",
"Forward": "Forward",
"Hard Reload": "Hard Reload",
"Help": "Help",
"History": "History",
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Log Out": "Log Out",
"Minimize": "Minimize",
"Paste": "Paste",
"Paste and Match Style": "Paste and Match Style",
"Quit": "Quit",
"Redo": "Redo",
"Reload": "Reload",
"Report an issue...": "Report an issue...",
"Reset App Settings": "Reset App Settings",
"Select All": "Select All",
"Show App Logs": "Show App Logs",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle Full Screen": "Toggle Full Screen",
"Toggle Sidebar": "Toggle Sidebar",
"Toggle Tray Icon": "Toggle Tray Icon",
"Undo": "Undo",
"View": "View",
"Window": "Window",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help"
}

39
app/translations/es.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "Acerca de Zulip",
"Actual Size": "Tamaño real",
"Back": "Espalda",
"Close": "Cerca",
"Copy": "Dupdo",
"Cut": "Cortar",
"Delete": "Borrar",
"Desktop App Settings": "Configuración de la aplicación de escritorio",
"Edit": "Editar",
"File": "Archivo",
"Forward": "Adelante",
"Hard Reload": "Recargar duro",
"Help": "Ayuda",
"History": "Historia",
"Keyboard Shortcuts": "Atajos de teclado",
"Log Out": "Cerrar sesión",
"Minimize": "Minimizar",
"Paste": "Pegar",
"Paste and Match Style": "Pegar y combinar estilo",
"Quit": "Dejar",
"Redo": "Rehacer",
"Reload": "Recargar",
"Report an issue...": "Reportar un problema...",
"Reset App Settings": "Restablecer configuraciones",
"Select All": "Seleccionar todo",
"Show App Logs": "Mostrar registros de aplicaciones",
"Toggle DevTools for Active Tab": "Alternar DevTools para Active Tab",
"Toggle DevTools for Zulip App": "Alternar DevTools para la aplicación Zulip",
"Toggle Full Screen": "Alternar pantalla completa",
"Toggle Sidebar": "Alternar barra lateral",
"Toggle Tray Icon": "Alternar el icono de la bandeja",
"Undo": "Deshacer",
"View": "Ver",
"Window": "Ventana",
"Zoom In": "Acercarse",
"Zoom Out": "Disminuir el zoom",
"Zulip Help": "Ayuda de Zulip"
}

39
app/translations/fr.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "À propos de Zulip",
"Actual Size": "Taille actuelle",
"Back": "Arrière",
"Close": "Fermer",
"Copy": "Copie",
"Cut": "Couper",
"Delete": "Effacer",
"Desktop App Settings": "Paramètres de l'application de bureau",
"Edit": "modifier",
"File": "Fichier",
"Forward": "Vers l'avant",
"Hard Reload": "Rechargement dur",
"Help": "Aidez-moi",
"History": "Histoire",
"Keyboard Shortcuts": "Raccourcis clavier",
"Log Out": "Connectez - Out",
"Minimize": "Minimiser",
"Paste": "Coller",
"Paste and Match Style": "Coller et assortir le style",
"Quit": "Quitter",
"Redo": "Refaire",
"Reload": "Recharger",
"Report an issue...": "Signaler un problème...",
"Reset App Settings": "Réinitialiser les paramètres",
"Select All": "Tout sélectionner",
"Show App Logs": "Afficher les journaux d'applications",
"Toggle DevTools for Active Tab": "Basculer DevTools pour l'onglet actif",
"Toggle DevTools for Zulip App": "Basculer DevTools pour l'application Zulip",
"Toggle Full Screen": "Basculer en plein écran",
"Toggle Sidebar": "Basculer la barre latérale",
"Toggle Tray Icon": "Toggle Tray Icône",
"Undo": "annuler",
"View": "Vue",
"Window": "Fenêtre",
"Zoom In": "Agrandir",
"Zoom Out": "Dézoomer",
"Zulip Help": "Aide Zulip"
}

39
app/translations/hi.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "Zulip के बारे में",
"Actual Size": "वास्तविक आकार",
"Back": "वापस",
"Close": "बंद करे",
"Copy": "प्रतिलिपि",
"Cut": "कट गया",
"Delete": "हटाना",
"Desktop App Settings": "डेस्कटॉप ऐप सेटिंग्स",
"Edit": "संपादित करें",
"File": "फ़ाइल",
"Forward": "आगे",
"Hard Reload": "कठिन पुन: लोड करें",
"Help": "मदद",
"History": "इतिहास",
"Keyboard Shortcuts": "कुंजीपटल अल्प मार्ग",
"Log Out": "लोग आउट",
"Minimize": "छोटा करना",
"Paste": "चिपकाएं",
"Paste and Match Style": "चिपकाएं और शैली का मिलान करें",
"Quit": "छोड़ना",
"Redo": "फिर से करना",
"Reload": "सीमा से अधिक लादना",
"Report an issue...": "मामले की रिपोर्ट करें...",
"Reset App Settings": "ऐप सेटिंग्स रीसेट करें",
"Select All": "सभी का चयन करे",
"Show App Logs": "ऐप लॉग दिखाएं",
"Toggle DevTools for Active Tab": "सक्रिय टैब के लिए DevTools टॉगल करें",
"Toggle DevTools for Zulip App": "Zulip ऐप के लिए DevTools टॉगल करें",
"Toggle Full Screen": "पूर्णस्क्रीन चालू करें",
"Toggle Sidebar": "साइडबार टॉगल करें",
"Toggle Tray Icon": "ट्रे आइकन टॉगल करें",
"Undo": "पूर्ववत करें",
"View": "राय",
"Window": "खिड़की",
"Zoom In": "ज़ूम इन",
"Zoom Out": "ज़ूम आउट",
"Zulip Help": "Zulip सहायता"
}

39
app/translations/hu.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "A Zulipról",
"Actual Size": "Valódi méret",
"Back": "Hát",
"Close": "Bezárás",
"Copy": "Másolat",
"Cut": "Vágott",
"Delete": "Töröl",
"Desktop App Settings": "Asztali alkalmazások beállításai",
"Edit": "szerkesztése",
"File": "fájl",
"Forward": "Előre",
"Hard Reload": "Hard Reload",
"Help": "Segítség",
"History": "Történelem",
"Keyboard Shortcuts": "Gyorsbillentyűket",
"Log Out": "Kijelentkezés",
"Minimize": "Kis méret",
"Paste": "Paszta",
"Paste and Match Style": "Beillesztés és stílusok illesztése",
"Quit": "quit",
"Redo": "Újra",
"Reload": "Reload",
"Report an issue...": "Hibajelentés ...",
"Reset App Settings": "Az alkalmazás beállításainak visszaállítása",
"Select All": "Mindet kiválaszt",
"Show App Logs": "Alkalmazásnaplók megjelenítése",
"Toggle DevTools for Active Tab": "A DevTools aktiválása az Aktív laphoz",
"Toggle DevTools for Zulip App": "Kapcsolja a DevTools-ot a Zulip App-hoz",
"Toggle Full Screen": "Teljes képernyőre váltás",
"Toggle Sidebar": "Oldalsáv átkapcsolása",
"Toggle Tray Icon": "Tálcaikon átkapcsolása",
"Undo": "kibont",
"View": "Kilátás",
"Window": "Ablak",
"Zoom In": "Nagyítás",
"Zoom Out": "Kicsinyítés",
"Zulip Help": "Zulip Súgó"
}

39
app/translations/id.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "Tentang Zulip",
"Actual Size": "Ukuran sebenarnya",
"Back": "Kembali",
"Close": "Dekat",
"Copy": "Salinan",
"Cut": "Memotong",
"Delete": "Menghapus",
"Desktop App Settings": "Setelan Aplikasi Desktop",
"Edit": "Edit",
"File": "Mengajukan",
"Forward": "Meneruskan",
"Hard Reload": "Hard Reload",
"Help": "Membantu",
"History": "Sejarah",
"Keyboard Shortcuts": "Cara pintas keyboard",
"Log Out": "Keluar",
"Minimize": "Memperkecil",
"Paste": "Pasta",
"Paste and Match Style": "Tempel dan Cocokkan Gaya",
"Quit": "Berhenti",
"Redo": "Mengulangi",
"Reload": "Reload",
"Report an issue...": "Laporkan masalah ...",
"Reset App Settings": "Setel ulang Pengaturan Aplikasi",
"Select All": "Pilih Semua",
"Show App Logs": "Tampilkan log aplikasi",
"Toggle DevTools for Active Tab": "Toggle DevTools untuk Tab Aktif",
"Toggle DevTools for Zulip App": "Toggle DevTools untuk Aplikasi Zulip",
"Toggle Full Screen": "Toggle Full Screen",
"Toggle Sidebar": "Toggle Sidebar",
"Toggle Tray Icon": "Toggle Tray Icon",
"Undo": "Membuka",
"View": "Melihat",
"Window": "Jendela",
"Zoom In": "Perbesar",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help"
}

39
app/translations/ja.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "チューリップについて",
"Actual Size": "実際のサイズ",
"Back": "バック",
"Close": "閉じる",
"Copy": "コピー",
"Cut": "カット",
"Delete": "削除",
"Desktop App Settings": "デスクトップアプリケーションの設定",
"Edit": "編集",
"File": "ファイル",
"Forward": "フォワード",
"Hard Reload": "ハードリロード",
"Help": "助けて",
"History": "歴史",
"Keyboard Shortcuts": "キーボードショートカット",
"Log Out": "ログアウト",
"Minimize": "最小化する",
"Paste": "ペースト",
"Paste and Match Style": "スタイルの貼り付けと一致",
"Quit": "終了する",
"Redo": "やり直し",
"Reload": "リロード",
"Report an issue...": "問題を報告する...",
"Reset App Settings": "アプリの設定をリセットする",
"Select All": "すべて選択",
"Show App Logs": "アプリケーションログを表示する",
"Toggle DevTools for Active Tab": "DevTools for Activeタブを切り替える",
"Toggle DevTools for Zulip App": "Zulip AppのDevToolsの切り替え",
"Toggle Full Screen": "フルスクリーン切り替え",
"Toggle Sidebar": "サイドバーの切り替え",
"Toggle Tray Icon": "トレイアイコンを切り替える",
"Undo": "元に戻す",
"View": "ビュー",
"Window": "窓",
"Zoom In": "ズームイン",
"Zoom Out": "ズームアウトする",
"Zulip Help": "チューリップヘルプ"
}

39
app/translations/ko.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "튤립 소개",
"Actual Size": "실제 크기",
"Back": "뒤로",
"Close": "닫기",
"Copy": "부",
"Cut": "절단",
"Delete": "지우다",
"Desktop App Settings": "데스크톱 앱 설정",
"Edit": "편집하다",
"File": "파일",
"Forward": "앞으로",
"Hard Reload": "하드 다시로드",
"Help": "도움",
"History": "역사",
"Keyboard Shortcuts": "키보드 단축키",
"Log Out": "로그 아웃",
"Minimize": "최소화",
"Paste": "풀",
"Paste and Match Style": "붙여 넣기 및 스타일 일치",
"Quit": "떠나다",
"Redo": "다시 하다",
"Reload": "다시로드",
"Report an issue...": "문제 신고 ...",
"Reset App Settings": "앱 설정 재설정",
"Select All": "모두 선택",
"Show App Logs": "앱 로그 표시",
"Toggle DevTools for Active Tab": "DevTools for Active Tab 토글",
"Toggle DevTools for Zulip App": "Zulip App 용 DevTools 토글",
"Toggle Full Screen": "전체 화면 토글",
"Toggle Sidebar": "사이드 바 전환",
"Toggle Tray Icon": "트레이 아이콘 토글",
"Undo": "끄르다",
"View": "전망",
"Window": "창문",
"Zoom In": "확대",
"Zoom Out": "축소",
"Zulip Help": "튤립 도움말"
}

39
app/translations/ml.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "സുലിപ്",
"Actual Size": "യഥാർത്ഥ വലുപ്പം",
"Back": "തിരികെ",
"Close": "അടയ്ക്കുക",
"Copy": "പകർത്തുക",
"Cut": "മുറിക്കുക",
"Delete": "ഇല്ലാതാക്കുക",
"Desktop App Settings": "ഡെസ്ക്ടോപ്പ് അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങൾ",
"Edit": "എഡിറ്റുചെയ്യുക",
"File": "ഫയൽ",
"Forward": "മുന്നോട്ട്",
"Hard Reload": "ഹാർഡ് റീലോഡ്",
"Help": "സഹായിക്കൂ",
"History": "ചരിത്രം",
"Keyboard Shortcuts": "കീബോർഡ് കുറുക്കുവഴികൾ",
"Log Out": "ലോഗ് ഔട്ട്",
"Minimize": "ചെറുതാക്കുക",
"Paste": "പേസ്റ്റ്",
"Paste and Match Style": "ശൈലി ഒട്ടിക്കുകയും പൊരുത്തപ്പെടുത്തുകയും ചെയ്യുക",
"Quit": "പുറത്തുകടക്കുക",
"Redo": "വീണ്ടും ചെയ്യുക",
"Reload": "വീണ്ടും ലോഡുചെയ്യുക",
"Report an issue...": "ഒരു പ്രശ്നം റിപ്പോർട്ടുചെയ്യുക ...",
"Reset App Settings": "അപ്ലിക്കേഷൻ ക്രമീകരണങ്ങൾ പുനഃസജ്ജമാക്കുക",
"Select All": "എല്ലാം തിരഞ്ഞെടുക്കുക",
"Show App Logs": "അപ്ലിക്കേഷൻ ലോഗുകൾ കാണിക്കുക",
"Toggle DevTools for Active Tab": "സജീവ ടാബിനായി DevTools ടോഗിൾ ചെയ്യുക",
"Toggle DevTools for Zulip App": "സുലിപ്പ് ആപ്ലിക്കേഷനായി DevTools ടോഗിൾ ചെയ്യുക",
"Toggle Full Screen": "പൂർണ്ണ സ്ക്രീൻ ടോഗിൾ ചെയ്യുക",
"Toggle Sidebar": "സൈഡ്ബാർ ടോഗിൾ ചെയ്യുക",
"Toggle Tray Icon": "ട്രേ ഐക്കൺ ടോഗിൾ ചെയ്യുക",
"Undo": "പഴയപടിയാക്കുക",
"View": "കാണുക",
"Window": "ജാലകം",
"Zoom In": "വലുതാക്കുക",
"Zoom Out": "സൂം ഔട്ട്",
"Zulip Help": "സുലിപ്പ് സഹായം"
}

39
app/translations/nl.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "Over Zulip",
"Actual Size": "Daadwerkelijke grootte",
"Back": "Terug",
"Close": "Dichtbij",
"Copy": "Kopiëren",
"Cut": "Besnoeiing",
"Delete": "Verwijder",
"Desktop App Settings": "Desktop-app-instellingen",
"Edit": "Bewerk",
"File": "het dossier",
"Forward": "Vooruit",
"Hard Reload": "Moeilijke herladen",
"Help": "Helpen",
"History": "Geschiedenis",
"Keyboard Shortcuts": "Toetsenbord sneltoetsen",
"Log Out": "Uitloggen",
"Minimize": "verkleinen",
"Paste": "Plakken",
"Paste and Match Style": "Plak en match stijl",
"Quit": "ophouden",
"Redo": "Opnieuw doen",
"Reload": "vernieuwen",
"Report an issue...": "Een probleem melden...",
"Reset App Settings": "App-instellingen opnieuw instellen",
"Select All": "Selecteer alles",
"Show App Logs": "App-logs weergeven",
"Toggle DevTools for Active Tab": "DevTools voor actieve tabblad omschakelen",
"Toggle DevTools for Zulip App": "DevTools voor Zulip-app omschakelen",
"Toggle Full Screen": "Volledig scherm activeren",
"Toggle Sidebar": "Zijbalk verschuiven",
"Toggle Tray Icon": "Pictogram Lade wisselen",
"Undo": "ongedaan maken",
"View": "Uitzicht",
"Window": "Venster",
"Zoom In": "In zoomen",
"Zoom Out": "Uitzoomen",
"Zulip Help": "Zulip Help"
}

39
app/translations/pl.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "O Zulip",
"Actual Size": "Rzeczywisty rozmiar",
"Back": "Plecy",
"Close": "Blisko",
"Copy": "Kopiuj",
"Cut": "Ciąć",
"Delete": "Kasować",
"Desktop App Settings": "Ustawienia aplikacji na komputer",
"Edit": "Edytować",
"File": "Plik",
"Forward": "Naprzód",
"Hard Reload": "Trudne przeładowanie",
"Help": "Wsparcie",
"History": "Historia",
"Keyboard Shortcuts": "Skróty klawiszowe",
"Log Out": "Wyloguj",
"Minimize": "Zminimalizować",
"Paste": "Pasta",
"Paste and Match Style": "Wklej i dopasuj styl",
"Quit": "Porzucić",
"Redo": "Przerobić",
"Reload": "Przeładować",
"Report an issue...": "Zgłoś problem...",
"Reset App Settings": "Zresetuj ustawienia aplikacji",
"Select All": "Zaznacz wszystko",
"Show App Logs": "Pokaż dzienniki aplikacji",
"Toggle DevTools for Active Tab": "Przełącz DevTools dla Active Tab",
"Toggle DevTools for Zulip App": "Przełącz DevTools dla aplikacji Zulip",
"Toggle Full Screen": "Przełącz tryb pełnoekranowy",
"Toggle Sidebar": "Przełącz boczny pasek",
"Toggle Tray Icon": "Przełącz ikonę tacy",
"Undo": "Cofnij",
"View": "Widok",
"Window": "Okno",
"Zoom In": "Zbliżenie",
"Zoom Out": "Pomniejsz",
"Zulip Help": "Zulip Help"
}

39
app/translations/pt.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "Sobre Zulip",
"Actual Size": "Tamanho atual",
"Back": "Costas",
"Close": "Fechar",
"Copy": "cópia de",
"Cut": "Cortar",
"Delete": "Excluir",
"Desktop App Settings": "Configurações da aplicação Desktop",
"Edit": "Editar",
"File": "Arquivo",
"Forward": "frente",
"Hard Reload": "Hard Recargar",
"Help": "Socorro",
"History": "História",
"Keyboard Shortcuts": "Atalhos do teclado",
"Log Out": "Sair",
"Minimize": "Minimizar",
"Paste": "Colar",
"Paste and Match Style": "Estilo de colar e combinar",
"Quit": "Sair",
"Redo": "Refazer",
"Reload": "recarregar",
"Report an issue...": "Relatar um problema ...",
"Reset App Settings": "Redefinir as configurações da aplicação",
"Select All": "Selecionar tudo",
"Show App Logs": "Mostrar logs de aplicativos",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle Full Screen": "Alternar para o modo tela cheia",
"Toggle Sidebar": "Toggle Sidebar",
"Toggle Tray Icon": "Ícone Toggle Tray",
"Undo": "Desfazer",
"View": "Visão",
"Window": "Janela",
"Zoom In": "Mais Zoom",
"Zoom Out": "Reduzir o zoom",
"Zulip Help": "Zulip Help"
}

39
app/translations/ru.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "О пользователе Zulip",
"Actual Size": "Фактический размер",
"Back": "назад",
"Close": "Закрыть",
"Copy": "копия",
"Cut": "Порез",
"Delete": "Удалить",
"Desktop App Settings": "Настройки настольных приложений",
"Edit": "редактировать",
"File": "файл",
"Forward": "Вперед",
"Hard Reload": "Hard Reload",
"Help": "Помогите",
"History": "история",
"Keyboard Shortcuts": "Горячие клавиши",
"Log Out": "Выйти",
"Minimize": "Минимизировать",
"Paste": "Вставить",
"Paste and Match Style": "Стиль вставки и совпадения",
"Quit": "Уволиться",
"Redo": "переделывать",
"Reload": "перезагружать",
"Report an issue...": "Сообщить о проблеме...",
"Reset App Settings": "Сбросить настройки приложения",
"Select All": "Выбрать все",
"Show App Logs": "Показать журналы приложений",
"Toggle DevTools for Active Tab": "Toggle DevTools для активной вкладки",
"Toggle DevTools for Zulip App": "Toggle DevTools для приложения Zulip",
"Toggle Full Screen": "Включить полноэкранный режим",
"Toggle Sidebar": "Переключить боковую панель",
"Toggle Tray Icon": "Иконка Toggle Tray",
"Undo": "расстегивать",
"View": "Посмотреть",
"Window": "Окно",
"Zoom In": "Приблизить",
"Zoom Out": "Уменьшить",
"Zulip Help": "Zulip Help"
}

39
app/translations/sr.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "О Зулипу",
"Actual Size": "Стварна величина",
"Back": "Назад",
"Close": "Близу",
"Copy": "Копирај",
"Cut": "Цут",
"Delete": "Избриши",
"Desktop App Settings": "Подешавања за десктоп апликације",
"Edit": "Уредити",
"File": "Филе",
"Forward": "Напријед",
"Hard Reload": "Хард Релоад",
"Help": "Помоћ",
"History": "Историја",
"Keyboard Shortcuts": "Пречице на тастатури",
"Log Out": "Одјавити се",
"Minimize": "Минимизирај",
"Paste": "Пасте",
"Paste and Match Style": "Пасте и Матцх Стиле",
"Quit": "Одустати",
"Redo": "Поново",
"Reload": "Освежи",
"Report an issue...": "Пријавите проблем...",
"Reset App Settings": "Поново поставите подешавања апликације",
"Select All": "Изабери све",
"Show App Logs": "Прикажи евиденције апликација",
"Toggle DevTools for Active Tab": "Пребаците ДевТоолс за Ацтиве Таб",
"Toggle DevTools for Zulip App": "Пребаците ДевТоолс за Зулип Апп",
"Toggle Full Screen": "Пребаците цео екран",
"Toggle Sidebar": "Пребаците бочну траку",
"Toggle Tray Icon": "Тоггле Траи Ицон",
"Undo": "Ундо",
"View": "Поглед",
"Window": "Прозор",
"Zoom In": "Увеличати",
"Zoom Out": "Зумирај",
"Zulip Help": "Зулип Хелп"
}

39
app/translations/ta.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "ஜுலிப் பற்றி",
"Actual Size": "உண்மையான அளவு",
"Back": "மீண்டும்",
"Close": "நெருக்கமான",
"Copy": "நகல்",
"Cut": "வெட்டு",
"Delete": "அழி",
"Desktop App Settings": "டெஸ்க்டாப் பயன்பாட்டு அமைப்புகள்",
"Edit": "தொகு",
"File": "கோப்பு",
"Forward": "முன்னோக்கி",
"Hard Reload": "கடினமாக மீண்டும் ஏற்றவும்",
"Help": "உதவி",
"History": "வரலாறு",
"Keyboard Shortcuts": "விசைப்பலகை குறுக்குவழிகள்",
"Log Out": "வெளியேறு",
"Minimize": "குறைத்தல்",
"Paste": "ஒட்டு",
"Paste and Match Style": "உடை ஒட்டு மற்றும் பொருந்தும்",
"Quit": "விட்டுவிட",
"Redo": "மீண்டும் செய்",
"Reload": "ஏற்றவும்",
"Report an issue...": "சிக்கலைப் புகார ...",
"Reset App Settings": "பயன்பாட்டு அமைப்புகளை மீட்டமைக்கவும்",
"Select All": "அனைத்தையும் தெரிவுசெய்",
"Show App Logs": "பயன்பாட்டு பதிவுகள் காட்டு",
"Toggle DevTools for Active Tab": "செயலில் தாவலுக்கு DevTools ஐ மாற்று",
"Toggle DevTools for Zulip App": "Zulip பயன்பாட்டிற்கான DevTools ஐ மாற்று",
"Toggle Full Screen": "மாற்று முழுத்திரை",
"Toggle Sidebar": "பக்கப்பட்டி மாறு",
"Toggle Tray Icon": "ட்ரே ஐகானை மாற்று",
"Undo": "செயல்தவிர்",
"View": "காண்க",
"Window": "ஜன்னல்",
"Zoom In": "பெரிதாக்க",
"Zoom Out": "பெரிதாக்குக",
"Zulip Help": "Zulip உதவி"
}

39
app/translations/tr.json Normal file
View File

@@ -0,0 +1,39 @@
{
"About Zulip": "Zulip Hakkinda",
"Actual Size": "Gerçek boyutu",
"Back": "Geri",
"Close": "Kapat",
"Copy": "kopya",
"Cut": "Kesmek",
"Delete": "silmek",
"Desktop App Settings": "Masaüstü Uygulama Ayarları",
"Edit": "Düzenle",
"File": "Dosya",
"Forward": "ileri",
"Hard Reload": "Sert Yeniden Yükle",
"Help": "yardım et",
"History": "Tarihçe",
"Keyboard Shortcuts": "Klavye kısayolları",
"Log Out": ıkış Yap",
"Minimize": "küçültmek",
"Paste": "Yapıştırmak",
"Paste and Match Style": "Stili Yapıştır ve Eşleştir",
"Quit": ıkmak",
"Redo": "yeniden yapmak",
"Reload": "Tekrar yükle",
"Report an issue...": "Bir sorun bildir ...",
"Reset App Settings": "Uygulama Ayarlarını Sıfırla",
"Select All": "Hepsini seç",
"Show App Logs": "Uygulama Günlüğlerini Göster",
"Toggle DevTools for Active Tab": "Aktif Sekme İçin DevTools'a Geçiş Yap",
"Toggle DevTools for Zulip App": "Zulip App için DevTools'a Geçiş Yap",
"Toggle Full Screen": "Tam ekrana geç",
"Toggle Sidebar": "Kenar Çubuğunu Aç / Kapat",
"Toggle Tray Icon": "Tepsi Simgesini Aç / Kapat",
"Undo": "Geri alma",
"View": "Görünüm",
"Window": "pencere",
"Zoom In": "Yakınlaştır",
"Zoom Out": "Uzaklaştır",
"Zulip Help": "Zulip Yardımı"
}

View File

@@ -17,7 +17,8 @@ install:
- npm install
- npm install -g gulp
build: off
build: off
test_script:
- npm run test
- npm run test-e2e

View File

@@ -15,6 +15,7 @@ To build and run the app from source, you'll need the following:
* [Python](https://www.python.org/downloads/release/python-2713/)
(v2.7.x recommended)
* A C++ compiler compatible with C++11
* Linux users also need [Snapcraft](https://snapcraft.io/)
* Development headers for the libXext, libXtst, and libxkbfile libraries
### Debian/Ubuntu and friends
@@ -25,7 +26,7 @@ manager (see [here][nodesource-install] for more on the first command):
```sh
$ curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
$ sudo apt install git nodejs python build-essential libxext-dev libxtst-dev libxkbfile-dev
$ 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.

View File

@@ -1,9 +1,10 @@
'use strict';
const gulp = require('gulp');
const mocha = require('gulp-mocha');
const electron = require('electron-connect').server.create({
verbose: true
});
const tape = require('gulp-tape');
const tapColorize = require('tap-colorize');
gulp.task('dev', () => {
// Start browser process
@@ -13,7 +14,7 @@ gulp.task('dev', () => {
// Reload renderer process
gulp.watch('app/renderer/css/*.css', ['reload:renderer']);
gulp.watch('app/renderer/*.html', ['reload:renderer']);
gulp.watch('app/renderer/js/*.js', ['reload:renderer']);
gulp.watch('app/renderer/js/**/*.js', ['reload:renderer']);
});
gulp.task('restart:browser', done => {
@@ -28,9 +29,11 @@ gulp.task('reload:renderer', done => {
done();
});
// Test app using mocha+spectron
gulp.task('test', () => {
return gulp.src('tests/index.js').pipe(mocha());
gulp.task('test-e2e', () => {
return gulp.src('tests/*.js')
.pipe(tape({
reporter: tapColorize()
}));
});
gulp.task('default', ['dev', 'test']);
gulp.task('default', ['dev', 'test-e2e']);

10607
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,14 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "1.5.0",
"version": "2.0.0",
"main": "./app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"email": "<svnitakash@gmail.com>",
"copyright": "©2017 Kandra Labs, Inc.",
"copyright": "Kandra Labs, Inc.",
"author": {
"name": "Kandra Labs, Inc.",
"email": "svnitakash@gmail.com"
"email": "support@zulipchat.com"
},
"repository": {
"type": "git",
@@ -18,16 +17,21 @@
"bugs": {
"url": "https://github.com/zulip/zulip-electron/issues"
},
"engines": {
"node": ">=6.0.0"
},
"scripts": {
"start": "electron app --disable-http-cache",
"reinstall": "rm -rf node_modules; rm -rf app/node_modules; npm install",
"start": "electron app --disable-http-cache --no-electron-connect",
"reinstall": "node ./tools/reinstall-node-modules.js",
"postinstall": "electron-builder install-app-deps",
"test": "xo",
"dev": "gulp dev",
"test-e2e": "gulp test-e2e",
"dev": "gulp dev & nodemon --watch app/main --watch app/renderer --exec 'npm test' -e html,css,js",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"mas": "electron-builder --mac mas",
"travis": "cd ./scripts && ./travis-build-test.sh"
"travis": "cd ./scripts && ./travis-build-test.sh",
"build-locales": "node tools/locale-helper"
},
"pre-commit": [
"test"
@@ -51,11 +55,17 @@
"target": [
"deb",
"zip",
"AppImage"
"AppImage",
"snap"
],
"maintainer": "Akash Nimare <svnitakash@gmail.com>"
},
"deb": {
"synopsis": "Zulip Desktop App",
"afterInstall": "./scripts/debian-add-repo.sh",
"afterRemove": "./scripts/debian-uninstaller.sh"
},
"snap": {
"synopsis": "Zulip Desktop App"
},
"dmg": {
@@ -105,17 +115,22 @@
],
"devDependencies": {
"assert": "1.4.1",
"cp-file": "^5.0.0",
"devtron": "1.4.0",
"electron-builder": "19.29.1",
"electron": "1.6.14",
"electron": "1.8.4",
"electron-builder": "20.8.1",
"electron-connect": "0.6.2",
"electron-debug": "1.4.0",
"google-translate-api": "2.3.0",
"gulp": "3.9.1",
"gulp-mocha": "4.3.1",
"chai-as-promised": "7.1.1",
"chai": "4.1.1",
"spectron": "3.7.2",
"xo": "0.18.2",
"pre-commit": "1.2.2"
"gulp-tape": "0.0.9",
"is-ci": "^1.0.10",
"nodemon": "^1.14.11",
"pre-commit": "1.2.2",
"spectron": "3.8.0",
"tap-colorize": "^1.2.0",
"tape": "^4.8.0",
"xo": "0.18.2"
},
"xo": {
"parserOptions": {
@@ -131,7 +146,11 @@
"rules": {
"max-lines": [
"warn",
500
{
"max": 500,
"skipBlankLines": true,
"skipComments": true
}
],
"no-warning-comments": 0,
"object-curly-spacing": 0,
@@ -148,7 +167,8 @@
}
],
"ignore": [
"tests/*.js"
"tests/*.js",
"tools/locale-helper/*.js"
],
"envs": [
"node",

View File

@@ -0,0 +1,14 @@
#!/bin/bash
# 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
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
echo "deb https://dl.bintray.com/zulip/debian/ stable main" | \
sudo tee -a /etc/apt/sources.list.d/zulip.list;
fi

View File

@@ -0,0 +1,35 @@
#!/bin/bash
# This script runs when user uninstall the debian package.
# It will remove all the config files and anything which was added by the app.
# Remove apt repository source list when user uninstalls Zulip app
if grep ^ /etc/apt/sources.list /etc/apt/sources.list.d/* | grep zulip.list; then
sudo apt-key del 69AD12704E71A4803DCA3A682424BE5AE9BD10D9;
sudo rm /etc/apt/sources.list.d/zulip.list;
fi
# Get the root user
if [ $SUDO_USER ];
then getSudoUser=$SUDO_USER;
else getSudoUser=`whoami`;
fi
# Get the path for Zulip's desktop entry which is created by auto-launch script
getDesktopEntry=/home/$getSudoUser/.config/autostart/zulip.desktop;
# Remove desktop entry if exists
if [ -f $getDesktopEntry ]; then
sudo rm $getDesktopEntry;
fi
# App directory which contains all the config, setting files
appDirectory=/home/$getSudoUser/.config/Zulip/;
if [ -d $appDirectory ]; then
sudo rm -rf $appDirectory;
fi
# Delete the link to the binary
echo 'Removing binary link'
sudo rm -f '/usr/local/bin/${executable}';

View File

@@ -1,10 +1,20 @@
#!/usr/bin/env bash
# exit script if fails
set -e;
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
export {no_proxy,NO_PROXY}="127.0.0.1,localhost"
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
sleep 3
export {no_proxy,NO_PROXY}="127.0.0.1,localhost"
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
sleep 3
echo 'Travis Screen Resolution:'
xdpyinfo | grep dimensions
fi
npm run test
if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then
npm run test-e2e
fi

5
scripts/travis-xvfb.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
/sbin/start-stop-daemon --start --quiet --pidfile /tmp/custom_xvfb_99.pid --make-pidfile --background --exec /usr/bin/Xvfb -- :99 -ac -screen 0 1280x1024x16
fi

37
snap/snapcraft.yaml Normal file
View File

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

7
tests/config.js Normal file
View File

@@ -0,0 +1,7 @@
const path = require('path')
const TEST_APP_PRODUCT_NAME = 'ZulipTest'
module.exports = {
TEST_APP_PRODUCT_NAME
}

View File

@@ -1,81 +1,13 @@
const assert = require('assert')
const Application = require('spectron').Application
const chai = require('chai')
const { expect } = chai
const chaiAsPromised = require('chai-as-promised')
chai.should()
chai.use(chaiAsPromised)
describe('application launch', function () {
this.timeout(15000)
beforeEach(function () {
this.app = new Application({
path: require('electron'),
args: [__dirname + '/../app/renderer/main.html']
})
return this.app.start()
})
beforeEach(function () {
chaiAsPromised.transferPromiseness = this.app.transferPromiseness
})
afterEach(function () {
if (this.app && this.app.isRunning()) {
return this.app.stop()
}
})
it('shows an initial window', function () {
return this.app.client.waitUntilWindowLoaded(5000)
.getWindowCount().should.eventually.equal(2)
.browserWindow.isMinimized().should.eventually.be.false
.browserWindow.isDevToolsOpened().should.eventually.be.false
.browserWindow.isVisible().should.eventually.be.true
.browserWindow.isFocused().should.eventually.be.true
.browserWindow.getBounds().should.eventually.have.property('width').and.be.above(0)
.browserWindow.getBounds().should.eventually.have.property('height').and.be.above(0)
})
it('sets up a default organization', function () {
let app = this.app
let self = this
app.client.execute(() => {
window.confirm = function () { return true }
})
function createOrg (client, name, url, winIndex) {
return client
// Focus on settings webview
.then(switchToWebviewAtIndex.bind(null, self.app.client, winIndex))
.pause(1000) // wait for settings to load
// Fill settings form
.click('#new-server-action')
.setValue('input[id="server-info-name"]', name)
.setValue('input[id="server-info-url"]', url)
.click('#save-server-action')
.pause(500) // Need to pause while server verification takes place
.then(() => app.browserWindow.reload())
.pause(1500) // Wait for webview of org to load
}
function switchToWebviewAtIndex(client, index) {
return client
.windowHandles()
.then(function (session) {
this.window(session.value[index])
})
}
return this.app.client.waitUntilWindowLoaded(5000)
.then(() => createOrg(self.app.client, 'Zulip 1', 'chat.zulip.org', 1))
.then(switchToWebviewAtIndex.bind(null, self.app.client, 0))
.click('#add-action > i').pause(500)
.then(switchToWebviewAtIndex.bind(null, self.app.client, 2))
.then(() => createOrg(self.app.client, 'Zulip 2', 'chat.zulip.org', 2))
})
})
const test = require('tape')
const setup = require('./setup')
test('app runs', function (t) {
t.timeoutAfter(10e3)
setup.resetTestDataDir()
const app = setup.createApp()
setup.waitForLoad(app, t)
.then(() => app.client.windowByIndex(1)) // focus on webview
.then(() => app.client.waitForExist('//*[@id="connect"]')) // id of the connect button
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})

99
tests/setup.js Normal file
View File

@@ -0,0 +1,99 @@
const Application = require('spectron').Application
const cpFile = require('cp-file')
const fs = require('fs')
const isCI = require('is-ci')
const mkdirp = require('mkdirp')
const path = require('path')
const rimraf = require('rimraf')
const config = require('./config')
module.exports = {
createApp,
endTest,
waitForLoad,
wait,
resetTestDataDir
}
// Runs Zulip Desktop.
// Returns a promise that resolves to a Spectron Application once the app has loaded.
// Takes a Tape test. Makes some basic assertions to verify that the app loaded correctly.
function createApp (t) {
generateTestAppPackageJson()
return new Application({
path: path.join(__dirname, '..', 'node_modules', '.bin',
'electron' + (process.platform === 'win32' ? '.cmd' : '')),
args: [path.join(__dirname)], // Ensure this dir has a package.json file with a 'main' entry piont
env: {NODE_ENV: 'test'},
waitTimeout: 10e3
})
}
// Generates package.json for test app
// Reads app package.json and updates the productName to config.TEST_APP_PRODUCT_NAME
// We do this so that the app integration doesn't doesn't share the same appDataDir as the dev application
function generateTestAppPackageJson () {
let packageJson = require(path.join(__dirname, '../package.json'))
packageJson.productName = config.TEST_APP_PRODUCT_NAME
packageJson.main = '../app/main'
const testPackageJsonPath = path.join(__dirname, 'package.json')
fs.writeFileSync(testPackageJsonPath, JSON.stringify(packageJson, null, ' '), 'utf-8')
}
// Starts the app, waits for it to load, returns a promise
function waitForLoad (app, t, opts) {
if (!opts) opts = {}
return app.start().then(function () {
return app.client.waitUntilWindowLoaded()
})
.then(function() {
return app.client.pause(2000);
})
.then(function () {
return app.webContents.getTitle()
}).then(function (title) {
t.equal(title, 'Zulip', 'html title')
})
}
// Returns a promise that resolves after 'ms' milliseconds. Default: 1 second
function wait (ms) {
if (ms === undefined) ms = 1000 // Default: wait long enough for the UI to update
return new Promise(function (resolve, reject) {
setTimeout(resolve, ms)
})
}
// Quit the app, end the test, either in success (!err) or failure (err)
function endTest (app, t, err) {
return app.stop().then(function () {
t.end(err)
})
}
function getAppDataDir () {
let base
if (process.platform === 'darwin') {
base = path.join(process.env.HOME, 'Library', 'Application Support')
} else if (process.platform === 'linux') {
base = process.env.XDG_CONFIG_HOME ?
process.env.XDG_CONFIG_HOME : path.join(process.env.HOME, '.config')
} else if (process.platform === 'win32') {
base = process.env.APPDATA
} else {
console.log('Could not detect app data dir base. Exiting...')
process.exit(1)
}
console.log('Detected App Data Dir base:', base)
return path.join(base, config.TEST_APP_PRODUCT_NAME)
}
// Resets the test directory, containing domain.json, window-state.json, etc
function resetTestDataDir () {
appDataDir = getAppDataDir()
rimraf.sync(appDataDir)
rimraf.sync(path.join(__dirname, 'package.json'))
}

View File

@@ -0,0 +1,19 @@
const test = require('tape')
const setup = require('./setup')
test('add-organization', 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.setValue('.setting-input-value', 'chat.zulip.org'))
.then(() => app.client.click('.server-save-action'))
.then(() => setup.wait(5000))
.then(() => app.client.windowByIndex(0)) // Switch focus back to main win
.then(() => app.client.windowByIndex(1)) // Switch focus back to org webview
.then(() => app.client.waitForExist('//*[@id="id_username"]'))
.then(() => setup.endTest(app, t),
(err) => setup.endTest(app, t, err || 'error'))
})

View File

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

16
tools/fetch-pull-request Executable file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
set -e
set -x
if ! git diff-index --quiet HEAD; then
set +x
echo "There are uncommitted changes:"
git status --short
echo "Doing nothing to avoid losing your work."
exit 1
fi
request_id="$1"
remote=${2:-"upstream"}
git fetch "$remote" "pull/$request_id/head"
git checkout -B "review-original-${request_id}"
git reset --hard FETCH_HEAD

23
tools/fetch-pull-request.cmd Executable file
View File

@@ -0,0 +1,23 @@
@echo off
git diff-index --quiet HEAD
if %ERRORLEVEL% NEQ 0 (
echo "There are uncommitted changes:"
git status --short
echo "Doing nothing to avoid losing your work."
exit /B 1
)
if "%~1"=="" (
echo "Error you must specify the PR number"
)
if "%~2"=="" (
set remote="upstream"
) else (
set remote=%2
)
set request_id="%1"
git fetch "%remote%" "pull/%request_id%/head"
git checkout -B "review-%request_id%"
git reset --hard FETCH_HEAD

17
tools/fetch-rebase-pull-request Executable file
View File

@@ -0,0 +1,17 @@
#!/bin/bash
set -e
set -x
if ! git diff-index --quiet HEAD; then
set +x
echo "There are uncommitted changes:"
git status --short
echo "Doing nothing to avoid losing your work."
exit 1
fi
request_id="$1"
remote=${2:-"upstream"}
git fetch "$remote" "pull/$request_id/head"
git checkout -B "review-${request_id}" $remote/master
git reset --hard FETCH_HEAD
git pull --rebase

View File

@@ -0,0 +1,24 @@
@echo off
git diff-index --quiet HEAD
if %errorlevel% neq 0 (
echo "There are uncommitted changes:"
git status --short
echo "Doing nothing to avoid losing your work."
exit \B 1
)
if "%~1"=="" (
echo "Error you must specify the PR number"
)
if "%~2"=="" (
set remote="upstream"
) else (
set remote=%2
)
set request_id="%1"
git fetch "%remote%" "pull/%request_id%/head"
git checkout -B "review-%request_id%" %remote%/master
git reset --hard FETCH_HEAD
git pull --rebase

View File

@@ -0,0 +1,30 @@
const translate = require('google-translate-api');
const path = require('path');
const fs = require('fs');
const translationDir = path.resolve(__dirname, '../../app/translations');
function writeJSON(file, data) {
const filePath = path.resolve(translationDir, file);
fs.writeFileSync(filePath, `${JSON.stringify(data, null, '\t')}\n`, 'utf8');
}
const { phrases } = require('./locale-template');
const supportedLocales = require('./supported-locales');
phrases.sort();
for (let locale in supportedLocales) {
console.log(`fetching translation for: ${supportedLocales[locale]} - ${locale}..`);
translate(phrases.join('\n'), { to: locale })
.then(res => {
const localeFile = `${locale}.json`;
const translatedText = res.text.split('\n');
const translationJSON = {};
phrases.forEach((phrase, index) => {
translationJSON[phrase] = translatedText[index];
});
writeJSON(localeFile, translationJSON);
console.log(`create: ${localeFile}`);
});
}

View File

@@ -0,0 +1,41 @@
{
"phrases": [
"About Zulip",
"Actual Size",
"Back",
"Close",
"Copy",
"Cut",
"Delete",
"Desktop App Settings",
"Edit",
"File",
"Forward",
"Hard Reload",
"Help",
"History",
"Keyboard Shortcuts",
"Log Out",
"Minimize",
"Paste",
"Paste and Match Style",
"Quit",
"Redo",
"Reload",
"Report an issue...",
"Reset App Settings",
"Select All",
"Show App Logs",
"Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App",
"Toggle Full Screen",
"Toggle Sidebar",
"Toggle Tray Icon",
"Undo",
"View",
"Window",
"Zoom In",
"Zoom Out",
"Zulip Help"
]
}

View File

@@ -0,0 +1,21 @@
module.exports = {
"de": "Deutsch",
"pl": "polski",
"en": "English",
"cs": "česky",
"hi": "Hindi",
"ru": "Русский",
"id": "Indonesia",
"bg": "български",
"hu": "Magyar",
"sr": "српски",
"ml": "Malayalam",
"ta": "தமிழ்",
"nl": "Nederlands",
"ja": "日本語",
"pt": "Português",
"tr": "Türkçe",
"es": "español",
"ko": "한국어",
"fr": "français"
};

10
tools/reinstall-node-modules Executable file
View File

@@ -0,0 +1,10 @@
#!/bin/bash
set -e
set -x
echo "Removing node_modules and app/node_modules"
rm -rf node_modules
rm -rf app/node_modules
echo "node_modules removed reinstalling npm packages"
npm i

View File

@@ -0,0 +1,8 @@
@echo off
echo "Removing node_modules and app/node_modules"
rmdir /s /q node_modules
rmdir /s /q app/node_modules
echo "node_modules removed reinstalling npm packages"
npm i

View File

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

Some files were not shown because too many files have changed in this diff Show More