Compare commits

..

420 Commits

Author SHA1 Message Date
akashnimare
3ed253d2e1 🎉 v1.4.0 2017-09-04 20:12:58 +05:30
akashnimare
5f672fe404 updated dependencies 2017-09-04 20:04:02 +05:30
akashnimare
10372787ac updated dependencies 2017-08-31 01:52:23 +05:30
Akash Nimare
68acf2ec64 Merge pull request #282 from zulip/auto-launch
Added auto launch Zulip on system startup setting option #192
2017-08-29 03:32:59 +05:30
akashnimare
afd24035f4 Don't run start at login script in dev mode 2017-08-29 03:28:07 +05:30
akashnimare
4f28f6b935 Add open at login setting [WIP] 2017-08-29 01:47:19 +05:30
akashnimare
52bd600690 Detailed certificate warning message #126 2017-08-28 23:50:15 +05:30
Akash Nimare
c7ce2a8a99 updated placeholder text
organization >> server
2017-08-28 20:19:36 +05:30
akashnimare
9760b1bf98 Add start app at login template 2017-08-28 17:11:27 +05:30
akashnimare
d579c267f0 code refactor 2017-08-28 16:34:33 +05:30
Akash Nimare
21f01d268a Merge pull request #280 from zulip/badge-setting
Add an option to show/hide badges and taskbar icons #192
2017-08-27 05:13:19 +05:30
akashnimare
33782e0492 disable text-selection in setting page 2017-08-27 05:00:18 +05:30
akashnimare
8d20568b7a Show badge on app load by default 2017-08-27 04:50:44 +05:30
akashnimare
06f38e92ce refactor code 2017-08-27 04:28:03 +05:30
akashnimare
855e96e40e refactoring code for badge/taskbar icon [WIP] 2017-08-27 03:58:08 +05:30
akashnimare
bb68720ab7 add badge-setting constructor [WIP] 2017-08-27 01:50:13 +05:30
akashnimare
044d007a0c Hide/Show badge setting option [WIP] 2017-08-26 05:28:59 +05:30
akashnimare
4ea95fe8e8 Relaunch app when new updated has been downloaded fixes #279 2017-08-26 03:18:06 +05:30
Akash Nimare
d17c685e4d Document windows portable installer fixes #148 2017-08-25 18:26:56 +05:30
Akash Nimare
bb174da59a Merge pull request #278 from geeeeeeeeek/feature/connect-through-proxy
Feature/connect through proxy
2017-08-25 18:15:36 +05:30
Akash Nimare
a028b80adb Add proxy rules examples 2017-08-25 18:08:39 +05:30
akashnimare
3683511e60 updated windows build config 2017-08-24 05:08:14 +05:30
Akash Nimare
bf359db7f4 Updated download instructions 2017-08-24 04:53:43 +05:30
Akash Nimare
949d786f1f installation instructions [WIP] 2017-08-24 04:49:25 +05:30
Zhongyi Tong
5cd0c1ca4e Do not show alert after turning off the proxy. 2017-08-21 11:10:01 -04:00
Zhongyi Tong
86674991c1 Fix incorrect base component. 2017-08-21 11:06:59 -04:00
Zhongyi Tong
8492cda092 Add placeholder for proxy inputs. 2017-08-20 23:04:40 -04:00
Zhongyi Tong
647303c001 Implement proxy. 2017-08-20 23:00:50 -04:00
Zhongyi Tong
fe34f8adad Finish setting section for proxy. 2017-08-20 21:59:31 -04:00
Zhongyi Tong
f142a2eb4e Move help.md outside app folder. 2017-08-20 16:59:48 -04:00
Zhongyi Tong
43eaa3dd63 Initialize user guide. 2017-08-20 16:58:31 -04:00
Zhongyi Tong
a3142713f7 Initialize setting section for proxy. 2017-08-20 16:35:39 -04:00
Zhongyi Tong
876936125a Refactor general setting section. 2017-08-20 16:00:10 -04:00
Zhongyi Tong
7261fb6cef Update settings layout. 2017-08-20 15:21:13 -04:00
akashnimare
fba52e6dff code refactoring 2017-08-20 03:51:31 +05:30
akashnimare
23ac347fb9 Additional app categories [Linux] 2017-08-19 20:52:14 +05:30
akashnimare
082aebb1e0 Add app category [Linux] fixes #277 2017-08-19 20:49:44 +05:30
akashnimare
3b18dc4df0 improve auto-updates logging
From now on, all the logs will be saved within the Zulip application directory.
2017-08-19 02:21:50 +05:30
akashnimare
39d30b92fa Fixing beta updates [WIP] #269 2017-08-18 20:47:33 +05:30
Akash Nimare
733209e04e Merge pull request #274 from geeeeeeeeek/feature/load-all-tabs-on-startup
Load all servers on app start-up.
2017-08-18 15:25:59 +05:30
Zhongyi Tong
a68d697fc5 Fix linting. 2017-08-17 14:16:12 -04:00
Zhongyi Tong
e52ece57df Load all servers on app start-up. 2017-08-17 14:14:23 -04:00
akashnimare
65681c7da9 Add pre-commit test 2017-08-17 16:40:20 +05:30
akashnimare
52b6209905 Allow spacing between curly braces [XO Linter] 2017-08-17 16:27:34 +05:30
akashnimare
0e37925418 Handle crash events 2017-08-17 16:23:18 +05:30
Akash Nimare
1a31d2e431 Merge pull request #273 from geeeeeeeeek/issue/new-server-icon-cache
Issue/new server icon cache
2017-08-16 23:52:58 +05:30
Zhongyi Tong
c2fab2c7bc Fix duplicate checking. 2017-08-16 13:22:59 -04:00
Zhongyi Tong
965b55ba5f Fix regression of avoiding duplicated servers. 2017-08-16 12:48:31 -04:00
akashnimare
2c7bb36f8e Submit new server form when Enter key is pressed 2017-08-16 04:48:41 +05:30
Zhongyi Tong
48b829b771 Lazy update server settings on reload. 2017-08-15 17:45:38 -04:00
Zhongyi Tong
f13c28db73 Generate stored icon path using url hash. 2017-08-15 17:42:40 -04:00
Akash Nimare
19157c77e4 Merge pull request #272 from Anshuman-Verma/es6-index.js
Use ES6 destructuring for grabbing elements
2017-08-15 00:10:51 +05:30
Akash Nimare
161cd80d38 fixed linting errors 2017-08-14 23:07:17 +05:30
Anshuman
65db4c8f39 Use ES6 destructuring for grabbing elements 2017-08-13 23:04:48 +05:30
akashnimare
d600d3a6ed clear autoupdate dialog message 2017-08-12 17:38:50 +05:30
akashnimare
5fdd971966 Check for duplicate domains 2017-08-11 03:26:34 +05:30
Akash Nimare
d77d39d6ad Merge pull request #270 from geeeeeeeeek/issue/refine-add-server
Issue/refine add server
2017-08-10 15:54:45 +05:30
Akash Nimare
bf35db26bd fixed linting errors 2017-08-10 15:44:25 +05:30
Zhongyi Tong
4d8c8e08a0 Shorten loading animation of functional tabs. 2017-08-10 13:04:07 +08:00
Zhongyi Tong
b34681ece8 Show new-server form by default. 2017-08-10 13:00:25 +08:00
akashnimare
58f97038b1 🎉 v1.3.0-beta 2017-08-09 23:30:04 +05:30
akashnimare
6030d4c62a Updated build configuration 2017-08-09 23:27:24 +05:30
Akash Nimare
206f726e4a Merge pull request #268 from zulip/dev
Updated dev and app dependencies
2017-08-09 17:43:15 +00:00
akashnimare
9a36fffaac Updated app dependencies 2017-08-09 02:17:32 +05:30
akashnimare
30af4277e0 Updated devDependencies 2017-08-09 02:10:53 +05:30
akashnimare
98d23aaae1 document focus event 2017-08-08 23:51:04 +05:30
Akash Nimare
20feb9bd38 Merge pull request #266 from zulip/window-focus-fix
🎉 Focus webview contents on Window focus #216 #251
2017-08-08 18:07:17 +00:00
Akash Nimare
56090151c2 Merge pull request #267 from geeeeeeeeek/window-focus-fix
Enhance error handling.
2017-08-08 18:06:11 +00:00
Zhongyi Tong
84a69ce455 Enhance error handling. 2017-08-09 01:56:00 +08:00
akashnimare
2424f7a995 Focus webview contents on Window focus #216 #251 2017-08-08 22:13:03 +05:30
Akash Nimare
c3c60c98d6 Focus Webview elements on Linux/Windows #251 2017-08-07 15:51:56 +05:30
Akash Nimare
24017631c0 Merge pull request #265 from geeeeeeeeek/feature/switch-servers-in-menu
Feature/switch servers in menu
2017-08-06 12:29:29 +00:00
Akash Nimare
82a9d13a3c Add back History menu 2017-08-06 17:37:34 +05:30
Akash Nimare
c064322234 Added History submenu on Linux + Window 2017-08-06 17:30:33 +05:30
Zhongyi Tong
32542d500a Add tabs in window submenu. 2017-08-06 11:52:09 +08:00
akashnimare
5e8a971789 Do not show icon in new server form [WIP] #264 2017-08-06 01:01:48 +05:30
Zhongyi Tong
a7a9e96a58 Set application menu in AppMenu.setMenu instead of in main.js. 2017-08-06 00:25:56 +08:00
Zhongyi Tong
347f6e50eb Update require. 2017-08-06 00:21:49 +08:00
Zhongyi Tong
b873b358fe Refactor Menu.js. 2017-08-06 00:19:35 +08:00
akashnimare
caf4545902 focus new server input on page load [WIP] #264 2017-08-05 03:40:46 +05:30
Akash Nimare
9c2b7eeb27 Merge pull request #262 from geeeeeeeeek/issue/view-server-in-settings
Click server name in settings to show webview.
2017-08-04 08:44:05 +00:00
Zhongyi Tong
d25d71cb91 Click server name in settings to show webview. 2017-08-04 13:04:43 +08:00
Akash Nimare
289417e5a9 Added few common build errors 2017-08-03 03:34:18 +05:30
Akash Nimare
fd549e44a6 Merge pull request #261 from brockwhittaker/capitalize-menu-items
darwin-menu: Capitalize menu items to style match.
2017-08-02 21:54:44 +00:00
brockwhittaker
ae1ffe7ccc darwin-menu: Capitalize menu items to style match.
This capitalizes the menu items to be consistent with how all other
menu items are capitalized on almost all applications across Mac native
menus.
2017-08-02 14:46:38 -07:00
akashnimare
a04e14545e Remove unused dependency https 2017-08-03 02:46:28 +05:30
Akash Nimare
9b928a16b7 Merge pull request #259 from zulip/restore-window-position
restore window position [WIP] #231
2017-08-02 20:44:22 +00:00
akashnimare
b54cedbdbb restore window position [WIP] #231 2017-08-03 02:12:45 +05:30
Akash Nimare
3f9ba0a2bb Merge pull request #252 from gnprice/setup
Clarify and expand the developer docs a bit
2017-08-02 19:36:33 +00:00
Akash Nimare
9e8ec3b6d4 Merge pull request #256 from geeeeeeeeek/issue/delay-webview-show-up
Delay webview fade-in.
2017-08-02 19:08:00 +00:00
Akash Nimare
c3072854fd Merge pull request #258 from zulip/fullscreen-exit-hotkey
notify exit fullscreen shortcut on entering fullscreen mode #247
2017-08-02 19:00:47 +00:00
akashnimare
197e9a0520 styling popup 2017-08-03 00:13:52 +05:30
Akash Nimare
ff84792374 Change font 2017-08-02 22:27:25 +05:30
akashnimare
a0ae29b34a notify exit fullscreen shortcut on entering fullscreen mode #247 2017-08-02 22:15:47 +05:30
Zhongyi Tong
f880683d9c Delay webview fade-in. 2017-08-02 22:56:24 +08:00
Akash Nimare
d80b4a813c setting up Wiki page [WIP] 2017-08-02 18:32:51 +05:30
Akash Nimare
3567f6be6c Setting up wiki page 2017-08-02 18:31:45 +05:30
Akash Nimare
03358b1e50 Added footer in wiki page 2017-08-02 18:04:16 +05:30
Akash Nimare
b098f9e616 Merge pull request #254 from geeeeeeeeek/issue/capitalize-issue/capitalize-menu-items
Capitalize menu items.
2017-08-02 12:27:56 +00:00
Zhongyi Tong
d2fc5bd5e8 Capitalize menu items. 2017-08-02 10:52:35 +08:00
akashnimare
f5e15e3c85 Quit launcher script + app on ctrl+c fixes #253 2017-08-02 04:25:35 +05:30
Greg Price
f7002ecdf3 docs: Briefly describe compiler requirements. 2017-08-01 14:28:54 -07:00
Greg Price
48e5396092 docs: More details about dist output, and small grammar fixes. 2017-08-01 14:24:44 -07:00
akashnimare
6901b5f128 Don't hide titlebar, setting it to default 2017-08-02 02:25:29 +05:30
akashnimare
df0adb373d updated electron to v1.6.11 2017-08-02 02:06:45 +05:30
Greg Price
1c0cf148f8 docs: Revise dev install instructions, especially for Debian/Ubuntu.
This gives an explicit and hopefully-complete set of steps to run
the app from source on a Debian-based system: worked for me on my
Debian "stretch" (the recent release) laptop, though no guarantees
there wasn't some previously-installed package I was relying on.

Also make the generic prerequisites section include everything we
know about, including in particular everything mentioned in the
Debian/Ubuntu section.  Hopefully this is helpful for people working
on other OSes too -- though specific instructions for those would be
very helpful!
2017-08-01 13:06:38 -07:00
akashnimare
c644fa2778 updated gitignore 2017-08-02 00:40:31 +05:30
Akash Nimare
7962ccf19a Merge pull request #241 from geeeeeeeeek/issue/216-webview-focus
Add an os check for webview focus events.
2017-08-01 18:25:55 +00:00
Zhongyi Tong
3ad73a1eaa Explicit focus webview on macOS alone. 2017-08-02 02:23:35 +08:00
Zhongyi Tong
4445baafa9 Add an os check for webview focus events. 2017-08-02 02:07:00 +08:00
Akash Nimare
63cfcbbaf1 Merge pull request #240 from geeeeeeeeek/issue/sidebar-hidden-on-first-use
Set show-sidebar to true by default.
2017-08-01 17:37:07 +00:00
Zhongyi Tong
769972fc4b Set show-sidebar to true by default. 2017-08-02 01:27:45 +08:00
akashnimare
2c8cf2b959 Handle full-reload event properly 2017-08-01 00:52:40 +05:30
Akash Nimare
068fe249ea Merge pull request #236 from geeeeeeeeek/issue/fetch-server-details-from-zulip-api
Issue/fetch server details from zulip api
2017-07-31 18:32:32 +00:00
Akash Nimare
d8c08c1c5b Merge pull request #239 from zulip/windows-path-issue
Fixing server-icon path error [Windows] #181
2017-07-31 18:26:36 +00:00
Zhongyi Tong
56189806a9 Fix a bug that new server form responds to mouse events when hidden. 2017-07-31 18:51:43 +08:00
Zhongyi Tong
0f6e48c65f Remove customized fields of add a server. 2017-07-31 18:17:52 +08:00
akashnimare
fa50243dbb Fixing server-icon path error [Windows]
Since URL of background-image creates path issue on windows, I have implemented the same styling with img src tag which works fine on windows as well.
2017-07-30 17:33:43 +05:30
Akash Nimare
9e4e5e9bfd Merge pull request #237 from zulip/link-issue
Handle new-window event properly #204 #164
2017-07-28 09:38:35 +00:00
Zhongyi Tong
c413a65f07 Make ServerName optional. 2017-07-28 00:06:28 +08:00
Zhongyi Tong
53c91e890a Fetch server details from zulip api. 2017-07-27 23:50:04 +08:00
Akash Nimare
6e3017d5e7 Merge pull request #235 from geeeeeeeeek/issue/reload-app-for-debugging
Add 'Hard Reload' mode for debugging.
2017-07-27 08:52:50 +00:00
Zhongyi Tong
1e57daa8bf Remove background color of action buttons on hover. 2017-07-27 14:16:05 +08:00
Zhongyi Tong
f1ed6695fb Add 'Hard Reload' mode for debugging. 2017-07-27 13:13:17 +08:00
akashnimare
c5d9eceb6d fixing taskbar overlay icon messagecount and styling 2017-07-27 01:39:12 +05:30
Akash Nimare
f2a7ce188d Merge pull request #230 from geeeeeeeeek/feature/collapse-button
Hide/Toggle left sidebar
2017-07-26 18:37:00 +00:00
Akash Nimare
3dd9c89a0e Show sidebar initially
We should not hide it by default since users might not able to find it. Let's show it initially and let them decide.
2017-07-26 23:55:54 +05:30
Akash Nimare
697948c9d8 Merge pull request #219 from geeeeeeeeek/issue/tab-style
Issue/tab style
2017-07-26 15:35:53 +00:00
Zhongyi Tong
5150b7c57c Add menu item for sidebar toggle. 2017-07-26 18:19:08 +08:00
Zhongyi Tong
ce88da3de9 Initialize sidebar appearance from settings. 2017-07-26 18:05:32 +08:00
Zhongyi Tong
0617f41a2d Revert collapse button. 2017-07-26 17:56:58 +08:00
Zhongyi Tong
975bcbbe31 Add settings item for sidebar toggle. 2017-07-26 17:54:03 +08:00
Zhongyi Tong
e44af311ce Snap unread count bubble to the right. 2017-07-26 16:19:03 +08:00
akashnimare
297ada5565 better certificate error message 2017-07-26 01:12:27 +05:30
akashnimare
1fe64cb8d7 Handle new-window event properly #204 #164 2017-07-26 00:35:06 +05:30
Akash Nimare
6557a7d606 Merge pull request #234 from zulip/dev
use relative path for background-image [WIP] #181
2017-07-25 18:27:19 +00:00
akashnimare
9a0f6648ff use relative path for bg-image [WIP] #181 2017-07-23 03:07:20 +05:30
Akash Nimare
3cd4890e60 Merge pull request #233 from zulip/tray
Don't create tray on Windows + macOS
2017-07-22 16:13:25 +00:00
akashnimare
dc287e7e57 Added comment about creating tray on linux 2017-07-22 21:38:32 +05:30
akashnimare
ac0d998804 Linting 2017-07-22 21:19:48 +05:30
akashnimare
1d38ebdf05 Don't create tray form unread counts on Windows + macOS 2017-07-22 21:17:16 +05:30
akashnimare
d70783600d Removed unused checkConnectivity function 2017-07-22 21:04:25 +05:30
akashnimare
527196cdbb Allow only positive position of app [WIP] #231 2017-07-22 20:29:57 +05:30
akashnimare
ffeb960851 left sidebar styling [WIP] 2017-07-22 16:00:45 +05:30
Zhongyi Tong
a36b39ec73 Reduce action button's tooltip size. 2017-07-21 18:06:33 +08:00
Zhongyi Tong
2deb63b557 Add CollapseButton component. 2017-07-21 17:54:53 +08:00
Zhongyi Tong
fcd97f3a32 Add collapse button. 2017-07-21 17:37:28 +08:00
Akash Nimare
67dc1e2a11 Merge pull request #229 from zulip/taskbar-icon
🎉 Added taskbar overlay icon [Windows]
2017-07-21 02:08:59 +00:00
akashnimare
c37ae73392 🎉 Added taskbar overlay icon 2017-07-21 06:42:05 +05:30
akashnimare
9e667e3cb0 Added flashing the taskbar icon #167
on windows, taskbar icon will flash on incoming messages
2017-07-21 05:06:20 +05:30
akashnimare
505fea0e91 Added shortcut to reload full app
useful while debugging
2017-07-21 03:48:14 +05:30
Akash Nimare
70ff8db756 Merge pull request #228 from zulip/dev
Add browser css + hide footer and header links
2017-07-20 18:36:01 +00:00
akashnimare
d6975f7b2a hide header and footer from /login page
we don't want users to navigate these links through app
2017-07-20 23:51:45 +05:30
Zhongyi Tong
6cbea1acba Update style. 2017-07-21 00:45:00 +08:00
Zhongyi Tong
d4decfb6af Remove space in shortcut text on Linux and Windows. 2017-07-20 11:30:49 +08:00
akashnimare
5bbf710529 injecting css in webviews 2017-07-20 01:10:48 +05:30
Zhongyi Tong
b057bffe42 Change to . 2017-07-19 23:47:35 +08:00
akashnimare
4e04c85258 Added basic tooltip 2017-07-18 23:42:04 +05:30
Akash Nimare
bfbd9d4578 Merge pull request #223 from zulip/revert-221-tooltip
Revert "Add tooltip"
2017-07-18 17:35:27 +00:00
Akash Nimare
6594da6ddd Revert "Add tooltip" 2017-07-18 23:04:58 +05:30
Akash Nimare
38abf2deab Merge pull request #221 from zulip/tooltip
Added tooltip
2017-07-18 17:33:11 +00:00
Akash Nimare
0004152e18 Merge branch 'master' into tooltip 2017-07-18 17:26:57 +00:00
akashnimare
e8be57d710 styling tooltip 2017-07-18 16:47:35 +05:30
akashnimare
64e8419410 Added more shortcuts for going back/forward #208 2017-07-18 15:27:07 +05:30
Akash Nimare
f35a3df63b Merge pull request #218 from geeeeeeeeek/feature/silent-reload
Provide silent & seamless reload for the app
2017-07-18 09:41:01 +00:00
Zhongyi Tong
9e15ed2699 Update bubble padding. 2017-07-18 09:21:42 +08:00
Zhongyi Tong
cdc99cda26 Darken shortcut text color. 2017-07-18 09:18:17 +08:00
Zhongyi Tong
7e08af5ced Fix bubble position. 2017-07-18 00:55:58 +08:00
Zhongyi Tong
0ee85bea16 Update style. 2017-07-18 00:47:00 +08:00
Zhongyi Tong
592584fcf4 Use in tab description on win/linux 2017-07-18 00:10:10 +08:00
Zhongyi Tong
8e1b7a0289 Remove empty line. 2017-07-17 23:35:22 +08:00
Zhongyi Tong
715cf8d86f Reload db after changes. 2017-07-17 23:33:01 +08:00
Zhongyi Tong
0dc20cc66c Provide silent & seamless reload for the app. 2017-07-17 23:09:58 +08:00
akashnimare
c860832a73 changed back/forward shortcut to CMD+Left/Right #208 2017-07-15 21:38:17 +05:30
akashnimare
27a29aeba6 change author to Kandra Labs, Inc. 2017-07-14 07:06:46 +05:30
akashnimare
d0bd7e1f1c Allow user to change installation directory fixes #205 2017-07-14 07:00:02 +05:30
akashnimare
72974b075f Add tooltip [WIP] #207 2017-07-14 05:58:20 +05:30
akashnimare
947bab657f Added Menu item History for back+forward #208 2017-07-14 03:29:17 +05:30
akashnimare
5a2975ca4d handle certs error properly [WIP] #211 2017-07-13 22:01:20 +05:30
Akash Nimare
499743e99d Merge pull request #206 from gnprice/new
settings: Tweak text of initial screen to be a bit clearer.
2017-07-13 02:26:42 +05:30
Greg Price
6bd4c44893 settings: Tweak text of initial screen to be a bit clearer.
The label "New Server" sounds to me like it's suggesting I create
a new actual server -- which I don't want to do, I want to connect
to an existing server where a community I know is talking.
Change the text to track a bit closer to what's really going on.

I think there's probably more that would be useful to do to
make this flow smoother for a new user, but this is a start.
2017-07-12 13:51:00 -07:00
akashnimare
5b82a82313 help >> https://zulipchat.com/help 2017-07-13 02:07:04 +05:30
akashnimare
0208e407f0 Added publisher name [Windows] 2017-07-13 00:47:05 +05:30
akashnimare
e3a622fc07 🎉 v1.2.0-beta 2017-07-12 15:57:56 +05:30
akashnimare
f39839618d open /apps, /api, /integration page in browser instead of app 2017-07-12 15:12:58 +05:30
akashnimare
3f93a3346f Allow server which is signed by root cert WIP #150
App used to throw an error because it assumes that the certificate is invalid or not signed properly.
The solution of this problem is same as self-signed certificate fix which is we simply show a warning dialog asking user if they trust the certificate.
2017-07-12 00:45:44 +05:30
Akash Nimare
9ab75b9800 Merge pull request #202 from geeeeeeeeek/feature/save-server-icon
Save server icon && Provide keyboard shortcut to switch between servers.
2017-07-11 10:30:09 -07:00
Zhongyi Tong
f600c4db0e Eliminate the race condition of pipe and reload. 2017-07-11 11:57:17 +08:00
Zhongyi Tong
4e5816697e Enhance the error handling of icon download. 2017-07-11 11:34:51 +08:00
Zhongyi Tong
dc3c446a46 Put local shortcuts back. 2017-07-11 11:12:39 +08:00
Zhongyi Tong
4cd8efa396 Fix linting errors. 2017-07-11 00:54:51 +08:00
Zhongyi Tong
541ba335ae Save server icon to a local folder. 2017-07-11 00:47:30 +08:00
Zhongyi Tong
3e226400c4 Unregister local shortcuts after reload. 2017-07-10 22:37:22 +08:00
Zhongyi Tong
890d7caea5 Provide keyboard shortcut to switch between servers. 2017-07-10 14:40:02 +08:00
akashnimare
45e7993d0c destroy tray on reload 2017-07-10 03:24:28 +05:30
Akash Nimare
8210a7c472 Merge pull request #200 from geeeeeeeeek/feature/move-add-server-button-up
Add the ability to open Settings with specific view
2017-07-09 14:49:50 -07:00
Zhongyi Tong
9e6bb1b48f Fix stying. 2017-07-09 23:08:59 +08:00
Zhongyi Tong
d3c2da7961 Support switching Settings view from different action buttons. 2017-07-09 23:01:46 +08:00
Zhongyi Tong
a5017456f2 Move add server buttom to above area. 2017-07-09 20:29:40 +08:00
akashnimare
76cd62d0c8 Added desktop notification support on windows 8 fixes #199 2017-07-08 04:43:16 +05:30
akashnimare
6b68217494 Unregister shortcut on window close
this will make sure that app's keyboard shortcut such as CTRL+R etc won't interfare
2017-07-08 03:06:32 +05:30
akashnimare
8148d83448 Add sound settings [WIP] #186 2017-07-07 00:08:14 +05:30
akashnimare
a55cda3b1f Added a warning dialog for deleting server 2017-07-06 19:23:35 +05:30
akashnimare
e381960206 hide Existing servers if there is no server 2017-07-06 00:17:12 +05:30
Akash Nimare
091b641abb Merge pull request #197 from zulip/dev
Add beta update option in settings page #192
2017-07-05 08:33:25 -07:00
akashnimare
af0ec80998 Refinements on settings template 2017-07-05 20:56:07 +05:30
akashnimare
5653a38d9b init betaupdates to false 2017-07-05 18:26:04 +05:30
Akash Nimare
01ea3beb99 Merge pull request #196 from geeeeeeeeek/dev
Reload db after update.
2017-07-05 05:53:01 -07:00
Zhongyi Tong
cd387aaf9c Reload db after update. 2017-07-05 20:41:01 +08:00
akashnimare
24b5e0412b changed betaUpdate >> BetaUpdate 2017-07-05 17:15:38 +05:30
Akash Nimare
8174f7b37e Merge pull request #195 from geeeeeeeeek/dev
Change config filename to avoid conflict.
2017-07-05 04:31:48 -07:00
Zhongyi Tong
d3a5eceaf9 Change config filename to avoid conflict. 2017-07-05 19:25:34 +08:00
Akash Nimare
31f285eba9 Merge pull request #194 from geeeeeeeeek/dev
Fix defaultValue not set on the first time.
2017-07-05 04:19:44 -07:00
Zhongyi Tong
b1365f9669 Fix defaultValue not set on the first time. 2017-07-05 18:58:25 +08:00
akashnimare
21351125fa update betaupdate settings 2017-07-05 16:00:43 +05:30
Akash Nimare
50b9ec7220 Merge pull request #193 from geeeeeeeeek/dev
Make ConfigUtil runnable in both processes.
2017-07-05 03:16:54 -07:00
Zhongyi Tong
46cf5c9a5c Make ConfigUtil runnable in both processes. 2017-07-05 18:05:50 +08:00
akashnimare
35a42f3873 Allow auto update for prerelease based on betaupdates setting 2017-07-05 03:12:01 +05:30
akashnimare
14b9fcf8d7 add settings for beta updates [WIP] #173 2017-07-05 03:05:02 +05:30
Akash Nimare
47cfe86a06 Merge pull request #190 from geeeeeeeeek/feature/settings-page
Feature/settings page
2017-07-02 11:43:14 -07:00
Zhongyi Tong
57ad2d63e0 Fix a styling bug. 2017-07-03 02:11:21 +08:00
Zhongyi Tong
2fcc5d9649 Fix linting errors. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
45523f41aa Add tray options in settings page. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
15b3af7b97 Update tray script to support shown/hidden state on start-up. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
81d3aa8a1b Add ConfigUtil to manage config items. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
321860a232 Disable preload script in functional tab. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
1c290fc2cd Add the ability to forward message from renderer to renderer. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
a74b17b989 Finish the framework of settings. 2017-07-03 02:02:43 +08:00
Zhongyi Tong
ffba6b68f8 Extract Nav from Preference. 2017-07-03 02:02:43 +08:00
akashnimare
b553b29328 ignore docs folder while building 2017-07-01 20:56:22 +05:30
akashnimare
d7b44b23d1 add new release checklist [WIP] 2017-07-01 20:54:52 +05:30
akashnimare
b991fac136 destroy multiple tray icons on reload - fixes #191 2017-07-01 12:22:59 +05:30
Akash Nimare
cda8aa3b09 Merge pull request #189 from geeeeeeeeek/issue/close-settings-bug
Fix close settings causing split webviews (#188).
2017-06-28 08:48:06 -07:00
Zhongyi Tong
1e60643ae9 Fix multiple settings tabs. 2017-06-28 20:37:31 +08:00
Zhongyi Tong
d9fbcaf38e Fix linting errors. 2017-06-28 00:47:42 +08:00
Zhongyi Tong
9e5a67c36b Fix close settings causing split webviews (#188). 2017-06-28 00:44:23 +08:00
akashnimare
a16181be33 🎉 v1.1.0-beta 2017-06-23 17:39:15 +05:30
akashnimare
cd0a7741b1 styling save-server-action button 2017-06-23 17:37:28 +05:30
akashnimare
6da523a18b Remove checkbox and redesign save-server-action 2017-06-23 17:24:37 +05:30
akashnimare
86302308a9 Enable quit app using CTRL+Q on windows as well 2017-06-22 02:44:34 +05:30
akashnimare
b0294db133 Allow global return + use script as sourceType 2017-06-22 00:53:35 +05:30
akashnimare
252586cf71 fix module error #185 2017-06-22 00:52:29 +05:30
Akash Nimare
52ea1a48fd Merge pull request #184 from geeeeeeeeek/issue/network-error-page
Improve network error display.
2017-06-21 06:55:55 -07:00
Zhongyi Tong
98e73f807c Reload the app when users try to resume the connection. 2017-06-21 20:47:40 +08:00
Zhongyi Tong
f96dd6e6bc Fix linting error. 2017-06-21 20:11:58 +08:00
Zhongyi Tong
1511ce4610 Reconnect app when network comes back. 2017-06-21 20:10:58 +08:00
Akash Nimare
c2db6fc0f0 updated multi team screenshot 2017-06-21 17:35:58 +05:30
Zhongyi Tong
937a193a61 Fix linting errors. 2017-06-21 15:59:09 +08:00
Zhongyi Tong
3823ac7f78 Improve network error display. 2017-06-21 15:56:05 +08:00
akashnimare
878cc3fe82 focus window onclicking tray menus #fixes #183 2017-06-20 15:19:57 +05:30
akashnimare
eace637f29 Toggle window on clicking tray icon [Windows only] 2017-06-20 15:00:43 +05:30
akashnimare
316b5fda9e ignore spellchecker for input field 2017-06-19 15:56:06 +05:30
akashnimare
e3b8d5ea2a 🎉 v1.0.0-beta 2017-06-19 13:40:36 +05:30
akashnimare
c8434cfd21 disable auto updates for pre-release 2017-06-19 13:32:33 +05:30
akashnimare
fbc048e8cb increased min. height of window 2017-06-19 13:19:28 +05:30
akashnimare
8a4483da80 sidebar styling 2017-06-19 13:06:01 +05:30
akashnimare
8252c9ae6c remove unused tray option 2017-06-19 12:45:33 +05:30
Akash Nimare
5beb425f1c Merge pull request #180 from geeeeeeeeek/issue/refinements-on-server-side
Refinements on the main process
2017-06-19 12:28:04 +05:30
Zhongyi Tong
44337dd04c Fix a bug which would cause WebView not shown after loading. 2017-06-19 00:32:24 +08:00
Zhongyi Tong
bb76e2c2f4 Fix isInternal and open image in browser. 2017-06-18 09:58:13 +08:00
Zhongyi Tong
71305bca4e Load default icon locally. 2017-06-18 09:56:28 +08:00
Zhongyi Tong
9cff5c5a4d Fix linting errors. 2017-06-18 02:28:50 +08:00
Zhongyi Tong
f3cf2229c6 Add the ability to close for functional tabs. 2017-06-18 02:17:18 +08:00
Zhongyi Tong
a62fc3d3bf Split Tab into ServerTab and FuntionalTab. 2017-06-18 00:38:43 +08:00
Zhongyi Tong
e538543512 Move About page into WebView. 2017-06-17 22:38:24 +08:00
Zhongyi Tong
f85bca9879 Save. 2017-06-17 22:38:24 +08:00
Zhongyi Tong
f548a0ae53 Change the way utils load. 2017-06-17 22:38:24 +08:00
Zhongyi Tong
7192dc69f6 Put LinkUtil to renderer-side. 2017-06-17 22:38:23 +08:00
Zhongyi Tong
47eec89a9b Remove debug-purpose-comments in appUpdater. 2017-06-17 22:38:23 +08:00
Akash Nimare
48ff506344 Merge pull request #179 from zulip/window-close-fix
Fixed window close/hide logic + keep app running in background on close
2017-06-16 09:21:34 -07:00
akashnimare
c2e6e9603f fixed hide/quit logic #169, #160 2017-06-16 21:45:45 +05:30
akashnimare
f4f4836887 add focus option in tray + handle quit event properly 2017-06-16 20:39:16 +05:30
akashnimare
02bc5e41a5 keep app running in background on close 2017-06-16 20:29:16 +05:30
Akash Nimare
68c90434b3 Merge pull request #168 from geeeeeeeeek/issue/refinements-on-multi-tab-view
Refinements on multi tab view
2017-06-16 06:41:23 -07:00
Zhongyi Tong
7f8d933ab7 Fix lint errors. 2017-06-16 21:07:48 +08:00
Akash Nimare
935b37705a Merge pull request #175 from veeloinc/package-whitespace
normalize whitespace
2017-06-13 14:50:21 -07:00
Kevin Turner
798235fb06 normalize whitespace 2017-06-13 08:26:38 -07:00
Zhongyi Tong
743d689281 Avoid multiple webviews show at the same time. 2017-06-13 02:36:47 +08:00
Zhongyi Tong
4c188bbdc8 Update tabs position. 2017-06-13 02:18:35 +08:00
Zhongyi Tong
26e0543ae2 Fix missing badge element. 2017-06-13 01:55:53 +08:00
Zhongyi Tong
463701c5f8 Fix user-agent. 2017-06-13 01:50:37 +08:00
Zhongyi Tong
4c33f0779c Update badge style when unread number too large. 2017-06-13 00:57:28 +08:00
Zhongyi Tong
4888efb9f2 Use Zulip as app title. 2017-06-13 00:57:28 +08:00
Zhongyi Tong
ea332a9ff3 Rebase with geeeeeeeeek/issue/network-disconnectivity-resolution. 2017-06-13 00:57:28 +08:00
Zhongyi Tong
f1b2fdcf99 Disable WebView:focus outline. 2017-06-13 00:57:27 +08:00
Zhongyi Tong
9f73160f74 Add the ability to show badge count for each server. 2017-06-13 00:57:27 +08:00
Zhongyi Tong
b18e3ad5d2 Update title and badge on title change. 2017-06-13 00:57:27 +08:00
Zhongyi Tong
ed6013fb5d Add the ability to reload active webview in UI. 2017-06-13 00:57:27 +08:00
Zhongyi Tong
39b436819c Componentize Tab. 2017-06-13 00:57:27 +08:00
Zhongyi Tong
3e74fc9b0a Finish the refactoring of WebView. 2017-06-13 00:57:25 +08:00
Zhongyi Tong
0708519816 Componentize WebView. 2017-06-13 00:56:36 +08:00
Zhongyi Tong
84808313fe Add the ability to open DevTools for active webview. 2017-06-13 00:51:44 +08:00
Zhongyi Tong
e7e55596c6 Update icon names. 2017-06-13 00:51:44 +08:00
Zhongyi Tong
1b8eb099ab Disable text selections in multi-tab view. 2017-06-13 00:51:44 +08:00
Zhongyi Tong
1fa276a400 Update css structure. 2017-06-13 00:51:44 +08:00
Zhongyi Tong
0751f6ac72 Remove obsoleted main.css and rename servermanager.css to main.css.
Since we adopt new multi-tab UI for the app, the old main.css was obsoleted and not in use. For clarity, We should change the name of main.js's css file to main.css.
2017-06-13 00:51:44 +08:00
Akash Nimare
3d8da55648 update node dependency 2017-06-12 17:05:51 +05:30
Akash Nimare
318a729a4a Merge pull request #174 from geeeeeeeeek/issue/set-ua-properly
Set user-agent from a singleton util.
2017-06-12 02:27:09 -07:00
Zhongyi Tong
aab581f204 Fix missing app version in user-agent. 2017-06-12 11:32:23 +08:00
Zhongyi Tong
c0075b4f1c Set user-agent from a singleton util. 2017-06-12 01:07:45 +08:00
akashnimare
7bd2e751c5 fixed linting 2017-06-10 15:39:05 +05:30
akashnimare
5ff2492c79 temporarily disable electron browserwindow and other tests
Because of the chromedriver issue other tests like linting is failing. Disablingthis particular test will allow us to test for linting errors. This is just a temporary workaround and will be fixed ASAP.
2017-06-10 15:11:25 +05:30
Akash Nimare
a26df708f4 Merge pull request #171 from zulip/dev
Set useragent correctly fixes #170
2017-06-09 20:54:25 +05:30
akashnimare
57df256a4a set useragent correctly [WIP] #170 2017-06-09 07:52:45 +05:30
Akash Nimare
7f1890d8a1 Merge pull request #165 from geeeeeeeeek/issue/network-disconnectivity-resolution
Issue/network disconnectivity resolution
2017-06-04 01:38:12 -07:00
Akash Nimare
ff12b041a1 Merge pull request #162 from geeeeeeeeek/master
Fix WebView resize problem
2017-06-01 10:33:25 -07:00
Zhongyi Tong
606e407aee Fix network disconnectivity issue. 2017-06-01 19:33:56 +08:00
Zhongyi Tong
b0db81095a Attach user-agent to webview requests. 2017-06-01 19:09:36 +08:00
Zhongyi Tong
2208b03612 Remove vscode conf. 2017-05-30 20:41:13 +08:00
Zhongyi Tong
7cf2422d76 Fix webview resize problem when switching back to that webview. 2017-05-30 20:32:55 +08:00
akashnimare
57d4e5c930 load icons locally fixes #152 2017-05-28 02:27:25 -07:00
Akash Nimare
deafa315df Merge pull request #159 from zulip/bugfix-#158-focus-webview-on-tab-context-switch
Bugfix #158 focus webview on tab app switch
2017-05-28 01:29:55 -07:00
simplyahmazing
4c09da791c remove unneeded listener 2017-05-28 04:22:20 -04:00
simplyahmazing
e3490dbfa5 makes webview focus when browser window gains focus 2017-05-28 04:20:54 -04:00
akashnimare
f6358a06fd fixing network issue #157 [WIP] 2017-05-25 15:50:15 -07:00
akashnimare
3ef346495f updated spectron dependency 2017-05-25 11:48:49 -07:00
Akash Nimare
64fcc51c7e add chai as dependency 2017-05-24 18:59:06 -07:00
Akash Nimare
a5299a6973 Merge pull request #156 from zulip/20170523-090000-multiple-organizations-test
commment test
2017-05-24 18:53:42 -07:00
Akash Nimare
154aa323ab Update README.md 2017-05-24 18:48:28 -07:00
simplyahmazing
c52318f5d6 add chai as a dep 2017-05-24 21:41:25 -04:00
Akash Nimare
bcd4048709 Merge pull request #155 from lonerz/patch-2
CONTRIBUTING.md: Fix styling issues.
2017-05-24 18:28:45 -07:00
Joshua Pan
f6cb262d4c CONTRIBUTING.md: Fix styling issues.
beta please
2017-05-24 18:27:44 -07:00
Akash Nimare
ca21912374 Merge pull request #154 from lonerz/patch-1
development.md: Fix some grammar issues.
2017-05-24 18:13:38 -07:00
Joshua Pan
472ba9a199 development.md: Fix some grammar issues. 2017-05-24 17:20:58 -07:00
akashnimare
f3a4d4225d :memo updating development guide [WIP] 2017-05-24 17:16:28 -07:00
Akash Nimare
62178b6035 Update development.md 2017-05-24 17:13:19 -07:00
Akash Nimare
2068ac8905 Update development.md 2017-05-24 16:59:38 -07:00
akashnimare
51352be1f6 :memo updating development guide [WIP] 2017-05-24 16:58:27 -07:00
akashnimare
8c3bfcbdd5 :memo docs updated [WIP] 2017-05-24 16:51:14 -07:00
Akash Nimare
06ed522714 Update development.md 2017-05-24 16:46:44 -07:00
akashnimare
d5526944fe :memo updated development guide [WIP] 2017-05-24 16:44:07 -07:00
akashnimare
616ed89d90 :memo docs updated [WIP] 2017-05-24 16:38:09 -07:00
akashnimare
92362653d3 :memo docs updated [WIP] 2017-05-24 16:07:26 -07:00
Akash Nimare
06a09574a5 Update README.md 2017-05-24 15:27:01 -07:00
Akash Nimare
c19a7f81c2 updated development guide [WIP] 2017-05-24 15:26:14 -07:00
Akash Nimare
635b6f6128 📝 docs improvements [WIP] 2017-05-24 15:21:12 -07:00
Akash Nimare
996084cd36 Docs improvement [WIP] 2017-05-24 12:27:37 -07:00
Akash Nimare
6a403e52e1 Improve docs [WIP] 2017-05-24 11:20:02 -07:00
Akash Nimare
aed58ed1a8 Update README.md 2017-05-24 10:52:56 -07:00
Akash Nimare
0412702e35 improve docs [WIP] 2017-05-24 10:52:34 -07:00
Akash Nimare
cf0086e324 📝 improving docs 2017-05-24 10:31:06 -07:00
Akash Nimare
d156ba99c8 📝 improving docs 2017-05-24 10:30:54 -07:00
akashnimare
7d71e2e04d add missing dependency 2017-05-23 19:41:14 -07:00
Akash Nimare
546abb28c0 Merge pull request #153 from zulip/20170523-090000-multiple-organizations-test
multiple organizations tests
2017-05-23 19:37:23 -07:00
simplyahmazing
a43d008aa9 commment test 2017-05-23 22:15:10 -04:00
simplyahmazing
777ed5c561 cleans up tests 2017-05-23 22:11:22 -04:00
simplyahmazing
61db04c574 Merge branch 'master' into 20170523-090000-multiple-organizations-test 2017-05-23 20:33:01 -04:00
simplyahmazing
93123401fe fixes test 2017-05-23 20:32:53 -04:00
simplyahmazing
dca4debee2 setting up multi org test 2017-05-23 20:31:51 -04:00
akashnimare
bef1503b9e 💄 styling setting sidebar 2017-05-23 13:01:40 -07:00
simplyahmazing
f9d430b2d2 sets chai promised tests; improves application launched test case 2017-05-23 13:44:09 -04:00
simplyahmazing
0d551861c7 fixes tests; fixes linting errors 2017-05-23 13:09:58 -04:00
simplyahmazing
4ced663f01 removes window frame 2017-05-23 12:42:16 -04:00
Akash Nimare
06f15eba2d Merge pull request #151 from zulip/140-fixes
💥 Added multiple server support feature
2017-05-22 18:53:21 -07:00
Akash Nimare
3008cd1f24 Merge pull request #140 from geeeeeeeeek/feature/#1-add-support-for-multiple-server
🎉  Add nice support for multiple Zulip servers
2017-05-22 18:43:20 -07:00
SimplyAhmazing
ea449965af Merge branch '140-fixes' into feature/#1-add-support-for-multiple-server 2017-05-22 18:42:24 -07:00
simplyahmazing
06ecdb678c focus webview after loading 2017-05-22 21:21:53 -04:00
simplyahmazing
dd99560426 removes commented setting 2017-05-22 20:41:24 -04:00
simplyahmazing
cebea28ba8 change badge count on organization nav change 2017-05-22 20:37:48 -04:00
Akash Nimare
da3e7e39b9 shrink left sidebar
Just to remove unnecessary spacing.
2017-05-22 17:09:49 -07:00
Zhongyi Tong
4b4e3a3d01 Update Save Server button text. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
aff4632927 Fix a typo. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
eeb9ee512f Enable asar. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
11785d78d2 Fix broken path issues using the module after packing. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
f8d8d0ce2e Fix test errors. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
b5af5d413d Add back badge count feature. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
d962bd6e60 Migrate /domain to /domains. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
0271ada591 Fix linter warnings. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
42fedf2d73 Add reload button to preference page. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
0b17dbb014 Initialize domains on first use. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
ae3c595d82 Integrate actions from menu and tray with webview. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
de34a22740 Move methods from mainWindow.webContents (index.js) to WebView (main.js). 2017-05-22 17:09:49 -07:00
Zhongyi Tong
865553fa45 Redirect to server settings page when domains are empty. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
23fd7ba2b3 Update DomainUtil and finish PreferenceView. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
e43b651060 Set up layout for PreferenceView. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
c0fc7718aa Use multiple webview to increase loading speed and allow servers to be online at the same time. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
3fe23e84b3 Refactor ServerManagerView and setup layout for PreferenceView. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
6b29139805 Finish interactions of switching servers in ServerManagerView. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
0bfa202763 Change domain config schema and update DomainUtil. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
8c494f329b Setup the layout for ServerManagerView. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
35bf2b0012 Add DomainUtil to manage domains in one place. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
5ac4ea71c9 Update prototype design. 2017-05-22 17:09:49 -07:00
Zhongyi Tong
f7eb4128cb Finish UI template of multi-server manager. 2017-05-22 17:09:49 -07:00
simplyahmazing
f9f21cd626 bring back electron frame; shrink organization switcher bar 2017-05-22 19:16:05 -04:00
simplyahmazing
4a84f17d86 merge usptream master 2017-05-22 19:07:01 -04:00
akashnimare
d5a92110db build for mac apple store 2017-05-22 15:31:03 -07:00
akashnimare
e9cf591559 add mas config 2017-05-22 14:57:33 -07:00
akashnimare
ba60c04452 🆙 update dependencies 2017-05-22 12:10:58 -07:00
simplyahmazing
f4567c762d update spellchecker and electron to latest 2017-05-22 14:38:21 -04:00
Akash Nimare
b2aec4b27e Downgrade electron 2017-05-22 10:16:52 -07:00
akashnimare
0fc58b4cc9 Enable zipping of linux installers 2017-05-21 17:14:14 -07:00
akashnimare
e9d7bfe48b bump electron to v1.6.8 2017-05-21 17:05:36 -07:00
Akash Nimare
070eb099d0 typo fixed 2017-05-19 16:13:25 -07:00
Akash Nimare
e35661993d Document windows installer #148 2017-05-19 16:12:34 -07:00
Akash Nimare
f6234cd2f6 Merge pull request #147 from Lplenka/sleepresume
[WIP]#143 Improve handling of coming back online with the desktop app
2017-05-19 05:26:04 +05:30
Lplenka
66475cf46c [WIP] #143 Fixed lint error 2017-05-04 16:39:19 +05:30
Lplenka
06734d2e56 [WIP]#143 powerMoniter now checks when app is resumed 2017-05-04 15:12:15 +05:30
Zhongyi Tong
1c012c7a28 Update Save Server button text. 2017-04-30 23:08:45 +08:00
Zhongyi Tong
7fdccd278b Fix a typo. 2017-04-30 01:14:42 +08:00
Zhongyi Tong
9a4556a59c Enable asar. 2017-04-30 01:06:05 +08:00
Zhongyi Tong
92b9388f9a Fix broken path issues using the module after packing. 2017-04-30 00:01:22 +08:00
Zhongyi Tong
869600e3d4 Fix test errors. 2017-04-29 19:56:39 +08:00
Zhongyi Tong
3c22d5462d Add back badge count feature. 2017-04-29 17:37:08 +08:00
Zhongyi Tong
92ff92f501 Migrate /domain to /domains. 2017-04-29 15:37:02 +08:00
Zhongyi Tong
47b3dd04fb Fix linter warnings. 2017-04-29 01:10:21 +08:00
Zhongyi Tong
9ed09c9e1c Add reload button to preference page. 2017-04-28 23:56:59 +08:00
Zhongyi Tong
239cce8a4c Initialize domains on first use. 2017-04-28 23:56:59 +08:00
Zhongyi Tong
d6a35408b8 Integrate actions from menu and tray with webview. 2017-04-28 23:56:59 +08:00
Zhongyi Tong
35e6f7dcdd Move methods from mainWindow.webContents (index.js) to WebView (main.js). 2017-04-28 23:56:56 +08:00
Zhongyi Tong
a38c933bc8 Redirect to server settings page when domains are empty. 2017-04-28 23:00:08 +08:00
Zhongyi Tong
720ccf5d00 Update DomainUtil and finish PreferenceView. 2017-04-28 23:00:08 +08:00
Zhongyi Tong
8dc87d0485 Set up layout for PreferenceView. 2017-04-28 23:00:08 +08:00
Zhongyi Tong
cd9f1c0c47 Use multiple webview to increase loading speed and allow servers to be online at the same time. 2017-04-28 23:00:08 +08:00
Zhongyi Tong
331452edbb Refactor ServerManagerView and setup layout for PreferenceView. 2017-04-28 23:00:08 +08:00
Zhongyi Tong
d18885ecc9 Finish interactions of switching servers in ServerManagerView. 2017-04-28 23:00:08 +08:00
Zhongyi Tong
1aef53ef94 Change domain config schema and update DomainUtil. 2017-04-28 23:00:08 +08:00
Zhongyi Tong
11086210de Setup the layout for ServerManagerView. 2017-04-28 23:00:08 +08:00
Zhongyi Tong
e0d693fa19 Add DomainUtil to manage domains in one place. 2017-04-28 23:00:08 +08:00
Zhongyi Tong
81c71b1f83 Update prototype design. 2017-04-28 23:00:07 +08:00
Zhongyi Tong
cf8c83e3cf Finish UI template of multi-server manager. 2017-04-28 23:00:07 +08:00
67 changed files with 3622 additions and 1271 deletions

27
.gitignore vendored
View File

@@ -1,9 +1,26 @@
node_modules
npm-debug.log
domain.json
dist
config.gypi
# Dependency directories
node_modules/
# npm cache directory
.npm
# Compiled binary build directory
dist/
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
// osx garbage
*.DS_Store
.DS_Store
# dotenv environment variables file
.env
# miscellaneous
.idea
config.gypi

1
.node-version Normal file
View File

@@ -0,0 +1 @@
6.9.4

1
.python-version Normal file
View File

@@ -0,0 +1 @@
2.7.9

View File

@@ -2,44 +2,46 @@
Thanks for taking the time to contribute!
The following is a set of guidelines for contributing to zulip-electron. These are just guidelines, not rules, use your best judgement and feel free to propose changes to this document in a pull request.
The following is a set of guidelines for contributing to Zulip Electron Desktop Client. These are just guidelines, not rules, so use your best judgement and feel free to propose changes to this document in a pull request.
## Getting Started
Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If you are new to Electron please head over to [this](http://jlord.us/essential-electron/) great article.
Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If you are new to Electron, please head over to [this](http://jlord.us/essential-electron/) great article.
## Community
* The whole zulip documentation such as development environment, setting up with zulip project, testing, code of conduct can be read [here](https://zulip.readthedocs.io).
* The whole Zulip documentation, such as setting up a development environment, setting up with the Zulip webapp project, and testing, can be read [here](https://zulip.readthedocs.io).
* If you have any questions regarding zulip-electron, open an [issue](https://github.com/zulip/zulip-electron/issues/new/) or ask it [here](https://chat.zulip.org/#narrow/stream/electron).
* If you have any questions regarding zulip-electron, open an [issue](https://github.com/zulip/zulip-electron/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/electron).
## Issue
Ensure the bug was not already reported by searching on GitHub under [Issues](https://github.com/zulip/zulip-electron/issues). If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/zulip/zulip-electron/issues/new). Please pay attention to following points while opening an issue.
Ensure the bug was not already reported by searching on GitHub under [issues](https://github.com/zulip/zulip-electron/issues). If you're unable to find an open issue addressing the bug, open a [new issue](https://github.com/zulip/zulip-electron/issues/new).
The [Zulipbot](https://github.com/zulip/zulipbot) helps to claim the issue by commenting the following in the comment section: "**@zulipbot** claim". **@zulipbot** will assign you to the issue and label the issue as **in progress**. For more details, check out [**@zulipbot**](https://github.com/zulip/zulipbot).
The [zulipbot](https://github.com/zulip/zulipbot) helps to claim an issue by commenting the following in the comment section: "**@zulipbot** claim". **@zulipbot** will assign you to the issue and label the issue as **in progress**. For more details, check out [**@zulipbot**](https://github.com/zulip/zulipbot).
Please pay attention to the following points while opening an issue.
### Does it happen on web browsers? (especially Chrome)
Zulip-Desktop is based on Electron, which integrates the Chrome engine within a standalone application.
If the problem you encounter can be reproduced on web browsers, it may be an issue with Zulip web app.
Zulip's desktop client is based on Electron, which integrates the Chrome engine within a standalone application.
If the problem you encounter can be reproduced on web browsers, it may be an issue with [Zulip web app](https://github.com/zulip/zulip).
### Write detailed information
Detailed information is very helpful to understand the problem.
Detailed information is very helpful to understand an issue.
For example:
* How to reproduce, step-by-step
* Expected behavior (or what is wrong)
* Screenshots (for GUI issues)
* Application version
* Operating system
* Zulip-Desktop version
* How to reproduce the issue, step-by-step.
* The expected behavior (or what is wrong).
* Screenshots for GUI issues.
* The application version.
* The operating system.
* The Zulip-Desktop version.
## Pull Requests
Pull Requests are welcome.
Pull Requests are always welcome.
1. When you edit the code, please run `npm run test` to check formatting of your code before git commit.
1. When you edit the code, please run `npm run test` to check the formatting of your code before you `git commit`.
2. Ensure the PR description clearly describes the problem and solution. It should include:
* Operating System version on which you tested
* Zulip-Desktop version on which you tested
* The relevant issue number if applicable
* The operating system on which you tested.
* The Zulip-Desktop version on which you tested.
* The relevant issue number, if applicable.

View File

@@ -1,82 +1,39 @@
# Zulip Desktop Client
# Zulip Desktop Client
[![Build Status](https://travis-ci.org/zulip/zulip-electron.svg?branch=master)](https://travis-ci.org/zulip/zulip-electron)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/zulip/zulip-electron?branch=master&svg=true)](https://ci.appveyor.com/project/akashnimare/zulip-electron/branch/master)
[![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo)
This is an experimental replacement for the [Zulip Desktop
app](https://github.com/zulip/zulip-desktop) implemented in
[Electron](http://electron.atom.io/).
Desktop client for Zulip. Available for Mac, Linux and Windows.
The goal is to achieve feature-compatibility with the old desktop app
and then start adding cool features like easy support for
multi-account, auto-updates etc.
<img src="http://i.imgur.com/ChzTq4F.png"/>
## Prerequisites
* node >= v6.3.1
> Use [nvm](https://github.com/creationix/nvm) to install the current stable version of node
# Download
Please see [installation guide](./how-to-install.md).
* python (v2.7.x recommended)
* If you're on Debian or Ubuntu, you'll need to install following packages:
```sh
$ sudo apt-get install build-essential libxext-dev libxtst-dev libxkbfile-dev
```
## Installation
Clone the source locally:
```sh
$ git clone https://github.com/zulip/zulip-electron
$ cd zulip-electron
```
Install project dependencies:
```sh
$ npm install
```
Start the app:
```sh
$ npm start
```
Start and watch changes
```sh
$ npm run dev
```
# Making a release
To package app into an installer use command:
```
npm run dist
```
It will start the packaging process for operating system you are running this command on. Ready for distribution file (e.g. dmg, windows installer, deb package) will be outputted to `dist` directory.
You can create Windows installer only when running on Windows, the same is true for Linux and OSX. So to generate all three installers you need all three operating systems.
## Features
- [x] Native Notifications
- [x] SpellChecker
- [x] OSX/Win/Linux installer
- [x] Automatic Updates (macOS/Windows)
- [x] Keyboard shortcuts
# Features
* Sign in to multiple teams
* Native desktop Notifications
* SpellChecker
* OSX/Win/Linux installers
* Automatic Updates (macOS/Windows)
* Keyboard shortcuts
Description | Keys
-----------------------| -----------------------
Default shortcuts | <kbd>Cmd/Ctrl</kbd> <kbd>k</kbd>
Change Zulip Server | <kbd>Cmd/Ctrl</kbd> <kbd>,</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.
## Contribute
# Contribute
If you want to contribute please make sure to read [our documentation about contributing](./CONTRIBUTING.md) first.
* [Issue Tracker](https://github.com/zulip/zulip-electron/issues)
* [Source Code](https://github.com/zulip/zulip-electron/)
# License
Released under the [Apache-2.0](./LICENSE) license.

View File

@@ -1,40 +1,52 @@
'use strict';
const {app, dialog} = require('electron');
const {autoUpdater} = require('electron-updater');
const fs = require('fs');
const { app, dialog } = require('electron');
const { autoUpdater } = require('electron-updater');
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') {
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');
log.transports.file.file = `${LogsDir}/updates.log`;
log.transports.file.level = 'info';
autoUpdater.logger = log;
/*
AutoUpdater.on('error', err => log.info(err));
autoUpdater.on('checking-for-update', () => log.info('checking-for-update'));
autoUpdater.on('update-available', () => log.info('update-available'));
autoUpdater.on('update-not-available', () => log.info('update-not-available'));
*/
// Handle auto updates for beta/pre releases
autoUpdater.allowPrerelease = ConfigUtil.getConfigItem('betaUpdate') || false;
// Ask the user if update is available
// eslint-disable-next-line no-unused-vars
autoUpdater.on('update-downloaded', (event, info) => {
// Let message = app.getName() + ' ' + info.releaseName + ' is now available. It will be installed the next time you restart the application.';
// if (info.releaseNotes) {
// const splitNotes = info.releaseNotes.split(/[^\r]\n/);
// message += '\n\nRelease notes:\n';
// splitNotes.forEach(notes => {
// message += notes + '\n\n';
// });
// }
autoUpdater.on('update-downloaded', event => {
// Ask user to update the app
dialog.showMessageBox({
type: 'question',
buttons: ['Install and Relaunch', 'Later'],
buttons: ['Install and Relaunch', 'Install Later'],
defaultId: 0,
message: 'A new version of ' + app.getName() + ' has been downloaded',
message: `A new update ${event.version} has been downloaded`,
detail: 'It will be installed the next time you restart the application'
}, response => {
if (response === 0) {
setTimeout(() => autoUpdater.quitAndInstall(), 1);
setTimeout(() => {
autoUpdater.quitAndInstall();
// force app to quit. This is just a workaround, ideally autoUpdater.quitAndInstall() should relaunch the app.
app.quit();
}, 1000);
}
});
});

View File

@@ -1,128 +1,27 @@
'use strict';
const path = require('path');
const fs = require('fs');
const os = require('os');
const electron = require('electron');
const {app} = require('electron');
const ipc = require('electron').ipcMain;
const {dialog} = require('electron');
const https = require('https');
const http = require('http');
const electronLocalshortcut = require('electron-localshortcut');
const Configstore = require('electron-config');
const JsonDB = require('node-json-db');
const isDev = require('electron-is-dev');
const windowStateKeeper = require('electron-window-state');
const appMenu = require('./menu');
const {linkIsInternal, skipImages} = require('./link-helper');
const {appUpdater} = require('./autoupdater');
const { appUpdater } = require('./autoupdater');
const { setAutoLaunch } = require('./startup');
const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
const data = db.getData('/');
const { app, ipcMain } = electron;
const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js');
// Adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')();
const conf = new Configstore();
function userOS() {
if (os.platform() === 'darwin') {
return 'Mac';
}
if (os.platform() === 'linux') {
return 'Linux';
}
if (os.platform() === 'win32' || os.platform() === 'win64') {
if (parseFloat(os.release()) < 6.2) {
return 'Windows 7';
} else {
return 'Windows 10';
}
}
}
// Setting userAgent so that server-side code can identify the desktop app
const isUserAgent = 'ZulipElectron/' + app.getVersion() + ' ' + userOS();
// Prevent window being garbage collected
let mainWindow;
let targetLink;
let badgeCount;
let isQuitting = false;
// Load this url in main window
const staticURL = 'file://' + path.join(__dirname, '../renderer', 'index.html');
const targetURL = function () {
if (data.domain === undefined) {
return staticURL;
}
return data.domain;
};
function serverError(targetURL) {
if (targetURL.indexOf('localhost:') < 0 && data.domain) {
const req = https.request(targetURL + '/static/audio/zulip.ogg', res => {
console.log('Server StatusCode:', res.statusCode);
console.log('You are connected to:', res.req._headers.host);
if (res.statusCode >= 500 && res.statusCode <= 599) {
return dialog.showErrorBox('SERVER IS DOWN!', 'We are getting a ' + res.statusCode + ' error status from the server ' + res.req._headers.host + '. Please try again after some time or you may switch server.');
}
});
req.on('error', e => {
if (e.toString().indexOf('Error: self signed certificate') >= 0) {
const url = targetURL.replace(/^https?:\/\//, '');
console.log('Server StatusCode:', 200);
console.log('You are connected to:', url);
} else {
console.error(e);
}
});
req.end();
} else if (data.domain) {
const req = http.request(targetURL + '/static/audio/zulip.ogg', res => {
console.log('Server StatusCode:', res.statusCode);
console.log('You are connected to:', res.req._headers.host);
});
req.on('error', e => {
console.error(e);
});
req.end();
}
}
function checkConnectivity() {
return dialog.showMessageBox({
title: 'Internet connection problem',
message: 'No internet available! Try again?',
type: 'warning',
buttons: ['Try again', 'Close'],
defaultId: 0
}, index => {
if (index === 0) {
mainWindow.webContents.reload();
mainWindow.webContents.send('destroytray');
}
if (index === 1) {
app.quit();
}
});
}
const connectivityERR = [
'ERR_INTERNET_DISCONNECTED',
'ERR_PROXY_CONNECTION_FAILED',
'ERR_CONNECTION_RESET',
'ERR_NOT_CONNECTED',
'ERR_NAME_NOT_RESOLVED'
];
function checkConnection() {
// eslint-disable-next-line no-unused-vars
mainWindow.webContents.on('did-fail-load', (event, errorCode, errorDescription, validatedURL) => {
const hasConnectivityErr = (connectivityERR.indexOf(errorDescription) >= 0);
if (hasConnectivityErr) {
console.error('error', errorDescription);
checkConnectivity();
}
});
}
const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html');
const isAlreadyRunning = app.makeSingleInstance(() => {
if (mainWindow) {
@@ -135,18 +34,7 @@ const isAlreadyRunning = app.makeSingleInstance(() => {
});
if (isAlreadyRunning) {
app.quit();
}
function checkWindowURL() {
if (data.domain !== undefined) {
return data.domain;
}
return targetLink;
}
function isWindowsOrmacOS() {
return process.platform === 'darwin' || process.platform === 'win32';
return app.quit();
}
const APP_ICON = path.join(__dirname, '../resources', 'Icon');
@@ -155,96 +43,64 @@ const iconPath = () => {
return APP_ICON + (process.platform === 'win32' ? '.ico' : '.png');
};
function onClosed() {
// Dereference the window
// For multiple windows, store them in an array
mainWindow = null;
}
function updateDockBadge(title) {
if (title.indexOf('Zulip') === -1) {
return;
}
let messageCount = (/\(([0-9]+)\)/).exec(title);
messageCount = messageCount ? Number(messageCount[1]) : 0;
if (process.platform === 'darwin') {
app.setBadgeCount(messageCount);
}
mainWindow.webContents.send('tray', messageCount);
}
function createMainWindow() {
// Load the previous state with fallback to defaults
const mainWindowState = windowStateKeeper({
defaultWidth: 1000,
defaultHeight: 600
});
const win = new electron.BrowserWindow({
// This settings needs to be saved in config
title: 'Zulip',
width: conf.get('width') || 1000,
height: conf.get('height') || 600,
icon: iconPath(),
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
minWidth: 600,
minHeight: 400,
minHeight: 500,
webPreferences: {
preload: path.join(__dirname, '../renderer/js/preload.js'),
plugins: true,
allowDisplayingInsecureContent: true,
nodeIntegration: false
nodeIntegration: true
},
show: false
});
win.on('focus', () => {
win.webContents.send('focus');
});
win.once('ready-to-show', () => {
win.show();
});
serverError(targetURL());
win.loadURL(mainURL);
win.loadURL(targetURL(), {
userAgent: isUserAgent + ' ' + win.webContents.getUserAgent()
// Keep the app running in background on close event
win.on('close', e => {
if (!isQuitting) {
e.preventDefault();
if (process.platform === 'darwin') {
app.hide();
} else {
win.hide();
}
}
// Unregister all the shortcuts so that they don't interfare with other apps
electronLocalshortcut.unregisterAll(mainWindow);
});
win.on('closed', onClosed);
win.setTitle('Zulip');
// Let's save browser window position
if (conf.get('x') || conf.get('y')) {
win.setPosition(conf.get('x'), conf.get('y'));
}
if (conf.get('maximize')) {
win.maximize();
}
// Handle sizing events so we can persist them.
win.on('maximize', () => {
conf.set('maximize', true);
win.on('enter-full-screen', () => {
win.webContents.send('enter-fullscreen');
});
win.on('unmaximize', () => {
conf.set('maximize', false);
});
win.on('resize', function () {
const size = this.getSize();
conf.set({
width: size[0],
height: size[1]
});
});
// On osx it's 'moved'
win.on('move', function () {
const pos = this.getPosition();
conf.set({
x: pos[0],
y: pos[1]
});
});
// Stop page to update it's title
win.on('page-title-updated', (e, title) => {
e.preventDefault();
updateDockBadge(title);
win.on('leave-full-screen', () => {
win.webContents.send('leave-fullscreen');
});
// To destroy tray icon when navigate to a new URL
@@ -254,33 +110,30 @@ function createMainWindow() {
}
});
// Let us register listeners on the window, so we can update the state
// automatically (the listeners will be removed when the window is closed)
// and restore the maximized or full screen state
mainWindowState.manage(win);
return win;
}
// TODO - fix certificate errors
// app.commandLine.appendSwitch('ignore-certificate-errors', 'true');
// For self-signed certificate
ipc.on('certificate-err', (e, domain) => {
const detail = `URL: ${domain} \n Error: Self-Signed Certificate`;
dialog.showMessageBox(mainWindow, {
title: 'Certificate error',
message: `Do you trust certificate from ${domain}?`,
// eslint-disable-next-line object-shorthand
detail: detail,
type: 'warning',
buttons: ['Yes', 'No'],
cancelId: 1
// eslint-disable-next-line object-shorthand
}, response => {
if (response === 0) {
// eslint-disable-next-line object-shorthand
db.push('/domain', domain);
mainWindow.loadURL(domain);
}
function registerLocalShortcuts(page) {
// Somehow, reload action cannot be overwritten by the menu item
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
page.send('reload-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');
});
}
// eslint-disable-next-line max-params
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
event.preventDefault();
@@ -290,9 +143,6 @@ app.on('certificate-error', (event, webContents, url, error, certificate, callba
app.on('window-all-closed', () => {
// Unregister all the shortcuts so that they don't interfare with other apps
electronLocalshortcut.unregisterAll(mainWindow);
if (process.platform !== 'darwin') {
app.quit();
}
});
app.on('activate', () => {
@@ -302,52 +152,90 @@ app.on('activate', () => {
});
app.on('ready', () => {
electron.Menu.setApplicationMenu(appMenu);
appMenu.setMenu({
tabs: []
});
mainWindow = createMainWindow();
// Not using for now // tray.create();
const page = mainWindow.webContents;
// TODO - use global shortcut instead
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
mainWindow.reload();
mainWindow.webContents.send('destroytray');
});
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
if (page.canGoBack()) {
page.goBack();
}
});
electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => {
if (page.canGoForward()) {
page.goForward();
}
});
registerLocalShortcuts(page);
page.on('dom-ready', () => {
page.insertCSS(fs.readFileSync(path.join(__dirname, '../renderer/css/preload.css'), 'utf8'));
mainWindow.show();
});
page.on('new-window', (event, url) => {
if (linkIsInternal(checkWindowURL(), url) && url.match(skipImages) === null) {
event.preventDefault();
return mainWindow.loadURL(url);
}
event.preventDefault();
electron.shell.openExternal(url);
page.once('did-frame-finish-load', () => {
// Initate auto-updates on MacOS and Windows
appUpdater();
});
page.once('did-frame-finish-load', () => {
const checkOS = isWindowsOrmacOS();
if (checkOS && !isDev) {
// Initate auto-updates on MacOS and Windows
appUpdater();
electron.powerMonitor.on('resume', () => {
page.send('reload-viewer');
});
ipcMain.on('focus-app', () => {
mainWindow.show();
});
ipcMain.on('quit-app', () => {
app.quit();
});
// Reload full app not just webview, useful in debugging
ipcMain.on('reload-full-app', () => {
mainWindow.reload();
page.send('destroytray');
});
ipcMain.on('toggle-app', () => {
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
}
});
checkConnection();
ipcMain.on('toggle-badge-option', () => {
BadgeSettings.updateBadge(badgeCount, mainWindow);
});
ipcMain.on('update-badge', (event, messageCount) => {
badgeCount = messageCount;
BadgeSettings.updateBadge(badgeCount, mainWindow);
page.send('tray', messageCount);
});
ipcMain.on('update-taskbar-icon', (event, data, text) => {
BadgeSettings.updateTaskbarIcon(data, text, mainWindow);
});
ipcMain.on('forward-message', (event, listener, ...params) => {
page.send(listener, ...params);
});
ipcMain.on('update-menu', (event, props) => {
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', () => {
@@ -355,20 +243,12 @@ app.on('will-quit', () => {
electronLocalshortcut.unregisterAll(mainWindow);
});
ipc.on('new-domain', (e, domain) => {
// MainWindow.loadURL(domain);
if (!mainWindow) {
mainWindow = createMainWindow();
mainWindow.loadURL(domain);
mainWindow.webContents.send('destroytray');
} else if (mainWindow.isMinimized()) {
mainWindow.webContents.send('destroytray');
mainWindow.loadURL(domain);
mainWindow.show();
} else {
mainWindow.webContents.send('destroytray');
mainWindow.loadURL(domain);
serverError(domain);
}
targetLink = domain;
app.on('before-quit', () => {
isQuitting = true;
});
// Send crash reports
process.on('uncaughtException', err => {
console.error(err);
console.error(err.stack);
});

View File

@@ -1,16 +0,0 @@
const wurl = require('wurl');
// Check link if it's internal/external
function linkIsInternal(currentUrl, newUrl) {
const currentDomain = wurl('hostname', currentUrl);
const newDomain = wurl('hostname', newUrl);
return currentDomain === newDomain;
}
// We'll be needing this to open images in default browser
const skipImages = '.jpg|.gif|.png|.jpeg|.JPG|.PNG';
module.exports = {
linkIsInternal,
skipImages
};

View File

@@ -1,363 +1,381 @@
'use strict';
const os = require('os');
const electron = require('electron');
const {dialog, app, shell, BrowserWindow, Menu} = require('electron');
const {dialog} = require('electron');
const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js');
const app = electron.app;
const BrowserWindow = electron.BrowserWindow;
const shell = electron.shell;
const appName = app.getName();
// Const tray = require('./tray');
const {addDomain, about} = require('./windowmanager');
function sendAction(action) {
const win = BrowserWindow.getAllWindows()[0];
if (process.platform === 'darwin') {
win.restore();
}
win.webContents.send(action);
}
function clearCache() {
const win = BrowserWindow.getAllWindows()[0];
const ses = win.webContents.session;
ses.clearCache(() => {
dialog.showMessageBox({type: 'info', buttons: [], message: 'Cache cleared!'});
});
}
const viewSubmenu = [
{
label: 'Reload',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.reload();
focusedWindow.webContents.send('destroytray');
}
}
},
{
type: 'separator'
},
{
role: 'togglefullscreen'
},
{
label: 'Zoom In',
accelerator: 'CommandOrControl+=',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('zoomIn');
}
}
},
{
label: 'Zoom Out',
accelerator: 'CommandOrControl+-',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('zoomOut');
}
}
},
{
label: 'Actual Size',
accelerator: 'CommandOrControl+0',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('zoomActualSize');
}
}
},
{
type: 'separator'
},
{
label: 'Toggle Tray Icon',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.send('toggletray');
}
}
},
{
label: 'Toggle Developer Tools',
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.toggleDevTools();
}
}
}
];
const helpSubmenu = [
{
label: `${appName} Website`,
click() {
shell.openExternal('https://zulip.org');
}
},
{
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)}`);
}
}
];
const darwinTpl = [
{
label: `${app.getName()}`,
submenu: [
{
label: 'Zulip desktop',
click() {
about();
class AppMenu {
getHistorySubmenu() {
return [{
label: 'Back',
accelerator: process.platform === 'darwin' ? 'Command+Left' : 'Alt+Left',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('back');
}
},
{
}
}, {
label: 'Forward',
accelerator: process.platform === 'darwin' ? 'Command+Right' : 'Alt+Right',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('forward');
}
}
}];
}
getViewSubmenu() {
return [{
label: 'Reload',
accelerator: 'CommandOrControl+R',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('reload-viewer');
}
}
}, {
label: 'Hard Reload',
accelerator: 'CommandOrControl+Shift+R',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('hard-reload');
}
}
}, {
type: 'separator'
}, {
role: 'togglefullscreen'
}, {
label: 'Zoom In',
accelerator: 'CommandOrControl+=',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomIn');
}
}
}, {
label: 'Zoom Out',
accelerator: 'CommandOrControl+-',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomOut');
}
}
}, {
label: 'Actual Size',
accelerator: 'CommandOrControl+0',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomActualSize');
}
}
}, {
type: 'separator'
}, {
label: 'Toggle Tray Icon',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.send('toggletray');
}
}
}, {
label: 'Toggle Sidebar',
accelerator: 'CommandOrControl+S',
click(item, focusedWindow) {
if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('showSidebar');
focusedWindow.webContents.send('toggle-sidebar', newValue);
ConfigUtil.setConfigItem('showSidebar', newValue);
}
}
}, {
label: 'Toggle DevTools for Zulip App',
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.toggleDevTools();
}
}
}, {
label: 'Toggle DevTools for Active Tab',
accelerator: process.platform === 'darwin' ? 'Alt+Command+U' : 'Ctrl+Shift+U',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('tab-devtools');
}
}
}];
}
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)}`);
}
}];
}
getWindowSubmenu(tabs, activeTabIndex) {
const initialSubmenu = [{
role: 'minimize'
}, {
role: 'close'
}];
if (tabs.length > 0) {
const ShortcutKey = process.platform === 'darwin' ? 'Cmd' : 'Ctrl';
initialSubmenu.push({
type: 'separator'
},
{
label: 'Change Zulip Server',
accelerator: 'Cmd+,',
click() {
addDomain();
});
for (let i = 0; i < tabs.length; i++) {
initialSubmenu.push({
label: tabs[i].webview.props.name,
accelerator: tabs[i].props.role === 'function' ? '' : `${ShortcutKey} + ${tabs[i].props.index + 1}`,
checked: tabs[i].props.index === activeTabIndex,
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('switch-server-tab', tabs[i].props.index);
}
},
type: 'radio'
});
}
}
return initialSubmenu;
}
getDarwinTpl(props) {
const {tabs, activeTabIndex} = props;
return [{
label: `${app.getName()}`,
submenu: [{
label: 'Zulip Desktop',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-about');
}
}
},
{
label: 'Keyboard shortcuts',
}, {
type: 'separator'
}, {
label: 'Settings',
accelerator: 'Cmd+,',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-settings');
}
}
}, {
label: 'Keyboard Shortcuts',
accelerator: 'Cmd+K',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('shortcut');
AppMenu.sendAction('shortcut');
}
}
},
{
}, {
type: 'separator'
},
{
}, {
label: 'Clear Cache',
click() {
clearCache();
AppMenu.clearCache();
}
},
{
}, {
label: 'Log Out',
accelerator: 'Cmd+L',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('log-out');
AppMenu.sendAction('log-out');
}
}
},
{
}, {
type: 'separator'
},
{
}, {
role: 'services',
submenu: []
},
{
}, {
type: 'separator'
},
{
}, {
role: 'hide'
},
{
}, {
role: 'hideothers'
},
{
}, {
role: 'unhide'
},
{
}, {
type: 'separator'
},
{
}, {
role: 'quit'
}
]
},
{
label: 'Edit',
submenu: [
{
}]
}, {
label: 'Edit',
submenu: [{
role: 'undo'
},
{
}, {
role: 'redo'
},
{
}, {
type: 'separator'
},
{
}, {
role: 'cut'
},
{
}, {
role: 'copy'
},
{
}, {
role: 'paste'
},
{
}, {
role: 'pasteandmatchstyle'
},
{
}, {
role: 'delete'
},
{
}, {
role: 'selectall'
}
]
},
{
label: 'View',
submenu: viewSubmenu
},
{
role: 'window',
submenu: [
{
role: 'minimize'
},
{
role: 'close'
},
{
type: 'separator'
},
{
role: 'front'
}
]
},
{
role: 'help',
submenu: helpSubmenu
}]
}, {
label: 'View',
submenu: this.getViewSubmenu()
}, {
label: 'History',
submenu: this.getHistorySubmenu()
}, {
label: 'Window',
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
}, {
role: 'help',
submenu: this.getHelpSubmenu()
}];
}
];
const otherTpl = [
{
label: 'File',
submenu: [
{
label: 'Zulip desktop',
click() {
about();
getOtherTpl(props) {
const {tabs, activeTabIndex} = props;
return [{
label: 'File',
submenu: [{
label: 'Zulip Desktop',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-about');
}
}
},
{
}, {
type: 'separator'
},
{
label: 'Change Zulip Server',
}, {
label: 'Settings',
accelerator: 'Ctrl+,',
click() {
addDomain();
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-settings');
}
}
},
{
}, {
type: 'separator'
},
{
label: 'Keyboard shortcuts',
}, {
label: 'Keyboard Shortcuts',
accelerator: 'Ctrl+K',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('shortcut');
AppMenu.sendAction('shortcut');
}
}
},
{
}, {
type: 'separator'
},
{
}, {
label: 'Clear Cache',
click() {
clearCache();
AppMenu.clearCache();
}
},
{
}, {
label: 'Log Out',
accelerator: 'Ctrl+L',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('log-out');
AppMenu.sendAction('log-out');
}
}
},
{
}, {
type: 'separator'
},
{
role: 'quit'
}
]
},
{
label: 'Edit',
submenu: [
{
}, {
role: 'quit',
accelerator: 'Ctrl+Q'
}]
}, {
label: 'Edit',
submenu: [{
role: 'undo'
},
{
}, {
role: 'redo'
},
{
}, {
type: 'separator'
},
{
}, {
role: 'cut'
},
{
}, {
role: 'copy'
},
{
}, {
role: 'paste'
},
{
}, {
role: 'pasteandmatchstyle'
},
{
}, {
role: 'delete'
},
{
}, {
type: 'separator'
},
{
}, {
role: 'selectall'
}
]
},
{
label: 'View',
submenu: viewSubmenu
},
{
role: 'help',
submenu: helpSubmenu
}]
}, {
label: 'View',
submenu: this.getViewSubmenu()
}, {
label: 'History',
submenu: this.getHistorySubmenu()
}, {
label: 'Window',
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
}, {
role: 'help',
submenu: this.getHelpSubmenu()
}];
}
];
const tpl = process.platform === 'darwin' ? darwinTpl : otherTpl;
static sendAction(action, ...params) {
const win = BrowserWindow.getAllWindows()[0];
module.exports = electron.Menu.buildFromTemplate(tpl);
if (process.platform === 'darwin') {
win.restore();
}
win.webContents.send(action, ...params);
}
static clearCache() {
const win = BrowserWindow.getAllWindows()[0];
const ses = win.webContents.session;
ses.clearCache(() => {
dialog.showMessageBox({type: 'info', buttons: [], message: 'Cache cleared!'});
});
}
setMenu(props) {
const tpl = process.platform === 'darwin' ? this.getDarwinTpl(props) : this.getOtherTpl(props);
const menu = Menu.buildFromTemplate(tpl);
Menu.setApplicationMenu(menu);
}
}
module.exports = new AppMenu();

34
app/main/startup.js Normal file
View File

@@ -0,0 +1,34 @@
'use strict';
const { app } = require('electron');
const AutoLaunch = require('auto-launch');
const isDev = require('electron-is-dev');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
const setAutoLaunch = AutoLaunchValue => {
// Don't run this in development
if (isDev) {
return;
}
// On Mac, work around a bug in auto-launch where it opens a Terminal window
// See https://github.com/Teamwork/node-auto-launch/issues/28#issuecomment-222194437
const appPath = process.platform === 'darwin' ? app.getPath('exe').replace(/\.app\/Content.*/, '.app') : undefined; // Use the default
const ZulipAutoLauncher = new AutoLaunch({
name: 'Zulip',
path: appPath,
isHidden: false
});
const autoLaunchOption = ConfigUtil.getConfigItem('startAtLogin', AutoLaunchValue);
if (autoLaunchOption) {
ZulipAutoLauncher.enable();
} else {
ZulipAutoLauncher.disable();
}
};
module.exports = {
setAutoLaunch
};

View File

@@ -1,100 +0,0 @@
'use strict';
const path = require('path');
const electron = require('electron');
const ipc = require('electron').ipcMain;
const APP_ICON = path.join(__dirname, '../resources', 'Icon');
const iconPath = () => {
return APP_ICON + (process.platform === 'win32' ? '.ico' : '.png');
};
let domainWindow;
let aboutWindow;
function onClosed() {
// Dereference the window
domainWindow = null;
aboutWindow = null;
}
// Change Zulip server Window
function createdomainWindow() {
const domainwin = new electron.BrowserWindow({
title: 'Switch Server',
frame: false,
height: 300,
resizable: false,
width: 400,
show: false,
icon: iconPath()
});
const domainURL = 'file://' + path.join(__dirname, '../renderer', 'pref.html');
domainwin.loadURL(domainURL);
domainwin.on('closed', onClosed);
return domainwin;
}
// Call this window onClick addDomain in tray
function addDomain() {
domainWindow = createdomainWindow();
domainWindow.once('ready-to-show', () => {
domainWindow.show();
});
setTimeout(() => {
if (domainWindow !== null) {
if (!domainWindow.isDestroyed()) {
domainWindow.destroy();
}
}
}, 15000);
}
// About window
function createAboutWindow() {
const aboutwin = new electron.BrowserWindow({
width: 500,
height: 500,
title: 'About Zulip Desktop',
show: false,
center: true,
fullscreen: false,
fullscreenable: false,
resizable: false
});
const aboutURL = 'file://' + path.join(__dirname, '../renderer', 'about.html');
aboutwin.loadURL(aboutURL);
aboutwin.on('closed', onClosed);
// Stop page to update it's title
aboutwin.on('page-title-updated', e => {
e.preventDefault();
});
aboutwin.on('closed', onClosed);
return aboutwin;
}
// Call this onClick About in tray
function about() {
aboutWindow = createAboutWindow();
aboutWindow.once('ready-to-show', () => {
aboutWindow.show();
});
}
ipc.on('trayabout', event => {
if (event) {
about();
}
});
ipc.on('traychangeserver', event => {
if (event) {
addDomain();
}
});
module.exports = {
addDomain,
about
};

View File

@@ -1,13 +1,13 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "0.5.10",
"version": "1.4.0",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"email": "<svnitakash@gmail.com>",
"copyright": "©2017 Kandra Labs, Inc.",
"author": {
"name": "Akash Nimare",
"name": "Kandra Labs, Inc.",
"email": "svnitakash@gmail.com"
},
"repository": {
@@ -27,16 +27,16 @@
"InstantMessaging"
],
"dependencies": {
"electron-config":"0.2.1",
"electron-debug": "1.1.0",
"electron-is-dev": "0.1.2",
"electron-localshortcut": "1.0.0",
"electron-log": "1.3.0",
"electron-spellchecker": "1.0.5",
"electron-updater": "1.11.2",
"https": "^1.0.0",
"electron-debug": "1.4.0",
"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",
"node-json-db": "0.7.3",
"request": "2.79.0",
"wurl": "2.1.0"
"request": "2.81.0",
"wurl": "2.5.0",
"electron-window-state": "4.1.1",
"auto-launch": "5.0.1"
}
}

View File

@@ -1,143 +1,321 @@
* {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI",
Helvetica, Arial, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol";
-webkit-font-smoothing: antialiased;
color: #444;
/*******************
* General rules *
*******************/
html,
body {
height: 100%;
margin: 0;
cursor: default;
user-select: none;
}
#content {
display: flex;
height: 100%;
background: #eee url(../img/ic_loading.gif) no-repeat;
background-size: 60px 60px;
background-position: center;
}
#sidebar {
background: #222c31;
width: 54px;
padding: 27px 0 20px 0;
justify-content: space-between;
display: flex;
flex-direction: column;
-webkit-app-region: drag;
overflow: hidden;
}
@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');
}
/*******************
* Left Sidebar *
*******************/
#tabs-container {
display: flex;
align-items: center;
flex-direction: column;
}
.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;
}
.action-button {
display: flex;
flex-direction: column;
align-items: center;
padding: 10px;
}
.action-button:hover {
cursor: pointer;
}
.action-button i {
color: #6c8592;
font-size: 28px;
}
.action-button:hover i {
color: #98a9b3;
}
.tab:first-child {
margin-top: 8px;
}
.tab {
position: relative;
margin: 2px 0;
cursor: pointer;
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
}
.tab .server-icons {
border-radius: 50%;
width: 30px;
padding: 3px;
height: 30px;
vertical-align: top;
}
body {
margin: 0;
background-image: url(../img/topography.png);
}
input,
button {
font-size: 1em;
}
header {
.tab .server-tab {
width: 100%;
height: 35px;
position: relative;
margin: 5px 0 2px 0;
z-index: 11;
line-height: 31px;
color: #eee;
text-align: center;
margin: 0px 0px 30px 0px;
overflow: hidden;
opacity: 0.6;
padding: 2px 0;
}
header img {
width: 50px;
.tab .server-tab:hover {
opacity: 0.8;
}
header h1 {
display: inline-block;
margin: 10px 0px 10px 10px;
font-weight: 500;
.tab.functional-tab {
height: 46px;
padding: 0;
}
section.server {
display: inline-block;
padding: 50px;
margin-top: calc(50vh - 135px);
text-align: left;
box-shadow: 0px 0px 100px rgba(0,0,0,0.15);
background-color: #fff;
animation-name: pulse;
animation-duration: 2s;
animation-iteration-count: infinite;
.tab.functional-tab.active .server-tab {
padding: 2px 0;
height: 40px;
background-color: rgba(255, 255, 255, 0.25);
}
section.shake {
animation-name: shake;
animation-duration: 0.5s;
animation-iteration-count: 1;
.tab.functional-tab .server-tab i {
font-size: 28px;
line-height: 36px;
}
form label {
.tab.active .server-tab {
opacity: 1;
background-color: #648478;
}
.tab .server-tab-badge.active {
border-radius: 9px;
min-width: 11px;
padding: 0 3px;
height: 17px;
background-color: #f44336;
font-size: 10px;
font-family: sans-serif;
position: absolute;
right: 5px;
z-index: 15;
top: 6px;
float: right;
color: #fff;
text-align: center;
line-height: 17px;
display: block;
right: 0;
}
font-weight: 600;
.tab .server-tab-badge {
display: none;
}
.tab .server-tab-badge.close-button {
width: 16px;
padding: 0;
}
.tab .server-tab-badge.close-button i {
font-size: 13px;
line-height: 17px;
}
#add-tab {
display: flex;
align-items: center;
flex-direction: column;
}
.tab .server-tab-shortcut {
color: #648478;
font-size: 12px;
text-align: center;
font-family: sans-serif;
margin-bottom: 5px;
}
form input[type=text] {
width: 300px;
border: 2px solid #ccc;
padding: 10px 10px;
outline: none;
/*******************
* Webview Area *
*******************/
transition: all 0.2s ease;
#webviews-container {
display: flex;
height: 100%;
width: 100%;
}
form input[type=text]:hover,
form input[type=text]:focus {
border-color: #aaa;
}
form button {
padding: 10px 10px;
background-color: #52c2af;
color: #fff;
border: 2px solid #52c2af;
outline: none;
cursor: pointer;
transition: all 0.2s ease;
}
form button:hover {
background-color: #30b09a;
border-color: #30b09a;
}
form button:active {
background-color: #14957f;
border-color: #14957f;
}
form #error {
margin: 5px 0px 0px;
font-size: 0.8em;
font-weight: 600;
color: rgb(201, 107, 107);
opacity: 0;
transform: translateY(-10px);
height: 15px;
transition: all 0.2s ease;
}
form #error.show {
webview {
opacity: 1;
transform: translateY(0px);
transition: opacity 0.3s ease-in;
flex-grow: 1;
}
/* -- generic components -- */
.center {
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:focus {
outline: 0px solid transparent;
}
/* Tooltip styling */
#reload-tooltip,
#setting-tooltip {
font-family: sans-serif;
background: #222c31;
margin-left: 68px;
padding: 6px 8px;
position: absolute;
margin-top: 0px;
z-index: 1000;
color: white;
border-radius: 4px;
text-align: center;
width: 55px;
font-size: 14px;
}
@keyframes pulse {
0% { box-shadow: 0px 0px 100px rgba(0,0,0,0.15); }
50% { box-shadow: 0px 0px 100px rgba(0,0,0,0.30); }
100% { box-shadow: 0px 0px 100px rgba(0,0,0,0.15); }
#reload-tooltip:after,
#setting-tooltip:after {
content: " ";
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 8px solid #222c31;
position: absolute;
top: 7px;
right: 68px;
}
@keyframes shake {
10%, 90% { transform: translate3d(-1px, 0, 0); }
20%, 80% { transform: translate3d(2px, 0, 0); }
30%, 50%, 70% { transform: translate3d(-4px, 0, 0); }
40%, 60% { transform: translate3d(4px, 0, 0); }
#collapse-button {
bottom: 30px;
left: 20px;
position: absolute;
width: 24px;
height: 24px;
background: #222c31;
border-radius: 20px;
cursor: pointer;
box-shadow: #999 1px 1px;
}
#collapse-button i {
color: #efefef;
}
#main-container {
display: flex;
height: 100%;
width: 100%;
position: relative;
}
.hidden {
display: none !important;
}
/* Full screen Popup container */
.popup .popuptext {
visibility: hidden;
background-color: #555;
color: #fff;
text-align: center;
border-radius: 6px;
padding: 9px 0;
position: absolute;
z-index: 1000;
font-family: arial;
width: 240px;
top: 15px;
height: 20px;
left: 43%;
}
.popup .show {
visibility: visible;
animation: cssAnimation 0s ease-in 5s forwards;
animation-fill-mode: forwards;
}
@keyframes cssAnimation {
from {
opacity: 0;
}
to {
width: 0;
height: 0;
overflow: hidden;
opacity: 1;
}
}

View File

@@ -0,0 +1,42 @@
html, body {
margin: 0;
cursor: default;
font-size: 14px;
color: #333;
background: #fff;
user-select:none;
}
#content {
display: flex;
flex-direction: column;
font-family: "Trebuchet MS", Helvetica, sans-serif;
margin: 100px 200px;
text-align: center;
}
#title {
font-size: 24px;
font-weight: bold;
margin: 20px 0;
}
#description {
font-size: 16px;
}
#reconnect {
font-size: 16px;
background: #009688;
color: #fff;
width: 84px;
height: 32px;
border-radius: 5px;
line-height: 32px;
margin: 20px auto 0;
cursor: pointer;
}
#reconnect:hover {
opacity: 0.8;
}

View File

@@ -1,74 +0,0 @@
body{
background-color: #6BB6C7;
}
.form {
position: absolute;
top: 35%;
width: 300px;
left: 9%;
}
.close {
background: transparent url('../img/close.png') no-repeat 4px 4px;
background-size: 24px 24px;
cursor: pointer;
display: inline-block;
height: 32px;
position: absolute;
right: 6px;
text-indent: -10000px;
top: 6px;
width: 32px;
z-index: 1;
-webkit-app-region: no-drag;
}
input[type="text"] {
display: block;
margin: 0;
width: 100%;
font-family: sans-serif;
font-size: 18px;
appearance: none;
box-shadow: none;
border-radius: none;
color: #646464;
}
input[type="text"]:focus {
outline: none;
}
.form input[type="text"] {
padding: 10px;
border: solid 1px #dcdcdc;
transition: box-shadow 0.3s, border 0.3s;
}
.form input[type="text"]:focus,
.form input[type="text"].focus {
border: solid 1px #707070;
box-shadow: 0 0 5px 1px #969696;
}
button {
border: none;
color: #fff;
padding: 12px 32px;
text-align: center;
text-decoration: none;
margin-left: 107px;
margin-top: 24px;
display: inline-block;
font-size: 16px;
background: #137b86;
}
button:focus {
outline: 0;
}
#urladded {
font-size: 20px;
position: absolute;
font-family: 'opensans';
top: -61%;
left: 25%;
text-align: center;
}

View File

@@ -0,0 +1,250 @@
html,
body {
height: 100%;
margin: 0;
cursor: default;
user-select: none;
font-size: 14px;
color: #333;
background: #efefef;
}
@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');
}
.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;
}
#content {
display: flex;
height: 100%;
font-family: sans-serif;
}
#sidebar {
width: 80px;
padding: 30px;
display: flex;
flex-direction: column;
font-size: 16px;
}
#nav-container {
padding: 20px 0;
}
.nav {
padding: 5px 0;
color: #999;
cursor: pointer;
}
.nav.active {
color: #464e5a;
cursor: default;
position: relative;
}
.nav.active::before {
background: #464e5a;
width: 3px;
height: 16px;
position: absolute;
left: -8px;
content: '';
}
#settings-header {
font-size: 22px;
color: #5c6166;
}
#settings-container {
width: 100%;
display: flex;
padding: 30px;
overflow-y: scroll;
}
#new-server-container {
opacity: 1;
transition: opacity 0.3s;
}
.title {
padding: 4px 0 6px 0;
font-weight: bold;
color: #1e1e1e;
}
.sub-title {
padding: 4px 0 6px 0;
font-weight: bold;
color: #616161;
}
img.server-info-icon {
width: 36px;
height: 36px;
padding: 4px;
}
.server-info-left {
margin: 10px 20px 0 0;
}
.server-info-right {
flex-grow: 1;
margin-right: 10px;
}
.server-info-row {
display: flex;
margin: 8px 0 0 0;
}
.server-info-alias {
font-weight: bold;
cursor: pointer;
}
.setting-input-key {
font-size: 14px;
height: 27px;
line-height: 27px;
font-weight: bold;
background: transparent;
flex-wrap: nowrap;
margin-right: 10px;
}
.setting-input-value {
flex-grow: 1;
font-size: 14px;
height: 24px;
border: none;
border-bottom: #ededed 1px solid;
outline-width: 0;
background: transparent;
max-width: 500px;
}
.setting-input-value:focus {
border-bottom: #7cb980 1px solid;
}
.setting-block {
width: 100%;
}
.actions-container {
display: flex;
font-size: 14px;
color: #235d3a;
vertical-align: middle;
margin: 10px 0;
flex-wrap: wrap;
}
.action {
display: flex;
align-items: center;
padding: 0 10px;
border-radius: 2px;
margin-right: 10px;
}
.action i {
margin-right: 5px;
font-size: 18px;
line-height: 27px;
}
.settings-pane {
flex-grow: 1;
}
.action:hover {
cursor: pointer;
}
.action.disabled:hover {
cursor: default;
}
.action.disabled {
color: #999;
}
.settings-card {
display: flex;
flex-wrap: wrap;
padding: 12px 30px;
margin: 10px 0 20px 0;
background: #fff;
border-radius: 2px;
width: 540px;
box-shadow: 1px 2px 4px #bcbcbc;
}
.hidden {
display: none;
margin: 5px !important;
}
.red {
color: #ef5350;
background: #ffebee;
border: 1px solid #ef5350;
}
.green {
color: #388E3C;
background: #E8F5E9;
border: 1px solid #388E3C;
}
.grey {
color: #9E9E9E;
background: #FAFAFA;
border: 1px solid #9E9E9E;
}
.setting-row {
display: flex;
justify-content: space-between;
width: 100%;
margin: 6px 0;
}
.code {
font-family: Courier New, Courier, monospace;
}
i.open-tab-button {
padding: 0 5px;
font-size: 18px;
cursor: pointer;
}

View File

@@ -1 +1,9 @@
/* We'll be overriding default styling so that app look more native * /
/* Override css rules */
.portico-wrap>.header {
display: none;
}
.portico-container>.footer {
display: none;
}

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

BIN
app/renderer/img/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -1,30 +0,0 @@
<!DOCTYPE html>
<html lang="en" class="responsive desktop">
<!--<![endif]-->
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width">
<title>Login - Zulip</title>
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen">
</head>
<body>
<div class="center">
<section class="server">
<header>
<img src="../resources/zulip.png" id="logo"/>
<h1>Zulip Login</h1>
</header>
<div class="container">
<form id="frm-signInForm" class="form-large" onsubmit="addDomain(); return false">
<label for="url">
Zulip Server URL
</label>
<input type="text" id="url" autofocus="autofocus" spellcheck="false" placeholder="Server URL">
<button type="submit" id="main" class="btn-primary btn-large" value="Submit">Connect</button>
<p id="error"></p>
</form>
</div>
</section>
</div>
</body>
</html>

View File

@@ -0,0 +1,11 @@
'use strict';
class BaseComponent {
generateNodeFromTemplate(template) {
const wrapper = document.createElement('div');
wrapper.innerHTML = template;
return wrapper.firstElementChild;
}
}
module.exports = BaseComponent;

View File

@@ -0,0 +1,43 @@
'use strict';
const Tab = require(__dirname + '/../components/tab.js');
class FunctionalTab extends Tab {
template() {
return `<div class="tab functional-tab">
<div class="server-tab-badge close-button">
<i class="material-icons">close</i>
</div>
<div class="server-tab">
<i class="material-icons">${this.props.materialIcon}</i>
</div>
</div>`;
}
init() {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$el);
this.$closeButton = this.$el.getElementsByClassName('server-tab-badge')[0];
this.registerListeners();
}
registerListeners() {
super.registerListeners();
this.$el.addEventListener('mouseover', () => {
this.$closeButton.classList.add('active');
});
this.$el.addEventListener('mouseout', () => {
this.$closeButton.classList.remove('active');
});
this.$closeButton.addEventListener('click', e => {
this.props.onDestroy();
e.stopPropagation();
});
}
}
module.exports = FunctionalTab;

View File

@@ -0,0 +1,58 @@
'use strict';
const Tab = require(__dirname + '/../components/tab.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js');
const {ipcRenderer} = require('electron');
class ServerTab extends Tab {
template() {
return `<div class="tab">
<div class="server-tab-badge"></div>
<div class="server-tab">
<img class="server-icons" src='${this.props.icon}'/>
</div>
<div class="server-tab-shortcut">${this.generateShortcutText()}</div>
</div>`;
}
init() {
super.init();
this.$badge = this.$el.getElementsByClassName('server-tab-badge')[0];
}
updateBadge(count) {
if (count > 0) {
const formattedCount = count > 999 ? '1K+' : count;
this.$badge.innerHTML = formattedCount;
this.$badge.classList.add('active');
} else {
this.$badge.classList.remove('active');
}
}
generateShortcutText() {
// Only provide shortcuts for server [0..10]
if (this.props.index >= 10) {
return '';
}
const shownIndex = this.props.index + 1;
let shortcutText = '';
if (SystemUtil.getOS() === 'Mac') {
shortcutText = `${shownIndex}`;
} else {
shortcutText = `Ctrl+${shownIndex}`;
}
ipcRenderer.send('register-server-tab-shortcut', shownIndex);
return shortcutText;
}
}
module.exports = ServerTab;

View File

@@ -0,0 +1,46 @@
'use strict';
const BaseComponent = require(__dirname + '/../components/base.js');
class Tab extends BaseComponent {
constructor(props) {
super();
this.props = props;
this.webview = this.props.webview;
this.init();
}
init() {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$el);
this.registerListeners();
}
registerListeners() {
this.$el.addEventListener('click', this.props.onClick);
}
isLoading() {
return this.webview.isLoading;
}
activate() {
this.$el.classList.add('active');
this.webview.load();
}
deactivate() {
this.$el.classList.remove('active');
this.webview.hide();
}
destroy() {
this.$el.parentNode.removeChild(this.$el);
this.webview.$el.parentNode.removeChild(this.webview.$el);
}
}
module.exports = Tab;

View File

@@ -0,0 +1,183 @@
'use strict';
const path = require('path');
const fs = require('fs');
const DomainUtil = require(__dirname + '/../utils/domain-util.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js');
const LinkUtil = require(__dirname + '/../utils/link-util.js');
const {shell} = require('electron').remote;
const BaseComponent = require(__dirname + '/../components/base.js');
class WebView extends BaseComponent {
constructor(props) {
super();
this.props = props;
this.zoomFactor = 1.0;
this.loading = false;
this.badgeCount = 0;
}
template() {
return `<webview
class="disabled"
src="${this.props.url}"
${this.props.nodeIntegration ? 'nodeIntegration' : ''}
disablewebsecurity
${this.props.preload ? 'preload="js/preload.js"' : ''}
partition="persist:webviewsession"
webpreferences="allowRunningInsecureContent, javascript=yes">
</webview>`;
}
init() {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$el);
this.registerListeners();
}
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);
}
});
this.$el.addEventListener('page-title-updated', event => {
const {title} = event;
this.badgeCount = this.getBadgeCount(title);
this.props.onTitleChange();
});
this.$el.addEventListener('dom-ready', () => {
if (this.props.role === 'server') {
this.$el.classList.add('onload');
}
this.show();
});
this.$el.addEventListener('did-fail-load', event => {
const {errorDescription} = event;
const hasConnectivityErr = (SystemUtil.connectivityERR.indexOf(errorDescription) >= 0);
if (hasConnectivityErr) {
console.error('error', errorDescription);
this.props.onNetworkError();
}
});
this.$el.addEventListener('did-start-loading', () => {
let userAgent = SystemUtil.getUserAgent();
if (!userAgent) {
SystemUtil.setUserAgent(this.$el.getUserAgent());
userAgent = SystemUtil.getUserAgent();
}
this.$el.setUserAgent(userAgent);
});
}
getBadgeCount(title) {
const messageCountInTitle = (/\(([0-9]+)\)/).exec(title);
return messageCountInTitle ? Number(messageCountInTitle[1]) : 0;
}
show() {
// Do not show WebView if another tab was selected and this tab should be in background.
if (!this.props.isActive()) {
return;
}
this.$el.classList.remove('disabled');
setTimeout(() => {
if (this.props.role === 'server') {
this.$el.classList.remove('onload');
}
}, 1000);
this.focus();
this.loading = false;
this.props.onTitleChange();
// Injecting preload css in webview to override some css rules
this.$el.insertCSS(fs.readFileSync(path.join(__dirname, '/../../css/preload.css'), 'utf8'));
}
focus() {
// focus Webview and it's contents when Window regain focus.
const webContents = this.$el.getWebContents();
if (webContents && !webContents.isFocused()) {
this.$el.focus();
webContents.focus();
}
}
hide() {
this.$el.classList.add('disabled');
}
load() {
if (this.$el) {
this.show();
} else {
this.init();
}
}
zoomIn() {
this.zoomFactor += 0.1;
this.$el.setZoomFactor(this.zoomFactor);
}
zoomOut() {
this.zoomFactor -= 0.1;
this.$el.setZoomFactor(this.zoomFactor);
}
zoomActualSize() {
this.zoomFactor = 1.0;
this.$el.setZoomFactor(this.zoomFactor);
}
logOut() {
this.$el.executeJavaScript('logout()');
}
showShortcut() {
this.$el.executeJavaScript('shortcut()');
}
openDevTools() {
this.$el.openDevTools();
}
back() {
if (this.$el.canGoBack()) {
this.$el.goBack();
}
}
forward() {
if (this.$el.canGoForward()) {
this.$el.goForward();
}
}
reload() {
this.hide();
this.$el.reload();
}
send(...param) {
this.$el.send(...param);
}
}
module.exports = WebView;

View File

@@ -1,80 +0,0 @@
const {app} = require('electron').remote;
const ipcRenderer = require('electron').ipcRenderer;
const JsonDB = require('node-json-db');
const request = require('request');
const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
window.addDomain = function () {
const el = sel => {
return document.querySelector(sel);
};
const $el = {
error: el('#error'),
main: el('#main'),
section: el('section')
};
const event = sel => {
return {
on: (event, callback) => {
document.querySelector(sel).addEventListener(event, callback);
}
};
};
const displayError = msg => {
$el.error.innerText = msg;
$el.error.classList.add('show');
$el.section.classList.add('shake');
};
let newDomain = document.getElementById('url').value;
newDomain = newDomain.replace(/^https?:\/\//, '');
if (newDomain === '') {
displayError('Please input a valid URL.');
} else {
el('#main').innerHTML = 'Checking...';
if (newDomain.indexOf('localhost:') >= 0) {
const domain = 'http://' + newDomain;
const checkDomain = domain + '/static/audio/zulip.ogg';
request(checkDomain, (error, response) => {
if (!error && response.statusCode !== 404) {
document.getElementById('main').innerHTML = 'Connect';
db.push('/domain', domain);
ipcRenderer.send('new-domain', domain);
} else {
$el.main.innerHTML = 'Connect';
displayError('Not a valid Zulip local server');
}
});
// });
} else {
const domain = 'https://' + newDomain;
const checkDomain = domain + '/static/audio/zulip.ogg';
request(checkDomain, (error, response) => {
if (!error && response.statusCode !== 404) {
$el.main.innerHTML = 'Connect';
db.push('/domain', domain);
ipcRenderer.send('new-domain', domain);
} else if (error.toString().indexOf('Error: self signed certificate') >= 0) {
$el.main.innerHTML = 'Connect';
ipcRenderer.send('certificate-err', domain);
} else {
$el.main.innerHTML = 'Connect';
displayError('Not a valid Zulip server');
}
});
}
}
event('#url').on('input', () => {
el('#error').classList.remove('show');
});
event('section').on('animationend', function () {
this.classList.remove('shake');
});
};

367
app/renderer/js/main.js Normal file
View File

@@ -0,0 +1,367 @@
'use strict';
require(__dirname + '/js/tray.js');
const {ipcRenderer, remote} = require('electron');
const {session} = remote;
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');
class ServerManagerView {
constructor() {
this.$addServerButton = document.getElementById('add-tab');
this.$tabsContainer = document.getElementById('tabs-container');
const $actionsContainer = document.getElementById('actions-container');
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
this.$webviewsContainer = document.getElementById('webviews-container');
this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip');
this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip');
this.$sidebar = document.getElementById('sidebar');
this.$fullscreenPopup = document.getElementById('fullscreen-popup');
this.$fullscreenEscapeKey = process.platform === 'darwin' ? '^⌘F' : 'F11';
this.$fullscreenPopup.innerHTML = `Press ${this.$fullscreenEscapeKey} to exit full screen`;
this.activeTabIndex = -1;
this.tabs = [];
this.functionalTabs = {};
}
init() {
this.loadProxy().then(() => {
this.initSidebar();
this.initTabs();
this.initActions();
this.registerIpcs();
});
}
loadProxy() {
return new Promise(resolve => {
const proxyEnabled = ConfigUtil.getConfigItem('useProxy', false);
if (proxyEnabled) {
session.fromPartition('persist:webviewsession').setProxy({
pacScript: ConfigUtil.getConfigItem('proxyPAC', ''),
proxyRules: ConfigUtil.getConfigItem('proxyRules', ''),
proxyBypassRules: ConfigUtil.getConfigItem('proxyBypass', '')
}, resolve);
} else {
session.fromPartition('persist:webviewsession').setProxy({
pacScript: '',
proxyRules: '',
proxyBypassRules: ''
}, resolve);
}
});
}
initSidebar() {
const showSidebar = ConfigUtil.getConfigItem('showSidebar', true);
this.toggleSidebar(showSidebar);
}
initTabs() {
const servers = DomainUtil.getDomains();
if (servers.length > 0) {
for (let i = 0; i < servers.length; i++) {
this.initServer(servers[i], i);
DomainUtil.updateSavedServer(servers[i].url, i);
this.activateTab(i);
}
this.activateTab(0);
} else {
this.openSettings('Servers');
}
ipcRenderer.send('local-shortcuts', true);
}
initServer(server, index) {
this.tabs.push(new ServerTab({
role: 'server',
icon: server.icon,
$root: this.$tabsContainer,
onClick: this.activateTab.bind(this, index),
index,
webview: new WebView({
$root: this.$webviewsContainer,
index,
url: server.url,
name: server.alias,
isActive: () => {
return index === this.activeTabIndex;
},
onNetworkError: this.openNetworkTroubleshooting.bind(this),
onTitleChange: this.updateBadge.bind(this),
nodeIntegration: false,
preload: true
})
}));
}
initActions() {
this.$reloadButton.addEventListener('click', () => {
this.tabs[this.activeTabIndex].webview.reload();
});
this.$addServerButton.addEventListener('click', () => {
this.openSettings('Servers');
});
this.$settingsButton.addEventListener('click', () => {
this.openSettings('General');
});
this.$reloadButton.addEventListener('mouseover', () => {
this.$reloadTooltip.removeAttribute('style');
});
this.$reloadButton.addEventListener('mouseout', () => {
this.$reloadTooltip.style.display = 'none';
});
this.$settingsButton.addEventListener('mouseover', () => {
this.$settingsTooltip.removeAttribute('style');
});
this.$settingsButton.addEventListener('mouseout', () => {
this.$settingsTooltip.style.display = 'none';
});
}
openFunctionalTab(tabProps) {
if (this.functionalTabs[tabProps.name] !== undefined) {
this.activateTab(this.functionalTabs[tabProps.name]);
return;
}
this.functionalTabs[tabProps.name] = this.tabs.length;
this.tabs.push(new FunctionalTab({
role: 'function',
materialIcon: tabProps.materialIcon,
$root: this.$tabsContainer,
index: this.functionalTabs[tabProps.name],
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],
url: tabProps.url,
name: tabProps.name,
isActive: () => {
return this.functionalTabs[tabProps.name] === this.activeTabIndex;
},
onNetworkError: this.openNetworkTroubleshooting.bind(this),
onTitleChange: this.updateBadge.bind(this),
nodeIntegration: true,
preload: false
})
}));
this.activateTab(this.functionalTabs[tabProps.name]);
}
openSettings(nav = 'General') {
this.openFunctionalTab({
name: 'Settings',
materialIcon: 'settings',
url: `file://${__dirname}/preference.html#${nav}`
});
this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav);
}
openAbout() {
this.openFunctionalTab({
name: 'About',
materialIcon: 'sentiment_very_satisfied',
url: `file://${__dirname}/about.html`
});
}
openNetworkTroubleshooting() {
this.openFunctionalTab({
name: 'Network Troubleshooting',
materialIcon: 'network_check',
url: `file://${__dirname}/network.html`
});
}
activateTab(index, hideOldTab = true) {
if (this.tabs[index].webview.loading) {
return;
}
if (this.activeTabIndex !== -1) {
if (this.activeTabIndex === index) {
return;
} else if (hideOldTab) {
this.tabs[this.activeTabIndex].deactivate();
}
}
this.activeTabIndex = index;
this.tabs[index].activate();
ipcRenderer.send('update-menu', {
tabs: this.tabs,
activeTabIndex: this.activeTabIndex
});
}
destroyTab(name, index) {
if (this.tabs[index].webview.loading) {
return;
}
this.tabs[index].destroy();
delete this.tabs[index];
delete this.functionalTabs[name];
// Issue #188: If the functional tab was not focused, do not activate another tab.
if (this.activeTabIndex === index) {
this.activateTab(0, false);
}
}
destroyView() {
// Clear global variables
this.activeTabIndex = -1;
this.tabs = [];
this.functionalTabs = {};
// Clear DOM elements
this.$tabsContainer.innerHTML = '';
this.$webviewsContainer.innerHTML = '';
// Destroy shortcuts
ipcRenderer.send('local-shortcuts', false);
}
reloadView() {
this.destroyView();
this.initTabs();
}
updateBadge() {
let messageCountAll = 0;
for (let i = 0; i < this.tabs.length; i++) {
if (this.tabs[i] && this.tabs[i].updateBadge) {
const count = this.tabs[i].webview.badgeCount;
messageCountAll += count;
this.tabs[i].updateBadge(count);
}
}
ipcRenderer.send('update-badge', messageCountAll);
}
toggleSidebar(show) {
if (show) {
this.$sidebar.classList.remove('hidden');
} else {
this.$sidebar.classList.add('hidden');
}
}
registerIpcs() {
const webviewListeners = {
'webview-reload': 'reload',
back: 'back',
focus: 'focus',
forward: 'forward',
zoomIn: 'zoomIn',
zoomOut: 'zoomOut',
zoomActualSize: 'zoomActualSize',
'log-out': 'logOut',
shortcut: 'showShortcut',
'tab-devtools': 'openDevTools'
};
for (const key in webviewListeners) {
ipcRenderer.on(key, () => {
const activeWebview = this.tabs[this.activeTabIndex].webview;
if (activeWebview) {
activeWebview[webviewListeners[key]]();
}
});
}
ipcRenderer.on('open-settings', (event, settingNav) => {
this.openSettings(settingNav);
});
ipcRenderer.on('open-about', this.openAbout.bind(this));
ipcRenderer.on('reload-viewer', this.reloadView.bind(this));
ipcRenderer.on('hard-reload', () => {
ipcRenderer.send('reload-full-app');
});
ipcRenderer.on('switch-server-tab', (event, index) => {
this.activateTab(index);
});
ipcRenderer.on('reload-proxy', (event, showAlert) => {
this.loadProxy().then(() => {
if (showAlert) {
alert('Proxy settings saved!');
}
});
});
ipcRenderer.on('toggle-sidebar', (event, show) => {
this.toggleSidebar(show);
});
ipcRenderer.on('enter-fullscreen', () => {
this.$fullscreenPopup.classList.add('show');
this.$fullscreenPopup.classList.remove('hidden');
});
ipcRenderer.on('leave-fullscreen', () => {
this.$fullscreenPopup.classList.remove('show');
});
ipcRenderer.on('render-taskbar-icon', (event, messageCount) => {
// Create a canvas from unread messagecounts
function createOverlayIcon(messageCount) {
const canvas = document.createElement('canvas');
canvas.height = 128;
canvas.width = 128;
canvas.style.letterSpacing = '-5px';
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#f42020';
ctx.beginPath();
ctx.ellipse(64, 64, 64, 64, 0, 0, 2 * Math.PI);
ctx.fill();
ctx.textAlign = 'center';
ctx.fillStyle = 'white';
if (messageCount > 99) {
ctx.font = '65px Helvetica';
ctx.fillText('99+', 64, 85);
} else if (messageCount < 10) {
ctx.font = '90px Helvetica';
ctx.fillText(String(Math.min(99, messageCount)), 64, 96);
} else {
ctx.font = '85px Helvetica';
ctx.fillText(String(Math.min(99, messageCount)), 64, 90);
}
return canvas;
}
ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount));
});
}
}
window.onload = () => {
const serverManagerView = new ServerManagerView();
serverManagerView.init();
window.addEventListener('online', () => {
serverManagerView.reloadView();
});
};

View File

@@ -0,0 +1,22 @@
'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 SilentNotification extends NativeNotification {
constructor(title, opts) {
opts.silent = ConfigUtil.getConfigItem('silent') || false;
super(title, opts);
}
}
window.Notification = SilentNotification;

View File

@@ -0,0 +1,20 @@
'use strict';
const {ipcRenderer} = require('electron');
class NetworkTroubleshootingView {
constructor() {
this.$reconnectButton = document.getElementById('reconnect');
}
init() {
this.$reconnectButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
}
window.onload = () => {
const networkTroubleshootingView = new NetworkTroubleshootingView();
networkTroubleshootingView.init();
};

View File

@@ -0,0 +1,63 @@
'use strict';
const electron = require('electron');
const { app } = require('electron');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
let instance = null;
class BadgeSettings {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
return instance;
}
showBadgeCount(messageCount, mainWindow) {
if (process.platform === 'darwin') {
app.setBadgeCount(messageCount);
}
if (process.platform === 'win32') {
this.updateOverlayIcon(messageCount, mainWindow);
}
}
hideBadgeCount(mainWindow) {
if (process.platform === 'darwin') {
app.setBadgeCount(0);
}
if (process.platform === 'win32') {
mainWindow.setOverlayIcon(null, '');
}
}
updateBadge(badgeCount, mainWindow) {
if (ConfigUtil.getConfigItem('badgeOption', true)) {
this.showBadgeCount(badgeCount, mainWindow);
} else {
this.hideBadgeCount(mainWindow);
}
}
updateOverlayIcon(messageCount, mainWindow) {
if (!mainWindow.isFocused()) {
mainWindow.flashFrame(true);
}
if (messageCount === 0) {
mainWindow.setOverlayIcon(null, '');
} else {
mainWindow.webContents.send('render-taskbar-icon', messageCount);
}
}
updateTaskbarIcon(data, text, mainWindow) {
const img = electron.nativeImage.createFromDataURL(data);
mainWindow.setOverlayIcon(img, text);
}
}
module.exports = new BadgeSettings();

View File

@@ -0,0 +1,40 @@
'use strict';
const {ipcRenderer} = require('electron');
const BaseComponent = require(__dirname + '/../../components/base.js');
class BaseSection extends BaseComponent {
generateSettingOption(props) {
const {$element, value, clickHandler} = props;
$element.innerHTML = '';
const $optionControl = this.generateNodeFromTemplate(this.generateOptionTemplate(value));
$element.appendChild($optionControl);
$optionControl.addEventListener('click', clickHandler);
}
generateOptionTemplate(settingOption) {
if (settingOption) {
return `
<div class="action green">
<span>On</span>
</div>
`;
} else {
return `
<div class="action red">
<span>Off</span>
</div>
`;
}
}
reloadApp() {
ipcRenderer.send('forward-message', 'reload-viewer');
}
}
module.exports = BaseSection;

View File

@@ -0,0 +1,144 @@
'use strict';
const { ipcRenderer } = require('electron');
const BaseSection = require(__dirname + '/base-section.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
class GeneralSection extends BaseSection {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="settings-pane">
<div class="title">Appearance</div>
<div id="appearance-option-settings" class="settings-card">
<div class="setting-row" id="tray-option">
<div class="setting-description">Show app icon in system tray</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="sidebar-option">
<div class="setting-description">Show sidebar (<span class="code">CmdOrCtrl+S</span>)</div>
<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>
<div class="title">Desktop Notification</div>
<div class="settings-card">
<div class="setting-row" id="silent-option">
<div class="setting-description">Mute all sounds from Zulip (requires reload)</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">App Updates</div>
<div class="settings-card">
<div class="setting-row" id="betaupdate-option">
<div class="setting-description">Get beta updates</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Functionality</div>
<div class="settings-card">
<div class="setting-row" id="startAtLogin-option">
<div class="setting-description">Start app at login</div>
<div class="setting-control"></div>
</div>
</div>
</div>
`;
}
init() {
this.props.$root.innerHTML = this.template();
this.updateTrayOption();
this.updateBadgeOption();
this.updateUpdateOption();
this.updateSilentOption();
this.updateSidebarOption();
this.updateStartAtLoginOption();
}
updateTrayOption() {
this.generateSettingOption({
$element: document.querySelector('#tray-option .setting-control'),
value: ConfigUtil.getConfigItem('trayIcon', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('trayIcon');
ConfigUtil.setConfigItem('trayIcon', newValue);
ipcRenderer.send('forward-message', 'toggletray');
this.updateTrayOption();
}
});
}
updateBadgeOption() {
this.generateSettingOption({
$element: document.querySelector('#badge-option .setting-control'),
value: ConfigUtil.getConfigItem('badgeOption', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('badgeOption');
ConfigUtil.setConfigItem('badgeOption', newValue);
ipcRenderer.send('toggle-badge-option', newValue);
this.updateBadgeOption();
}
});
}
updateUpdateOption() {
this.generateSettingOption({
$element: document.querySelector('#betaupdate-option .setting-control'),
value: ConfigUtil.getConfigItem('betaUpdate', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('betaUpdate');
ConfigUtil.setConfigItem('betaUpdate', newValue);
this.updateUpdateOption();
}
});
}
updateSilentOption() {
this.generateSettingOption({
$element: document.querySelector('#silent-option .setting-control'),
value: ConfigUtil.getConfigItem('silent', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('silent', true);
ConfigUtil.setConfigItem('silent', newValue);
this.updateSilentOption();
}
});
}
updateSidebarOption() {
this.generateSettingOption({
$element: document.querySelector('#sidebar-option .setting-control'),
value: ConfigUtil.getConfigItem('showSidebar', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('showSidebar');
ConfigUtil.setConfigItem('showSidebar', newValue);
ipcRenderer.send('forward-message', 'toggle-sidebar', newValue);
this.updateSidebarOption();
}
});
}
updateStartAtLoginOption() {
this.generateSettingOption({
$element: document.querySelector('#startAtLogin-option .setting-control'),
value: ConfigUtil.getConfigItem('startAtLogin', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('startAtLogin');
ConfigUtil.setConfigItem('startAtLogin', newValue);
ipcRenderer.send('toggleAutoLauncher', newValue);
this.updateStartAtLoginOption();
}
});
}
}
module.exports = GeneralSection;

View File

@@ -0,0 +1,67 @@
'use strict';
const BaseComponent = require(__dirname + '/../../components/base.js');
class PreferenceNav extends BaseComponent {
constructor(props) {
super();
this.props = props;
this.navItems = ['General', 'Network', 'Servers'];
this.init();
}
template() {
let navItemsTemplate = '';
for (const navItem of this.navItems) {
navItemsTemplate += `<div class="nav" id="nav-${navItem}">${navItem}</div>`;
}
return `
<div>
<div id="settings-header">Settings</div>
<div id="nav-container">${navItemsTemplate}</div>
</div>
`;
}
init() {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$el);
this.registerListeners();
}
registerListeners() {
for (const navItem of this.navItems) {
const $item = document.getElementById(`nav-${navItem}`);
$item.addEventListener('click', () => {
this.props.onItemSelected(navItem);
});
}
}
select(navItemToSelect) {
for (const navItem of this.navItems) {
if (navItem === navItemToSelect) {
this.activate(navItem);
} else {
this.deactivate(navItem);
}
}
}
activate(navItem) {
const $item = document.getElementById(`nav-${navItem}`);
$item.classList.add('active');
}
deactivate(navItem) {
const $item = document.getElementById(`nav-${navItem}`);
$item.classList.remove('active');
}
}
module.exports = PreferenceNav;

View File

@@ -0,0 +1,102 @@
'use strict';
const {ipcRenderer} = require('electron');
const BaseSection = require(__dirname + '/base-section.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
class NetworkSection extends BaseSection {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="settings-pane">
<div class="title">Proxy</div>
<div id="appearance-option-settings" class="settings-card">
<div class="setting-row" id="use-proxy-option">
<div class="setting-description">Connect servers through a proxy</div>
<div class="setting-control"></div>
</div>
<div class="setting-block">
<div class="setting-row" id="proxy-pac-option">
<span class="setting-input-key">PAC script</span>
<input class="setting-input-value" placeholder="e.g. foobar.com/pacfile.js"/>
</div>
<div class="setting-row" id="proxy-rules-option">
<span class="setting-input-key">Proxy rules</span>
<input class="setting-input-value" placeholder="e.g. http=foopy:80;ftp=foopy2"/>
</div>
<div class="setting-row" id="proxy-bypass-option">
<span class="setting-input-key">Proxy bypass rules</span>
<input class="setting-input-value" placeholder="e.g. foobar.com"/>
</div>
<div class="setting-row">
<div class="action green" id="proxy-save-action">
<i class="material-icons">check_box</i>
<span>Save</span>
</div>
</div>
</div>
</div>
</div>
`;
}
init() {
this.props.$root.innerHTML = this.template();
this.$proxyPAC = document.querySelector('#proxy-pac-option .setting-input-value');
this.$proxyRules = document.querySelector('#proxy-rules-option .setting-input-value');
this.$proxyBypass = document.querySelector('#proxy-bypass-option .setting-input-value');
this.$proxySaveAction = document.getElementById('proxy-save-action');
this.$settingBlock = this.props.$root.querySelector('.setting-block');
this.initProxyOption();
this.$proxyPAC.value = ConfigUtil.getConfigItem('proxyPAC', '');
this.$proxyRules.value = ConfigUtil.getConfigItem('proxyRules', '');
this.$proxyBypass.value = ConfigUtil.getConfigItem('proxyBypass', '');
this.$proxySaveAction.addEventListener('click', () => {
ConfigUtil.setConfigItem('proxyPAC', this.$proxyPAC.value);
ConfigUtil.setConfigItem('proxyRules', this.$proxyRules.value);
ConfigUtil.setConfigItem('proxyBypass', this.$proxyBypass.value);
ipcRenderer.send('forward-message', 'reload-proxy', true);
});
}
initProxyOption() {
const proxyEnabled = ConfigUtil.getConfigItem('useProxy', false);
this.toggleProxySettings(proxyEnabled);
this.updateProxyOption();
}
toggleProxySettings(option) {
if (option) {
this.$settingBlock.classList.remove('hidden');
} else {
this.$settingBlock.classList.add('hidden');
}
}
updateProxyOption() {
this.generateSettingOption({
$element: document.querySelector('#use-proxy-option .setting-control'),
value: ConfigUtil.getConfigItem('useProxy', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('useProxy');
ConfigUtil.setConfigItem('useProxy', newValue);
this.toggleProxySettings(newValue);
if (newValue === false) {
// Reload proxy if the proxy is turned off
ipcRenderer.send('forward-message', 'reload-proxy', false);
}
this.updateProxyOption();
}
});
}
}
module.exports = NetworkSection;

View File

@@ -0,0 +1,68 @@
'use strict';
const BaseComponent = require(__dirname + '/../../components/base.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
class NewServerForm extends BaseComponent {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="settings-card">
<div class="server-info-right">
<div class="server-info-row">
<input class="setting-input-value" autofocus placeholder="Entert the URL of your Zulip organization..."/>
</div>
<div class="server-info-row">
<div class="action green server-save-action">
<i class="material-icons">check_box</i>
<span>Save</span>
</div>
</div>
</div>
</div>
`;
}
init() {
this.initForm();
this.initActions();
}
initForm() {
this.$newServerForm = this.generateNodeFromTemplate(this.template());
this.$saveServerButton = this.$newServerForm.getElementsByClassName('server-save-action')[0];
this.props.$root.innerHTML = '';
this.props.$root.appendChild(this.$newServerForm);
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.setting-input-value')[0];
}
submitFormHandler() {
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
DomainUtil.addDomain(serverConf).then(() => {
this.props.onChange(this.props.index);
});
}, errorMessage => {
alert(errorMessage);
});
}
initActions() {
this.$saveServerButton.addEventListener('click', () => {
this.submitFormHandler();
});
this.$newServerUrl.addEventListener('keypress', event => {
const EnterkeyCode = event.keyCode;
// Submit form when Enter key is pressed
if (EnterkeyCode === 13) {
this.submitFormHandler();
}
});
}
}
module.exports = NewServerForm;

View File

@@ -0,0 +1,75 @@
'use strict';
const BaseComponent = require(__dirname + '/js/components/base.js');
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');
class PreferenceView extends BaseComponent {
constructor() {
super();
this.$sidebarContainer = document.getElementById('sidebar');
this.$settingsContainer = document.getElementById('settings-container');
}
init() {
this.nav = new Nav({
$root: this.$sidebarContainer,
onItemSelected: this.handleNavigation.bind(this)
});
this.setDefaultView();
this.registerIpcs();
}
setDefaultView() {
let nav = 'General';
const hasTag = window.location.hash;
if (hasTag) {
nav = hasTag.substring(1);
}
this.handleNavigation(nav);
}
handleNavigation(navItem) {
this.nav.select(navItem);
switch (navItem) {
case 'Servers': {
this.section = new ServersSection({
$root: this.$settingsContainer
});
break;
}
case 'General': {
this.section = new GeneralSection({
$root: this.$settingsContainer
});
break;
}
case 'Network': {
this.section = new NetworkSection({
$root: this.$settingsContainer
});
break;
}
default: break;
}
this.section.init();
window.location.hash = `#${navItem}`;
}
registerIpcs() {
ipcRenderer.on('switch-settings-nav', (event, navItem) => {
this.handleNavigation(navItem);
});
}
}
window.onload = () => {
const preferenceView = new PreferenceView();
preferenceView.init();
};

View File

@@ -0,0 +1,77 @@
'use strict';
const {dialog} = require('electron').remote;
const {ipcRenderer} = require('electron');
const BaseComponent = require(__dirname + '/../../components/base.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
class ServerInfoForm extends BaseComponent {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<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>
</div>
<div class="server-info-row">
<input class="setting-input-value" disabled value="${this.props.server.url}"/>
</div>
<div class="server-info-row">
<div class="action red server-delete-action">
<i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
</div>
</div>
</div>
</div>
`;
}
init() {
this.initForm();
this.initActions();
}
initForm() {
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
this.$serverInfoAlias = this.$serverInfoForm.getElementsByClassName('server-info-alias')[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);
}
initActions() {
this.$deleteServerButton.addEventListener('click', () => {
dialog.showMessageBox({
type: 'warning',
buttons: ['YES', 'NO'],
defaultId: 0,
message: 'Are you sure you want to delete this server?'
}, response => {
if (response === 0) {
DomainUtil.removeDomain(this.props.index);
this.props.onChange(this.props.index);
}
});
});
this.$openServerButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
});
this.$serverInfoAlias.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
});
}
}
module.exports = ServerInfoForm;

View File

@@ -0,0 +1,62 @@
'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');
class ServersSection extends BaseSection {
constructor(props) {
super();
this.props = props;
}
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>
`;
}
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');
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();
for (let i = 0; i < servers.length; i++) {
new ServerInfoForm({
$root: this.$serverInfoContainer,
server: servers[i],
index: i,
onChange: this.reloadApp
}).init();
}
}
initNewServerForm() {
new NewServerForm({
$root: this.$newServerContainer,
onChange: this.reloadApp
}).init();
}
}
module.exports = ServersSection;

View File

@@ -1,69 +0,0 @@
'use strict';
// eslint-disable-next-line import/no-extraneous-dependencies
const {remote} = require('electron');
const prefWindow = remote.getCurrentWindow();
document.getElementById('close-button').addEventListener('click', () => {
prefWindow.close();
});
document.addEventListener('keydown', event => {
if (event.key === 'Escape' || event.keyCode === 27) {
prefWindow.close();
}
});
// eslint-disable-next-line no-unused-vars
window.prefDomain = function () {
const request = require('request');
// eslint-disable-next-line import/no-extraneous-dependencies
const ipcRenderer = require('electron').ipcRenderer;
const JsonDB = require('node-json-db');
// eslint-disable-next-line import/no-extraneous-dependencies
const {app} = require('electron').remote;
const db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
let newDomain = document.getElementById('url').value;
newDomain = newDomain.replace(/^https?:\/\//, '');
newDomain = newDomain.replace(/^http?:\/\//, '');
if (newDomain === '') {
document.getElementById('urladded').innerHTML = 'Please input a value';
} else {
document.getElementById('main').innerHTML = 'Checking...';
if (newDomain.indexOf('localhost:') >= 0) {
const domain = 'http://' + newDomain;
const checkDomain = domain + '/static/audio/zulip.ogg';
request(checkDomain, (error, response) => {
if (!error && response.statusCode !== 404) {
document.getElementById('main').innerHTML = 'Switch';
document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain;
db.push('/domain', domain);
ipcRenderer.send('new-domain', domain);
} else {
document.getElementById('main').innerHTML = 'Switch';
document.getElementById('urladded').innerHTML = 'Not a valid Zulip Local Server.';
}
});
} else {
const domain = 'https://' + newDomain;
const checkDomain = domain + '/static/audio/zulip.ogg';
request(checkDomain, (error, response) => {
if (!error && response.statusCode !== 404) {
document.getElementById('main').innerHTML = 'Switch';
document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain;
db.push('/domain', domain);
ipcRenderer.send('new-domain', domain);
} else if (error.toString().indexOf('Error: self signed certificate') >= 0) {
document.getElementById('main').innerHTML = 'Switch';
ipcRenderer.send('certificate-err', domain);
document.getElementById('urladded').innerHTML = 'Switched to ' + newDomain;
} else {
document.getElementById('main').innerHTML = 'Switch';
document.getElementById('urladded').innerHTML = 'Not a valid Zulip Server.';
}
});
}
}
};

View File

@@ -1,56 +1,19 @@
'use strict';
const ipcRenderer = require('electron').ipcRenderer;
const {webFrame} = require('electron');
const {spellChecker} = require('./spellchecker');
const _setImmediate = setImmediate;
const _clearImmediate = clearImmediate;
process.once('loaded', () => {
global.setImmediate = _setImmediate;
global.clearImmediate = _clearImmediate;
});
const { ipcRenderer } = require('electron');
const { spellChecker } = require('./spellchecker');
// eslint-disable-next-line import/no-unassigned-import
require('./domain');
// eslint-disable-next-line import/no-unassigned-import
require('./tray.js');
// Calling Tray.js in renderer process everytime app window loads
require('./notification');
// Handle zooming functionality
const zoomIn = () => {
webFrame.setZoomFactor(webFrame.getZoomFactor() + 0.1);
};
const zoomOut = () => {
webFrame.setZoomFactor(webFrame.getZoomFactor() - 0.1);
};
const zoomActualSize = () => {
webFrame.setZoomFactor(1);
};
// Get zooming actions from main process
ipcRenderer.on('zoomIn', () => {
zoomIn();
});
ipcRenderer.on('zoomOut', () => {
zoomOut();
});
ipcRenderer.on('zoomActualSize', () => {
zoomActualSize();
});
ipcRenderer.on('log-out', () => {
const logout = () => {
// Create the menu for the below
document.querySelector('.dropdown-toggle').click();
const nodes = document.querySelectorAll('.dropdown-menu li:last-child a');
nodes[nodes.length - 1].click();
});
};
ipcRenderer.on('shortcut', () => {
const shortcut = () => {
// Create the menu for the below
const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
// Additional check
@@ -60,10 +23,23 @@ ipcRenderer.on('shortcut', () => {
// Atleast click the dropdown
document.querySelector('.dropdown-toggle').click();
}
};
process.once('loaded', () => {
global.logout = logout;
global.shortcut = shortcut;
});
// To prevent failing this script on linux we need to load it after the document loaded
document.addEventListener('DOMContentLoaded', () => {
// Init spellchecker
spellChecker();
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
if (getRestartButton) {
getRestartButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
});

View File

@@ -1,3 +1,5 @@
'use strict';
const {SpellCheckHandler, ContextMenuListener, ContextMenuBuilder} = require('electron-spellchecker');
function spellChecker() {

View File

@@ -5,10 +5,12 @@ const electron = require('electron');
const {ipcRenderer, remote} = electron;
const {Tray, Menu, nativeImage} = remote;
const {Tray, Menu, nativeImage, BrowserWindow} = remote;
const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const iconPath = () => {
if (process.platform === 'linux') {
return APP_ICON + 'linux.png';
@@ -45,7 +47,8 @@ const config = {
const renderCanvas = function (arg) {
config.unreadCount = arg;
return new Promise((resolve, reject) => {
return new Promise(resolve => {
const SIZE = config.size * config.pixelRatio;
const PADDING = SIZE * 0.05;
const CENTER = SIZE / 2;
@@ -83,8 +86,6 @@ const renderCanvas = function (arg) {
}
resolve(canvas);
} else {
reject(canvas);
}
});
};
@@ -102,31 +103,43 @@ const renderNativeImage = function (arg) {
});
};
function sendAction(action) {
const win = BrowserWindow.getAllWindows()[0];
if (process.platform === 'darwin') {
win.restore();
}
win.webContents.send(action);
}
const createTray = function () {
window.tray = new Tray(iconPath());
const contextMenu = Menu.buildFromTemplate([{
label: 'About',
click() {
ipcRenderer.send('trayabout');
// We need to focus the main window first
ipcRenderer.send('focus-app');
sendAction('open-about');
}
},
{
type: 'separator'
},
{
label: 'Change Zulip server',
label: 'Focus',
click() {
ipcRenderer.send('traychangeserver');
ipcRenderer.send('focus-app');
}
},
{
type: 'separator'
},
{
label: 'Reload',
label: 'Settings',
click() {
remote.getCurrentWindow().reload();
window.tray.destroy();
ipcRenderer.send('focus-app');
sendAction('open-settings');
}
},
{
@@ -135,14 +148,24 @@ const createTray = function () {
{
label: 'Quit',
click() {
remote.getCurrentWindow().close();
ipcRenderer.send('quit-app');
}
}
]);
window.tray.setContextMenu(contextMenu);
window.tray.on('click', () => {
// Click event only works on Windows
if (process.platform === 'win32') {
ipcRenderer.send('toggle-app');
}
});
};
ipcRenderer.on('destroytray', event => {
if (!window.tray) {
return;
}
window.tray.destroy();
if (window.tray.isDestroyed()) {
window.tray = null;
@@ -154,35 +177,47 @@ ipcRenderer.on('destroytray', event => {
});
ipcRenderer.on('tray', (event, arg) => {
if (arg === 0) {
unread = arg;
// Message Count // console.log("message count is zero.");
window.tray.setImage(iconPath());
window.tray.setToolTip('No unread messages');
} else {
unread = arg;
renderNativeImage(arg).then(image => {
window.tray.setImage(image);
window.tray.setToolTip(arg + ' unread messages');
});
if (!window.tray) {
return;
}
});
ipcRenderer.on('toggletray', event => {
if (event) {
if (window.tray) {
window.tray.destroy();
if (window.tray.isDestroyed()) {
window.tray = null;
}
// We don't want to create tray from unread messages on windows and macOS since these systems already have dock badges and taskbar overlay icon.
if (process.platform === 'linux') {
if (arg === 0) {
unread = arg;
// Message Count // console.log("message count is zero.");
window.tray.setImage(iconPath());
window.tray.setToolTip('No unread messages');
} else {
createTray();
renderNativeImage(unread).then(image => {
unread = arg;
renderNativeImage(arg).then(image => {
window.tray.setImage(image);
window.tray.setToolTip(unread + ' unread messages');
window.tray.setToolTip(arg + ' unread messages');
});
}
}
});
createTray();
function toggleTray() {
if (window.tray) {
window.tray.destroy();
if (window.tray.isDestroyed()) {
window.tray = null;
}
ConfigUtil.setConfigItem('trayIcon', false);
} else {
createTray();
if (process.platform === 'linux') {
renderNativeImage(unread).then(image => {
window.tray.setImage(image);
window.tray.setToolTip(unread + ' unread messages');
});
}
ConfigUtil.setConfigItem('trayIcon', true);
}
}
ipcRenderer.on('toggletray', toggleTray);
if (ConfigUtil.getConfigItem('trayIcon', true)) {
createTray();
}

View File

@@ -0,0 +1,54 @@
'use strict';
const process = require('process');
const JsonDB = require('node-json-db');
let instance = null;
let app = null;
/* To make the util runnable in both main and renderer process */
if (process.type === 'renderer') {
app = require('electron').remote.app;
} else {
app = require('electron').app;
}
class ConfigUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.reloadDB();
return instance;
}
getConfigItem(key, defaultValue = null) {
this.reloadDB();
const value = this.db.getData('/')[key];
if (value === undefined) {
this.setConfigItem(key, defaultValue);
return defaultValue;
} else {
return value;
}
}
setConfigItem(key, value) {
this.db.push(`/${key}`, value, true);
this.reloadDB();
}
removeConfigItem(key) {
this.db.delete(`/${key}`);
this.reloadDB();
}
reloadDB() {
this.db = new JsonDB(app.getPath('userData') + '/settings.json', true, true);
}
}
module.exports = new ConfigUtil();

View File

@@ -0,0 +1,250 @@
'use strict';
const { app, dialog } = require('electron').remote;
const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const request = require('request');
let instance = null;
const defaultIconUrl = '../renderer/img/icon.png';
class DomainUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.reloadDB();
// Migrate from old schema
if (this.db.getData('/').domain) {
this.addDomain({
alias: 'Zulip',
url: this.db.getData('/domain')
});
this.db.delete('/domain');
}
return instance;
}
getDomains() {
this.reloadDB();
if (this.db.getData('/').domains === undefined) {
return [];
} else {
return this.db.getData('/domains');
}
}
getDomain(index) {
this.reloadDB();
return this.db.getData(`/domains[${index}]`);
}
updateDomain(index, server) {
this.reloadDB();
this.db.push(`/domains[${index}]`, server, true);
}
addDomain(server) {
return new Promise(resolve => {
if (server.icon) {
this.saveServerIcon(server.icon).then(localIconUrl => {
server.icon = localIconUrl;
this.db.push('/domains[]', server, true);
this.reloadDB();
resolve();
});
} else {
server.icon = defaultIconUrl;
this.db.push('/domains[]', server, true);
this.reloadDB();
resolve();
}
});
}
removeDomains() {
this.db.delete('/domains');
this.reloadDB();
}
removeDomain(index) {
this.db.delete(`/domains[${index}]`);
this.reloadDB();
}
// Check if domain is already added
duplicateDomain(domain) {
domain = this.formatUrl(domain);
const servers = this.getDomains();
for (const i in servers) {
if (servers[i].url === domain) {
return true;
}
}
return false;
}
checkDomain(domain, silent = false) {
if (!silent && this.duplicateDomain(domain)) {
// Do not check duplicate in silent mode
alert('This server has been added.');
return;
}
domain = this.formatUrl(domain);
const checkDomain = domain + '/static/audio/zulip.ogg';
const serverConf = {
icon: defaultIconUrl,
url: domain,
alias: domain
};
return new Promise((resolve, reject) => {
request(checkDomain, (error, response) => {
const certsError =
['Error: self signed certificate',
'Error: unable to verify the first certificate'
];
if (!error && response.statusCode !== 404) {
// Correct
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
}, () => {
resolve(serverConf);
});
} else if (certsError.indexOf(error.toString()) >= 0) {
if (silent) {
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
}, () => {
resolve(serverConf);
});
} else {
const certErrorMessage = `Do you trust certificate from ${domain}? \n ${error}`;
const certErrorDetail = `The server you're connecting to is either someone impersonating the Zulip server you entered, or the server you're trying to connect to is configured in an insecure way.
\n Unless you have a good reason to believe otherwise, you should not proceed.
\n You can click here if you'd like to proceed with the connection.`;
dialog.showMessageBox({
type: 'warning',
buttons: ['Yes', 'No'],
defaultId: 0,
message: certErrorMessage,
detail: certErrorDetail
}, response => {
if (response === 0) {
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
}, () => {
resolve(serverConf);
});
} else {
reject('Untrusted Certificate.');
}
});
}
} else {
reject('Not a valid Zulip server');
}
});
});
}
getServerSettings(domain) {
const serverSettingsUrl = domain + '/api/v1/server_settings';
return new Promise((resolve, reject) => {
request(serverSettingsUrl, (error, response) => {
if (!error && response.statusCode === 200) {
const data = JSON.parse(response.body);
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
resolve({
icon: data.realm_uri + data.realm_icon,
url: data.realm_uri,
alias: data.realm_name
});
}
} else {
reject('Zulip server version < 1.6.');
}
});
});
}
saveServerIcon(url) {
// The save will always succeed. If url is invalid, downgrade to default icon.
return new Promise(resolve => {
const filePath = this.generateFilePath(url);
const file = fs.createWriteStream(filePath);
try {
request(url).on('response', response => {
response.on('error', err => {
console.log(err);
resolve(defaultIconUrl);
});
response.pipe(file).on('finish', () => {
resolve(filePath);
});
}).on('error', err => {
console.log(err);
resolve(defaultIconUrl);
});
} catch (err) {
console.log(err);
resolve(defaultIconUrl);
}
});
}
updateSavedServer(url, index) {
// Does not promise successful update
this.checkDomain(url, true).then(newServerConf => {
this.saveServerIcon(newServerConf.icon).then(localIconUrl => {
newServerConf.icon = localIconUrl;
this.updateDomain(index, newServerConf);
this.reloadDB();
});
});
}
reloadDB() {
this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
}
generateFilePath(url) {
const dir = `${app.getPath('userData')}/server-icons`;
const extension = path.extname(url).split('?')[0];
let hash = 5381;
let len = url.length;
while (len) {
hash = (hash * 33) ^ url.charCodeAt(--len);
}
// Create 'server-icons' directory if not existed
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
return `${dir}/${hash >>> 0}${extension}`;
}
formatUrl(domain) {
const hasPrefix = (domain.indexOf('http') === 0);
if (hasPrefix) {
return domain;
} else {
return (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`;
}
}
}
module.exports = new DomainUtil();

View File

@@ -0,0 +1,26 @@
'use strict';
const wurl = require('wurl');
let instance = null;
class LinkUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
return instance;
}
isInternal(currentUrl, newUrl) {
const currentDomain = wurl('hostname', currentUrl);
const newDomain = wurl('hostname', newUrl);
return (currentDomain === newDomain) && newUrl.includes('/#narrow');
}
}
module.exports = new LinkUtil();

View File

@@ -0,0 +1,55 @@
'use strict';
const {app} = require('electron').remote;
const os = require('os');
let instance = null;
class SystemUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.connectivityERR = [
'ERR_INTERNET_DISCONNECTED',
'ERR_PROXY_CONNECTION_FAILED',
'ERR_CONNECTION_RESET',
'ERR_NOT_CONNECTED',
'ERR_NAME_NOT_RESOLVED',
'ERR_NETWORK_CHANGED'
];
this.userAgent = null;
return instance;
}
getOS() {
if (os.platform() === 'darwin') {
return 'Mac';
}
if (os.platform() === 'linux') {
return 'Linux';
}
if (os.platform() === 'win32' || os.platform() === 'win64') {
if (parseFloat(os.release()) < 6.2) {
return 'Windows 7';
} else {
return 'Windows 10';
}
}
}
setUserAgent(webViewUserAgent) {
this.userAgent = 'ZulipElectron/' + app.getVersion() + ' ' + webViewUserAgent;
}
getUserAgent() {
return this.userAgent;
}
}
module.exports = new SystemUtil();

43
app/renderer/main.html Normal file
View File

@@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en" class="responsive desktop">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width">
<title>Zulip</title>
<link rel="stylesheet" href="css/main.css" type="text/css" media="screen">
</head>
<body>
<div id="content">
<div class="popup">
<span class="popuptext hidden" id="fullscreen-popup"></span>
</div>
<div id="sidebar">
<div id="view-controls-container">
<div id="tabs-container"></div>
<div id="add-tab" class="tab functional-tab">
<div class="server-tab" id="add-action">
<i class="material-icons">add</i>
</div>
</div>
</div>
<div id="actions-container">
<div class="action-button" id="reload-action">
<i class="material-icons md-48">refresh</i>
<span id="reload-tooltip" style="display:none">Reload</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>
</div>
</div>
</div>
<div id="main-container">
<div id="webviews-container"></div>
</div>
</div>
</body>
<script src="js/main.js"></script>
</html>

21
app/renderer/network.html Normal file
View File

@@ -0,0 +1,21 @@
<!DOCTYPE html>
<html lang="en" class="responsive desktop">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width">
<title>Zulip - Network Troubleshooting</title>
<link rel="stylesheet" href="css/network.css" type="text/css" media="screen">
</head>
<body>
<div id="content">
<div id="picture"><img src="img/zulip_network.png"></div>
<div id="title">Zulip can't connect</div>
<div id="description">
<div>Your computer seems to be offline.</div>
<div>We will keep trying to reconnect, or you can try now.</div>
</div>
<div id="reconnect">Try now</div>
</div>
</body>
<script src="js/pages/network.js"></script>
</html>

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="css/pref.css">
</head>
<body>
<div class="close" id="close-button">Close</div>
<div class="form">
<form onsubmit="prefDomain(); return false">
<input id="url" type="text" placeholder="Server URL">
<button type="submit" id="main" value="Submit">
Switch</button>
</form>
<p id="urladded"><p>
</div>
<script src="js/pref.js"></script>
</body>
</html>

View File

@@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en" class="responsive desktop">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width">
<title>Zulip - Settings</title>
<link rel="stylesheet" href="css/preference.css" type="text/css" media="screen">
</head>
<body>
<div id="content">
<div id="sidebar"></div>
<div id="settings-container"></div>
</div>
</body>
<script src="js/pages/preference/preference.js"></script>
</html>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.network.client</key>
<true/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.downloads.read-write</key>
<true/>
</dict>
</plist>

84
development.md Normal file
View File

@@ -0,0 +1,84 @@
# Development setup
This is a guide to running the Zulip desktop app from a source tree,
in order to contribute to developing it.
## Prerequisites
To build and run the app from source, you'll need the following:
* [Git](http://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node.js](https://nodejs.org) >= v6.9.0
* [NPM](https://www.npmjs.com/get-npm) and
[node-gyp](https://github.com/nodejs/node-gyp#installation),
if they don't come bundled with your Node.js installation
* [Python](https://www.python.org/downloads/release/python-2713/)
(v2.7.x recommended)
* A C++ compiler compatible with C++11
* Development headers for the libXext, libXtst, and libxkbfile libraries
### Debian/Ubuntu and friends
On a system running Debian, Ubuntu, or another Debian-based Linux
distribution, you can install all dependencies through the package
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
```
[nodesource-install]: https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions
### Other OSes
Other developers run the app on Windows, macOS, and possibly other OSes.
PRs to add specific instructions to this doc are welcome!
On Windows, your C++ compiler should be Visual Studio 2015 or later.
## Download, build, and run
Clone the source locally:
```sh
$ git clone https://github.com/zulip/zulip-electron
$ cd zulip-electron
```
Install project dependencies:
```sh
$ npm install
```
Start the app:
```sh
$ npm start
```
Start and watch changes:
```sh
$ npm run dev
```
## Troubleshooting
If you have any problems running the app, see the [most common
issues](./troubleshooting.md).
## Making a release
To package the app into an installer:
```
npm run dist
```
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
`AppImage` file.
To generate all three types, you will need all three operating
systems.
The output files appear in the `dist/` directory.

2
docs/Home.md Normal file
View File

@@ -0,0 +1,2 @@
# Installation instructions
* [[Windows]]

1
docs/Windows.md Normal file
View File

@@ -0,0 +1 @@
** Windows Set up instructions **

3
docs/_Footer.md Normal file
View File

@@ -0,0 +1,3 @@
### Want to contribute to this Wiki?
[Edit `/docs` files and send a pull request.](https://github.com/zulip/zulip-electron/tree/master/docs)

26
docs/desktop-release.md Normal file
View File

@@ -0,0 +1,26 @@
# New release checklist -
## We need to cross check following things before pushing a new release + after updating electron version. This is just to make sure that nothing gets broken.
## - Desktop notifications
## - Spellchecker
## - Auto updates
**Check for the logs in -**
- **on Linux:** `~/.config/Zulip/log.log`
- **on OS X:** `~/Library/Logs/Zulip/log.log`
- **on Windows:** `%USERPROFILE%\AppData\Roaming\Zulip\log.log`
## - All the installer i.e.
- Linux (.deb, AppImage)
- Mac - (.dmg)
- Windows - (web installer for 32/64bit)
## - Check for errors in console (if any)
## - Code signing verification on Mac and Windows
## - Tray and menu options
# We need to cross check all these things on -
- Windows 7
- Windows 8
- Windows 10
- Ubuntu 14.04/16.04
- macOSX

105
help.md Normal file
View File

@@ -0,0 +1,105 @@
# User Guide
> Welcome! This guide will walk you through the basics of using Zulip Desktop.
## Get Zulip Desktop
## Connect to a Server
### Connect through a proxy
It's possible to connect to your server through a proxy.
You can enter the proxy settings in the `Network` section of App Settings.
There are three fields provided:
* `PAC script` - The URL associated with the PAC file.
* `Proxy rules` - Rules indicating which proxies to use.
* `Proxy bypass rules` - Rules indicating which URLs should
bypass the proxy settings.
For a typical setup where internet access is required to use an HTTP proxy,
but URLs on the local network should be accessed directly, configure as follows:
`Proxy rules = proxy.example.com`
Your HTTP proxy server
`Proxy bypass rules = *.example.com;10.0.0.0/8`
Directly connect to your own domain and private IP subnet
for more complex setups, read below to configure complex proxy rules and proxy bypass rules.
### Sets the proxy settings.
When `PAC script` and `Proxy rules` are provided together, the `Proxy rules`
option is ignored and `PAC script` configuration is applied.
The `Proxy rules` has to follow the rules below:
```
proxyRules = schemeProxies[";"<schemeProxies>]
schemeProxies = [<urlScheme>"="]<proxyURIList>
urlScheme = "http" | "https" | "ftp" | "socks"
proxyURIList = <proxyURL>[","<proxyURIList>]
proxyURL = [<proxyScheme>"://"]<proxyHost>[":"<proxyPort>]
```
For example:
* `http=foopy:80;ftp=foopy2` - Use HTTP proxy `foopy:80` for `http://` URLs, and
HTTP proxy `foopy2:80` for `ftp://` URLs.
* `foopy:80` - Use HTTP proxy `foopy:80` for all URLs.
* `foopy:80,bar,direct://` - Use HTTP proxy `foopy:80` for all URLs, failing
over to `bar` if `foopy:80` is unavailable, and after that using no proxy.
* `socks4://foopy` - Use SOCKS v4 proxy `foopy:1080` for all URLs.
* `http=foopy,socks5://bar.com` - Use HTTP proxy `foopy` for http URLs, and fail
over to the SOCKS5 proxy `bar.com` if `foopy` is unavailable.
* `http=foopy,direct://` - Use HTTP proxy `foopy` for http URLs, and use no
proxy if `foopy` is unavailable.
* `http=foopy;socks=foopy2` - Use HTTP proxy `foopy` for http URLs, and use
`socks4://foopy2` for all other URLs.
The `Proxy bypass rules` is a comma separated list of rules described below:
* `[ URL_SCHEME "://" ] HOSTNAME_PATTERN [ ":" <port> ]`
Match all hostnames that match the pattern HOSTNAME_PATTERN.
Examples:
"foobar.com", "*foobar.com", "*.foobar.com", "*foobar.com:99",
"https://x.*.y.com:99"
* `"." HOSTNAME_SUFFIX_PATTERN [ ":" PORT ]`
Match a particular domain suffix.
Examples:
".google.com", ".com", "http://.google.com"
* `[ SCHEME "://" ] IP_LITERAL [ ":" PORT ]`
Match URLs which are IP address literals.
Examples:
"127.0.1", "[0:0::1]", "[::1]", "http://[::1]:99"
* `IP_LITERAL "/" PREFIX_LENGHT_IN_BITS`
Match any URL that is to an IP literal that falls between the
given range. IP range is specified using CIDR notation.
Examples:
"192.168.1.1/16", "fefe:13::abc/33".
* `<local>`
Match local addresses. The meaning of `<local>` is whether the
host matches one of: "127.0.0.1", "::1", "localhost".
## Change App Preferences
## Reporting an Issue

68
how-to-install.md Normal file
View File

@@ -0,0 +1,68 @@
# How to install
**Note:** If you download from the [releases page](https://github.com/zulip/zulip-electron/releases), be careful what version you pick. Releases that end with `-beta` are beta releases and the rest are stable.
- **beta:** these releases are the right balance between getting new features early while staying away from nasty bugs.
- **stable:** these releases are more thoroughly tested; they receive new features later, but there's a lower chance that things will go wrong.
[LR]: https://github.com/zulip/zulip-electron/releases
## OS X
**DMG or zip**:
1. Download [Zulip-x.x.x.dmg][LR] or [Zulip-x.x.x-mac.zip][LR]
2. Open or unzip the file and drag the app into the `Applications` folder
3. Done! The app will update automatically
**Using brew**:
1. Run `brew cask install zulip` in your terminal
2. The app will be installed in your `Applications`
3. Done! The app will update automatically (you can also use `brew update && brew upgrade zulip`)
## Windows
**Installer (recommended)**:
1. Download [Zulip-Web-Setup-x.x.x.exe][LR]
2. Run the installer, wait until it finishes
3. Done! The app will update automatically
**Portable**:
1. Download [zulip-x.x.x-arch.nsis.7z][LR] [*here arch = ia32 (32-bit), x64 (64-bit)*]
2. Extract the zip wherever you want (e.g. a flash drive) and run the app from there
## Linux
**Ubuntu, Debian 8+ (deb package)**:
1. Download [Zulip-x.x.x-amd64.deb][LR]
2. Double click and install, or run `dpkg -i Zulip-x.x.x-amd64.deb` in the terminal
3. Start the app with your app launcher or by running `zulip` in a terminal
4. Done! The app will NOT update automatically, but you can still check for updates
**Other distros (Fedora, CentOS, Arch Linux etc)** :
1. Download Zulip-x.x.x-x86_64.AppImage[LR]
2. Make it executable using chmod a+x Zulip-x.x.x-x86_64.AppImage
3. Start the app with your app launcher
**You can also use `apt-get` (recommended)**:
* First download our signing key to make sure the deb you download is correct:
```
sudo apt-key adv --keyserver pool.sks-keyservers.net --recv 69AD12704E71A4803DCA3A682424BE5AE9BD10D9
```
* Add the repo to your apt source list :
```
echo "deb https://dl.bintray.com/zulip/debian/ beta main" |
sudo tee -a /etc/apt/sources.list.d/zulip.list
```
* Now install the client :
```
sudo apt-get update
sudo apt-get install zulip
```

View File

@@ -1,14 +1,14 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "0.5.10",
"version": "1.4.0",
"main": "./app/main",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"email": "<svnitakash@gmail.com>",
"copyright": "©2017 Kandra Labs, Inc.",
"author": {
"name": "Akash Nimare",
"name": "Kandra Labs, Inc.",
"email": "svnitakash@gmail.com"
},
"repository": {
@@ -19,20 +19,24 @@
"url": "https://github.com/zulip/zulip-electron/issues"
},
"scripts": {
"start": "electron ./app/main",
"postinstall": "install-app-deps",
"test": "gulp test && xo",
"start": "electron app --disable-http-cache",
"postinstall": "electron-builder install-app-deps",
"test": "xo",
"dev": "gulp dev",
"pack": "build --dir",
"dist": "build",
"build:win": "build --win nsis-web --ia32 --x64",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"mas": "electron-builder --mac mas",
"travis": "cd ./scripts && ./travis-build-test.sh"
},
"pre-commit": [
"test"
],
"build": {
"appId": "org.zulip.zulip-electron",
"asar": true,
"files": [
"**/*",
"!docs${/*}",
"!node_modules/@paulcbetts/cld/deps/cld${/*}"
],
"copyright": "©2017 Kandra Labs, Inc.",
@@ -40,11 +44,12 @@
"category": "public.app-category.productivity"
},
"linux": {
"category": "",
"category": "Chat;GNOME;GTK;Network;InstantMessaging",
"packageCategory": "GNOME;GTK;Network;InstantMessaging",
"description": "Zulip Desktop Client for Linux",
"target": [
"deb",
"zip",
"AppImage"
],
"maintainer": "Akash Nimare <svnitakash@gmail.com>"
@@ -71,12 +76,22 @@
]
},
"win": {
"target": "nsis",
"icon": "build/icon.ico"
},
"target": [
{
"target": "nsis-web",
"arch": [
"x64",
"ia32"
]
}
],
"icon": "build/icon.ico",
"publisherName": "Kandra Labs, Inc."
},
"nsis": {
"perMachine": true,
"oneClick": false
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
},
"keywords": [
@@ -90,29 +105,44 @@
"devDependencies": {
"assert": "1.4.1",
"devtron": "1.4.0",
"electron-builder": "16.6.0",
"electron": "1.4.15",
"electron-connect": "0.4.8",
"electron-builder": "19.27.3",
"electron": "1.6.11",
"electron-connect": "0.6.2",
"gulp": "3.9.1",
"gulp-mocha": "3.0.1",
"spectron": "3.6.1",
"xo": "0.18.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"
},
"xo": {
"parserOptions": {
"sourceType": "script",
"ecmaFeatures": {
"globalReturn": true
}
},
"esnext": true,
"overrides": [
{
"files": "app/main/*.js",
"files": "app*/**/*.js",
"rules": {
"max-lines": [
"warn",
500
],
"no-warning-comments": 0,
"object-curly-spacing": 0,
"capitalized-comments": 0,
"no-else-return": 0,
"no-path-concat": 0,
"no-alert": 0,
"guard-for-in": 0,
"prefer-promise-reject-errors": 0,
"import/no-unresolved": 0,
"import/no-extraneous-dependencies": 0
"import/no-extraneous-dependencies": 0,
"no-prototype-builtins": 0
}
}
],
@@ -125,4 +155,4 @@
"mocha"
]
}
}
}

View File

@@ -1,5 +1,11 @@
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)
@@ -7,11 +13,15 @@ describe('application launch', function () {
beforeEach(function () {
this.app = new Application({
path: require('electron'),
args: [__dirname + '/../app/renderer/index.html']
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()
@@ -19,9 +29,53 @@ describe('application launch', function () {
})
it('shows an initial window', function () {
return this.app.client.getWindowCount().then(function (count) {
assert.equal(count, 1)
})
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))
})
})

11
troubleshooting.md Normal file
View File

@@ -0,0 +1,11 @@
# Troubleshooting
* App icon will only show in the release version. The dev version will use the Electron icon
* If you see issue, try deleting `node_modules` and `npm install`
* Electron is more or less Chrome, you can get developer tools using `CMD+ALT+I`
### Error : ChecksumMismatchError
- Try deleteing `node_modules` && `app/node_modules` directories. Re-install dependencies using `npm install`.
### Error : Module version mismatch. Expected 50, got 51
- Make sure you have installed [node-gyp](https://github.com/nodejs/node-gyp#installation) dependencies properly.

View File

@@ -103,7 +103,20 @@ cleanUp()
# }}}
# this function is called when user hits Ctrl-C
catchControl_c () {
echo -en "\n## Ctrl-C caught; Quitting \n"
# exit shell script
exit $?;
}
envSetup $*
gitCheckout
npmInstallStart
cleanUp
# initialise trap to call catchControl_c function and trap keyboard interrupt (control-c)
trap catchControl_c SIGINT
sleep 1000