Compare commits

...

176 Commits

Author SHA1 Message Date
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
38 changed files with 1808 additions and 777 deletions

26
.gitignore vendored
View File

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

View File

@@ -2,12 +2,15 @@
const {app, dialog} = require('electron');
const {autoUpdater} = require('electron-updater');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
function appUpdater() {
// Log whats happening
const log = require('electron-log');
log.transports.file.level = 'info';
autoUpdater.logger = log;
autoUpdater.allowPrerelease = false;
// 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

View File

@@ -4,18 +4,14 @@ const electron = require('electron');
const {app} = require('electron');
const ipc = require('electron').ipcMain;
const electronLocalshortcut = require('electron-localshortcut');
const Configstore = require('electron-config');
const isDev = require('electron-is-dev');
const windowStateKeeper = require('electron-window-state');
const appMenu = require('./menu');
const {appUpdater} = require('./autoupdater');
// Adds debug features like hotkeys for triggering dev tools and reload
require('electron-debug')();
const conf = new Configstore();
// Setting userAgent so that server-side code can identify the desktop app
// Prevent window being garbage collected
let mainWindow;
@@ -49,12 +45,19 @@ const iconPath = () => {
};
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: 500,
webPreferences: {
@@ -86,43 +89,19 @@ function createMainWindow() {
win.hide();
}
}
// Unregister all the shortcuts so that they don't interfare with other apps
electronLocalshortcut.unregisterAll(mainWindow);
});
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]
});
win.on('leave-full-screen', () => {
win.webContents.send('leave-fullscreen');
});
// To destroy tray icon when navigate to a new URL
@@ -132,9 +111,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;
}
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();
@@ -153,25 +153,14 @@ app.on('activate', () => {
});
app.on('ready', () => {
electron.Menu.setApplicationMenu(appMenu);
appMenu.setMenu({
tabs: []
});
mainWindow = createMainWindow();
const page = mainWindow.webContents;
// TODO - use global shortcut instead
electronLocalshortcut.register(mainWindow, 'CommandOrControl+R', () => {
// page.send('reload');
mainWindow.reload();
// page.send('destroytray');
});
electronLocalshortcut.register(mainWindow, 'CommandOrControl+[', () => {
page.send('back');
});
electronLocalshortcut.register(mainWindow, 'CommandOrControl+]', () => {
page.send('forward');
});
registerLocalShortcuts(page);
page.on('dom-ready', () => {
mainWindow.show();
@@ -184,9 +173,9 @@ app.on('ready', () => {
appUpdater();
}
});
electron.powerMonitor.on('resume', () => {
mainWindow.reload();
mainWindow.webContents.send('destroytray');
page.send('reload-viewer');
});
ipc.on('focus-app', () => {
@@ -197,8 +186,10 @@ app.on('ready', () => {
app.quit();
});
ipc.on('reload-main', () => {
page.reload();
// Reload full app not just webview, useful in debugging
ipc.on('reload-full-app', () => {
mainWindow.reload();
page.send('destroytray');
});
ipc.on('toggle-app', () => {
@@ -213,8 +204,46 @@ app.on('ready', () => {
if (process.platform === 'darwin') {
app.setBadgeCount(messageCount);
}
if (process.platform === 'win32') {
if (!mainWindow.isFocused()) {
mainWindow.flashFrame(true);
}
if (messageCount === 0) {
mainWindow.setOverlayIcon(null, '');
} else {
page.send('render-taskbar-icon', messageCount);
}
}
page.send('tray', messageCount);
});
ipc.on('update-taskbar-icon', (event, data, text) => {
const img = electron.nativeImage.createFromDataURL(data);
mainWindow.setOverlayIcon(img, text);
});
ipc.on('forward-message', (event, listener, ...params) => {
page.send(listener, ...params);
});
ipc.on('update-menu', (event, props) => {
appMenu.setMenu(props);
});
ipc.on('register-server-tab-shortcut', (event, index) => {
electronLocalshortcut.register(mainWindow, `CommandOrControl+${index}`, () => {
// Array index == Shown index - 1
page.send('switch-server-tab', index - 1);
});
});
ipc.on('local-shortcuts', (event, enable) => {
if (enable) {
registerLocalShortcuts(page);
} else {
electronLocalshortcut.unregisterAll(mainWindow);
}
});
});
app.on('will-quit', () => {

View File

@@ -1,377 +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();
function sendAction(action) {
const win = BrowserWindow.getAllWindows()[0];
if (process.platform === 'darwin') {
win.restore();
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');
}
}
}];
}
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) {
sendAction('reload');
getViewSubmenu() {
return [{
label: 'Reload',
accelerator: 'CommandOrControl+R',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('reload-viewer');
}
}
}
},
{
type: 'separator'
},
{
role: 'togglefullscreen'
},
{
label: 'Zoom In',
accelerator: 'CommandOrControl+=',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('zoomIn');
}, {
label: 'Hard Reload',
accelerator: 'CommandOrControl+Shift+R',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('hard-reload');
}
}
}
},
{
label: 'Zoom Out',
accelerator: 'CommandOrControl+-',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('zoomOut');
}, {
type: 'separator'
}, {
role: 'togglefullscreen'
}, {
label: 'Zoom In',
accelerator: 'CommandOrControl+=',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomIn');
}
}
}
},
{
label: 'Actual Size',
accelerator: 'CommandOrControl+0',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('zoomActualSize');
}, {
label: 'Zoom Out',
accelerator: 'CommandOrControl+-',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomOut');
}
}
}
},
{
type: 'separator'
},
{
label: 'Toggle Tray Icon',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.send('toggletray');
}, {
label: 'Actual Size',
accelerator: 'CommandOrControl+0',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomActualSize');
}
}
}
},
{
label: 'Toggle DevTools for Zulip App',
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.toggleDevTools();
}, {
type: 'separator'
}, {
label: 'Toggle Tray Icon',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.send('toggletray');
}
}
}
},
{
label: 'Toggle DevTools for Active Tab',
accelerator: process.platform === 'darwin' ? 'Alt+Command+U' : 'Ctrl+Shift+U',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('tab-devtools');
}, {
label: 'Toggle Sidebar',
accelerator: 'CommandOrControl+S',
click(item, focusedWindow) {
if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('show-sidebar');
focusedWindow.webContents.send('toggle-sidebar', newValue);
ConfigUtil.setConfigItem('show-sidebar', 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');
}
}
}];
}
];
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)}`);
}
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)}`);
}
}];
}
];
const darwinTpl = [
getWindowSubmenu(tabs, activeTabIndex) {
const initialSubmenu = [{
role: 'minimize'
}, {
role: 'close'
}];
{
label: `${app.getName()}`,
submenu: [
{
label: 'Zulip desktop',
if (tabs.length > 0) {
const ShortcutKey = process.platform === 'darwin' ? 'Cmd' : 'Ctrl';
initialSubmenu.push({
type: 'separator'
});
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) {
sendAction('open-about');
AppMenu.sendAction('open-about');
}
}
},
{
}, {
type: 'separator'
},
{
label: 'Manage Zulip Servers',
}, {
label: 'Settings',
accelerator: 'Cmd+,',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('open-settings');
AppMenu.sendAction('open-settings');
}
}
},
{
label: 'Keyboard shortcuts',
}, {
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',
getOtherTpl(props) {
const {tabs, activeTabIndex} = props;
return [{
label: 'File',
submenu: [{
label: 'Zulip Desktop',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('open-about');
AppMenu.sendAction('open-about');
}
}
},
{
}, {
type: 'separator'
},
{
label: 'Manage Zulip Servers',
}, {
label: 'Settings',
accelerator: 'Ctrl+,',
click(item, focusedWindow) {
if (focusedWindow) {
sendAction('open-settings');
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',
accelerator: 'Ctrl+Q'
}
]
},
{
label: 'Edit',
submenu: [
{
}]
}, {
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();

View File

@@ -1,13 +1,13 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "1.1.0-beta",
"version": "1.3.0-beta",
"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,15 @@
"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.8",
"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.5",
"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"
}
}

View File

@@ -1,11 +1,13 @@
/*******************
* General rules *
*******************/
html, body {
html,
body {
height: 100%;
margin: 0;
cursor: default;
user-select:none;
user-select: none;
}
#content {
@@ -24,43 +26,44 @@ html, body {
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');
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 {
#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;
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 {
@@ -70,6 +73,10 @@ html, body {
padding: 10px;
}
.action-button:hover {
cursor: pointer;
}
.action-button i {
color: #6c8592;
font-size: 28px;
@@ -79,54 +86,65 @@ html, body {
color: #98a9b3;
}
.tab {
position: relative;
margin: 5px 0;
cursor: pointer;
.tab:first-child {
margin-top: 8px;
}
.tab.active::before {
content: "";
background: #fff;
border-radius: 0 3px 3px 0;
width: 4px;
position: absolute;
height: 35px;
left: -10px;
top: 5px;
.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;
}
.tab .server-tab {
background: #a4d3c4;
background-size: 100%;
border-radius: 4px;
width: 35px;
width: 100%;
height: 35px;
position: relative;
margin: 5px 0;
margin: 5px 0 2px 0;
z-index: 11;
line-height: 31px;
color: #194a2b;
color: #eee;
text-align: center;
overflow: hidden;
opacity: 0.6;
padding: 2px 0;
}
.tab .server-tab:hover {
opacity: 0.8;
}
.tab .functional-tab {
background: #eee;
.tab.functional-tab {
height: 46px;
padding: 0;
}
.tab .functional-tab i {
.tab.functional-tab.active .server-tab {
padding: 2px 0;
height: 40px;
background-color: rgba(255, 255, 255, 0.25);
}
.tab.functional-tab .server-tab i {
font-size: 28px;
line-height: 36px;
}
.tab.active .server-tab {
opacity: 1;
background-color: #648478;
}
.tab .server-tab-badge.active {
@@ -138,14 +156,15 @@ html, body {
font-size: 10px;
font-family: sans-serif;
position: absolute;
right: -6px;
right: 5px;
z-index: 15;
top: -2px;
top: 6px;
float: right;
color: #fff;
text-align: center;
line-height: 17px;
display: block;
right: 0;
}
.tab .server-tab-badge {
@@ -154,7 +173,7 @@ html, body {
.tab .server-tab-badge.close-button {
width: 16px;
padding: 0 0 0 1px;
padding: 0;
}
.tab .server-tab-badge.close-button i {
@@ -162,23 +181,141 @@ html, body {
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;
}
/*******************
* Webview Area *
*******************/
#webviews-container {
display: flex;
height: 100%;
width: 100%;
}
webview {
opacity: 1;
transition: opacity 0.3s;
transition: opacity 0.3s ease-in;
flex-grow: 1;
}
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;
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;
}
#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;
}
#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

@@ -5,7 +5,35 @@ body {
cursor: default;
font-size: 14px;
color: #333;
background: #fff;
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 {
@@ -22,23 +50,23 @@ body {
font-size: 16px;
}
#tabs-container {
#nav-container {
padding: 20px 0;
}
.tab {
.nav {
padding: 5px 0;
color: #999;
cursor: pointer;
}
.tab.active {
.nav.active {
color: #464e5a;
cursor: default;
position: relative;
}
.tab.active::before {
.nav.active::before {
background: #464e5a;
width: 3px;
height: 16px;
@@ -59,57 +87,62 @@ body {
overflow-y: scroll;
}
.server-info-container {
#new-server-container {
margin: 20px 0;
opacity: 1;
transition: opacity 0.3s;
}
.title {
padding: 4px 0 6px 0;
font-size: 18px;
color: #000;
font-weight: bold;
color: #1e1e1e;
}
.sub-title {
padding: 4px 0 6px 0;
font-weight: bold;
color: #616161;
}
img.server-info-icon {
background: #a4d3c4;
background-size: 100%;
border-radius: 4px;
width: 44px;
height: 44px;
width: 36px;
height: 36px;
padding: 4px;
}
.server-info-left {
margin-right: 20px;
margin: 10px 20px 0 0;
}
.server-info-right {
flex-grow: 1;
margin-right: 10px;
}
.server-info-row {
display: flex;
line-height: 26px;
height: 40px;
margin: 8px 0 0 0;
}
.server-info-key {
width: 40px;
margin-right: 20px;
text-align: right;
.server-info-alias {
font-weight: bold;
cursor: pointer;
}
.server-info-value {
.server-info-url {
flex-grow: 1;
font-size: 14px;
height: 24px;
border: none;
border-bottom: #ddd 1px solid;
border-bottom: #ededed 1px solid;
outline-width: 0;
background: transparent;
max-width: 500px;
}
.server-info-value:focus {
border-bottom: #b0d8ce 2px solid;
border-bottom: #388E3C 1px solid;
}
.actions-container {
@@ -124,12 +157,15 @@ img.server-info-icon {
.action {
display: flex;
align-items: center;
margin-right: 20px;
padding: 0 10px;
border-radius: 2px;
margin-right: 10px;
}
.action i {
margin-right: 5px;
font-size: 18px;
line-height: 27px;
}
.settings-pane {
@@ -148,34 +184,51 @@ img.server-info-icon {
color: #999;
}
.server-info.active {
background: #ecf4ef;
}
.server-info {
.settings-card {
display: flex;
padding: 10px;
margin: 10px 0 10px 0;
padding: 16px 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;
}
.save-server-button {
display: inline-block;
.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%;
}
.code {
font-family: Courier New, Courier, monospace;
}
i.open-tab-button {
padding: 0 5px;
font-size: 18px;
cursor: pointer;
padding: 7px 14px;
background-color: #52c2af;
border-radius: 4px;
border: 0;
font-size: 1em;
font-weight: 600;
color: #fff;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
transition: all .2s ease;
}
.save-server-button:hover {
background-color: #32a692;
}
}

View File

@@ -0,0 +1,9 @@
/* Override css rules */
.portico-wrap>.header {
display: none;
}
.portico-container>.footer {
display: none;
}

View File

@@ -4,11 +4,11 @@ const Tab = require(__dirname + '/../components/tab.js');
class FunctionalTab extends Tab {
template() {
return `<div class="tab">
return `<div class="tab functional-tab">
<div class="server-tab-badge close-button">
<i class="material-icons">close</i>
</div>
<div class="server-tab functional-tab">
<div class="server-tab">
<i class="material-icons">${this.props.materialIcon}</i>
</div>
</div>`;

View File

@@ -1,12 +1,18 @@
'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" style="background-image: url(${this.props.icon});"></div>
<div class="server-tab">
<img class="server-icons" src='${this.props.icon}'/>
</div>
<div class="server-tab-shortcut">${this.generateShortcutText()}</div>
</div>`;
}
@@ -26,6 +32,27 @@ class ServerTab extends Tab {
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

@@ -43,7 +43,4 @@ class Tab extends BaseComponent {
}
}
Tab.SERVER_TAB = 0;
Tab.SETTINGS_TAB = 1;
module.exports = Tab;

View File

@@ -1,10 +1,12 @@
'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 {app, dialog, shell} = require('electron').remote;
const {ipcRenderer} = require('electron');
const {shell} = require('electron').remote;
const BaseComponent = require(__dirname + '/../components/base.js');
@@ -25,7 +27,7 @@ class WebView extends BaseComponent {
src="${this.props.url}"
${this.props.nodeIntegration ? 'nodeIntegration' : ''}
disablewebsecurity
preload="js/preload.js"
${this.props.preload ? 'preload="js/preload.js"' : ''}
webpreferences="allowRunningInsecureContent, javascript=yes">
</webview>`;
}
@@ -42,7 +44,7 @@ class WebView extends BaseComponent {
const {url} = event;
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
if (LinkUtil.isInternal(domainPrefix, url)) {
if (LinkUtil.isInternal(domainPrefix, url) || url === (domainPrefix + '/')) {
event.preventDefault();
this.$el.loadURL(url);
} else {
@@ -57,7 +59,10 @@ class WebView extends BaseComponent {
this.props.onTitleChange();
});
this.$el.addEventListener('dom-ready', this.show.bind(this));
this.$el.addEventListener('dom-ready', () => {
this.$el.classList.add('onload');
this.show();
});
this.$el.addEventListener('did-fail-load', event => {
const {errorDescription} = event;
@@ -90,13 +95,23 @@ class WebView extends BaseComponent {
}
this.$el.classList.remove('disabled');
setTimeout(() => {
this.$el.classList.remove('onload');
}, 1000);
this.focus();
this.loading = false;
this.props.onTitleChange(this.$el.getTitle());
// Injecting preload css in webview to override some css rules
this.$el.insertCSS(fs.readFileSync(path.join(__dirname, '/../../css/preload.css'), 'utf8'));
}
focus() {
this.$el.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() {
@@ -111,25 +126,6 @@ class WebView extends BaseComponent {
}
}
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) {
this.reload();
ipcRenderer.send('reload');
ipcRenderer.send('destroytray');
}
if (index === 1) {
app.quit();
}
});
}
zoomIn() {
this.zoomFactor += 0.1;
this.$el.setZoomFactor(this.zoomFactor);
@@ -173,6 +169,10 @@ class WebView extends BaseComponent {
this.hide();
this.$el.reload();
}
send(...param) {
this.$el.send(...param);
}
}
module.exports = WebView;

View File

@@ -7,16 +7,25 @@ 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.$addServerButton = $actionsContainer.querySelector('#add-action');
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
this.$content = document.getElementById('content');
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 = [];
@@ -24,11 +33,17 @@ class ServerManagerView {
}
init() {
this.initSidebar();
this.initTabs();
this.initActions();
this.registerIpcs();
}
initSidebar() {
const showSidebar = ConfigUtil.getConfigItem('show-sidebar', true);
this.toggleSidebar(showSidebar);
}
initTabs() {
const servers = DomainUtil.getDomains();
if (servers.length > 0) {
@@ -37,17 +52,21 @@ class ServerManagerView {
}
this.activateTab(0);
} else {
this.openSettings();
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.$content,
$root: this.$webviewsContainer,
index,
url: server.url,
name: server.alias,
@@ -56,7 +75,8 @@ class ServerManagerView {
},
onNetworkError: this.openNetworkTroubleshooting.bind(this),
onTitleChange: this.updateBadge.bind(this),
nodeIntegration: false
nodeIntegration: false,
preload: true
})
}));
}
@@ -65,12 +85,28 @@ class ServerManagerView {
this.$reloadButton.addEventListener('click', () => {
this.tabs[this.activeTabIndex].webview.reload();
});
this.$addServerButton.addEventListener('click', this.openSettings.bind(this));
this.$settingsButton.addEventListener('click', this.openSettings.bind(this));
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]) {
if (this.functionalTabs[tabProps.name] !== undefined) {
this.activateTab(this.functionalTabs[tabProps.name]);
return;
}
@@ -78,12 +114,14 @@ class ServerManagerView {
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.$content,
$root: this.$webviewsContainer,
index: this.functionalTabs[tabProps.name],
url: tabProps.url,
name: tabProps.name,
@@ -92,19 +130,21 @@ class ServerManagerView {
},
onNetworkError: this.openNetworkTroubleshooting.bind(this),
onTitleChange: this.updateBadge.bind(this),
nodeIntegration: true
nodeIntegration: true,
preload: false
})
}));
this.activateTab(this.functionalTabs[tabProps.name]);
}
openSettings() {
openSettings(nav = 'General') {
this.openFunctionalTab({
name: 'Settings',
materialIcon: 'settings',
url: `file://${__dirname}/preference.html`
url: `file://${__dirname}/preference.html#${nav}`
});
this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav);
}
openAbout() {
@@ -124,7 +164,7 @@ class ServerManagerView {
}
activateTab(index, hideOldTab = true) {
if (this.tabs[index].loading) {
if (this.tabs[index].webview.loading) {
return;
}
@@ -138,10 +178,15 @@ class ServerManagerView {
this.activeTabIndex = index;
this.tabs[index].activate();
ipcRenderer.send('update-menu', {
tabs: this.tabs,
activeTabIndex: this.activeTabIndex
});
}
destroyTab(name, index) {
if (this.tabs[index].loading) {
if (this.tabs[index].webview.loading) {
return;
}
@@ -150,7 +195,29 @@ class ServerManagerView {
delete this.tabs[index];
delete this.functionalTabs[name];
this.activateTab(0, false);
// 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() {
@@ -166,6 +233,14 @@ class ServerManagerView {
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',
@@ -189,16 +264,71 @@ class ServerManagerView {
});
}
ipcRenderer.on('open-settings', this.openSettings.bind(this));
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('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', () => {
ipcRenderer.send('reload-main');
});
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

@@ -9,7 +9,7 @@ class NetworkTroubleshootingView {
init() {
this.$reconnectButton.addEventListener('click', () => {
ipcRenderer.send('reload-main');
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
}

View File

@@ -1,144 +0,0 @@
'use strict';
const {ipcRenderer} = require('electron');
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
class PreferenceView {
constructor() {
this.$newServerButton = document.getElementById('new-server-action');
this.$saveServerButton = document.getElementById('save-server-action');
this.$reloadServerButton = document.getElementById('reload-server-action');
this.$serverInfoContainer = document.querySelector('.server-info-container');
}
init() {
this.initServers();
this.initActions();
}
initServers() {
const servers = DomainUtil.getDomains();
this.$serverInfoContainer.innerHTML = servers.length ? '' : 'Add your first server to get started!';
this.initNewServerForm();
for (const i in servers) {
this.initServer(servers[i], i);
}
}
initServer(server, index) {
const {
alias,
url,
icon
} = server;
const serverInfoTemplate = `
<div class="server-info">
<div class="server-info-left">
<img class="server-info-icon" src="${icon}"/>
</div>
<div class="server-info-right">
<div class="server-info-row">
<span class="server-info-key">Name</span>
<input class="server-info-value" disabled value="${alias}"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Url</span>
<input class="server-info-value" disabled value="${url}"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Icon</span>
<input class="server-info-value" disabled value="${icon}"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Actions</span>
<div class="action server-info-value" id="delete-server-action-${index}">
<i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
</div>
</div>
</div>
</div>`;
this.$serverInfoContainer.appendChild(this.insertNode(serverInfoTemplate));
document.getElementById(`delete-server-action-${index}`).addEventListener('click', () => {
DomainUtil.removeDomain(index);
this.initServers();
// alert('Success. Reload to apply changes.');
ipcRenderer.send('reload-main');
this.$reloadServerButton.classList.remove('hidden');
});
}
initNewServerForm() {
const newServerFormTemplate = `
<div class="server-info active hidden">
<div class="server-info-left">
<img class="server-info-icon" src="https://chat.zulip.org/static/images/logo/zulip-icon-128x128.271d0f6a0ca2.png"/>
</div>
<div class="server-info-right">
<div class="server-info-row">
<span class="server-info-key">Name</span>
<input id="server-info-name" class="server-info-value" placeholder="(Required)"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Url</span>
<input id="server-info-url" spellcheck="false" class="server-info-value" placeholder="(Required)"/>
</div>
<div class="server-info-row">
<span class="server-info-key">Icon</span>
<input id="server-info-icon" class="server-info-value" placeholder="(Optional)"/>
</div>
</div>
</div>
`;
this.$serverInfoContainer.appendChild(this.insertNode(newServerFormTemplate));
this.$newServerForm = document.querySelector('.server-info.active');
this.$newServerAlias = this.$newServerForm.querySelectorAll('input.server-info-value')[0];
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.server-info-value')[1];
this.$newServerIcon = this.$newServerForm.querySelectorAll('input.server-info-value')[2];
}
initActions() {
this.$newServerButton.addEventListener('click', () => {
this.$newServerForm.classList.remove('hidden');
this.$saveServerButton.classList.remove('hidden');
this.$newServerButton.classList.add('hidden');
});
this.$saveServerButton.addEventListener('click', () => {
DomainUtil.checkDomain(this.$newServerUrl.value).then(domain => {
const server = {
alias: this.$newServerAlias.value,
url: domain,
icon: this.$newServerIcon.value
};
DomainUtil.addDomain(server);
this.$saveServerButton.classList.add('hidden');
this.$newServerButton.classList.remove('hidden');
this.$newServerForm.classList.add('hidden');
this.initServers();
// alert('Success. Reload to apply changes.');
ipcRenderer.send('reload-main');
this.$reloadServerButton.classList.remove('hidden');
}, errorMessage => {
alert(errorMessage);
});
});
this.$reloadServerButton.addEventListener('click', () => {
ipcRenderer.send('reload-main');
});
}
insertNode(html) {
const wrapper = document.createElement('div');
wrapper.innerHTML = html;
return wrapper.firstElementChild;
}
}
window.onload = () => {
const preferenceView = new PreferenceView();
preferenceView.init();
};

View File

@@ -0,0 +1,156 @@
'use strict';
const {ipcRenderer} = require('electron');
const BaseComponent = require(__dirname + '/../../components/base.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
class GeneralSection extends BaseComponent {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="settings-pane" id="server-settings-pane">
<div class="title">Tray Options</div>
<div id="tray-option-settings" class="settings-card">
<div class="setting-row">
<div class="setting-description">Show app icon in system tray</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">App Updates</div>
<div id="betaupdate-option-settings" class="settings-card">
<div class="setting-row">
<div class="setting-description">Get beta updates</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Desktop Notification</div>
<div id="silent-option-settings" class="settings-card">
<div class="setting-row">
<div class="setting-description">Mute all sounds from Zulip (requires reload)</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">User Interface</div>
<div id="ui-option-settings" class="settings-card">
<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>
</div>
`;
}
settingsOptionTemplate(settingOption) {
if (settingOption) {
return `
<div class="action green">
<span>On</span>
</div>
`;
} else {
return `
<div class="action red">
<span>Off</span>
</div>
`;
}
}
trayOptionTemplate(trayOption) {
this.settingsOptionTemplate(trayOption);
}
updateOptionTemplate(updateOption) {
this.settingsOptionTemplate(updateOption);
}
silentOptionTemplate(silentOption) {
this.settingsOptionTemplate(silentOption);
}
sidebarToggleTemplate(toggleOption) {
this.settingsOptionTemplate(toggleOption);
}
init() {
this.props.$root.innerHTML = this.template();
this.initTrayOption();
this.initUpdateOption();
this.initSilentOption();
this.initSidebarToggle();
}
initTrayOption() {
this.$trayOptionSettings = document.querySelector('#tray-option-settings .setting-control');
this.$trayOptionSettings.innerHTML = '';
const trayOption = ConfigUtil.getConfigItem('trayIcon', true);
const $trayOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(trayOption));
this.$trayOptionSettings.appendChild($trayOption);
$trayOption.addEventListener('click', () => {
const newValue = !ConfigUtil.getConfigItem('trayIcon');
ConfigUtil.setConfigItem('trayIcon', newValue);
ipcRenderer.send('forward-message', 'toggletray');
this.initTrayOption();
});
}
initUpdateOption() {
this.$updateOptionSettings = document.querySelector('#betaupdate-option-settings .setting-control');
this.$updateOptionSettings.innerHTML = '';
const updateOption = ConfigUtil.getConfigItem('betaUpdate', false);
const $updateOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(updateOption));
this.$updateOptionSettings.appendChild($updateOption);
$updateOption.addEventListener('click', () => {
const newValue = !ConfigUtil.getConfigItem('betaUpdate');
ConfigUtil.setConfigItem('betaUpdate', newValue);
this.initUpdateOption();
});
}
initSilentOption() {
this.$silentOptionSettings = document.querySelector('#silent-option-settings .setting-control');
this.$silentOptionSettings.innerHTML = '';
const silentOption = ConfigUtil.getConfigItem('silent', false);
const $silentOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(silentOption));
this.$silentOptionSettings.appendChild($silentOption);
$silentOption.addEventListener('click', () => {
const newValue = !ConfigUtil.getConfigItem('silent', true);
ConfigUtil.setConfigItem('silent', newValue);
this.initSilentOption();
});
}
initSidebarToggle() {
this.$sidebarOptionSettings = document.querySelector('#ui-option-settings #sidebar-option .setting-control');
this.$sidebarOptionSettings.innerHTML = '';
const sidebarOption = ConfigUtil.getConfigItem('show-sidebar', true);
const $sidebarOption = this.generateNodeFromTemplate(this.settingsOptionTemplate(sidebarOption));
this.$sidebarOptionSettings.appendChild($sidebarOption);
$sidebarOption.addEventListener('click', () => {
const newValue = !ConfigUtil.getConfigItem('show-sidebar');
ConfigUtil.setConfigItem('show-sidebar', newValue);
ipcRenderer.send('forward-message', 'toggle-sidebar', newValue);
this.initSidebarToggle();
});
}
handleServerInfoChange() {
ipcRenderer.send('forward-message', 'reload-viewer');
}
}
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', '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,57 @@
'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" style="border: solid 1px #4CAF50;">
<div class="server-info-right">
<div class="server-info-row">
<input class="server-info-url" autofocus placeholder="Enter the url of your Zulip server..."/>
</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.server-info-url')[0];
}
initActions() {
this.$saveServerButton.addEventListener('click', () => {
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
DomainUtil.addDomain(serverConf).then(() => {
this.props.onChange(this.props.index);
});
}, errorMessage => {
alert(errorMessage);
});
});
}
}
module.exports = NewServerForm;

View File

@@ -0,0 +1,68 @@
'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');
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;
}
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="server-info-url" 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,81 @@
'use strict';
const {ipcRenderer} = require('electron');
const BaseComponent = require(__dirname + '/../../components/base.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 BaseComponent {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="settings-pane" id="server-settings-pane">
<div class="title">Manage Servers</div>
<div class="actions-container">
<div class="action green" id="new-server-action">
<i class="material-icons">add_box</i>
<span>Add Server</span>
</div>
</div>
<div id="new-server-container" class="hidden"></div>
<div class="sub-title" id="existing-servers"></div>
<div id="server-info-container"></div>
</div>
`;
}
init() {
this.initServers();
this.initActions();
}
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.handleServerInfoChange.bind(this)
}).init();
}
}
initNewServerForm() {
new NewServerForm({
$root: this.$newServerContainer,
onChange: this.handleServerInfoChange.bind(this)
}).init();
}
initActions() {
this.$newServerContainer.classList.remove('hidden');
this.$newServerButton.classList.remove('green');
this.$newServerButton.classList.add('grey');
}
handleServerInfoChange() {
ipcRenderer.send('forward-message', 'reload-viewer');
}
}
module.exports = ServersSection;

View File

@@ -1,6 +1,9 @@
'use strict';
const {ipcRenderer} = require('electron');
const {spellChecker} = require('./spellchecker');
// eslint-disable-next-line import/no-unassigned-import
require('./notification');
const logout = () => {
// Create the menu for the below
@@ -34,6 +37,6 @@ document.addEventListener('DOMContentLoaded', () => {
// redirect users to network troubleshooting page
document.querySelector('.restart_get_events_button').addEventListener('click', () => {
ipcRenderer.send('reload-main');
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

@@ -9,6 +9,8 @@ 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);
}
});
};
@@ -135,7 +136,7 @@ const createTray = function () {
type: 'separator'
},
{
label: 'Manage Zulip servers',
label: 'Settings',
click() {
ipcRenderer.send('focus-app');
sendAction('open-settings');
@@ -161,6 +162,10 @@ const createTray = function () {
};
ipcRenderer.on('destroytray', event => {
if (!window.tray) {
return;
}
window.tray.destroy();
if (window.tray.isDestroyed()) {
window.tray = null;
@@ -172,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,53 @@
'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) {
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

@@ -1,12 +1,15 @@
'use strict';
const {app} = require('electron').remote;
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 = __dirname + '../../../img/icon.png';
const defaultIconUrl = '../renderer/img/icon.png';
class DomainUtil {
constructor() {
if (instance) {
@@ -15,7 +18,7 @@ class DomainUtil {
instance = this;
}
this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
this.reloadDB();
// Migrate from old schema
if (this.db.getData('/').domain) {
this.addDomain({
@@ -29,6 +32,7 @@ class DomainUtil {
}
getDomains() {
this.reloadDB();
if (this.db.getData('/').domains === undefined) {
return [];
} else {
@@ -37,20 +41,36 @@ class DomainUtil {
}
getDomain(index) {
this.reloadDB();
return this.db.getData(`/domains[${index}]`);
}
addDomain(server) {
server.icon = server.icon || defaultIconUrl;
this.db.push('/domains[]', server, true);
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();
}
checkDomain(domain) {
@@ -61,22 +81,103 @@ class DomainUtil {
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) {
resolve(domain);
} else if (error.toString().indexOf('Error: self signed certificate') >= 0) {
if (window.confirm(`Do you trust certificate from ${domain}?`)) {
resolve(domain);
} else {
reject('Untrusted Certificate.');
}
// Correct
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
}, () => {
resolve(serverConf);
});
} else if (certsError.indexOf(error.toString()) >= 0) {
dialog.showMessageBox({
type: 'question',
buttons: ['Yes', 'No'],
defaultId: 0,
message: `Do you trust certificate from ${domain}? \n ${error}`
}, 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.
const dir = `${app.getPath('userData')}/server-icons`;
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
return new Promise(resolve => {
const filePath = `${dir}/${new Date().getMilliseconds()}${path.extname(url).split('?')[0]}`;
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);
}
});
}
reloadDB() {
this.db = new JsonDB(app.getPath('userData') + '/domain.json', true, true);
}
}
module.exports = new DomainUtil();

View File

@@ -19,10 +19,7 @@ class LinkUtil {
const currentDomain = wurl('hostname', currentUrl);
const newDomain = wurl('hostname', newUrl);
const skipImages = '.jpg|.gif|.png|.jpeg|.JPG|.PNG';
// We'll be needing this to open images in default browser
return (currentDomain === newDomain) && !newUrl.match(skipImages);
return (currentDomain === newDomain) && newUrl.includes('/#narrow');
}
}

View File

@@ -1,28 +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 id="sidebar">
<div id="tabs-container"></div>
<div id="actions-container">
<div class="action-button" id="reload-action">
<i class="material-icons md-48">refresh</i>
</div>
<div class="action-button" id="add-action">
<i class="material-icons md-48">add_circle</i>
</div>
<div class="action-button" id="settings-action">
<i class="material-icons md-48">settings</i>
</div>
<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>
</body>
<script src="js/main.js"></script>
</html>
<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>

View File

@@ -5,40 +5,12 @@
<meta name="viewport" content="width=device-width">
<title>Zulip - Settings</title>
<link rel="stylesheet" href="css/preference.css" type="text/css" media="screen">
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
</head>
<body>
<div id="content">
<div id="sidebar">
<div id="settings-header">Settings</div>
<div id="tabs-container">
<div class="tab" id="general-settings">General</div>
<div class="tab active" id="server-settings">Servers</div>
<div class="tab" id="about-settings">About</div>
</div>
</div>
<div id="settings-container">
<div class="settings-pane" id="server-settings-pane">
<div class="title">Manage Servers</div>
<div class="actions-container">
<div class="action" id="new-server-action">
<i class="material-icons">add_box</i>
<span>New Server</span>
</div>
<div class="action hidden" id="save-server-action">
<button class="save-server-button">Save</button>
</div>
<div class="action hidden" id="reload-server-action">
<i class="material-icons">refresh</i>
<span>Reload to Apply Changes</span>
</div>
</div>
<div class="server-info-container">
</div>
</div>
</div>
<div id="sidebar"></div>
<div id="settings-container"></div>
</div>
</body>
<script src="js/pages/preference.js"></script>
<script src="js/pages/preference/preference.js"></script>
</html>

View File

@@ -1,56 +1,84 @@
# Development guide
# 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
* [python](https://www.python.org/downloads/release/python-2713/) (v2.7.x recommended)
* [node-gyp](https://github.com/nodejs/node-gyp#installation)
* [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
## System specific dependencies
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):
### Linux
Install following packages:
```sh
$ sudo apt-get install build-essential libxext-dev libxtst-dev libxkbfile-dev
$ 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
```
## Installation
[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:
Start the app:
```sh
$ npm start
```
Start and watch changes
Start and watch changes:
```sh
$ npm run dev
```
### Making a release
To package app into an installer use command:
## 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
```
It will start the packaging process for the operating system you are running this command on. The ready for distribution file (e.g. dmg, windows installer, deb package) will be outputted to the `dist` directory.
You can create a Windows installer only when running on Windows and similarly for Linux and OSX. So, to generate all three installers, you will need all three operating systems.
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.
# Troubleshooting
If you have any problems running the app please see the [most common issues](./troubleshooting.md).
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

View File

@@ -1,14 +1,14 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "1.1.0-beta",
"version": "1.3.0-beta",
"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": {
@@ -20,13 +20,13 @@
},
"scripts": {
"start": "electron app --disable-http-cache",
"postinstall": "install-app-deps",
"postinstall": "electron-builder install-app-deps",
"test": "xo",
"dev": "gulp dev",
"pack": "build --dir",
"dist": "build",
"mas": "build --mac mas",
"build:win": "build --win nsis-web --ia32 --x64",
"pack": "electron-builder --dir",
"dist": "electron-builder",
"mas": "electron-builder --mac mas",
"build:win": "electron-builder --win nsis-web --ia32 --x64",
"travis": "cd ./scripts && ./travis-build-test.sh"
},
"build": {
@@ -34,6 +34,7 @@
"asar": true,
"files": [
"**/*",
"!docs${/*}",
"!node_modules/@paulcbetts/cld/deps/cld${/*}"
],
"copyright": "©2017 Kandra Labs, Inc.",
@@ -74,11 +75,13 @@
},
"win": {
"target": "nsis",
"icon": "build/icon.ico"
"icon": "build/icon.ico",
"publisherName": "Kandra Labs, Inc."
},
"nsis": {
"perMachine": true,
"oneClick": false
"oneClick": false,
"allowToChangeInstallationDirectory": true
}
},
"keywords": [
@@ -92,15 +95,15 @@
"devDependencies": {
"assert": "1.4.1",
"devtron": "1.4.0",
"electron-builder": "17.10.0",
"electron": "1.6.8",
"electron-connect": "0.4.8",
"electron-builder": "19.19.1",
"electron": "1.6.11",
"electron-connect": "0.6.2",
"gulp": "3.9.1",
"gulp-mocha": "3.0.1",
"chai-as-promised": "6.0.0",
"chai": "^3.5.0",
"spectron": "3.6.4",
"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"
},
"xo": {
"parserOptions": {
@@ -126,7 +129,8 @@
"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
}
}
],

View File

@@ -3,3 +3,9 @@
* 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