Compare commits

...

305 Commits

Author SHA1 Message Date
Akash Nimare
be869a52d5 release: v4.0.3. 2020-02-28 10:14:22 +05:30
Akash Nimare
fcbb5da18e webview: Update web security preference.
Electron docs suggests that we should not use
`disablewebsecurity` thus removing the same.
2020-02-28 10:13:30 +05:30
Akash Nimare
95a9568ece beta-release: New release v4.0.2-beta. 2019-11-13 14:56:34 +05:30
Kanishk Kakar
e7a885a1fb macos: Enable notarization for macOS Catalina.
This fixes the issue for Catalina users.
2019-11-13 14:55:11 +05:30
Akash Nimare
17d4d97e2e certificate: Make certificate location dynamic.
* certificate: Make certificate location dynamic.

* Update certificate location for old servers.
2019-11-01 19:56:22 +05:30
Akash Nimare
3b14684058 Add docs for translation 2019-10-30 02:10:38 +05:30
Akash Nimare
7d592a0a1c validation: Add SSL troubleshooting guide in error message. 2019-10-25 21:03:28 +05:30
Ross Brunton
eb1be7106b Added option to quit on closing the window.
This adds a configuration option to quit the app (rather than going to
the tray) when the "close" button is clicked.
2019-10-14 01:05:18 +05:30
Tim Abbott
1da6e5d51d README: Fix typo in link to server/webapp repository. 2019-10-07 11:51:57 -07:00
Tim Abbott
dae7089c7e README: Explicitly address where to report bugs. 2019-10-01 15:41:39 -07:00
Muskan Khedia
30b40e2ff2 network: Prompts Desktop App to ask for network setting in add-org page.
Fixes: #540.
2019-09-28 17:44:34 +05:30
Akash Nimare
b76f01349a docs: Update electron tutorial guide.
Fixes: #826.
2019-09-27 23:02:25 +05:30
Kanishk Kakar
8446deb673 sidebar: Improve UX for notification settings.
* sidebar: Disable notif settings if not logged in.

* sidebar: Activate relevant tab for notif settings.
2019-09-25 18:25:00 +05:30
Kanishk Kakar
d4b9663257 network: Tackle network issues independently.
Few changes -
* webview: Show connection failure per server.
* network: Try to reconnect diff servers.
* Fixes concern that some proxy networks may allow only specific servers
to be reachable.
* domains: Show network error on server invalidation.
* webview: Handle network errors in preload script.
Fixes: #591, #312.
2019-09-24 18:22:19 +05:30
Akash Nimare
77044fd9fa enterprise: Document the enterprise feature. 2019-09-18 23:26:44 +05:30
Kanishk Kakar
177b77f0b5 sidebar: Add option to open notification setting from the context menu. 2019-09-15 21:20:08 +05:30
Kanishk Kakar
99b154b8ae system-util: Set User-Agent from main process.
* Sets user-agent config item when the app's DOM is ready.
* App sends the right User-Agent to the server-settings API.
 
Fixes #817.
2019-09-11 16:39:56 +05:30
Kanishk Kakar
3fd8aedf81 network: Reactivate network.js script.
Fixes an issue introduced during TS migration that rendered network.ts
ineffective because exports were not defined.
2019-09-02 00:25:17 +05:30
Rhythm Sharma
b4d2e55c6f linux: Fix broken icon issue for snap package. 2019-08-29 02:20:12 +05:30
Kanishk Kakar
3c701ff518 sidebar: Load last active server before others.
Fixes: #551.
2019-08-28 14:12:01 +05:30
ViPuL
1f79a97b05 system-presence: Pass system active status to webapp.
We check user status every 15 seconds and update the status accordingly
to every organization connected. The webapp then uses this system presence data
we send to set the user status based on system activity.

Fixes #352.
2019-08-19 20:09:27 -04:00
Nikita
90e8e9a806 il8n: Fix translations for ru locales. 2019-08-17 10:52:36 -04:00
Kanishk Kakar
59ef505efd settings: Fix trailing brackets.
This accidentally slipped by in the translation work we recently did.
2019-08-17 10:49:24 -04:00
Akash Nimare
8d0a111c91 version: Update app to v4.0.1. 2019-08-17 01:41:15 +05:30
Akash Nimare
a10fa8f3ad badge-count: Show badge-count on Linux.
Added support for showing the badge counts in the Unity launcher.
This should work on elementary OS and Ubuntu.

More info -
https://github.com/electron/electron/issues/16001
https://github.com/signalapp/Signal-Desktop/issues/3387
2019-08-17 01:32:50 +05:30
Akash Nimare
39427091f5 linux: Add desktopName config for Linux.
This will help in creating .desktop file for Linux.
2019-08-17 01:31:31 +05:30
ViPuL
f8d93cf397 build: Add MSI installer support.
This PR adds MSI in the target option. MSI is useful for sysadmins. There is no support for auto-updates since it often manages by the admins. 
More info -
https://github.com/electron-userland/electron-builder/releases/tag/v19.41.0
https://github.com/electron-userland/electron-builder/issues/3322

Fixes: #641.
2019-08-15 00:35:00 +05:30
Priyank Patel
ab62b8b5bb Remove accidently commited translation-util.js file.
This file was accidently committed in 77a1fc0bd3
when migrating old PR to use typescript. This commit removes it.
2019-08-14 12:50:47 -04:00
Kanishk Kakar
ce071dcac9 docs: Add release notes for v4.0.0. 2019-08-11 01:13:37 +05:30
Akash Nimare
04c1109d43 release: New release v4.0.0. 2019-08-07 22:17:34 +05:30
Prakhar Uniyal
26f321e7f9 preference: Fix visually broken delete custom css button.
There were couple of issue solved by this commit:
  - The button had extra 10px margin to right; this comes from .action class
  - The "Delete" text and icon were not aligned
  - The button looks bigger than other buttons

Both of which made the button look broken. We fix first by changing
.css-delete-action to #css-delete-action in css file; git grep shows
this is only used once and is used as id not a class. Then we use
display flex to fix the alinging of icon and text. We add box-sizing border-box
which solves the bigger width issue. We remove extra 10px margin that comes from
.action.

Fixes: #801.
2019-08-07 20:57:44 +05:30
Kanishk Kakar
47b729cbc2 i18n: Use English as fallback language.
In case app.getLocale() returns a falsey value, English is used as the
app language.
2019-08-02 00:45:12 +05:30
Kanishk Kakar
6d34da5953 i18n: Add translations to the new server form. 2019-08-02 00:45:12 +05:30
Kanishk Kakar
cfd1ebc643 i18n: Add translations to settings sidebar. 2019-08-02 00:45:12 +05:30
Kanishk Kakar
dab30dfb7a i18n: Add translations for shortcuts pane. 2019-08-02 00:45:12 +05:30
Kanishk Kakar
e2bb28c738 i18n: Add translations for connected orgs. 2019-08-02 00:45:12 +05:30
Kanishk Kakar
ebd4d9a69b i18n: Add translations for network settings. 2019-08-02 00:45:12 +05:30
Kanishk Kakar
ab80315846 i18n: Add translations for general settings. 2019-08-02 00:45:12 +05:30
Kanishk Kakar
c78ea51ec9 i18n: Change menu according to system locale. 2019-08-02 00:45:12 +05:30
Kanishk Kakar
d521f533f0 i18n: Add new values to translations 2019-08-02 00:45:12 +05:30
Kanishk Kakar
67a69f0dc2 i18n: Add translations to app menu
* Uses new keys added to locale-template.json
* Disregards org names
2019-08-02 00:45:12 +05:30
Kanishk Kakar
d99cc0d49c locales: Change translate API to handle rate limit
Also add catch block to log possible errors.
2019-08-02 00:45:12 +05:30
Kanishk Kakar
77a1fc0bd3 i18n: Setup module and add translation-util.js
* Add i18n package.
* Use system locale for TranslationUtil.
2019-08-02 00:45:12 +05:30
Akash Nimare
cf96e94470 zoom: Fix zoom-in shortcut symbol.
Use symbol instead of symbol name in accelerators in menu items.

This is a hackish solution for the following upstream issue -
https://github.com/electron/electron/issues/10371

Fixes: #718.
2019-07-31 18:37:03 +05:30
Akash Nimare
4395edc3de macos-shortcut: Add meta key for cmd. 2019-07-31 18:37:03 +05:30
Akash Nimare
66479e0d88 docs: Update auto-update docs for preset orgs. 2019-07-31 04:43:58 +05:30
Kanishk Kakar
7c44ef5e41 enterprise: Restrict EnterpriseUtil method calls.
* Add a boolean flag to EnterpriseUtil so unnecessary function calls
don't take place for non-admin users.
2019-07-31 04:32:10 +05:30
Kanishk Kakar
296c83949f typescript: Define DialogBoxError in messages.ts. 2019-07-31 04:32:10 +05:30
Kanishk Kakar
ac6f14f5b4 settings: Show tooltip when setting disabled.
If setting has been locked by an admin, then a tooltip is shown in the
settings page.
2019-07-31 04:32:10 +05:30
Kanishk Kakar
20ac6bf921 enterprise: Silently fail config updates.
If a config item exists in enterprise config and we're not trying to
override, then setConfigItem returns.
2019-07-31 04:32:10 +05:30
Kanishk Kakar
5e9f9aa9a2 enterprise: Disable autoUpdate on admin setting. 2019-07-31 04:32:10 +05:30
Kanishk Kakar
4efa98118b enterprise: Init enterpriseSettings from file.
Read enterprise settings while loading the app and give preference to
global config.
2019-07-31 04:32:10 +05:30
Kanishk Kakar
a7887211ac enterprise: Raise error when removing orgs.
When a user tries to remove a preset org, we raise an error asking them
to contact their sys admin.
2019-07-31 04:32:10 +05:30
Kanishk Kakar
33fadcd876 enterprise: Stop loading gif if no domains added.
If no domains have been added, stop the loading gif and open the new
server form.
2019-07-31 04:32:10 +05:30
Kanishk Kakar
e7e92ebecc enterprise: Show error when all preset orgs fail.
On app load, if all the preset orgs fail to get added to the server,
then we show an errorBox.
2019-07-31 04:32:10 +05:30
Kanishk Kakar
6c29af6766 docs: Add enterprise docs for preset orgs. 2019-07-31 04:32:10 +05:30
Kanishk Kakar
eabac3ae81 enterprise: Show loading gif view.
When no other orgs have been added and presetOrgs have been specified in
enterprise config, loading gif will appear.
2019-07-31 04:32:10 +05:30
Kanishk Kakar
fc0385433e enterprise: Detect changes in orgs config.
Allow app to identify if presetOrgs have changed in enterprise config
and change domains based on user response.
2019-07-31 04:32:10 +05:30
Kanishk Kakar
400c02f264 enterprise: Add support for preset orgs.
Enable configuration of preset organizations in the enterprise config
file and load them when app loads.
Partially valid array of URLs accepted while loading app.
2019-07-31 04:32:10 +05:30
Kanishk Kakar
be841cff34 enterpise: Add enterprise-util for custom configs. 2019-07-31 04:32:10 +05:30
Kanishk Kakar
7d426c4791 sidebar: Revert to fallback icon only when needed.
Fix an issue where server icon was replaced with character icon on
network issues.
2019-07-30 02:14:40 +05:30
Akash Nimare
939aa50e9b menu: Disable window sub-menu.
Disable switch to next-org and previous org when the
server length is less than one.

Fixes: #799.
2019-07-26 05:14:45 +05:30
Akash Nimare
dc9b9a1dc3 zoom-in: Fix zoom-in shortcut. 2019-07-24 17:28:59 +05:30
Kanishk Kakar
b0da718bfc sidebar: Sync loading indicator with loading GIF.
* Disable loading indicator (same as going back in settings)
* Show loading indicator on reload
2019-07-24 02:34:05 +05:30
Priyank Patel
7ea6255e65 darwin-notifications: Use electron_bridge notification reply api if supported.
To avoid using the brittle code we have for inline notification reply
currently, we added more future proof in webapp via the electron_bridge,
we use the newer API if it is supported.

Fixes: #794.
2019-07-23 22:08:24 +05:30
Kanishk Kakar
7338f802e1 docs: Add release notes for v3.1.0-beta. 2019-07-22 20:09:04 +05:30
Akash Nimare
6860f6d836 release: New release v3.1.0-beta. 2019-07-19 00:37:12 +05:30
Kanishk Kakar
088ddf9c62 dev: Use .env file for Sentry DSN.
Reads .env file in root folder of repo to get Sentry DSN for builds.
2019-07-19 00:23:10 +05:30
Priyank Patel
95da6c0d58 dependencies: Remove @types/node.
Electron already depends on the @types/node and extends
it in electron.d.ts.
2019-07-17 00:22:17 +05:30
Akash Nimare
1a3d3fc5ff system-util: Fix user-agent. 2019-07-17 00:22:17 +05:30
Priyank Patel
535039216b package.json: Add clean-ts-files script. 2019-07-17 00:22:17 +05:30
Priyank Patel
f449db1dc1 package.json: Run tsc before running electron-builder. 2019-07-17 00:22:17 +05:30
vsvipul
73e0e81abd gulp: Add tasks for compile and run from src. 2019-07-17 00:22:17 +05:30
Priyank Patel
dd4f3047c3 tools: Add tools for running typescript compiler and electron app.
This tool handles edge cases where all the typescript files might not
be compiled when the electron process starts this defer that; it runs
tsc compiler first.

Next, we want to only run the compiler if the files are not
compiled.
2019-07-17 00:22:17 +05:30
vsvipul
e4ef0e195a typescript: Do final cleanup of issues.
Using `import * as` import syntax causes some problem if the
module exports a class or function. Because the whole point of
star import is to import every property the module exports. It turns
out we have been using it incorrectly in many places which this commit
fixes.

Then we fix a linting error by adding a eslint disable rule to solve
it along with a TODO because the way we currently do it is wrong.

Finally, to conclude this cleanup, we merge all the .gitignore paths
into once now that we can.
2019-07-17 00:22:17 +05:30
Priyank Patel
00a925e39e typscript: Fix electron-debug not found error in app.
The import in question is electron-is-dev one, others we are
just fixing while we are at it. The problem with this way of importing
`import * as` is that the isDev would be { default: true | false } not
true | false as we like it to be. This causes and error in production app
because electron-debug isn't installed.

Next, we fix the electron-connect import in main.js.
2019-07-17 00:22:17 +05:30
Priyank Patel
c34b4f9151 tsconfig: Remove declare module * declaration and normalize imports.
The change in this commits are pretty involved but cannot be split
into small commits. The main changes in this commits are:
    * Remove declare module * now that we don't need it
    * Normalize import paths so typescript is happy

Previously, we were using wrong import paths and so typescript couldn't
really provide full types information for imports. The wrong paths isn't
a bug because it was done to make sure it work when it was imported via a
script tag; we fix this by using require inside the script tag in main.html.

Also, did audit to make sure we correctly use __dirname not that it's
value will be diffrent, it won't be js/ but will be respective to the file
path of the module.
2019-07-17 00:22:17 +05:30
Priyank Patel
8a13f7914c typescript: Fix incorrect types for feedback.ts.
Not that the previous types were incorrect but they were wrong.
SendFeedback extends the HTMLElement and is a class so it has it's
own properties so extended the properties accordingly.
2019-07-17 00:22:17 +05:30
vsvipul
b13c86e2db typescript: Migrate main.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
4fb4773a2b typescript: Migrate tray.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
deda98b9d4 typescript: Migrate preload.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
f98457a0cb typescript: Add typings for window and page_params. 2019-07-17 00:22:17 +05:30
vsvipul
3436fb3e68 domain-util: Change object to any in domain-util. 2019-07-17 00:22:17 +05:30
vsvipul
448822a5fb typescript: Migrate messages.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
6e07945d7c typescript: Migrate spellchecker.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
54a8e33b01 typescript: Migrate feedback.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
93c22ca89d gitignore: Add js and resources dir to gitignore. 2019-07-17 00:22:17 +05:30
vsvipul
a2fca89551 typescript: Migrate electron-bridge.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
1516452a8c tsconfig: Turn off noUnusedParamaters rule.
We want to turn off this rule because this requires us to add _
in front of parameter that are unused which we have lot of, for example,
the ipc listeners.
2019-07-17 00:22:17 +05:30
vsvipul
700e73105e typescript: Migrate network.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
c41bbf9dfd typescript: Migrate shortcuts-section.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
347de5981a typescript: Migrate servers-section.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
d932a2b2c4 typescript: Migrate server-info-form.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
34e403d0d2 typescript: Migrate preference.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
77c5b8af30 typescript: Migrate new-server-form.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
b2bc268c09 typescript: Migrate network-section.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
8c52b563bf typescript: Migrate nav.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
a8109c5106 typescript: Migrate general-section.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
76818b7def typescript: Migrate find-accounts.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
a5ea3d475f typescript: Migrate connected-org-section.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
2f9205b0c4 typescript: Migrate base-section.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
3f00beb291 typescript: Migrate badge-settings.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
72f356de9c typescript: Migrate add-certificate.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
482264d878 gitignore: Add pages/ dir to gitignore. 2019-07-17 00:22:17 +05:30
Priyank Patel
d7725c121c typescript: Migrate default-notification to typescript. 2019-07-17 00:22:17 +05:30
Priyank Patel
2b19cdecf2 typescript: Migrate index.js to typescript. 2019-07-17 00:22:17 +05:30
Priyank Patel
1182af23e4 typescript: Migrate darwin-notification to typescript. 2019-07-17 00:22:17 +05:30
Priyank Patel
6192801083 typescript: Migrate helper.js to typescript. 2019-07-17 00:22:17 +05:30
Priyank Patel
63a913487c .gitignore: Ignore typescript compiled files in notifications directory. 2019-07-17 00:22:17 +05:30
vsvipul
77899f9f48 typescript: Migrate webview.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
2b1e594286 tabs: Fix constructors in webview tabs.
template() property used earlier in tab.ts was a
property of its child classes, which causes problems
in compiler, which this PR fixes.
2019-07-17 00:22:17 +05:30
vsvipul
312dc1daaf typescript: Migrate functional-tab.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
744d056e26 typescript: Migrate server-tab.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
433f11ebd1 typescript: Migrate tab.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
506982b4c6 typescript: Migrate handle-external-link.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
4adda8b8ae typescript: Migrate base.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
fbef3e4d7b utils: Change export statement in domain util. 2019-07-17 00:22:17 +05:30
vsvipul
f076343068 gitignore: Ignore typescript compiled file in js/components. 2019-07-17 00:22:17 +05:30
Priyank Patel
d5c9e638ca typescript: Migrate preventdrag to typescript. 2019-07-17 00:22:17 +05:30
Priyank Patel
d5a343d549 typescript: Add needed type annotations after rebasing to master. 2019-07-17 00:22:17 +05:30
Priyank Patel
4412f60435 typescript: Add types to node-json-db variables.
This adds JsonDB type to db variables we use, which were
previously any due to issue with node-json-db not specifying
the types field in package.json.
2019-07-17 00:22:17 +05:30
Priyank Patel
cec2b3136d dependencies: Upgrade node-json-db to v0.9.2.
This version was released just for the fact that the
bundles typescript declaration file was not defined
in package.json hence typescript couldn't detect it.
2019-07-17 00:22:17 +05:30
Priyank Patel
9ba2e0ad1e .editconfig: Set the indent_size to 4.
This will make GitHub show 4 spaces for a tab instead of 8; makes
PR diffs more readable.
2019-07-17 00:22:17 +05:30
vsvipul
eb30f48a82 typescript: Migrate index.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
edc5b92ab4 typescript: Migrate linuxupdater to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
29753b1a46 typescript: Migrate autoupdater to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
f490efb2df .gitignore: Ignore .js files in app dir. 2019-07-17 00:22:17 +05:30
vsvipul
bb7426a789 utils: Change export statement in linux-update-util. 2019-07-17 00:22:17 +05:30
vsvipul
e7edfffa89 config: Linting changes to package.json and tsconfig.json 2019-07-17 00:22:17 +05:30
Priyank Patel
fb700350f9 typescript: Fix some issues with recent changes.
In domain-util, we were using `import * as ` syntax which
compiled down to `__importStar(require('...'))` which were
not giving the same result as before and causing errors. This
fixes that.

In logger-util, we were missing the call to .apply function
so it should have been `this._log.apply(...)`, but then when we
use apply typescript still complains about merging string with any[]
so we decided to go with bind.
2019-07-17 00:22:17 +05:30
Priyank Patel
79d0688bcd .gitignore: Ignore typescript compiled files. 2019-07-17 00:22:17 +05:30
Priyank Patel
c6c36b698d typescript: Migrate domain-util to typescript. 2019-07-17 00:22:17 +05:30
Priyank Patel
8ef39553b8 typescript: Migrate linux-update-util to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
7374fcbe78 typescript: Migrate config-util to typescript. 2019-07-17 00:22:17 +05:30
Priyank Patel
aa2c55538f typescript: Migrate params-util.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
4d679acb0c typescript: Migrate logger-util to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
795b9fdc17 xo: Upgrade xo to v0.24.0.
This version also includes new rule we want to ignore.
2019-07-17 00:22:17 +05:30
Priyank Patel
82d9f795c2 xo: Disable some of the typescript-eslint rule.
One rule requires use to move all the functions which is
unnessacary churn.
2019-07-17 00:22:17 +05:30
vsvipul
ec91d46f7b typescript: Migrate menu.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
728418742d typescript: Migrate proxy-util to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
8b0e9b55fb typescript: Migrate reconnect-util to typescript. 2019-07-17 00:22:17 +05:30
Priyank Patel
b40be8d891 xo: Disable lines-between-class-members rule.
Apparently this doesn't work well with typescript files.
For example this will throw this linting error:
class Test {
  a: boolean;
  b: string; // needs a new line before this one to fix this linting error
}
2019-07-17 00:22:17 +05:30
Priyank Patel
e12d69720d typing.d.ts: Add comment for why we use declare module. 2019-07-17 00:22:17 +05:30
vsvipul
8353c4de16 typescript: Migrate dnd-util to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
8c3a2a80c7 typescript: Migrate link-util to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
5c4221fc31 typescript: Migrate default-util to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
a57c269935 typescript: Migrate common-util to typescript. 2019-07-17 00:22:17 +05:30
Priyank Patel
ce81a1b367 typescript: Fix wrong import syntax in request-util.ts.
Using the previous syntax was wrong because the file export
the class For example we would use previous syntax if the class
was export by `module.export = { ProxyUtil }`.
2019-07-17 00:22:17 +05:30
vsvipul
26e97c8746 typescript: Migrate certificate-util to typescript. 2019-07-17 00:22:17 +05:30
Priyank Patel
393a0fc2be request-util: Fix linting error. 2019-07-17 00:22:17 +05:30
vsvipul
b0348d6bca typescript: Migrate system-util.js to typescript.
We use to check for a case where os.platform() == win64
which is not possible so removed the condition.
2019-07-17 00:22:17 +05:30
vsvipul
9c68fcdda8 typescript: Migrate request-util to typescript.
We use newer javascript method for string that linting enforces like
startsWith, includes.
2019-07-17 00:22:17 +05:30
vsvipul
543ead4da3 typescript: Migrate sentry-util.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
b5f8d54aea typescript: Migrate startup.js to typescript. 2019-07-17 00:22:17 +05:30
vsvipul
7b07a20c04 typescript: Setup typescript project.
This adds all the dependencies and tsconfig.json and setup
for working on the typescript migration.
2019-07-17 00:22:17 +05:30
Akash Nimare
6b1a55c7e4 macOS: Support dark mode on macOS.
Fixes: #792.
2019-07-16 21:14:05 +05:30
Akash Nimare
d46bdba8ce asar: Unpack node binding files.
We have noticed that on Windows, Zulip app is creating a temp file at %appdata%/Local/Temp
as <guid>.tmp.node on each run. Adding *.node files to asarUnpack fixes this issue.
More info - https://github.com/brave/browser-laptop/issues/12534

Improves: #789.
2019-07-15 18:45:27 +05:30
Tyler Thrailkill
d3bc765e59 Add missing transitive dependencies.
zulip was using a dependency on fs-extra that was resolving transitively
through electron. This adds the dependency explicitly. This is safer and
allows tools like pnpm to function.
2019-07-02 15:27:46 +05:30
Priyank Patel
613e44ef8d reconnect-util: Gracefully reload current webview. (#783)
Before we were destroying, removing the webview elements when we
need to reload webviews which cause unwanted side-effect for example
drafts not saving because no beforeunload event handler isn't run.

Fixes: #767.
2019-07-01 23:35:50 +05:30
ViPuL
f0e4a3bc53 menu: Disable menu items on non-server pages.
Disables 'Copy Zulip URL' and 'Back' and 'Forward'
when user is on non-server pages, like Settings page.
Also, simplifies the logic for setting enable state
for 'Switch to Next Organization' and 'Switch to
Previous Organization'.
2019-06-29 01:46:14 +05:30
Kanishk Kakar
427c86afaf spellchecker: Improve auto detection of spellchecker language.
*Enabling spellchecker forces app to auto-detect the language the user
is typing in.

*Provide context of the server language when initialising spellchecker.

Fixes: #542.
2019-06-25 22:29:13 +05:30
vsvipul
0e89880cfb proxy-util: Fix syntax error.
Fixes a wrongly-placed bracket.
2019-06-25 21:05:41 +05:30
vsvipul
b1f1b49982 preference: Remove unused defaultId parameter.
defaultId is not a property of OpenDialogOptions.
2019-06-25 21:05:41 +05:30
Priyank Patel
390bf907e5 components: Remove unused isLoading function from Tab. 2019-06-23 00:46:38 +05:30
Akash Nimare
02c8a27567 menu: Open help page of current active server.
* menu: Open help page of the currently active server.

Fixes: #758.
2019-06-21 01:02:48 +05:30
Kanishk Kakar
db4e8e5129 db: Fix JSON db errors on tab switch. 2019-06-20 20:54:30 +05:30
Kanishk Kakar
88b764dcc9 sidebar: Add loading indicator.
* Browser-like loading indicator added to the sidebar.
Shows when the app is loading a page. Inactive once the user starts
navigating on the webpage, and in settings.

* Add tooltip saying "Loading" to indicator.

Fixes #430.
2019-06-15 16:03:50 +05:30
Kanishk Kakar
9fe72c0d21 mennu: Add option to hide menu bar to View menu.
* Add View menu shortcut to hide menu bar
The hide menu bar setting available in General Preferences has been added to the View menu.

* settings: Add method to coordinate menu & settings
Use autoHideMenuBar setting with said method.
2019-06-15 04:29:06 +05:30
Kanishk Kakar
31b00ee6a6 Update server validation logic.
This PR removes .ogg file check (supported only by very old servers). Other enhancements in server validation logic -
* Reject domains with no organizations. 
* Convert validation methods to async await
* Add messages.js for returning error message strings.

Fixes: #596, #573.
2019-06-13 00:26:32 +05:30
ViPuL
ecec7f8b89 docs: Improve development guide.
Minifies the current development guide by
a significant amount. Also, removes system requirements
as no credible source for them. Removes dependency
specific instructions and adds links to instructions
to install them.

Fixes #341.
2019-06-11 22:44:06 +05:30
Kanishk Kakar
139496b716 settings: Add field to find accounts by email.
Takes in an organization URL for taking the user to /accounts/find the page.
2019-06-04 21:29:33 +05:30
Akash Nimare
f6239132c1 electron: Update electron to v3.1.10. 2019-05-30 00:59:01 +05:30
Kanishk Kakar
6451373ff6 changelog: Add release notes for v3.0.0. 2019-05-22 01:20:21 +05:30
Akash Nimare
6f74fea0f3 release: New release v3.0.0. 2019-05-21 02:58:23 +05:30
Akash Nimare
405d938223 Update docs and code to point to new repo url.
This is because we have changed the repo name to zulip-desktop.
2019-05-21 02:33:13 +05:30
ViPuL
c6b7e9a9ee settings: Add option to toggle Error Reporting.
Adds an option to enable or disable sentry error
reporting under Advanced section in General Settings. 
Handles both main and renderer processes.

Also, changes the domain used to resolve proxy
in proxy-util from google.com to example.com.

Fixes #702.
2019-05-15 15:00:22 +05:30
Akash Nimare
b4073ba7ac Update package.json 2019-05-12 03:24:23 +05:30
Akash Nimare
b504f31c26 config: Remove unused icon file. 2019-05-12 03:20:22 +05:30
Akash Nimare
f71660b3f0 builder: Update electron-builder to v20.40.2. 2019-05-12 03:16:38 +05:30
Akash Nimare
5d0516887d windows: Add a config for installer name.
There is a bug with electron-builder where it doesn't name the installer properly. 
Without this config, it always names the installer with spaces in between 
i.e. "Zulip Web Setup.exe".
2019-05-12 03:14:41 +05:30
ViPuL
0dd86f0814 nsis: Allow installing app without admin previliges.
Allow the nsis installer to install the app to
AppData for a single user so that administrator
previliges are not required while installing.

Fixes #720.
2019-05-12 02:46:19 +05:30
Rhythm Sharma
eaecc92055 spellchecker: Use server language for spellchecker for all platforms.
This is an experimental fix for spellchecker. Previously, we were only using this on macOS since on other platforms, the spellcheck-module claimed to auto-detect + switch language on the fly but looks like it's not working as expected.
2019-05-10 03:43:49 +05:30
ViPuL
4f65c36a72 menu: Remove minimize and close from File menu.
Remove duplicate entries of minimize and close from
File menu as they are already present in Window menu.
2019-05-04 14:57:58 +05:30
ViPuL
2380b650c9 focus: Fix focus after clicking back button.
Addresses the issue of the webview not being in focus
after the Back button is clicked. Now, the webview is focused
explicitly by calling focus() on click.
2019-05-03 22:03:03 +05:30
ViPuL
1d713f1df2 logs: Limit the number of lines in log files.
Limits the numbers of lines in log files to 500.

Fixes a part of #727.
2019-05-03 14:57:00 +05:30
ViPuL
7fa9c291cb lint: Implement HTML Linting with htmlhint and fix indent.
* lint: Implement HTML Linting with htmlhint and fix indent.

Implements HTML linting using htmlhint and uses
indentation rules of zulip webapp - 4 spaces.
Creates a separate file .htmlhintrc for the rules, most
of which are defaults. Also, fixes indentation in html
files and adds a missing title in about.html.

* deps: Change versioning of various dependencies to exact versions.

As we prefer to use exact working version
for dependencies, changed the versions to
exact versions.

Fixes #676.
2019-04-24 00:44:01 +05:30
ViPuL
8a40e36a63 lint: Update css lint config to match zulip config.
Updated .stylelintrc to match zulip webapp's
.stylelintrc file. Also, made the required changes
to all CSS files.

Fixes remaining CSS part of #676.
2019-04-21 17:10:26 +05:30
Kanishk Kakar
3456720135 Unify case across menus and settings.
* menu: Convert to title case.

Also, change corresponding shortcuts to title case.

* settings: Convert options to sentence case.
2019-04-18 19:38:01 +05:30
Abhigyan Khaund
43840a5245 webview: Add "role" key to webview property. 2019-04-18 19:35:21 +05:30
ViPuL
d99c29301e lint: Implement CSS linting with stylelint.
* lint: Implement CSS linting with stylelint.
* lint: Format CSS files according to lintconfig.

Adds CSS linting following the rules of zulip webapp.
Some rules have been ommitted because they required
significant changes in code. Also, creates a separate
.stylelintrc for storing css config.

Fixes CSS part of #676.
2019-04-15 16:54:02 +05:30
ViPuL
6e760973ff shortcut: Allow zoom options from numpad.
Numpad keys by default aren't supported by electron
for using in menu accelarators. Uses a workaround to
make zoom options work with Numpad keys.

Fixes: #344.
2019-04-12 17:16:04 +05:30
Rhythm Sharma
88c64e9dd6 docs: improve development guide.
Update development instructions for all the systems.
2019-04-12 17:11:08 +05:30
Rhythm Sharma
e9db11c156 shortcuts-section: Fix tip's place for Windows & Linux. 2019-04-12 17:03:15 +05:30
ViPuL
8e8de212d8 menu: Reorder file menu and add option to Add Organization.
Reorder the File Menu. Also, adds a new option to
add a new organization to the file menu.
2019-04-10 22:03:43 +05:30
Puneeth Chaganti
0e0d7e7c4a webview: Work around buggy focus switching in Electron 3.0.10.
This commit works around a couple of bugs, which seem to be upstream bugs in the
current version of Electron.

First, `webContents.isFocused()` seems to be true even if we just switched to
a tab, and are trying to set it as the focused element. To work around this, we
check if the `webview` element is the same as `document.activeElement`.

Also, as per https://github.com/electron/electron/issues/15718, it looks like
`blur` needs to be called on the currently active element, before switching
focus on another element i.e., calling `focus` on it.

Fixes: #634.
2019-04-05 15:34:39 +05:30
ViPuL
6b98a49245 webview: fix focus after soft reload.
Fixes: #697.
2019-04-04 20:35:31 +05:30
ViPuL
8e5c326d74 context-menu: Add option to copy zulip url.
Added, "copy Zulip URL" in 
* Context menu in left sidebar 
* Application menu item

Fixes: #649.
2019-04-04 00:35:23 +05:30
Rhythm Sharma
862e9e2c8c DND: Fix text for Toggle DND in sidebar on hover. 2019-04-04 00:21:59 +05:30
Akash Nimare
8a0b047d8f snap: Update snap config.
Updating the config as suggested by the snap community here - 
https://forum.snapcraft.io/t/snap-fails-to-load-error-minidump-segmentation-fault/8691/5

Should improve #593.
2019-04-03 16:37:33 +05:30
Akash Nimare
4a833ef603 docs: Fix typo error in issue template. 2019-04-02 16:13:44 +05:30
ViPuL
fefb7247d8 domain-util: add requestOptions to replace request instances.
This simplifies the request instances.

Fixes #603.
2019-04-02 16:11:23 +05:30
ViPuL
54a6903236 sidebar: Fix organization cycling using Ctrl+Tab.
This fixes two issues -
a) While on the first server, Ctrl+Shift+Tab doesn't work. This fixes that and now cycles to the last active webview.
b) Disable org cycling for non-server pages likes settings.

Fixes #691.
2019-04-01 17:34:08 +05:30
Rhythm Sharma
92ef1bd532 settings: Modify reset app data button. 2019-03-23 16:20:34 +05:30
Kanishk Kakar
bf2e04b3bb sidebar: Switch to next server on Ctrl+Tab.
* Adds a new option to the Window menu that allows users to change to the
next organization cyclically. Activated by Ctrl + Tab.
* Switch to the previous server on Ctrl + Shift + Tab.
2019-03-20 22:15:25 +05:30
ViPuL
3e389ea5db requests: Allow insecure requests on user request.
In certain requests like for fetching realm icons, request tries to verify server certificate and fails when the user has chosen to ignore cert warnings and has not uploaded the cert.
In those cases, we should mark that this server verification is ignored and ask request to ignore the cert verification bypassing that parameter.

Fixes #684.
2019-03-18 23:18:06 +05:30
SankalpSh
8edff28daa changelog: Fix grammatical errors. 2019-03-13 14:04:44 +05:30
Kanishk Kakar
ef6abbf49e settings: Default to starting app on login.
Acts on an observation that it is easier to turn off auto-start on login
than it is to turn on.
2019-03-12 20:35:20 +05:30
Ján Hrnko
0f638dd4b5 docs: Fix broken link.
Fix broken link http://jlord.us/essential-electron/ to https://jlord.dev/blog/essential-electron
2019-03-02 23:33:54 +05:30
Akash Nimare
92f0e46927 New beta release v2.5.0. 2019-03-01 18:51:38 +05:30
Rhythm Sharma
a0dd3832c2 settings: Shorcut section horizontal alignment fix. 2019-02-18 17:11:27 +05:30
ViPuL
a2e84595c9 pdf-viewer: disable pdf-viewer window.
Disable the pdf window for now since this is broken in v3
of electron.

Fixes #640.
2019-02-09 14:04:50 +05:30
Kanishk Kakar
9e33ebae62 context-menu: Fix context menu indexing.
Addresses a problem where the context menu would get the incorrect index
of server in sidebar and cause a crash when attempting to remove the
server.

Add comment justifying extra index parameter.
2019-01-30 12:22:36 +05:30
Abhigyan Khaund
9bdc5dd9e4 certificate: Use path.basename to get certificate file name. 2019-01-24 11:15:40 +05:30
Puneeth Chaganti
ac89ac1cb4 Browser-window: Change the window title to contain active Realm's name. 2019-01-23 13:35:18 +05:30
Abhigyan Khaund
31da1131a4 certificate: Use path.sep for path separator to support Windows.
Fixes the certificate path issue on Windows.
2019-01-21 21:44:23 +05:30
Priyank Patel
4980c71e5a notification: Ensure backward compatibilty when using narrow.by_topic.
We should ensure backward compatibilty when using narrow.by_topic since
the recent rename in the webapp of the function narrow.by_subject to
narrow.by_topic is only in master and not in any stable release yet it
could break the notification function for many people. Furthermore this ensure
general backward-compatibilty.
2019-01-08 21:57:13 +05:30
Akash Nimare
3e73511357 notification: Fix reply from notification.
The function "by_subject" is now being updated to the "by_topic" in
the webapp.

Fixes: #624.
2019-01-08 11:30:57 +05:30
Akash Nimare
5f23c8570b security: Update dependencies to fix minor dev security alerts. 2019-01-07 18:10:41 +05:30
Akash Nimare
12f79e18a1 test: Update test config files. 2019-01-07 12:27:20 +05:30
Akash Nimare
ad7f760444 windows: Install per user on Windows.
The official docs of the electron-builder says to install
the app per user on a particular system instead of all users.
2019-01-07 12:21:16 +05:30
Kanishk Kakar
7314c1f1dd context-menu: Fix Context Menu not working properly.
This fixes a case where the context menu doesn't work as expected. This used to happen when we
show the character icon.

Fixes: #608.
2018-12-30 20:48:38 +05:30
Akash Nimare
46c3d352a1 release: 🎉 new release v2.4.0. 2018-12-24 23:11:01 +05:30
Akash Nimare
cef8eadc8f sentry: Update electron-sentry to v0.14.0. 2018-12-24 23:08:49 +05:30
Akash Nimare
d3f742719c builder: Update electron builder to v20.38.4.
Also, updated electron-updater to v4.0.6.

Fixes: #618.
2018-12-24 20:17:18 +05:30
Priyank Patel
171d88755c electron: Update electron to v3.0.10.
* dependecies: Upgrade electron to v3.0.10.

This also makes sure we don't pass properties on this.tabs
that makes ipcRenderer crash on v3.x.x.

* tray: Use new way for using nativeImage.createFromBuffer.

The previous way of passing scaleFactor as a second argument
is now deprecated and will be a breaking change in 4.0

* app: Use new requestSingleInstanceLock api and remove deceprated makeSingleInstance.

* Update node-mac-notifier to v1.1.0.
2018-12-22 19:44:44 +05:30
ThePiyushGupta
09ac1bd338 setting: Minimize to tray on startup.
This hides the taskbar icon if the start to minimized setting is turned on.

Fixes: #565.
2018-12-22 19:19:24 +05:30
Akash Nimare
48dd12b738 request: Send user-agent with request.
We now send the user-agent with the request.
Fixes #611.
2018-12-19 12:23:18 +05:30
Akash Nimare
61bbd1cf22 electron: Update electron to v2.0.15. 2018-12-17 13:25:34 +05:30
Akash Nimare
bf722da390 git: Ignore unnecessary binary files. 2018-12-12 15:42:59 +05:30
Akash Nimare
c24f5b3e45 lint: Add eol for linebreaks on windows. 2018-12-10 18:36:44 +05:30
Akash Nimare
3d0f4d88af settings: Increase width of add a new org button. 2018-12-07 15:05:11 +05:30
Akash Nimare
a2f412c3de Improve wording of adding a new org button. 2018-12-06 18:14:55 +05:30
Akash Nimare
08559c2f4a setting: Improve organization page.
This adds a new button in the connected organization
page by which a user can quickly add a new Zulip organization.

Fixes: #607.
2018-12-05 23:00:07 +05:30
Akash Nimare
1f4509a6d2 codebase: Fix a typo. 2018-12-05 20:01:44 +05:30
Akash Nimare
06c8b2e8a2 context-menu: Fix context menu not working on adding new org.
Two changes -
* Fixes the context menu not working on adding a new org. This is
because previously the context menu listner only called when loading
the app. Updated the same to fix the issue.
* Refactor the codebase for left-sidebar.

Improves #599.
2018-12-05 19:55:56 +05:30
Kanishk Kakar
2aa15ee11a context-menu: Add context menu in left sidebar.
This adds a context menu in the left sidebar. For now, there is one sub-item called - Disconnect organization by which a user can delete an added org from the app.

Fixes: #599.
2018-12-05 19:42:47 +05:30
Akash Nimare
3c676672ec shortcut: Document show sidebar shortcut properly.
Fixes: #606.
2018-12-05 13:42:40 +05:30
Abhigyan Khaund
c937317ecf domain-util: Include certificates in all requests for icon.
Fixes: #463.
2018-12-05 12:43:34 +05:30
Akash Nimare
44dceda50e dependency: Update electron-window-state to v5.0.3.
Fixes: #231.
2018-11-30 21:37:02 +05:30
Akash Nimare
e1407cb6f7 dependency: Update electron-window-state to v5.0.2. 2018-11-22 20:37:19 +05:30
Michel Tomas
6899a6bc20 network: Fix request ecdhCurve mismatch errors.
The HTTP Node now uses auto for ecdhCurve for SSL connections. This fixes the SSL
handshake error while connecting to some Zulip instances. Setting the ecdhCurve to auto
is the recommended method for Node > 8.5, more info here -
https://github.com/nodejs/node/issues/16196

Fixes: #594.
2018-11-19 16:11:23 +05:30
Akash Nimare
925fec71d5 Add release notes for v2.3.82. 2018-11-05 00:24:25 +05:30
Akash Nimare
f847c565f9 menu: Update menu items on setting page.
This PR adds a functionality to update the menu items.
Some menu items like logout, shortcut etc are not needed
on setting page. We can control the same using this flag.

Fixes: #587.
2018-10-28 15:11:40 +05:30
Kanishk Kakar
c2a380d308 setting: Disable beta updates if auto updates disabled.
* Disable beta updates if auto updates disabled
2018-10-27 23:22:14 +05:30
Akash Nimare
ac6b206583 sentry: Update sentry to v0.12.1. 2018-10-24 19:27:38 +05:30
Akash Nimare
988405c665 dependency: Update node-json-db to v0.9.1. 2018-10-24 14:07:07 +05:30
Akash Nimare
a209804692 setting: Set custom css to false by default. 2018-10-23 15:09:28 +05:30
Akash Nimare
95c4df1898 network: Fix typo in network error message. 2018-10-23 14:56:56 +05:30
Rishi Gupta
1048c91d9e about: Remove Found bug button. 2018-10-21 05:05:44 +05:30
Akash Nimare
d723c5cd1c proxy: Do not reload app when turning off the SPS setting. 2018-10-17 16:58:26 +05:30
Akash Nimare
dcc7cd118c release: 🎉 new release v2.3.82. 2018-10-11 21:53:25 +05:30
Abhigyan Khaund
d2649dd5d7 native-menus: Add tools submenu and reword a few options.
Check for Updates → Move to Tools
Reset App Settings → Move to Tools, rename “Factory reset”
Both Dev Tools → Move to Tools
Download App Logs → Move to Tools
What’s new → Move to Tools, and rename “Release notes”.
2018-10-10 13:53:25 +05:30
Akash Nimare
25e6f6d482 sentry: Update Sentry to v0.11.0.
This could potentially fix #577 since the issue is caused by Sentry.
2018-10-09 13:51:12 +05:30
Akash Nimare
bd805b8324 native-menus: Rename a few options of the submenus. 2018-10-07 19:17:31 +05:30
Abhigyan Khaund
e1d98e0d8e native-menus: Rename a few options of the submenus. 2018-10-07 17:41:57 +05:30
Akash Nimare
89e5d2892b readme: Replace the word team with organizations. 2018-10-04 00:37:49 +05:30
Akash Nimare
ab28b56377 tray: Remove about menu item and rename focus. 2018-10-03 20:47:24 +05:30
Abhigyan Khaund
71aa7e28ba readme: Replace word team with organizations. 2018-10-02 13:50:57 +05:30
Akash Nimare
ba2bfba459 settings: Rename custom css section. 2018-10-01 18:10:40 +05:30
Akash Nimare
d181b0c2e5 settings: Move custom css section to advanced. 2018-10-01 18:07:20 +05:30
Akash Nimare
bda5c62928 settings: Put shortcut tip on the top. 2018-10-01 17:53:11 +05:30
Akash Nimare
b2b4fd1003 settings: UI fixes in general settings. 2018-09-28 21:03:49 +05:30
Akash Nimare
19770f5fbf settings: Update wordings of org settings. 2018-09-28 13:28:47 +05:30
Akash Nimare
4592396c4b settings: Update wordings of general settings. 2018-09-28 13:18:06 +05:30
Akash Nimare
19d3c0f5b5 left-sidebar: Update the border-radius of org icon.
Org icon now have rounded squares.

Fixes: #574.
2018-09-28 13:06:19 +05:30
Akash Nimare
af87789c7f feedback: Upgrade send feedback to latest. 2018-09-27 19:43:18 +05:30
Priyank Patel
36e710dbfe gitattributes: Mark package-lock.json files binary.
So they don't show up during `git diff`.
2018-09-26 20:46:36 -04:00
Priyank Patel
a6e1c93d81 dependencies: Upgrade send-feedback module to v1.0.8.
Fixes: https://github.com/zulip/zulip-electron/issues/569
2018-09-26 20:46:36 -04:00
Akash Nimare
684d88dd7f menus: Rename a few menu items. 2018-09-26 13:44:51 +05:30
Akash Nimare
45aedbd9db tray: Add a separator in tray before quit item. 2018-09-26 13:44:01 +05:30
Akash Nimare
6f411a819c menu: Remove whitespace from app version. 2018-09-26 12:27:09 +05:30
Rishi Gupta
097738332b tray: Remove separators. 2018-09-25 12:43:32 -07:00
Rishi Gupta
b777f8a50f menus: Rename a few menu items. 2018-09-25 09:51:01 -07:00
Akash Nimare
50f06239bb Add release notes for v2.3.8. 2018-09-25 18:58:19 +05:30
Akash Nimare
94cbc786d6 release: 🎉 new release v2.3.8. 2018-09-24 16:45:50 +05:30
Akash Nimare
75abf38515 sentry: Update Sentry to v0.10.1. 2018-09-24 16:45:50 +05:30
Akash Nimare
c1af019182 sentry: Add ignore errors to sentry configuration.
We could ignore the validate realm  error since it's harmless and we know the reason behind this.
It mostly comes from the console logs. This is a temp solution until Sentry supports disabling
the console logs.
2018-09-24 16:43:32 +05:30
Akash Nimare
1ea8aea22a sentry: Update Sentry to v0.9.0. 2018-09-21 14:50:47 +05:30
Akash Nimare
a7e418885a Merge branch 'master' of github.com:zulip/zulip-electron 2018-09-20 23:11:19 +05:30
Akash Nimare
0ac823c85b Update package-lock.json. 2018-09-20 23:10:47 +05:30
Akash Nimare
1096188ddf Add v2.3.7-beta release notes. 2018-09-20 23:06:49 +05:30
Akash Nimare
8d0b68316c menubar: Auto hide menubar on Windows/Linux.
Fixes: #563.
2018-09-19 15:09:39 +05:30
Akash Nimare
c45ad7096f menubar: Document auto-hide menubar setting. 2018-09-18 17:59:51 +05:30
Akash Nimare
5ca7937e52 menubar: Fix menubar not hiding properly. 2018-09-18 17:36:55 +05:30
Akash Nimare
481b99a2ab menubar: Fix a bug in menubar settings. 2018-09-18 13:47:16 +05:30
Akash Nimare
7eb4adf722 menubar: Update menubar on changing the autoHideMenubar setting. 2018-09-18 13:41:47 +05:30
Akash Nimare
4d21a8f8d8 menubar: Auto hide menubar on Windows/Linux.
This PR adds a setting option to auto hide the menubar.

Fixes: #563.
2018-09-18 12:32:23 +05:30
Akash Nimare
94270af265 settings: Minor changes in font-color. 2018-09-17 21:30:50 +05:30
Akash Nimare
177bff6113 UI: Clean up design of setting page.
This commit cleans up the design of setting page and
remove the inconsistency in the page design.

Few changes -
* Update the border-radius to 4px for all the buttons
* Better button UI
* Improve the layout of the setting page
* Cleaner toggle switch
2018-09-17 16:42:23 +05:30
Akash Nimare
b4fa4cacb6 electron: Update electron to v2.0.9. 2018-09-14 22:47:38 +05:30
Akash Nimare
06abf6716a Add dev into master. 2018-09-14 19:19:19 +05:30
Priyank Patel
0ad4ea4fdf left-sidebar: Add initial character of Realm Name instead of default icon.
This PR adds a feature for showing the first char of realm name as the server icon when the
icon fails to load for some reasons.
2018-09-14 18:18:45 +05:30
Akash Nimare
2ece432d19 Linux: Add label for help menu item. 2018-09-13 14:31:36 +05:30
Akash Nimare
10cd7850ca linux: Fix ALT+SHIFT opening menu items on Linux.
It's an upstream bug where if you press ALT+SHIFT on
Linux, it opens up the app menu items. The fix is adding
the & to all the menu labels.

Fixes: #528.
2018-09-13 13:13:18 +05:30
bemyak
86e3d06e52 Improve tray icon behavior. 2018-09-12 18:25:55 +05:30
Abhigyan Khaund
6ebc9acbf4 realm name: Update sidebar realm name when it's changed in webapp.
This commit updates the realm name in the sidebar tooltip,
domains.json and Window menu when it is updated in the server
through electron_bridge.
This commit also removes the server.alias parameter from the
tooltip onHover function and inserts it in the innerHTML of the
tooltip DOM.

Fixes: #425
2018-09-11 21:03:33 +05:30
Akash Nimare
278dc686e1 file-attachments: Allow multiple downloads of same file name.
Previous flow used to overwrite the file if it has the same name.
Current flow is same as what Chrome uses (though we use timestamp
in the file name rather than increaments).

Fixes: #558.
2018-09-10 17:49:09 +05:30
Akash Nimare
cec98c030e lint-xo: Update the max lines in a file. 2018-09-07 01:26:05 +05:30
Akash Nimare
f10d6eb115 setting: Update silent setting option.
Correctly update the audio of the webview on toogle-silent event.
2018-09-07 01:23:17 +05:30
Akash Nimare
8c5e928e9c settings: Clean up toggle sidebar setting.
This removes the two copies of the same toggle-sidebar
ipc event.
2018-09-07 01:20:49 +05:30
Akash Nimare
8d142c8c3f ipc: Move IPC listners to different function.
Those IPCs were wrongly put in the activateTab function.
2018-09-07 00:56:19 +05:30
Akash Nimare
a09769c560 release: 🎉 new beta release v2.3.7-beta. 2018-09-03 15:30:35 +05:30
Abhigyan Khaund
bb99015fd2 config-util: Use package reload instead of custom reload.
This is a WIP commit to fix the errors while saving settings that occur from
reloadDB function.
2018-09-03 15:25:07 +05:30
Akash Nimare
24f5c9b226 builder: Update electron-builder to v20.28.3. 2018-09-03 15:25:07 +05:30
Akash Nimare
dc6c4be5fe updater: Update electron-updater to v3.1.2. 2018-09-03 15:25:07 +05:30
131 changed files with 13153 additions and 5804 deletions

View File

@@ -2,6 +2,7 @@ root = true
[*]
indent_style = tab
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true

17
.gitattributes vendored
View File

@@ -1 +1,16 @@
* text=auto
* text=auto eol=lf
package-lock.json binary
app/package-lock.json binary
*.gif binary
*.jpg binary
*.jpeg binary
*.eot binary
*.woff binary
*.woff2 binary
*.svg binary
*.ttf binary
*.png binary
*.otf binary
*.tif binary
*.ogg binary

View File

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

5
.gitignore vendored
View File

@@ -33,6 +33,9 @@ yarn-error.log*
config.gypi
# Test generated files
tests/package.json
# tests/package.json
.python-version
# Ignore all the typescript compiled files
app/**/*.js

25
.htmlhintrc Normal file
View File

@@ -0,0 +1,25 @@
{
"tagname-lowercase": true,
"attr-lowercase": true,
"attr-value-double-quotes": true,
"attr-value-not-empty": false,
"attr-no-duplication": true,
"doctype-first": true,
"tag-pair": true,
"empty-tag-not-self-closed": true,
"spec-char-escape": true,
"id-unique": true,
"src-not-empty": true,
"title-require": true,
"alt-require": false,
"doctype-html5": true,
"id-class-value": "dash",
"style-disabled": false,
"inline-style-disabled": false,
"inline-script-disabled": false,
"space-tab-mixed-disabled": "space4",
"id-class-ad-disabled": false,
"href-abs-or-rel": false,
"attr-unsafe-chars": true,
"head-script-disabled": true
}

67
.stylelintrc Normal file
View File

@@ -0,0 +1,67 @@
{
"rules": {
# Stylistic rules for CSS.
"function-comma-space-after": "always",
"function-comma-space-before": "never",
"function-max-empty-lines": 0,
"function-whitespace-after": "always",
"value-keyword-case": "lower",
"value-list-comma-newline-after": "always-multi-line",
"value-list-comma-space-after": "always-single-line",
"value-list-comma-space-before": "never",
"value-list-max-empty-lines": 0,
"unit-case": "lower",
"property-case": "lower",
"color-hex-case": "lower",
"declaration-bang-space-before": "always",
"declaration-colon-newline-after": "always-multi-line",
"declaration-colon-space-after": "always-single-line",
"declaration-colon-space-before": "never",
"declaration-block-semicolon-newline-after": "always",
"declaration-block-semicolon-space-before": "never",
"declaration-block-trailing-semicolon": "always",
"block-closing-brace-empty-line-before": "never",
"block-closing-brace-newline-after": "always",
"block-closing-brace-newline-before": "always",
"block-opening-brace-newline-after": "always",
"block-opening-brace-space-before": "always",
"selector-attribute-brackets-space-inside": "never",
"selector-attribute-operator-space-after": "never",
"selector-attribute-operator-space-before": "never",
"selector-combinator-space-after": "always",
"selector-combinator-space-before": "always",
"selector-descendant-combinator-no-non-space": true,
"selector-pseudo-class-parentheses-space-inside": "never",
"selector-pseudo-element-case": "lower",
"selector-pseudo-element-colon-notation": "double",
"selector-type-case": "lower",
"selector-list-comma-newline-after": "always",
"selector-list-comma-space-before": "never",
"media-feature-colon-space-after": "always",
"media-feature-colon-space-before": "never",
"media-feature-name-case": "lower",
"media-feature-parentheses-space-inside": "never",
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "always",
"media-query-list-comma-newline-after": "always",
"media-query-list-comma-space-before": "never",
"at-rule-name-case": "lower",
"at-rule-name-space-after": "always",
"at-rule-semicolon-newline-after": "always",
"at-rule-semicolon-space-before": "never",
"comment-whitespace-inside": "always",
"indentation": 4,
# Limit language features
"color-no-hex": true,
"color-named": "never",
}
}

View File

@@ -2,20 +2,20 @@
Thanks for taking the time to contribute!
The following is a set of guidelines for contributing to Zulip Electron Desktop Client. These are just guidelines, not rules, so use your best judgement and feel free to propose changes to this document in a pull request.
The following is a set of guidelines for contributing to Zulip's desktop Client. These are just guidelines, not rules, so use your best judgement and feel free to propose changes to this document in a pull request.
## Getting Started
Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If you are new to Electron, please head over to [this](http://jlord.us/essential-electron/) great article.
Zulip-Desktop app is built on top of [Electron](http://electron.atom.io/). If you are new to Electron, please head over to [this](https://jlord.us/essential-electron) great article.
## Community
* The whole Zulip documentation, such as setting up a development environment, setting up with the Zulip webapp project, and testing, can be read [here](https://zulip.readthedocs.io).
* If you have any questions regarding zulip-electron, open an [issue](https://github.com/zulip/zulip-electron/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/16-desktop).
* If you have any questions regarding zulip-desktop, open an [issue](https://github.com/zulip/zulip-desktop/issues/new/) or ask it on [chat.zulip.org](https://chat.zulip.org/#narrow/stream/16-desktop).
## Issue
Ensure the bug was not already reported by searching on GitHub under [issues](https://github.com/zulip/zulip-electron/issues). If you're unable to find an open issue addressing the bug, open a [new issue](https://github.com/zulip/zulip-electron/issues/new).
Ensure the bug was not already reported by searching on GitHub under [issues](https://github.com/zulip/zulip-desktop/issues). If you're unable to find an open issue addressing the bug, open a [new issue](https://github.com/zulip/zulip-desktop/issues/new).
The [zulipbot](https://github.com/zulip/zulipbot) helps to claim an issue by commenting the following in the comment section: "**@zulipbot** claim". **@zulipbot** will assign you to the issue and label the issue as **in progress**. For more details, check out [**@zulipbot**](https://github.com/zulip/zulipbot).
@@ -38,7 +38,7 @@ For example:
## Pull Requests
Pull Requests are always welcome.
Pull Requests are always welcome.
1. When you edit the code, please run `npm run test` to check the formatting of your code before you `git commit`.
2. Ensure the PR description clearly describes the problem and solution. It should include:

View File

@@ -1,6 +1,6 @@
# Zulip Desktop Client
[![Build Status](https://travis-ci.org/zulip/zulip-electron.svg?branch=master)](https://travis-ci.org/zulip/zulip-electron)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/zulip/zulip-electron?branch=master&svg=true)](https://ci.appveyor.com/project/akashnimare/zulip-electron/branch/master)
[![Build Status](https://travis-ci.org/zulip/zulip-desktop.svg?branch=master)](https://travis-ci.org/zulip/zulip-desktop)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/zulip/zulip-desktop?branch=master&svg=true)](https://ci.appveyor.com/project/zulip/zulip-desktop/branch/master)
[![XO code style](https://img.shields.io/badge/code_style-XO-5ed9c7.svg)](https://github.com/sindresorhus/xo)
[![project chat](https://img.shields.io/badge/zulip-join_chat-brightgreen.svg)](https://chat.zulip.org)
@@ -12,15 +12,24 @@ Desktop client for Zulip. Available for Mac, Linux, and Windows.
Please see the [installation guide](https://zulipchat.com/help/desktop-app-install-guide).
# Features
* Sign in to multiple teams
* Sign in to multiple organizations
* Desktop notifications with inline reply
* Tray/dock integration
* Multi-language spell checker
* Automatic updates
# Reporting issues
This desktop client shares most of its code with the Zulip webapp.
Issues in an individual organization's Zulip window should be reported
in the [Zulip server and webapp
project](https://github.com/zulip/zulip/issues/new). Other
issues in the desktop app and its settings should be reported [in this
project](https://github.com/zulip/zulip-desktop/issues/new).
# Contribute
First, join us on the [Zulip community server](https://zulip.readthedocs.io/en/latest/contributing/chat-zulip-org.html)!
First, join us on the [Zulip community server](https://zulip.readthedocs.io/en/latest/contributing/chat-zulip-org.html)!
Also see our [contribution guidelines](./CONTRIBUTING.md) and our [development guide](./development.md).
# License

View File

@@ -1,18 +1,19 @@
'use strict';
const { app, dialog, shell } = require('electron');
const { autoUpdater } = require('electron-updater');
const isDev = require('electron-is-dev');
import { app, dialog, shell } from 'electron';
import { autoUpdater } from 'electron-updater';
import { linuxUpdateNotification } from './linuxupdater'; // Required only in case of linux
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
import log = require('electron-log');
import isDev = require('electron-is-dev');
import ConfigUtil = require('../renderer/js/utils/config-util');
function appUpdater(updateFromMenu = false) {
export function appUpdater(updateFromMenu = false): void {
// Don't initiate auto-updates in development
if (isDev) {
return;
}
if (process.platform === 'linux' && !process.env.APPIMAGE) {
const { linuxUpdateNotification } = require('./linuxupdater');
linuxUpdateNotification();
return;
}
@@ -23,8 +24,6 @@ function appUpdater(updateFromMenu = false) {
const LogsDir = `${app.getPath('userData')}/Logs`;
// Log whats happening
const log = require('electron-log');
log.transports.file.file = `${LogsDir}/updates.log`;
log.transports.file.level = 'info';
autoUpdater.logger = log;
@@ -84,7 +83,6 @@ Current Version: ${app.getVersion()}`
});
// Ask the user if update is available
// eslint-disable-next-line no-unused-vars
autoUpdater.on('update-downloaded', event => {
// Ask user to update the app
dialog.showMessageBox({
@@ -106,7 +104,3 @@ Current Version: ${app.getVersion()}`
// Init for updates
autoUpdater.checkForUpdates();
}
module.exports = {
appUpdater
};

View File

@@ -1,322 +0,0 @@
'use strict';
const path = require('path');
const electron = require('electron');
const windowStateKeeper = require('electron-window-state');
const isDev = require('electron-is-dev');
const appMenu = require('./menu');
const { appUpdater } = require('./autoupdater');
const { setAutoLaunch } = require('./startup');
const { app, ipcMain } = electron;
const BadgeSettings = require('./../renderer/js/pages/preference/badge-settings.js');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
const ProxyUtil = require('./../renderer/js/utils/proxy-util.js');
const { sentryInit } = require('./../renderer/js/utils/sentry-util.js');
// Adds debug features like hotkeys for triggering dev tools and reload
// in development mode
if (isDev) {
require('electron-debug')();
}
// Prevent window being garbage collected
let mainWindow;
let badgeCount;
let isQuitting = false;
// Load this url in main window
const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html');
const isAlreadyRunning = app.makeSingleInstance(() => {
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.show();
}
});
if (isAlreadyRunning) {
return app.quit();
}
const APP_ICON = path.join(__dirname, '../resources', 'Icon');
const iconPath = () => {
return APP_ICON + (process.platform === 'win32' ? '.ico' : '.png');
};
function createMainWindow() {
// Load the previous state with fallback to defaults
const mainWindowState = windowStateKeeper({
defaultWidth: 1100,
defaultHeight: 720,
path: `${app.getPath('userData')}/config`
});
// Let's keep the window position global so that we can access it in other process
global.mainWindowState = mainWindowState;
const win = new electron.BrowserWindow({
// This settings needs to be saved in config
title: 'Zulip',
icon: iconPath(),
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
minWidth: 300,
minHeight: 400,
webPreferences: {
plugins: true,
nodeIntegration: true,
partition: 'persist:webviewsession'
},
show: false
});
win.on('focus', () => {
win.webContents.send('focus');
});
win.once('ready-to-show', () => {
if (ConfigUtil.getConfigItem('startMinimized')) {
win.minimize();
} else {
win.show();
}
});
win.loadURL(mainURL);
// Keep the app running in background on close event
win.on('close', e => {
if (!isQuitting) {
e.preventDefault();
if (process.platform === 'darwin') {
app.hide();
} else {
win.hide();
}
}
});
win.setTitle('Zulip');
win.on('enter-full-screen', () => {
win.webContents.send('enter-fullscreen');
});
win.on('leave-full-screen', () => {
win.webContents.send('leave-fullscreen');
});
// To destroy tray icon when navigate to a new URL
win.webContents.on('will-navigate', e => {
if (e) {
win.webContents.send('destroytray');
}
});
// 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;
}
// Decrease load on GPU (experimental)
app.disableHardwareAcceleration();
// Temporary fix for Electron render colors differently
// More info here - https://github.com/electron/electron/issues/10732
app.commandLine.appendSwitch('force-color-profile', 'srgb');
// eslint-disable-next-line max-params
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
event.preventDefault();
callback(true);
});
app.on('activate', () => {
if (!mainWindow) {
mainWindow = createMainWindow();
}
});
app.on('ready', () => {
appMenu.setMenu({
tabs: []
});
mainWindow = createMainWindow();
// Initialize sentry for main process
sentryInit();
const isSystemProxy = ConfigUtil.getConfigItem('useSystemProxy');
if (isSystemProxy) {
ProxyUtil.resolveSystemProxy(mainWindow);
}
const page = mainWindow.webContents;
page.on('dom-ready', () => {
if (ConfigUtil.getConfigItem('startMinimized')) {
mainWindow.minimize();
} else {
mainWindow.show();
}
});
page.once('did-frame-finish-load', () => {
// Initiate auto-updates on MacOS and Windows
if (ConfigUtil.getConfigItem('autoUpdate')) {
appUpdater();
}
});
// Temporarily remove this event
// electron.powerMonitor.on('resume', () => {
// mainWindow.reload();
// page.send('destroytray');
// });
ipcMain.on('focus-app', () => {
mainWindow.show();
});
ipcMain.on('quit-app', () => {
app.quit();
});
// Show pdf in a new BrowserWindow
ipcMain.on('pdf-view', (event, url) => {
// Paddings for pdfWindow so that it fits into the main browserWindow
const paddingWidth = 55;
const paddingHeight = 22;
// Get the config of main browserWindow
const mainWindowState = global.mainWindowState;
// Window to view the pdf file
const pdfWindow = new electron.BrowserWindow({
x: mainWindowState.x + paddingWidth,
y: mainWindowState.y + paddingHeight,
width: mainWindowState.width - paddingWidth,
height: mainWindowState.height - paddingHeight,
webPreferences: {
plugins: true,
partition: 'persist:webviewsession'
}
});
pdfWindow.loadURL(url);
// We don't want to have the menu bar in pdf window
pdfWindow.setMenu(null);
});
// Reload full app not just webview, useful in debugging
ipcMain.on('reload-full-app', () => {
mainWindow.reload();
page.send('destroytray');
});
ipcMain.on('clear-app-settings', () => {
global.mainWindowState.unmanage(mainWindow);
app.relaunch();
app.exit();
});
ipcMain.on('toggle-app', () => {
if (mainWindow.isVisible()) {
mainWindow.hide();
} else {
mainWindow.show();
}
});
ipcMain.on('toggle-badge-option', () => {
BadgeSettings.updateBadge(badgeCount, mainWindow);
});
ipcMain.on('update-badge', (event, messageCount) => {
badgeCount = messageCount;
BadgeSettings.updateBadge(badgeCount, mainWindow);
page.send('tray', messageCount);
});
ipcMain.on('update-taskbar-icon', (event, data, text) => {
BadgeSettings.updateTaskbarIcon(data, text, mainWindow);
});
ipcMain.on('forward-message', (event, listener, ...params) => {
page.send(listener, ...params);
});
ipcMain.on('update-menu', (event, props) => {
appMenu.setMenu(props);
});
ipcMain.on('toggleAutoLauncher', (event, AutoLaunchValue) => {
setAutoLaunch(AutoLaunchValue);
});
ipcMain.on('downloadFile', (event, url, downloadPath) => {
page.downloadURL(url);
page.session.once('will-download', (event, item) => {
const filePath = path.join(downloadPath, item.getFilename());
item.setSavePath(filePath);
item.on('updated', (event, state) => {
switch (state) {
case 'interrupted': {
// Can interrupted to due to network error, cancel download then
console.log('Download interrupted, cancelling and fallback to dialog download.');
item.cancel();
break;
}
case 'progressing': {
if (item.isPaused()) {
item.cancel();
}
// This event can also be used to show progres in percentage in future.
break;
}
default: {
console.info('Unknown updated state of download item');
}
}
});
item.once('done', (event, state) => {
if (state === 'completed') {
page.send('downloadFileCompleted', item.getSavePath(), item.getFilename());
} else {
console.log('Download failed state: ', state);
page.send('downloadFileFailed');
}
// To stop item for listening to updated events of this file
item.removeAllListeners('updated');
});
});
});
ipcMain.on('realm-icon-changed', (event, serverURL, iconURL) => {
page.send('update-realm-icon', serverURL, iconURL);
});
});
app.on('before-quit', () => {
isQuitting = true;
});
// Send crash reports
process.on('uncaughtException', err => {
console.error(err);
console.error(err.stack);
});

402
app/main/index.ts Normal file
View File

@@ -0,0 +1,402 @@
'use strict';
import { sentryInit } from '../renderer/js/utils/sentry-util';
import { appUpdater } from './autoupdater';
import { setAutoLaunch } from './startup';
import windowStateKeeper = require('electron-window-state');
import path = require('path');
import fs = require('fs');
import isDev = require('electron-is-dev');
import electron = require('electron');
const { app, ipcMain, session } = electron;
import AppMenu = require('./menu');
import BadgeSettings = require('../renderer/js/pages/preference/badge-settings');
import ConfigUtil = require('../renderer/js/utils/config-util');
import ProxyUtil = require('../renderer/js/utils/proxy-util');
interface PatchedGlobal extends NodeJS.Global {
mainWindowState: windowStateKeeper.State;
}
const globalPatched = global as PatchedGlobal;
// Adds debug features like hotkeys for triggering dev tools and reload
// in development mode
if (isDev) {
require('electron-debug')();
}
// Prevent window being garbage collected
let mainWindow: Electron.BrowserWindow;
let badgeCount: number;
let isQuitting = false;
// Load this url in main window
const mainURL = 'file://' + path.join(__dirname, '../renderer', 'main.html');
const singleInstanceLock = app.requestSingleInstanceLock();
if (singleInstanceLock) {
app.on('second-instance', () => {
if (mainWindow) {
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
mainWindow.show();
}
});
} else {
app.quit();
}
const APP_ICON = path.join(__dirname, '../resources', 'Icon');
const iconPath = (): string => {
return APP_ICON + (process.platform === 'win32' ? '.ico' : '.png');
};
function createMainWindow(): Electron.BrowserWindow {
// Load the previous state with fallback to defaults
const mainWindowState: windowStateKeeper.State = windowStateKeeper({
defaultWidth: 1100,
defaultHeight: 720,
path: `${app.getPath('userData')}/config`
});
// Let's keep the window position global so that we can access it in other process
globalPatched.mainWindowState = mainWindowState;
const win = new electron.BrowserWindow({
// This settings needs to be saved in config
title: 'Zulip',
icon: iconPath(),
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
minWidth: 300,
minHeight: 400,
webPreferences: {
plugins: true,
nodeIntegration: true,
partition: 'persist:webviewsession'
},
show: false
});
win.on('focus', () => {
win.webContents.send('focus');
});
win.loadURL(mainURL);
// Keep the app running in background on close event
win.on('close', e => {
if (ConfigUtil.getConfigItem("quitOnClose")) {
app.quit();
}
if (!isQuitting) {
e.preventDefault();
if (process.platform === 'darwin') {
app.hide();
} else {
win.hide();
}
}
});
win.setTitle('Zulip');
win.on('enter-full-screen', () => {
win.webContents.send('enter-fullscreen');
});
win.on('leave-full-screen', () => {
win.webContents.send('leave-fullscreen');
});
// To destroy tray icon when navigate to a new URL
win.webContents.on('will-navigate', e => {
if (e) {
win.webContents.send('destroytray');
}
});
// 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;
}
// Decrease load on GPU (experimental)
app.disableHardwareAcceleration();
// Temporary fix for Electron render colors differently
// More info here - https://github.com/electron/electron/issues/10732
app.commandLine.appendSwitch('force-color-profile', 'srgb');
// eslint-disable-next-line max-params
app.on('certificate-error', (event: Event, _webContents: Electron.WebContents, _url: string, _error: string, _certificate: any, callback) => {
event.preventDefault();
callback(true);
});
app.on('activate', () => {
if (!mainWindow) {
mainWindow = createMainWindow();
}
});
app.on('ready', () => {
AppMenu.setMenu({
tabs: []
});
mainWindow = createMainWindow();
// Auto-hide menu bar on Windows + Linux
if (process.platform !== 'darwin') {
const shouldHideMenu = ConfigUtil.getConfigItem('autoHideMenubar') || false;
mainWindow.setAutoHideMenuBar(shouldHideMenu);
mainWindow.setMenuBarVisibility(!shouldHideMenu);
}
// Initialize sentry for main process
const errorReporting = ConfigUtil.getConfigItem('errorReporting');
if (errorReporting) {
sentryInit();
}
const isSystemProxy = ConfigUtil.getConfigItem('useSystemProxy');
if (isSystemProxy) {
ProxyUtil.resolveSystemProxy(mainWindow);
}
const page = mainWindow.webContents;
page.on('dom-ready', () => {
if (ConfigUtil.getConfigItem('startMinimized')) {
mainWindow.hide();
} else {
mainWindow.show();
}
if (!ConfigUtil.isConfigItemExists('userAgent')) {
const userAgent = session.fromPartition('webview:persistsession').getUserAgent();
ConfigUtil.setConfigItem('userAgent', userAgent);
}
});
page.once('did-frame-finish-load', () => {
// Initiate auto-updates on MacOS and Windows
if (ConfigUtil.getConfigItem('autoUpdate')) {
appUpdater();
}
});
// Temporarily remove this event
// electron.powerMonitor.on('resume', () => {
// mainWindow.reload();
// page.send('destroytray');
// });
ipcMain.on('focus-app', () => {
mainWindow.show();
});
ipcMain.on('quit-app', () => {
app.quit();
});
// Code to show pdf in a new BrowserWindow (currently commented out due to bug-upstream)
// ipcMain.on('pdf-view', (event, url) => {
// // Paddings for pdfWindow so that it fits into the main browserWindow
// const paddingWidth = 55;
// const paddingHeight = 22;
// // Get the config of main browserWindow
// const mainWindowState = global.mainWindowState;
// // Window to view the pdf file
// const pdfWindow = new electron.BrowserWindow({
// x: mainWindowState.x + paddingWidth,
// y: mainWindowState.y + paddingHeight,
// width: mainWindowState.width - paddingWidth,
// height: mainWindowState.height - paddingHeight,
// webPreferences: {
// plugins: true,
// partition: 'persist:webviewsession'
// }
// });
// pdfWindow.loadURL(url);
// // We don't want to have the menu bar in pdf window
// pdfWindow.setMenu(null);
// });
// Reload full app not just webview, useful in debugging
ipcMain.on('reload-full-app', () => {
mainWindow.reload();
page.send('destroytray');
});
ipcMain.on('clear-app-settings', () => {
globalPatched.mainWindowState.unmanage();
app.relaunch();
app.exit();
});
ipcMain.on('toggle-app', () => {
if (!mainWindow.isVisible() || mainWindow.isMinimized()) {
mainWindow.show();
} else {
mainWindow.hide();
}
});
ipcMain.on('toggle-badge-option', () => {
BadgeSettings.updateBadge(badgeCount, mainWindow);
});
ipcMain.on('toggle-menubar', (_event: Electron.IpcMessageEvent, showMenubar: boolean) => {
mainWindow.setAutoHideMenuBar(showMenubar);
mainWindow.setMenuBarVisibility(!showMenubar);
page.send('toggle-autohide-menubar', showMenubar, true);
});
ipcMain.on('update-badge', (_event: Electron.IpcMessageEvent, messageCount: number) => {
badgeCount = messageCount;
BadgeSettings.updateBadge(badgeCount, mainWindow);
page.send('tray', messageCount);
});
ipcMain.on('update-taskbar-icon', (_event: Electron.IpcMessageEvent, data: any, text: string) => {
BadgeSettings.updateTaskbarIcon(data, text, mainWindow);
});
ipcMain.on('forward-message', (_event: Electron.IpcMessageEvent, listener: any, ...params: any[]) => {
page.send(listener, ...params);
});
ipcMain.on('update-menu', (_event: Electron.IpcMessageEvent, props: any) => {
AppMenu.setMenu(props);
const activeTab = props.tabs[props.activeTabIndex];
if (activeTab) {
mainWindow.setTitle(`Zulip - ${activeTab.webview.props.name}`);
}
});
ipcMain.on('toggleAutoLauncher', (_event: Electron.IpcMessageEvent, AutoLaunchValue: boolean) => {
setAutoLaunch(AutoLaunchValue);
});
ipcMain.on('downloadFile', (_event: Electron.IpcMessageEvent, url: string, downloadPath: string) => {
page.downloadURL(url);
page.session.once('will-download', (_event: Event, item) => {
const filePath = path.join(downloadPath, item.getFilename());
const getTimeStamp = (): any => {
const date = new Date();
return date.getTime();
};
const formatFile = (filePath: string): string => {
const fileExtension = path.extname(filePath);
const baseName = path.basename(filePath, fileExtension);
return `${baseName}-${getTimeStamp()}${fileExtension}`;
};
// Update the name and path of the file if it already exists
const updatedFilePath = path.join(downloadPath, formatFile(filePath));
const setFilePath = fs.existsSync(filePath) ? updatedFilePath : filePath;
item.setSavePath(setFilePath);
item.on('updated', (_event: Event, state) => {
switch (state) {
case 'interrupted': {
// Can interrupted to due to network error, cancel download then
console.log('Download interrupted, cancelling and fallback to dialog download.');
item.cancel();
break;
}
case 'progressing': {
if (item.isPaused()) {
item.cancel();
}
// This event can also be used to show progress in percentage in future.
break;
}
default: {
console.info('Unknown updated state of download item');
}
}
});
item.once('done', (_event: Event, state) => {
const getFileName = fs.existsSync(filePath) ? formatFile(filePath) : item.getFilename();
if (state === 'completed') {
page.send('downloadFileCompleted', item.getSavePath(), getFileName);
} else {
console.log('Download failed state: ', state);
page.send('downloadFileFailed');
}
// To stop item for listening to updated events of this file
item.removeAllListeners('updated');
});
});
});
ipcMain.on('realm-name-changed', (_event: Electron.IpcMessageEvent, serverURL: string, realmName: string) => {
page.send('update-realm-name', serverURL, realmName);
});
ipcMain.on('realm-icon-changed', (_event: Electron.IpcMessageEvent, serverURL: string, iconURL: string) => {
page.send('update-realm-icon', serverURL, iconURL);
});
// Using event.sender.send instead of page.send here to
// make sure the value of errorReporting is sent only once on load.
ipcMain.on('error-reporting', (event: Electron.IpcMessageEvent) => {
event.sender.send('error-reporting-val', errorReporting);
});
ipcMain.on('save-last-tab', (_event: Electron.IpcMessageEvent, index: number) => {
ConfigUtil.setConfigItem('lastActiveTab', index);
});
// Update user idle status for each realm after every 15s
const idleCheckInterval = 15 * 1000; // 15 seconds
setInterval(() => {
// Set user idle if no activity in 1 second (idleThresholdSeconds)
const idleThresholdSeconds = 1; // 1 second
// TODO: Remove typecast to any when types get added
// TODO: use powerMonitor.getSystemIdleState when upgrading electron
// powerMonitor.querySystemIdleState is deprecated in current electron
// version at the time of writing.
const powerMonitor = electron.powerMonitor as any;
powerMonitor.querySystemIdleState(idleThresholdSeconds, (idleState: string) => {
if (idleState === 'active') {
page.send('set-active');
} else {
page.send('set-idle');
}
});
}, idleCheckInterval);
});
app.on('before-quit', () => {
isQuitting = true;
});
// Send crash reports
process.on('uncaughtException', err => {
console.error(err);
console.error(err.stack);
});

View File

@@ -1,30 +1,30 @@
const { app } = require('electron');
const { Notification } = require('electron');
import { app, Notification } from 'electron';
const request = require('request');
const semver = require('semver');
const ConfigUtil = require('../renderer/js/utils/config-util');
const ProxyUtil = require('../renderer/js/utils/proxy-util');
const LinuxUpdateUtil = require('../renderer/js/utils/linux-update-util');
const Logger = require('../renderer/js/utils/logger-util');
import request = require('request');
import semver = require('semver');
import ConfigUtil = require('../renderer/js/utils/config-util');
import ProxyUtil = require('../renderer/js/utils/proxy-util');
import LinuxUpdateUtil = require('../renderer/js/utils/linux-update-util');
import Logger = require('../renderer/js/utils/logger-util');
const logger = new Logger({
file: 'linux-update-util.log',
timestamp: true
});
function linuxUpdateNotification() {
let url = 'https://api.github.com/repos/zulip/zulip-electron/releases';
export function linuxUpdateNotification(): void {
let url = 'https://api.github.com/repos/zulip/zulip-desktop/releases';
url = ConfigUtil.getConfigItem('betaUpdate') ? url : url + '/latest';
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const options = {
url,
headers: {'User-Agent': 'request'},
proxy: proxyEnabled ? ProxyUtil.getProxy(url) : ''
proxy: proxyEnabled ? ProxyUtil.getProxy(url) : '',
ecdhCurve: 'auto'
};
request(options, (error, response, body) => {
request(options, (error: any, response: any, body: any) => {
if (error) {
logger.error('Linux update error.');
logger.error(error);
@@ -46,7 +46,3 @@ function linuxUpdateNotification() {
}
});
}
module.exports = {
linuxUpdateNotification
};

View File

@@ -1,474 +0,0 @@
'use strict';
const path = require('path');
const { app, shell, BrowserWindow, Menu, dialog } = require('electron');
const fs = require('fs-extra');
const AdmZip = require('adm-zip');
const { appUpdater } = require('./autoupdater');
const ConfigUtil = require(__dirname + '/../renderer/js/utils/config-util.js');
const DNDUtil = require(__dirname + '/../renderer/js/utils/dnd-util.js');
const Logger = require(__dirname + '/../renderer/js/utils/logger-util.js');
const appName = app.getName();
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class AppMenu {
getHistorySubmenu() {
return [{
label: 'Back',
accelerator: process.platform === 'darwin' ? 'Command+Left' : 'Alt+Left',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('back');
}
}
}, {
label: 'Forward',
accelerator: process.platform === 'darwin' ? 'Command+Right' : 'Alt+Right',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('forward');
}
}
}];
}
getViewSubmenu() {
return [{
label: 'Reload',
accelerator: 'CommandOrControl+R',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('reload-current-viewer');
}
}
}, {
label: 'Hard Reload',
accelerator: 'CommandOrControl+Shift+R',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('hard-reload');
}
}
}, {
type: 'separator'
}, {
role: 'togglefullscreen'
}, {
label: 'Zoom In',
accelerator: process.platform === 'darwin' ? 'Command+Plus' : 'Control+=',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomIn');
}
}
}, {
label: 'Zoom Out',
accelerator: 'CommandOrControl+-',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomOut');
}
}
}, {
label: 'Actual Size',
accelerator: 'CommandOrControl+0',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('zoomActualSize');
}
}
}, {
type: 'separator'
}, {
label: 'Toggle Tray Icon',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.send('toggletray');
}
}
}, {
label: 'Toggle Sidebar',
accelerator: 'CommandOrControl+Shift+S',
click(item, focusedWindow) {
if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('showSidebar');
focusedWindow.webContents.send('toggle-sidebar', newValue);
ConfigUtil.setConfigItem('showSidebar', newValue);
}
}
}, {
label: 'Toggle DevTools for Zulip App',
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click(item, focusedWindow) {
if (focusedWindow) {
focusedWindow.webContents.toggleDevTools();
}
}
}, {
label: 'Toggle DevTools for Active Tab',
accelerator: process.platform === 'darwin' ? 'Alt+Command+U' : 'Ctrl+Shift+U',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('tab-devtools');
}
}
}];
}
getHelpSubmenu() {
return [
{
label: `${appName + ' Desktop-'} v${app.getVersion()}`,
enabled: false
},
{
label: `What's New...`,
click() {
shell.openExternal(`https://github.com/zulip/zulip-electron/releases/tag/v${app.getVersion()}`);
}
},
{
label: `${appName} Help`,
click() {
shell.openExternal('https://zulipchat.com/help/');
}
}, {
label: 'Show App Logs',
click() {
const zip = new AdmZip();
let date = new Date();
date = date.toLocaleDateString().replace(/\//g, '-');
// Create a zip file of all the logs and config data
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/Logs`);
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/config`);
// Put the log file in downloads folder
const logFilePath = `${app.getPath('downloads')}/Zulip-logs-${date}.zip`;
zip.writeZip(logFilePath);
// Open and select the log file
shell.showItemInFolder(logFilePath);
}
}, {
label: 'Report an Issue...',
click() {
// the goal is to notify the main.html BrowserWindow
// which may not be the focused window.
BrowserWindow.getAllWindows().forEach(window => {
window.webContents.send('open-feedback-modal');
});
}
}];
}
getWindowSubmenu(tabs, activeTabIndex) {
const initialSubmenu = [{
role: 'minimize'
}, {
role: 'close'
}];
if (tabs.length > 0) {
const ShortcutKey = process.platform === 'darwin' ? 'Cmd' : 'Ctrl';
initialSubmenu.push({
type: 'separator'
});
for (let i = 0; i < tabs.length; i++) {
// Do not add functional tab settings to list of windows in menu bar
if (tabs[i].props.role === 'function' && tabs[i].webview.props.name === 'Settings') {
continue;
}
initialSubmenu.push({
label: tabs[i].webview.props.name,
accelerator: tabs[i].props.role === 'function' ? '' : `${ShortcutKey} + ${tabs[i].props.index + 1}`,
checked: tabs[i].props.index === activeTabIndex,
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('switch-server-tab', tabs[i].props.index);
}
},
type: 'checkbox'
});
}
}
return initialSubmenu;
}
getDarwinTpl(props) {
const { tabs, activeTabIndex } = props;
return [{
label: `${app.getName()}`,
submenu: [{
label: 'About Zulip',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-about');
}
}
}, {
label: `Check for Update`,
click() {
AppMenu.checkForUpdate();
}
}, {
type: 'separator'
}, {
label: 'Desktop App Settings',
accelerator: 'Cmd+,',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-settings');
}
}
}, {
label: 'Keyboard Shortcuts',
accelerator: 'Cmd+Shift+K',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
}
}
}, {
type: 'separator'
}, {
label: 'Toggle Do Not Disturb',
accelerator: 'Command+Shift+M',
click() {
const dndUtil = DNDUtil.toggle();
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
}
}, {
label: 'Reset App Settings',
accelerator: 'Command+Shift+D',
click() {
AppMenu.resetAppSettings();
}
}, {
label: 'Log Out',
accelerator: 'Cmd+L',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('log-out');
}
}
}, {
type: 'separator'
}, {
role: 'services',
submenu: []
}, {
type: 'separator'
}, {
role: 'hide'
}, {
role: 'hideothers'
}, {
role: 'unhide'
}, {
type: 'separator'
}, {
role: 'quit'
}]
}, {
label: 'Edit',
submenu: [{
role: 'undo'
}, {
role: 'redo'
}, {
type: 'separator'
}, {
role: 'cut'
}, {
role: 'copy'
}, {
role: 'paste'
}, {
role: 'pasteandmatchstyle'
}, {
role: 'delete'
}, {
role: 'selectall'
}]
}, {
label: 'View',
submenu: this.getViewSubmenu()
}, {
label: 'History',
submenu: this.getHistorySubmenu()
}, {
label: 'Window',
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
}, {
role: 'help',
submenu: this.getHelpSubmenu()
}];
}
getOtherTpl(props) {
const { tabs, activeTabIndex } = props;
return [{
label: 'File',
submenu: [{
label: 'About Zulip',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-about');
}
}
}, {
label: `Check for Update`,
click() {
AppMenu.checkForUpdate();
}
}, {
type: 'separator'
}, {
label: 'Desktop App Settings',
accelerator: 'Ctrl+,',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-settings');
}
}
}, {
type: 'separator'
}, {
label: 'Keyboard Shortcuts',
accelerator: 'Ctrl+Shift+K',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
}
}
}, {
type: 'separator'
}, {
label: 'Toggle Do Not Disturb',
accelerator: 'Ctrl+Shift+M',
click() {
const dndUtil = DNDUtil.toggle();
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
}
}, {
label: 'Reset App Settings',
accelerator: 'Ctrl+Shift+D',
click() {
AppMenu.resetAppSettings();
}
}, {
label: 'Log Out',
accelerator: 'Ctrl+L',
click(item, focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('log-out');
}
}
}, {
type: 'separator'
}, {
role: 'quit',
accelerator: 'Ctrl+Q'
}]
}, {
label: 'Edit',
submenu: [{
role: 'undo'
}, {
role: 'redo'
}, {
type: 'separator'
}, {
role: 'cut'
}, {
role: 'copy'
}, {
role: 'paste'
}, {
role: 'pasteandmatchstyle'
}, {
role: 'delete'
}, {
type: 'separator'
}, {
role: 'selectall'
}]
}, {
label: 'View',
submenu: this.getViewSubmenu()
}, {
label: 'History',
submenu: this.getHistorySubmenu()
}, {
label: 'Window',
submenu: this.getWindowSubmenu(tabs, activeTabIndex)
}, {
role: 'help',
submenu: this.getHelpSubmenu()
}];
}
static sendAction(action, ...params) {
const win = BrowserWindow.getAllWindows()[0];
if (process.platform === 'darwin') {
win.restore();
}
win.webContents.send(action, ...params);
}
static checkForUpdate() {
appUpdater(true);
}
static resetAppSettings() {
const resetAppSettingsMessage = 'By proceeding you will be removing all connected organizations and preferences from Zulip.';
// We save App's settings/configurations in following files
const settingFiles = ['config/window-state.json', 'config/domain.json', 'config/settings.json', 'config/certificates.json'];
dialog.showMessageBox({
type: 'warning',
buttons: ['YES', 'NO'],
defaultId: 0,
message: 'Are you sure?',
detail: resetAppSettingsMessage
}, response => {
if (response === 0) {
settingFiles.forEach(settingFileName => {
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName);
fs.access(getSettingFilesPath, error => {
if (error) {
logger.error('Error while resetting app settings.');
logger.error(error);
} else {
fs.unlink(getSettingFilesPath, () => {
AppMenu.sendAction('clear-app-data');
});
}
});
});
}
});
}
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();

601
app/main/menu.ts Normal file
View File

@@ -0,0 +1,601 @@
'use strict';
import { app, shell, BrowserWindow, Menu, dialog } from 'electron';
import { appUpdater } from './autoupdater';
import AdmZip = require('adm-zip');
import fs = require('fs-extra');
import path = require('path');
import DNDUtil = require('../renderer/js/utils/dnd-util');
import Logger = require('../renderer/js/utils/logger-util');
import ConfigUtil = require('../renderer/js/utils/config-util');
import t = require('../renderer/js/utils/translation-util');
const appName = app.getName();
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class AppMenu {
getHistorySubmenu(enableMenu: boolean): Electron.MenuItemConstructorOptions[] {
return [{
label: t.__('Back'),
accelerator: process.platform === 'darwin' ? 'Command+Left' : 'Alt+Left',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('back');
}
}
}, {
label: t.__('Forward'),
accelerator: process.platform === 'darwin' ? 'Command+Right' : 'Alt+Right',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('forward');
}
}
}];
}
getToolsSubmenu(): Electron.MenuItemConstructorOptions[] {
return [{
label: t.__(`Check for Updates`),
click() {
AppMenu.checkForUpdate();
}
},
{
label: t.__(`Release Notes`),
click() {
shell.openExternal(`https://github.com/zulip/zulip-desktop/releases/tag/v${app.getVersion()}`);
}
},
{
type: 'separator'
},
{
label: t.__('Factory Reset'),
accelerator: process.platform === 'darwin' ? 'Command+Shift+D' : 'Ctrl+Shift+D',
click() {
AppMenu.resetAppSettings();
}
},
{
label: t.__('Download App Logs'),
click() {
const zip = new AdmZip();
const date = new Date();
const dateString = date.toLocaleDateString().replace(/\//g, '-');
// Create a zip file of all the logs and config data
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/Logs`);
zip.addLocalFolder(`${app.getPath('appData')}/${appName}/config`);
// Put the log file in downloads folder
const logFilePath = `${app.getPath('downloads')}/Zulip-logs-${dateString}.zip`;
zip.writeZip(logFilePath);
// Open and select the log file
shell.showItemInFolder(logFilePath);
}
},
{
type: 'separator'
},
{
label: t.__('Toggle DevTools for Zulip App'),
accelerator: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
focusedWindow.webContents.openDevTools({mode: 'undocked'});
}
}
},
{
label: t.__('Toggle DevTools for Active Tab'),
accelerator: process.platform === 'darwin' ? 'Alt+Command+U' : 'Ctrl+Shift+U',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('tab-devtools');
}
}
}];
}
getViewSubmenu(): Electron.MenuItemConstructorOptions[] {
return [{
label: t.__('Reload'),
accelerator: 'CommandOrControl+R',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('reload-current-viewer');
}
}
}, {
label: t.__('Hard Reload'),
accelerator: 'CommandOrControl+Shift+R',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('hard-reload');
}
}
}, {
type: 'separator'
}, {
label: t.__('Toggle Full Screen'),
role: 'togglefullscreen'
}, {
label: t.__('Zoom In'),
role: 'zoomin',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('zoomIn');
}
}
}, {
label: t.__('Zoom Out'),
accelerator: 'CommandOrControl+-',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('zoomOut');
}
}
}, {
label: t.__('Actual Size'),
accelerator: 'CommandOrControl+0',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('zoomActualSize');
}
}
}, {
type: 'separator'
}, {
label: t.__('Toggle Tray Icon'),
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
focusedWindow.webContents.send('toggletray');
}
}
}, {
label: t.__('Toggle Sidebar'),
accelerator: 'CommandOrControl+Shift+S',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('showSidebar');
focusedWindow.webContents.send('toggle-sidebar', newValue);
ConfigUtil.setConfigItem('showSidebar', newValue);
}
}
}, {
label: t.__('Auto hide Menu bar'),
checked: ConfigUtil.getConfigItem('autoHideMenubar', false),
visible: process.platform !== 'darwin',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
const newValue = !ConfigUtil.getConfigItem('autoHideMenubar');
focusedWindow.setAutoHideMenuBar(newValue);
focusedWindow.setMenuBarVisibility(!newValue);
focusedWindow.webContents.send('toggle-autohide-menubar', newValue);
ConfigUtil.setConfigItem('autoHideMenubar', newValue);
}
},
type: 'checkbox'
}];
}
getHelpSubmenu(): Electron.MenuItemConstructorOptions[] {
return [
{
label: `${appName + ' Desktop'} v${app.getVersion()}`,
enabled: false
},
{
label: t.__('About Zulip'),
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('open-about');
}
}
},
{
label: t.__(`Help Center`),
click(focusedWindow) {
if (focusedWindow) {
AppMenu.sendAction('open-help');
}
}
},
{
label: t.__('Report an Issue'),
click() {
// the goal is to notify the main.html BrowserWindow
// which may not be the focused window.
BrowserWindow.getAllWindows().forEach(window => {
window.webContents.send('open-feedback-modal');
});
}
}
];
}
getWindowSubmenu(tabs: any[], activeTabIndex: number, enableMenu: boolean): Electron.MenuItemConstructorOptions[] {
const initialSubmenu: any[] = [{
label: t.__('Minimize'),
role: 'minimize'
}, {
label: t.__('Close'),
role: 'close'
}];
if (tabs.length > 0) {
const ShortcutKey = process.platform === 'darwin' ? 'Cmd' : 'Ctrl';
initialSubmenu.push({
type: 'separator'
});
tabs.forEach(tab => {
// Do not add functional tab settings to list of windows in menu bar
if (tab.props.role === 'function' && tab.props.name === 'Settings') {
return;
}
initialSubmenu.push({
label: tab.props.name,
accelerator: tab.props.role === 'function' ? '' : `${ShortcutKey} + ${tab.props.index + 1}`,
checked: tab.props.index === activeTabIndex,
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('switch-server-tab', tab.props.index);
}
},
type: 'checkbox'
});
});
initialSubmenu.push({
type: 'separator'
});
initialSubmenu.push({
label: t.__('Switch to Next Organization'),
accelerator: `Ctrl+Tab`,
enabled: tabs.length > 1,
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('switch-server-tab', AppMenu.getNextServer(tabs, activeTabIndex));
}
}
}, {
label: t.__('Switch to Previous Organization'),
accelerator: `Ctrl+Shift+Tab`,
enabled: tabs.length > 1,
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('switch-server-tab', AppMenu.getPreviousServer(tabs, activeTabIndex));
}
}
});
}
return initialSubmenu;
}
getDarwinTpl(props: any): Electron.MenuItemConstructorOptions[] {
const { tabs, activeTabIndex, enableMenu } = props;
return [{
label: `${app.getName()}`,
submenu: [{
label: t.__('Add Organization'),
accelerator: 'Cmd+Shift+N',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('new-server');
}
}
}, {
label: t.__('Toggle Do Not Disturb'),
accelerator: 'Cmd+Shift+M',
click() {
const dndUtil = DNDUtil.toggle();
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
}
}, {
label: t.__('Desktop Settings'),
accelerator: 'Cmd+,',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('open-settings');
}
}
}, {
label: t.__('Keyboard Shortcuts'),
accelerator: 'Cmd+Shift+K',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
}
}
}, {
type: 'separator'
}, {
label: t.__('Copy Zulip URL'),
accelerator: 'Cmd+Shift+C',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('copy-zulip-url');
}
}
}, {
label: t.__('Log Out of Organization'),
accelerator: 'Cmd+L',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('log-out');
}
}
}, {
type: 'separator'
}, {
label: t.__('Services'),
role: 'services',
submenu: []
}, {
type: 'separator'
}, {
label: t.__('Hide'),
role: 'hide'
}, {
label: t.__('Hide Others'),
role: 'hideothers'
}, {
label: t.__('Unhide'),
role: 'unhide'
}, {
type: 'separator'
}, {
label: t.__('Minimize'),
role: 'minimize'
}, {
label: t.__('Close'),
role: 'close'
}, {
label: t.__('Quit'),
role: 'quit'
}]
}, {
label: t.__('Edit'),
submenu: [{
label: t.__('Undo'),
role: 'undo'
}, {
label: t.__('Redo'),
role: 'redo'
}, {
type: 'separator'
}, {
label: t.__('Cut'),
role: 'cut'
}, {
label: t.__('Copy'),
role: 'copy'
}, {
label: t.__('Paste'),
role: 'paste'
}, {
label: t.__('Paste and Match Style'),
role: 'pasteandmatchstyle'
}, {
label: t.__('Select All'),
role: 'selectall'
}]
}, {
label: t.__('View'),
submenu: this.getViewSubmenu()
}, {
label: t.__('History'),
submenu: this.getHistorySubmenu(enableMenu)
}, {
label: t.__('Window'),
submenu: this.getWindowSubmenu(tabs, activeTabIndex, enableMenu)
}, {
label: t.__('Tools'),
submenu: this.getToolsSubmenu()
}, {
label: t.__('Help'),
role: 'help',
submenu: this.getHelpSubmenu()
}];
}
getOtherTpl(props: any): Electron.MenuItemConstructorOptions[] {
const { tabs, activeTabIndex, enableMenu } = props;
return [{
label: t.__('File'),
submenu: [{
label: t.__('Add Organization'),
accelerator: 'Ctrl+Shift+N',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('new-server');
}
}
}, {
type: 'separator'
}, {
label: t.__('Toggle Do Not Disturb'),
accelerator: 'Ctrl+Shift+M',
click() {
const dndUtil = DNDUtil.toggle();
AppMenu.sendAction('toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
}
}, {
label: t.__('Desktop Settings'),
accelerator: 'Ctrl+,',
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('open-settings');
}
}
}, {
label: t.__('Keyboard Shortcuts'),
accelerator: 'Ctrl+Shift+K',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('shortcut');
}
}
}, {
type: 'separator'
}, {
label: t.__('Copy Zulip URL'),
accelerator: 'Ctrl+Shift+C',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('copy-zulip-url');
}
}
}, {
label: t.__('Log Out of Organization'),
accelerator: 'Ctrl+L',
enabled: enableMenu,
click(_item: any, focusedWindow: any) {
if (focusedWindow) {
AppMenu.sendAction('log-out');
}
}
}, {
type: 'separator'
}, {
label: t.__('Minimize'),
role: 'minimize'
}, {
label: t.__('Close'),
role: 'close'
}, {
label: t.__('Quit'),
role: 'quit',
accelerator: 'Ctrl+Q'
}]
}, {
label: t.__('Edit'),
submenu: [{
label: t.__('Undo'),
role: 'undo'
}, {
label: t.__('Redo'),
role: 'redo'
}, {
type: 'separator'
}, {
label: t.__('Cut'),
role: 'cut'
}, {
label: t.__('Copy'),
role: 'copy'
}, {
label: t.__('Paste'),
role: 'paste'
}, {
label: t.__('Paste and Match Style'),
role: 'pasteandmatchstyle'
}, {
type: 'separator'
}, {
label: t.__('Select All'),
role: 'selectall'
}]
}, {
label: t.__('View'),
submenu: this.getViewSubmenu()
}, {
label: t.__('History'),
submenu: this.getHistorySubmenu(enableMenu)
}, {
label: t.__('Window'),
submenu: this.getWindowSubmenu(tabs, activeTabIndex, enableMenu)
}, {
label: t.__('Tools'),
submenu: this.getToolsSubmenu()
}, {
label: t.__('Help'),
role: 'help',
submenu: this.getHelpSubmenu()
}];
}
static sendAction(action: any, ...params: any[]): void {
const win = BrowserWindow.getAllWindows()[0];
if (process.platform === 'darwin') {
win.restore();
}
win.webContents.send(action, ...params);
}
static checkForUpdate(): void {
appUpdater(true);
}
static getNextServer(tabs: any[], activeTabIndex: number): number {
do {
activeTabIndex = (activeTabIndex + 1) % tabs.length;
}
while (tabs[activeTabIndex].props.role !== 'server');
return activeTabIndex;
}
static getPreviousServer(tabs: any[], activeTabIndex: number): number {
do {
activeTabIndex = (activeTabIndex - 1 + tabs.length) % tabs.length;
}
while (tabs[activeTabIndex].props.role !== 'server');
return activeTabIndex;
}
static resetAppSettings(): void {
const resetAppSettingsMessage = 'By proceeding you will be removing all connected organizations and preferences from Zulip.';
// We save App's settings/configurations in following files
const settingFiles = ['config/window-state.json', 'config/domain.json', 'config/settings.json', 'config/certificates.json'];
dialog.showMessageBox({
type: 'warning',
buttons: ['YES', 'NO'],
defaultId: 0,
message: 'Are you sure?',
detail: resetAppSettingsMessage
}, response => {
if (response === 0) {
settingFiles.forEach(settingFileName => {
const getSettingFilesPath = path.join(app.getPath('appData'), appName, settingFileName);
fs.access(getSettingFilesPath, (error: any) => {
if (error) {
logger.error('Error while resetting app settings.');
logger.error(error);
} else {
fs.unlink(getSettingFilesPath, () => {
AppMenu.sendAction('clear-app-data');
});
}
});
});
}
});
}
setMenu(props: any): void {
const tpl = process.platform === 'darwin' ? this.getDarwinTpl(props) : this.getOtherTpl(props);
const menu = Menu.buildFromTemplate(tpl);
Menu.setApplicationMenu(menu);
}
}
export = new AppMenu();

View File

@@ -1,10 +1,11 @@
'use strict';
const { app } = require('electron');
const AutoLaunch = require('auto-launch');
const isDev = require('electron-is-dev');
const ConfigUtil = require('./../renderer/js/utils/config-util.js');
import { app } from 'electron';
const setAutoLaunch = AutoLaunchValue => {
import AutoLaunch = require('auto-launch');
import isDev = require('electron-is-dev');
import ConfigUtil = require('../renderer/js/utils/config-util');
export const setAutoLaunch = (AutoLaunchValue: boolean): void => {
// Don't run this in development
if (isDev) {
return;
@@ -28,7 +29,3 @@ const setAutoLaunch = AutoLaunchValue => {
ZulipAutoLauncher.disable();
}
};
module.exports = {
setAutoLaunch
};

621
app/package-lock.json generated
View File

@@ -1,13 +1,13 @@
{
"name": "zulip",
"version": "2.3.6",
"version": "4.0.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@electron-elements/send-feedback": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@electron-elements/send-feedback/-/send-feedback-1.0.7.tgz",
"integrity": "sha1-HmTsFMvNkmZAngeu1BDoUi45deA=",
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/@electron-elements/send-feedback/-/send-feedback-1.0.8.tgz",
"integrity": "sha512-spIZIYSe0Bp028sbZ7bcGOaUYhMyFArk+Tv7kQ4XCRNuVWRkw3SfbR8zIljZAdUavJp0wX6prSWrqLL4Dpd3Fw==",
"requires": {
"@electron-elements/utils": "1.0.3"
}
@@ -37,90 +37,93 @@
}
},
"@sentry/browser": {
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-4.0.0-beta.12.tgz",
"integrity": "sha512-At8qp2aM4q4KnuIQuC6wbbQ6gLykmzwZdHwWRN99GS5TNC7kXJNgO9WgiMLadA4ph12JtC1petrUIgY8t+aoSA==",
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-4.3.4.tgz",
"integrity": "sha512-odRXxnhcSYzyR4YvTolNEyrz3fdDVw308l+9RBRJA9yOFVlezaz1mXH6Gv00F7cIj9yE/JtezDyhP339WsWy3w==",
"requires": {
"@sentry/core": "4.0.0-beta.12",
"@sentry/hub": "4.0.0-beta.12",
"@sentry/minimal": "4.0.0-beta.12",
"@sentry/types": "4.0.0-beta.12",
"@sentry/utils": "4.0.0-beta.12"
"@sentry/core": "4.3.4",
"@sentry/types": "4.3.4",
"@sentry/utils": "4.3.4",
"tslib": "^1.9.3"
}
},
"@sentry/core": {
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-4.0.0-beta.12.tgz",
"integrity": "sha512-DGZqBOHejHPYP2I0MdF7APEaews7gLrfxHDh9rbwDLJJiuwPE/ridH97amxy/j2g4X4EZEc3sO3Ptv4nMaHK+Q==",
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@sentry/core/-/core-4.3.4.tgz",
"integrity": "sha512-KwolQmAnXiFMeXBuxPUM8fW+2bOICdHfpjdf83qD7WSeuKqGvXhxXyApWNSLE+l2DPO6/8UKnIGmR8bEn0G7QA==",
"requires": {
"@sentry/hub": "4.0.0-beta.12",
"@sentry/minimal": "4.0.0-beta.12",
"@sentry/types": "4.0.0-beta.12",
"@sentry/utils": "4.0.0-beta.12"
"@sentry/hub": "4.3.4",
"@sentry/minimal": "4.3.4",
"@sentry/types": "4.3.4",
"@sentry/utils": "4.3.4",
"tslib": "^1.9.3"
}
},
"@sentry/electron": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/@sentry/electron/-/electron-0.8.1.tgz",
"integrity": "sha512-y3CJ7547Rhpg+6bnGP0mXLj9pgpjMLaRji704TdzTV0afr8KamBXySNhNHosHLaKm1OLg6p8U4pdVB7xQZ+X3g==",
"version": "0.14.0",
"resolved": "https://registry.npmjs.org/@sentry/electron/-/electron-0.14.0.tgz",
"integrity": "sha512-f0Aj2gKcX9LampOEFER0m6cC4etT1B3bsuY7E1R3vzZXcL7y5TaH2Omgk5atzAX5ycBk8LHxhVqUtE6fZBWlFA==",
"requires": {
"@sentry/browser": "4.0.0-beta.12",
"@sentry/core": "4.0.0-beta.12",
"@sentry/hub": "4.0.0-beta.12",
"@sentry/minimal": "4.0.0-beta.12",
"@sentry/node": "4.0.0-beta.12",
"@sentry/types": "4.0.0-beta.12",
"@sentry/utils": "4.0.0-beta.12",
"electron-fetch": "^1.1.0",
"form-data": "^2.3.2",
"util.promisify": "^1.0.0"
"@sentry/browser": "~4.3.4",
"@sentry/core": "~4.3.4",
"@sentry/minimal": "~4.3.4",
"@sentry/node": "~4.3.4",
"@sentry/types": "~4.3.4",
"@sentry/utils": "~4.3.4",
"electron-fetch": "1.1.0",
"form-data": "2.3.2",
"util.promisify": "1.0.0"
}
},
"@sentry/hub": {
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-4.0.0-beta.12.tgz",
"integrity": "sha512-fmLaPaD6F/ViWDTz3hRVnNvqgnAmX0foDsUGVVp0Dt2lT9apM5NECKV6761XFVMrbGGulCg9fguv2KV5IbQcIw==",
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-4.3.4.tgz",
"integrity": "sha512-vaBGCnhinLB8N4aQLMiPPhnlTkIUwU/dxWzw/xsuKY3MYWrmfMUyWgMZF60Mz3B4F0lW1lsg5jnJz9xPnjZowg==",
"requires": {
"@sentry/types": "4.0.0-beta.12",
"@sentry/utils": "4.0.0-beta.12"
"@sentry/types": "4.3.4",
"@sentry/utils": "4.3.4",
"tslib": "^1.9.3"
}
},
"@sentry/minimal": {
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-4.0.0-beta.12.tgz",
"integrity": "sha512-1V3Lrm7wIOOqG+l1uyVZ7iQysPGXszwfsd6DTVDPT19PSey7+KbvPNcQfh+GqMRMz8jzyg9XlwUxckg7qbSztQ==",
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-4.3.4.tgz",
"integrity": "sha512-EBmQgdAQgxkhWFsBO4TmsP3cg5yTzg48HmPe3Dyt7PtF5Umw3DFW6qboAqnN1+KF+pHNuxkqevvgBTFp7b4Saw==",
"requires": {
"@sentry/hub": "4.0.0-beta.12",
"@sentry/types": "4.0.0-beta.12"
"@sentry/hub": "4.3.4",
"@sentry/types": "4.3.4",
"tslib": "^1.9.3"
}
},
"@sentry/node": {
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-4.0.0-beta.12.tgz",
"integrity": "sha512-oekXtdaaElt/lSfyKtAyTLcTofciMRuptsGOTMX4qQ7hoHESB6DUw55Or/RRMaJYm/nckM2VFzWDATd20NVV4w==",
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@sentry/node/-/node-4.3.4.tgz",
"integrity": "sha512-4ro8eFnIuuYrF/TLw788xU10AYJl9HvOYC5G3jLrHXTjRNLp7jJo9lZrUexjCvwM2WF/srF3+Z0Pwr+HwdKVVw==",
"requires": {
"@sentry/core": "4.0.0-beta.12",
"@sentry/hub": "4.0.0-beta.12",
"@sentry/minimal": "4.0.0-beta.12",
"@sentry/types": "4.0.0-beta.12",
"@sentry/utils": "4.0.0-beta.12",
"@sentry/core": "4.3.4",
"@sentry/hub": "4.3.4",
"@sentry/types": "4.3.4",
"@sentry/utils": "4.3.4",
"@types/stack-trace": "0.0.29",
"cookie": "0.3.1",
"lsmod": "1.0.0",
"md5": "2.2.1",
"stack-trace": "0.0.10"
"stack-trace": "0.0.10",
"tslib": "^1.9.3"
}
},
"@sentry/types": {
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-4.0.0-beta.12.tgz",
"integrity": "sha512-xRsTfRcb8OvMbLjl64bPNv3feHiXBprtRZqTlVUbDJmSNrm8wg+tKIPYsP90dxKc50CZBk59urIbgvpPg0mCLw=="
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@sentry/types/-/types-4.3.4.tgz",
"integrity": "sha512-qsqrcyNilpbzYjqef+km0Grh5BckSFD4MUdJDNkUE5XU/ImniYddj18bMDlQxluJlTPDjUFQ37FXtEmxLeOwkQ=="
},
"@sentry/utils": {
"version": "4.0.0-beta.12",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-4.0.0-beta.12.tgz",
"integrity": "sha512-lpTE2GlaztATlFfIWMN8RSQFP0z6PkPPoDz0KKRmK4ZxjPxVX2sAGbKJGCpf9fdUG23NVg3FNiqfpUbjhvd9YA==",
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-4.3.4.tgz",
"integrity": "sha512-CMGMdIv5RHUCKRF4aWPZ/gFRTfBQpLVVJEGCeFGZLXHBdpgQac0lf3jlu8sND0KZ0S3C5x3tGS/eEqmOZRQ/pw==",
"requires": {
"@sentry/types": "4.0.0-beta.12"
"@sentry/types": "4.3.4",
"tslib": "^1.9.3"
}
},
"@sindresorhus/is": {
@@ -128,6 +131,16 @@
"resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz",
"integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow=="
},
"@types/stack-trace": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/stack-trace/-/stack-trace-0.0.29.tgz",
"integrity": "sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g=="
},
"abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"adm-zip": {
"version": "0.4.11",
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.11.tgz",
@@ -153,6 +166,41 @@
"json-schema-traverse": "^0.3.0"
}
},
"ambi": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/ambi/-/ambi-2.5.0.tgz",
"integrity": "sha1-fI43K+SIkRV+fOoBy2+RQ9H3QiA=",
"requires": {
"editions": "^1.1.1",
"typechecker": "^4.3.0"
},
"dependencies": {
"semver": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
},
"typechecker": {
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/typechecker/-/typechecker-4.7.0.tgz",
"integrity": "sha512-4LHc1KMNJ6NDGO+dSM/yNfZQRtp8NN7psYrPHUblD62Dvkwsp3VShsbM78kOgpcmMkRTgvwdKOTjctS+uMllgQ==",
"requires": {
"editions": "^2.1.0"
},
"dependencies": {
"editions": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/editions/-/editions-2.1.3.tgz",
"integrity": "sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw==",
"requires": {
"errlop": "^1.1.1",
"semver": "^5.6.0"
}
}
}
}
}
},
"applescript": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/applescript/-/applescript-1.0.0.tgz",
@@ -161,7 +209,7 @@
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha1-vNZ5HqWuCXJeF+WtmIE0zUCz2RE=",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
@@ -176,6 +224,11 @@
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"async": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz",
"integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
@@ -229,16 +282,16 @@
"optional": true
},
"bluebird": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
"integrity": "sha1-2VUfnemPH82h5oPRfukaBgLuLrk="
"version": "3.5.3",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz",
"integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw=="
},
"bluebird-lst": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.5.tgz",
"integrity": "sha1-vryDAmt+kqcocaPcWZ4hnL+wAqk=",
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/bluebird-lst/-/bluebird-lst-1.0.6.tgz",
"integrity": "sha512-CBWFoPuUPpcvMUxfyr8DKdI5d4kjxFl1h39+VbKxP3KJWJHEsLtuT4pPLkjpxCGU6Ask21tvbnftWXdqIxYldQ==",
"requires": {
"bluebird": "^3.5.1"
"bluebird": "^3.5.2"
}
},
"boom": {
@@ -252,35 +305,40 @@
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"buffer-from": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.0.tgz",
"integrity": "sha512-c5mRlguI/Pe2dSZmpER62rSCu0ryKmWddzRYsuXc50U2/g8jMOulc31VZMa4mYx31U5xsmSOpDCgH88Vl9cDGQ=="
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz",
"integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A=="
},
"builder-util-runtime": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-4.4.0.tgz",
"integrity": "sha512-tkTF1o7XAX79ZkMo8822ZdQMpEBGSgfJ9kEYgyTAja90BPu7HO8C02pb8iSlFXfmK0Q0UA6D8MmnSNNPi0JLeg==",
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/builder-util-runtime/-/builder-util-runtime-8.1.0.tgz",
"integrity": "sha512-s1mlJ28mv+56Iebh6c9aXjVe11O3Z0cDTwAGeB0PCcUzHA37fDxGgS8ZGoYNMZP+rBHj21d/od1wuYofTVLaQg==",
"requires": {
"bluebird-lst": "^1.0.5",
"debug": "^3.1.0",
"fs-extra-p": "^4.6.1",
"bluebird-lst": "^1.0.6",
"debug": "^4.1.0",
"fs-extra-p": "^7.0.0",
"sax": "^1.2.4"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
"requires": {
"ms": "2.0.0"
"ms": "^2.1.1"
}
},
"ms": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
"integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg=="
}
}
},
@@ -392,6 +450,11 @@
}
}
},
"csextends": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/csextends/-/csextends-1.2.0.tgz",
"integrity": "sha512-S/8k1bDTJIwuGgQYmsRoE+8P+ohV32WhQ0l4zqrc0XDdxOhjQQD7/wTZwCzoZX53jSX3V/qwjT+OkPTxWQcmjg=="
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
@@ -421,18 +484,12 @@
"mimic-response": "^1.0.0"
}
},
"deep-equal": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
"integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU="
},
"define-properties": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
"integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=",
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"requires": {
"foreach": "^2.0.5",
"object-keys": "^1.0.8"
"object-keys": "^1.0.12"
}
},
"delayed-stream": {
@@ -457,11 +514,24 @@
"dns-packet": "^1.1.0"
}
},
"dotenv": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.0.0.tgz",
"integrity": "sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg=="
},
"duplexer3": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz",
"integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI="
},
"eachr": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/eachr/-/eachr-2.0.4.tgz",
"integrity": "sha1-Rm98qhBwj2EFCeMsgHqv5X/BIr8=",
"requires": {
"typechecker": "^2.0.8"
}
},
"ecc-jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz",
@@ -471,10 +541,15 @@
"jsbn": "~0.1.0"
}
},
"editions": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz",
"integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg=="
},
"electron-fetch": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/electron-fetch/-/electron-fetch-1.2.1.tgz",
"integrity": "sha512-pQy0el/vxu30sL9JjXss8yZL+ztRnmioffPQsLL4UYnfSUFTuGBlgCPCxxKYJV7y52WfaIEZQ9tlbac1YHrH0w==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/electron-fetch/-/electron-fetch-1.1.0.tgz",
"integrity": "sha512-eBtqbB522c/RJwC5v1yF/Y0SMVLiwel98BWGx050GucWbKlejHtPkfpq/vfMxCjg0SGpHGUISYUaMfu65QH+xg==",
"requires": {
"encoding": "^0.1.12"
}
@@ -522,46 +597,35 @@
}
},
"electron-updater": {
"version": "2.23.3",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-2.23.3.tgz",
"integrity": "sha512-ZoQZpiEbhT3xA5Oxn7a5o4Z9adRaYs901pnTKBVBxPWmc0Qw5sZXAHkRjftlRmEn3RiEVkJtBPQSfx8kIkRcUA==",
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/electron-updater/-/electron-updater-4.0.6.tgz",
"integrity": "sha512-JPGLME6fxJcHG8hX7HWFl6Aew6iVm0DkcrENreKa5SUJCHG+uUaAhxDGDt+YGcNkyx1uJ6eBGMvFxDTLUv67pg==",
"requires": {
"bluebird-lst": "^1.0.5",
"builder-util-runtime": "~4.4.0",
"electron-is-dev": "^0.3.0",
"fs-extra-p": "^4.6.1",
"bluebird-lst": "^1.0.6",
"builder-util-runtime": "~8.1.0",
"fs-extra-p": "^7.0.0",
"js-yaml": "^3.12.0",
"lazy-val": "^1.0.3",
"lodash.isequal": "^4.5.0",
"semver": "^5.5.0",
"source-map-support": "^0.5.6"
"pako": "^1.0.7",
"semver": "^5.6.0",
"source-map-support": "^0.5.9"
},
"dependencies": {
"semver": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz",
"integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA=="
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz",
"integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg=="
}
}
},
"electron-window-state": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/electron-window-state/-/electron-window-state-4.1.1.tgz",
"integrity": "sha1-azT9wxs4UU3+yLfI97XUrdtnYy0=",
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/electron-window-state/-/electron-window-state-5.0.3.tgz",
"integrity": "sha512-1mNTwCfkolXl3kMf50yW3vE2lZj0y92P/HYWFBrb+v2S/pCka5mdwN3cagKm458A7NjndSwijynXgcLWRodsVg==",
"requires": {
"deep-equal": "^1.0.1",
"jsonfile": "^2.2.3",
"jsonfile": "^4.0.0",
"mkdirp": "^0.5.1"
},
"dependencies": {
"jsonfile": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
"requires": {
"graceful-fs": "^4.1.6"
}
}
}
},
"encoding": {
@@ -572,10 +636,34 @@
"iconv-lite": "~0.4.13"
}
},
"errlop": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/errlop/-/errlop-1.1.1.tgz",
"integrity": "sha512-WX7QjiPHhsny7/PQvrhS5VMizXXKoKCS3udaBp8gjlARdbn+XmK300eKBAAN0hGyRaTCtRpOaxK+xFVPUJ3zkw==",
"requires": {
"editions": "^2.1.2"
},
"dependencies": {
"editions": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/editions/-/editions-2.1.3.tgz",
"integrity": "sha512-xDZyVm0A4nLgMNWVVLJvcwMjI80ShiH/27RyLiCnW1L273TcJIA25C4pwJ33AWV01OX6UriP35Xu+lH4S7HWQw==",
"requires": {
"errlop": "^1.1.1",
"semver": "^5.6.0"
}
},
"semver": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA=="
}
}
},
"es-abstract": {
"version": "1.11.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz",
"integrity": "sha512-ZnQrE/lXTTQ39ulXZ+J1DTFazV9qBy61x2bY071B+qGco8Z8q1QddsLdt/EF8Ai9hcWH72dWS0kFqXLxOxqslA==",
"version": "1.12.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz",
"integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==",
"requires": {
"es-to-primitive": "^1.1.1",
"function-bind": "^1.1.1",
@@ -585,13 +673,13 @@
}
},
"es-to-primitive": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz",
"integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=",
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
"integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
"requires": {
"is-callable": "^1.1.1",
"is-callable": "^1.1.4",
"is-date-object": "^1.0.1",
"is-symbol": "^1.0.1"
"is-symbol": "^1.0.2"
}
},
"escape-html": {
@@ -600,9 +688,9 @@
"integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg="
},
"esprima": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz",
"integrity": "sha1-RJnt3NERDgshi6zy+n9/WfVcqAQ="
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"event-kit": {
"version": "2.4.0",
@@ -620,6 +708,36 @@
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz",
"integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ="
},
"extendr": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/extendr/-/extendr-2.1.0.tgz",
"integrity": "sha1-MBqgu+pWX00tyPVw8qImEahSe1Y=",
"requires": {
"typechecker": "~2.0.1"
},
"dependencies": {
"typechecker": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/typechecker/-/typechecker-2.0.8.tgz",
"integrity": "sha1-6D2oS7ZMWEzLNFg4V2xAsDN9uC4="
}
}
},
"extract-opts": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/extract-opts/-/extract-opts-2.2.0.tgz",
"integrity": "sha1-H6KOunNSxttID4hc63GkaBC+bX0=",
"requires": {
"typechecker": "~2.0.1"
},
"dependencies": {
"typechecker": {
"version": "2.0.8",
"resolved": "https://registry.npmjs.org/typechecker/-/typechecker-2.0.8.tgz",
"integrity": "sha1-6D2oS7ZMWEzLNFg4V2xAsDN9uC4="
}
}
},
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
@@ -635,11 +753,6 @@
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
},
"foreach": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k="
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
@@ -665,9 +778,9 @@
}
},
"fs-extra": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-6.0.1.tgz",
"integrity": "sha512-GnyIkKhhzXZUWFCaJzvyDLEEgDkPfb4/TPvJCJVuS8MWZgoSsErf++QpiAlDnKFcqhRlm+tIOcencCjyJE6ZCA==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
"requires": {
"graceful-fs": "^4.1.2",
"jsonfile": "^4.0.0",
@@ -675,12 +788,12 @@
}
},
"fs-extra-p": {
"version": "4.6.1",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-4.6.1.tgz",
"integrity": "sha512-IsTMbUS0svZKZTvqF4vDS9c/L7Mw9n8nZQWWeSzAGacOSe+8CzowhUN0tdZEZFIJNP5HC7L9j3MMikz/G4hDeQ==",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/fs-extra-p/-/fs-extra-p-7.0.0.tgz",
"integrity": "sha512-5tg5jBOd0xIXjwj4PDnafOXL5TyPVzjxLby4DPKev53wurEXp7IsojBaD4Lj5M5w7jxw0pbkEU0fFEPmcKoMnA==",
"requires": {
"bluebird-lst": "^1.0.5",
"fs-extra": "^6.0.1"
"bluebird-lst": "^1.0.6",
"fs-extra": "^7.0.0"
}
},
"fs.realpath": {
@@ -768,6 +881,11 @@
"resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz",
"integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw=="
},
"has-symbols": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
"integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q="
},
"has-to-string-tag-x": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz",
@@ -779,7 +897,7 @@
"hashids": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/hashids/-/hashids-1.1.4.tgz",
"integrity": "sha1-5P+SrWa2hKO9aqznwX1mYY7l+iE="
"integrity": "sha512-U/fnTE3edW0AV92ZI/BfEluMZuVcu3MDOopsN7jS+HqDYcarQo8rXQiWlsBlm0uX48/taYSdxRsfzh2HRg5Z6w=="
},
"hawk": {
"version": "6.0.2",
@@ -812,14 +930,41 @@
"sshpk": "^1.7.0"
}
},
"i18n": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/i18n/-/i18n-0.8.3.tgz",
"integrity": "sha1-LYzxwkciYCwgQdAbpq5eqlE4jw4=",
"requires": {
"debug": "*",
"make-plural": "^3.0.3",
"math-interval-parser": "^1.1.0",
"messageformat": "^0.3.1",
"mustache": "*",
"sprintf-js": ">=1.0.3"
}
},
"iconv-lite": {
"version": "0.4.23",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz",
"integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==",
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
"integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
"requires": {
"safer-buffer": ">= 2.1.2 < 3"
}
},
"ignorefs": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/ignorefs/-/ignorefs-1.2.0.tgz",
"integrity": "sha1-2ln7hYl25KXkNwLM0fKC/byeV1Y=",
"requires": {
"editions": "^1.3.3",
"ignorepatterns": "^1.1.0"
}
},
"ignorepatterns": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ignorepatterns/-/ignorepatterns-1.1.0.tgz",
"integrity": "sha1-rI9DbyI5td+2bV8NOpBKh6xnzF4="
},
"indent-string": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz",
@@ -864,9 +1009,9 @@
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"is-callable": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz",
"integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI="
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
"integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA=="
},
"is-date-object": {
"version": "1.0.1",
@@ -926,9 +1071,12 @@
"integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
},
"is-symbol": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz",
"integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI="
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
"integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
"requires": {
"has-symbols": "^1.0.0"
}
},
"is-typedarray": {
"version": "1.0.0",
@@ -1028,7 +1176,7 @@
"lazy-val": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/lazy-val/-/lazy-val-1.0.3.tgz",
"integrity": "sha1-u5eyAO8AgB2UwxfincbtOeMcXtw="
"integrity": "sha512-pjCf3BYk+uv3ZcPzEVM0BFvO9Uw58TmlrU0oG5tTrr9Kcid3+kdKxapH8CjdYmVa2nO5wOoZn2rdvZx2PKj/xg=="
},
"lodash.assign": {
"version": "4.2.0",
@@ -1064,6 +1212,30 @@
"resolved": "https://registry.npmjs.org/lsmod/-/lsmod-1.0.0.tgz",
"integrity": "sha1-mgD3bco26yP6BTUK/htYXUKZ5ks="
},
"make-plural": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/make-plural/-/make-plural-3.0.6.tgz",
"integrity": "sha1-IDOgO6wpC487uRJY9lud9+iwHKc=",
"requires": {
"minimist": "^1.2.0"
},
"dependencies": {
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"optional": true
}
}
},
"math-interval-parser": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-interval-parser/-/math-interval-parser-1.1.0.tgz",
"integrity": "sha1-2+2lsGsySZc8bfYXD94jhvCv2JM=",
"requires": {
"xregexp": "^2.0.0"
}
},
"md5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
@@ -1074,6 +1246,32 @@
"is-buffer": "~1.1.1"
}
},
"messageformat": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/messageformat/-/messageformat-0.3.1.tgz",
"integrity": "sha1-5Y//gkXps5cXmeW0PbWLPpQX9aI=",
"requires": {
"async": "~1.5.2",
"glob": "~6.0.4",
"make-plural": "~3.0.3",
"nopt": "~3.0.6",
"watchr": "~2.4.13"
},
"dependencies": {
"glob": {
"version": "6.0.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
"integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
"requires": {
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "2 || 3",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
}
}
},
"mime-db": {
"version": "1.33.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz",
@@ -1095,7 +1293,7 @@
"minimatch": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -1118,35 +1316,42 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"mustache": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mustache/-/mustache-3.0.1.tgz",
"integrity": "sha512-jFI/4UVRsRYdUbuDTKT7KzfOp7FiD5WzYmmwNwXyUVypC0xjoTL78Fqc0jHUPIvvGD+6DQSPHIt1NE7D1ArsqA=="
},
"nan": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha1-ltDNYQ69WNS03pzAxoKM2pnHVI8="
},
"node-json-db": {
"version": "0.7.3",
"resolved": "https://registry.npmjs.org/node-json-db/-/node-json-db-0.7.3.tgz",
"integrity": "sha1-v2Mf9NTPQhHL3/5srmqq/m7lTN8=",
"version": "0.9.2",
"resolved": "https://registry.npmjs.org/node-json-db/-/node-json-db-0.9.2.tgz",
"integrity": "sha512-vd8A6wznm3hi82IKlVKdomZvsEnASYWl0n4iwLkI7ukbIQkXTtFdJG8aZg0BNbEN+Vr/VktDFSqwWx/iSdf31Q==",
"requires": {
"mkdirp": "0.5.x"
}
},
"node-mac-notifier": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/node-mac-notifier/-/node-mac-notifier-0.1.0.tgz",
"integrity": "sha512-K07p0jW6BhE4Jxpem4pcOdOCDgF/vy3xIqZoZpqme5JAfeW0cEkQ5Z3AmsM7fhJB59kDeOiouyykwPnvLV+hAg==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/node-mac-notifier/-/node-mac-notifier-1.1.0.tgz",
"integrity": "sha512-Fwv09eKMGkM4xn+Eby5g7lvmYF+1KYWX4V5QNn27l4cVFVS7MNYOxhbvGGHT5VuTWbWDQIi/Lr7l6so5vvmqVw==",
"optional": true,
"requires": {
"bindings": "^1.2.1",
"event-target-shim": "^1.1.1",
"node-uuid": "^1.4.7"
"uuid": "^3.3.2"
}
},
"node-uuid": {
"version": "1.4.8",
"resolved": "https://registry.npmjs.org/node-uuid/-/node-uuid-1.4.8.tgz",
"integrity": "sha1-sEDrCSOWivq/jTL7HxfxFn/auQc=",
"optional": true
"nopt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
"integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
"requires": {
"abbrev": "1"
}
},
"normalize-url": {
"version": "2.0.1",
@@ -1236,6 +1441,11 @@
"p-finally": "^1.0.0"
}
},
"pako": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/pako/-/pako-1.0.7.tgz",
"integrity": "sha512-3HNK5tW4x8o5mO8RuHZp3Ydw9icZXx0RANAOMzlMzx7LVXhMJ4mo3MOBpzyd7r/+RUu8BmndP47LXT+vzjtWcQ=="
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@@ -1451,6 +1661,14 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safefs": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/safefs/-/safefs-3.2.2.tgz",
"integrity": "sha1-gXDBRE1wOOCMrqBaN0+uL6NJ4Vw=",
"requires": {
"graceful-fs": "*"
}
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
@@ -1459,7 +1677,17 @@
"sax": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha1-KBYjTiN4vdxOU1T6tcqold9xANk="
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
},
"scandirectory": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/scandirectory/-/scandirectory-2.5.0.tgz",
"integrity": "sha1-bOA/VKCQtmjjy+2/IO354xBZPnI=",
"requires": {
"ignorefs": "^1.0.0",
"safefs": "^3.1.2",
"taskgroup": "^4.0.5"
}
},
"semver": {
"version": "5.4.1",
@@ -1485,12 +1713,12 @@
"source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha1-dHIq8y6WFOnCh6jQu95IteLxomM="
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="
},
"source-map-support": {
"version": "0.5.6",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.6.tgz",
"integrity": "sha512-N4KXEz7jcKqPf2b2vZF11lQIz9W5ZMuUcIOGj243lduidkf2fjkVKJS9vNxVWn3u/uxX38AcE8U9nnH9FPcq+g==",
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz",
"integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==",
"requires": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
@@ -1499,7 +1727,7 @@
"spawn-rx": {
"version": "2.0.12",
"resolved": "https://registry.npmjs.org/spawn-rx/-/spawn-rx-2.0.12.tgz",
"integrity": "sha1-tihSlEmUJgib7qDDwewy1/xXo3Y=",
"integrity": "sha512-gOPXiQQFQ9lTOLuys0iMn3jfxxv9c7zzwhbYLOEbQGvEShHVJ5sSR1oD3Daj88os7jKArDYT7rbOKdvNhe7iEg==",
"requires": {
"debug": "^2.5.1",
"lodash.assign": "^4.2.0",
@@ -1554,6 +1782,15 @@
"resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz",
"integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ="
},
"taskgroup": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/taskgroup/-/taskgroup-4.3.1.tgz",
"integrity": "sha1-feGT/r12gnPEV3MElwJNUSwnkVo=",
"requires": {
"ambi": "^2.2.0",
"csextends": "^1.0.3"
}
},
"timed-out": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz",
@@ -1567,6 +1804,11 @@
"punycode": "^1.4.1"
}
},
"tslib": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz",
"integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ=="
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
@@ -1581,6 +1823,11 @@
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"optional": true
},
"typechecker": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/typechecker/-/typechecker-2.1.0.tgz",
"integrity": "sha1-0cIJOlT/ihn1jP+HfuqlTyJC04M="
},
"underscore": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.9.0.tgz",
@@ -1628,6 +1875,12 @@
"object.getownpropertydescriptors": "^2.0.3"
}
},
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"optional": true
},
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
@@ -1638,6 +1891,21 @@
"extsprintf": "^1.2.0"
}
},
"watchr": {
"version": "2.4.13",
"resolved": "https://registry.npmjs.org/watchr/-/watchr-2.4.13.tgz",
"integrity": "sha1-10hHu01vkPYf4sdPn2hmKqDgdgE=",
"requires": {
"eachr": "^2.0.2",
"extendr": "^2.1.0",
"extract-opts": "^2.2.0",
"ignorefs": "^1.0.0",
"safefs": "^3.1.2",
"scandirectory": "^2.5.0",
"taskgroup": "^4.2.0",
"typechecker": "^2.0.8"
}
},
"winreg": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.4.tgz",
@@ -1658,6 +1926,11 @@
"resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz",
"integrity": "sha1-Z/4HXFwk/vOfnWX197f+dRcZaPw="
},
"xregexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xregexp/-/xregexp-2.0.0.tgz",
"integrity": "sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM="
},
"yallist": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",

View File

@@ -1,7 +1,8 @@
{
"name": "zulip",
"productName": "Zulip",
"version": "2.3.6",
"version": "4.0.3",
"desktopName": "zulip.desktop",
"description": "Zulip Desktop App",
"license": "Apache-2.0",
"copyright": "Kandra Labs, Inc.",
@@ -11,10 +12,10 @@
},
"repository": {
"type": "git",
"url": "https://github.com/zulip/zulip-electron.git"
"url": "https://github.com/zulip/zulip-desktop.git"
},
"bugs": {
"url": "https://github.com/zulip/zulip-electron/issues"
"url": "https://github.com/zulip/zulip-desktop/issues"
},
"main": "main/index.js",
"keywords": [
@@ -26,23 +27,25 @@
"InstantMessaging"
],
"dependencies": {
"@electron-elements/send-feedback": "1.0.7",
"@sentry/electron": "0.8.1",
"@electron-elements/send-feedback": "1.0.8",
"@sentry/electron": "0.14.0",
"adm-zip": "0.4.11",
"auto-launch": "5.0.5",
"backoff": "2.5.0",
"dotenv": "8.0.0",
"electron-is-dev": "0.3.0",
"electron-log": "2.2.14",
"electron-spellchecker": "1.1.2",
"electron-updater": "2.23.3",
"electron-window-state": "4.1.1",
"electron-updater": "4.0.6",
"electron-window-state": "5.0.3",
"escape-html": "1.0.3",
"is-online": "7.0.0",
"node-json-db": "0.7.3",
"i18n": "0.8.3",
"node-json-db": "0.9.2",
"request": "2.85.0",
"semver": "5.4.1",
"wurl": "2.5.0"
},
"optionalDependencies": {
"node-mac-notifier": "0.1.0"
"node-mac-notifier": "1.1.0"
}
}
}

View File

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

View File

@@ -1,82 +1,66 @@
body {
background: #fafafa;
font-family: menu, "Helvetica Neue", sans-serif;
-webkit-font-smoothing: subpixel-antialiased;
background: rgba(250, 250, 250, 1.000);
font-family: menu, "Helvetica Neue", sans-serif;
-webkit-font-smoothing: subpixel-antialiased;
}
.logo {
display: block;
margin: -40px auto;
display: block;
margin: -40px auto;
}
#version {
color: #444343;
font-size: 1.3em;
padding-top: 40px;
color: rgba(68, 67, 67, 1.000);
font-size: 1.3em;
padding-top: 40px;
}
.about {
margin: 25vh auto;
height: 25vh;
text-align: center;
margin: 25vh auto;
height: 25vh;
text-align: center;
}
.about p {
font-size: 20px;
color: rgba(0, 0, 0, 0.62);
font-size: 20px;
color: rgba(0, 0, 0, 0.62);
}
.about img {
width: 150px;
width: 150px;
}
.detail {
text-align: center;
text-align: center;
}
.detail.maintainer {
font-size: 1.2em;
font-weight: 500;
font-size: 1.2em;
font-weight: 500;
}
.detail.license {
font-size: 0.8em;
font-size: 0.8em;
}
.maintenance-info {
cursor: pointer;
position: absolute;
width: 100%;
left: 0px;
color: #444;
cursor: pointer;
position: absolute;
width: 100%;
left: 0px;
color: rgba(68, 68, 68, 1.000);
}
.maintenance-info p {
margin: 0;
font-size: 1em;
width: 100%;
}
.maintenance-info .bug {
display: inline-block;
padding: 8px 15px;
margin-top: 30px;
text-decoration: none;
background-color: #52c2af;
color: #fff;
border-radius: 4px;
transition: background-color 0.2s ease;
}
.maintenance-info .bug:hover {
background-color: #32a692;
margin: 0;
font-size: 1em;
width: 100%;
}
p.detail a {
color: #355f4c;
color: rgba(53, 95, 76, 1.000);
}
p.detail a:hover {
text-decoration: underline;
text-decoration: underline;
}

View File

@@ -16,7 +16,7 @@ body {
}
.toggle-sidebar {
background: #222c31;
background: rgba(34, 44, 49, 1.000);
width: 54px;
padding: 27px 0 20px 0;
justify-content: space-between;
@@ -43,25 +43,25 @@ body {
}
#view-controls-container {
height: calc(100% - 208px);
overflow-y: hidden;
height: calc(100% - 208px);
overflow-y: hidden;
}
#view-controls-container:hover {
overflow-y: overlay;
overflow-y: overlay;
}
#view-controls-container::-webkit-scrollbar {
width: 4px;
width: 4px;
}
#view-controls-container::-webkit-scrollbar-track {
box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
}
#view-controls-container::-webkit-scrollbar-thumb {
background-color: darkgrey;
outline: 1px solid slategrey;
background-color: rgba(169, 169, 169, 1.000);
outline: 1px solid rgba(169, 169, 169, 1.000);
}
@font-face {
@@ -119,12 +119,12 @@ body {
}
.action-button i {
color: #6c8592;
color: rgba(108, 133, 146, 1.000);
font-size: 28px;
}
.action-button:hover i {
color: #98a9b3;
color: rgba(152, 169, 179, 1.000);
}
.action-button.disable {
@@ -136,22 +136,22 @@ body {
}
.action-button.disable:hover i {
color: #6c8592;
color: rgba(108, 133, 146, 1.000);
}
.action-button.active {
/* background-color: rgba(255, 255, 255, 0.25); */
background-color: #efefef;
background-color: rgba(239, 239, 239, 1.000);
opacity: 0.9;
padding-right: 14px;
}
.action-button.active i {
color: #1c262b;
color: rgba(28, 38, 43, 1.000);
}
.tab:first-child {
margin-top: 8px;
margin-top: 9px;
}
.tab {
@@ -165,25 +165,33 @@ body {
}
.tab .server-icons {
border-radius: 50%;
width: 30px;
padding: 3px;
height: 30px;
width: 35px;
vertical-align: top;
border-radius: 4px;
}
.tab .server-tab {
width: 100%;
height: 35px;
position: relative;
margin: 5px 0 2px 0;
margin-top: 5px;
z-index: 11;
line-height: 31px;
color: #eee;
color: rgba(238, 238, 238, 1.000);
text-align: center;
overflow: hidden;
opacity: 0.6;
padding: 2px 0;
padding: 6px 0;
}
.server-tab .alt-icon {
font-family: Verdana;
font-weight: 600;
font-size: 22px;
border: 2px solid rgba(34, 44, 49, 1.000);
margin-left: 17%;
width: 35px;
border-radius: 4px;
}
.tab .server-tab:hover {
@@ -208,7 +216,7 @@ body {
.tab.active .server-tab {
opacity: 1;
background-color: #648478;
background-color: rgba(100, 132, 120, 1.000);
}
.tab .server-tab-badge.active {
@@ -216,7 +224,7 @@ body {
min-width: 11px;
padding: 0 3px;
height: 17px;
background-color: #f44336;
background-color: rgba(244, 67, 54, 1.000);
font-size: 10px;
font-family: sans-serif;
position: absolute;
@@ -224,7 +232,7 @@ body {
z-index: 15;
top: 6px;
float: right;
color: #fff;
color: rgba(255, 255, 255, 1.000);
text-align: center;
line-height: 17px;
display: block;
@@ -252,13 +260,25 @@ body {
}
.tab .server-tab-shortcut {
color: #648478;
color: rgba(100, 132, 120, 1.000);
font-size: 12px;
text-align: center;
font-family: sans-serif;
margin-bottom: 5px;
}
.refresh {
animation: rotate-loader 1s linear infinite;
}
@keyframes rotate-loader {
from {
transform: rotate(0);
}
to {
transform: rotate(-360deg);
}
}
/*******************
* Webview Area *
@@ -270,19 +290,19 @@ body {
width: 100%;
}
/*Pseudo element for loading indicator*/
/* Pseudo element for loading indicator */
#webviews-container::before {
content: "";
position: absolute;
z-index: 1;
background: #fff url(../img/ic_loading.gif) no-repeat;
background: rgba(255, 255, 255, 1.000) url(../img/ic_loading.gif) no-repeat;
background-size: 60px 60px;
background-position: center;
width: 100%;
height: 100%;
}
/*When the active webview is loaded*/
/* When the active webview is loaded */
#webviews-container.loaded::before {
opacity: 0;
z-index: -1;
@@ -320,32 +340,34 @@ webview.focus {
/* Tooltip styling */
#loading-tooltip,
#dnd-tooltip,
#back-tooltip,
#reload-tooltip,
#setting-tooltip {
font-family: sans-serif;
background: #222c31;
background: rgba(34, 44, 49, 1.000);
margin-left: 48px;
padding: 6px 8px;
position: absolute;
margin-top: 0px;
z-index: 1000;
color: white;
color: rgba(255, 255, 255, 1.000);
border-radius: 4px;
text-align: center;
width: 55px;
font-size: 14px;
}
#dnd-tooltip:after,
#back-tooltip:after,
#reload-tooltip:after,
#setting-tooltip:after {
#loading-tooltip::after,
#dnd-tooltip::after,
#back-tooltip::after,
#reload-tooltip::after,
#setting-tooltip::after {
content: " ";
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 8px solid #222c31;
border-right: 8px solid rgba(34, 44, 49, 1.000);
position: absolute;
top: 7px;
right: 68px;
@@ -354,25 +376,25 @@ webview.focus {
#add-server-tooltip,
.server-tooltip {
font-family: 'arial';
background: #222c31;
background: rgba(34, 44, 49, 1.000);
left: 56px;
padding: 10px 20px;
position: fixed;
margin-top: 8px;
margin-top: 11px;
z-index: 5000 !important;
color: #fff;
color: rgba(255, 255, 255, 1.000);
border-radius: 4px;
text-align: center;
width: max-content;
font-size: 14px;
}
#add-server-tooltip:after,
.server-tooltip:after {
#add-server-tooltip::after,
.server-tooltip::after {
content: " ";
border-top: 8px solid transparent;
border-bottom: 8px solid transparent;
border-right: 8px solid #222c31;
border-right: 8px solid rgba(34, 44, 49, 1.000);
position: absolute;
top: 10px;
left: -5px;
@@ -384,14 +406,14 @@ webview.focus {
position: absolute;
width: 24px;
height: 24px;
background: #222c31;
background: rgba(34, 44, 49, 1.000);
border-radius: 20px;
cursor: pointer;
box-shadow: #999 1px 1px;
box-shadow: rgba(153, 153, 153, 1.000) 1px 1px;
}
#collapse-button i {
color: #efefef;
color: rgba(239, 239, 239, 1.000);
}
#main-container {
@@ -412,8 +434,8 @@ webview.focus {
.popup .popuptext {
visibility: hidden;
background-color: #555;
color: #fff;
background-color: rgba(85, 85, 85, 1.000);
color: rgba(255, 255, 255, 1.000);
text-align: center;
border-radius: 6px;
padding: 9px 0;
@@ -445,24 +467,24 @@ webview.focus {
}
send-feedback {
width: 60%;
height: 85%;
width: 60%;
height: 85%;
}
#feedback-modal {
display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(68, 67, 67, 0.81);
align-items: center;
justify-content: center;
z-index: 2;
transition: all 1s ease-out;
display: none;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(68, 67, 67, 0.81);
align-items: center;
justify-content: center;
z-index: 2;
transition: all 1s ease-out;
}
#feedback-modal.show {
display: flex;
display: flex;
}

View File

@@ -1,10 +1,11 @@
html, body {
html,
body {
margin: 0;
cursor: default;
font-size: 14px;
color: #333;
background: #fff;
user-select:none;
color: rgba(51, 51, 51, 1.000);
background: rgba(255, 255, 255, 1.000);
user-select: none;
}
#content {
@@ -16,27 +17,43 @@ html, body {
}
#title {
text-align: left;
font-size: 24px;
font-weight: bold;
margin: 20px 0;
}
#subtitle {
font-size: 20px;
text-align: left;
margin: 12px 0;
}
#description {
text-align: left;
font-size: 16px;
list-style-position: inside;
}
#reconnect {
float: left;
}
#settings {
margin-left: 116px;
}
.button {
font-size: 16px;
background: #009688;
color: #fff;
width: 84px;
background: rgba(0, 150, 136, 1.000);
color: rgba(255, 255, 255, 1.000);
width: 96px;
height: 32px;
border-radius: 5px;
line-height: 32px;
margin: 20px auto 0;
cursor: pointer;
}
#reconnect:hover {
.button:hover {
opacity: 0.8;
}

View File

@@ -7,23 +7,25 @@ body {
font-family: menu, "Helvetica Neue", sans-serif;
-webkit-font-smoothing: antialiased;
font-size: 14px;
color: #333;
background: #efefef;
background: rgba(239, 239, 239, 1.000);
letter-spacing: -.08px;
line-height: 18px;
color: rgba(139, 142, 143, 1.000);
}
kbd {
display: inline-block;
border: 1px solid #ccc;
border-radius: 3px;
border: 1px solid rgba(204, 204, 204, 1.000);
border-radius: 4px;
font-size: 15px;
font-family: Courier New, Courier, monospace;
font-weight: bold;
white-space: nowrap;
background-color: #f7f7f7;
color: #333;
background-color: rgba(247, 247, 247, 1.000);
color: rgba(51, 51, 51, 1.000);
margin: 0 0.1em;
padding: 0.3em 0.8em;
text-shadow: 0 1px 0 #fff;
text-shadow: 0 1px 0 rgba(255, 255, 255, 1.000);
line-height: 1.4;
}
@@ -31,7 +33,7 @@ table,
th,
td {
border-collapse: collapse;
color: #383430;
color: rgba(56, 52, 48, 1.000);
}
table {
@@ -41,11 +43,11 @@ table {
}
table tr:nth-child(even) {
background-color: #fafafa;
background-color: rgba(250, 250, 250, 1.000);
}
table tr:nth-child(odd) {
background-color: #fff;
background-color: rgba(255, 255, 255, 1.000);
}
td {
@@ -101,7 +103,7 @@ td:nth-child(odd) {
display: flex;
flex-direction: column;
font-size: 16px;
background: #f2f2f2;
background: rgba(242, 242, 242, 1.000);
}
#nav-container {
@@ -109,19 +111,19 @@ td:nth-child(odd) {
}
.nav {
padding: 5px 0;
color: #999;
padding: 7px 0;
color: rgba(153, 153, 153, 1.000);
cursor: pointer;
}
.nav.active {
color: #4ebfac;
color: rgba(78, 191, 172, 1.000);
cursor: default;
position: relative;
}
.nav.active::before {
background: #464e5a;
background: rgba(70, 78, 90, 1.000);
width: 3px;
height: 18px;
position: absolute;
@@ -129,7 +131,6 @@ td:nth-child(odd) {
content: '';
}
/* We don't want to show this in nav item since we have the + button for adding an Organization */
#nav-AddServer {
@@ -138,7 +139,7 @@ td:nth-child(odd) {
#settings-header {
font-size: 22px;
color: #222c31;
color: rgba(34, 44, 49, 1.000);
font-weight: bold;
text-transform: uppercase;
}
@@ -158,11 +159,11 @@ td:nth-child(odd) {
.title {
font-weight: 500;
color: #222c31;
color: rgba(34, 44, 49, 1.000);
}
.page-title {
color: #222c31;
color: rgba(34, 44, 49, 1.000);
font-size: 15px;
font-weight: bold;
padding: 4px 0 6px 0;
@@ -181,13 +182,13 @@ td:nth-child(odd) {
.sub-title {
padding: 4px 0 6px 0;
font-weight: bold;
color: #616161;
color: rgba(97, 97, 97, 1.000);
}
img.server-info-icon {
width: 36px;
height: 36px;
padding: 4px;
padding: 4px 4px 4px 10px;
cursor: pointer;
vertical-align: middle;
}
@@ -246,16 +247,20 @@ img.server-info-icon {
.setting-input-value {
flex-grow: 1;
font-size: 14px;
border-radius: 3px;
border-radius: 4px;
padding: 13px;
border: #ededed 2px solid;
border: rgba(237, 237, 237, 1.000) 2px solid;
outline-width: 0;
background: transparent;
max-width: 450px;
}
.setting-input-value:focus {
border: #4EBFAC 2px solid;
border: rgba(78, 191, 172, 1.000) 2px solid;
}
.invalid-input-value:focus {
border: rgba(239, 83, 80, 1) 2px solid;
}
.manual-proxy-block {
@@ -265,7 +270,7 @@ img.server-info-icon {
.actions-container {
display: flex;
font-size: 14px;
color: #235d3a;
color: rgba(35, 93, 58, 1.000);
vertical-align: middle;
margin: 10px 0;
flex-wrap: wrap;
@@ -286,6 +291,7 @@ img.server-info-icon {
.settings-pane {
flex-grow: 1;
min-width: 550px;
}
.action:hover {
@@ -297,7 +303,7 @@ img.server-info-icon {
}
.action.disabled {
color: #999;
color: rgba(153, 153, 153, 1.000);
}
.settings-card {
@@ -305,8 +311,8 @@ img.server-info-icon {
flex-wrap: wrap;
padding: 12px 30px;
margin: 10px 0 20px 0;
background: #fff;
width: 70%;
background: rgba(255, 255, 255, 1.000);
width: 80%;
transition: all 0.2s;
}
@@ -320,29 +326,59 @@ img.server-info-icon {
}
.red {
color: #ef5350;
padding: 8px;
border: rgba(239, 83, 80, 0.5) solid 1px;
color: rgb(240, 148, 148);
background: rgba(255, 255, 255, 1.000);
border-radius: 4px;
display: inline-block;
border: 2px solid rgb(240, 148, 148);
padding: 10px;
width: 100px;
cursor: pointer;
font-size: 1rem;
font-weight: bold;
transition: background-color 0.2s ease;
text-decoration: none;
text-align: center;
}
.red:hover {
color: #e63431;
border: rgba(239, 83, 80, 0.7) solid 1px;
;
background-color: rgb(240, 148, 148);
color: rgba(255, 255, 255, 1.000);
}
.blue {
color: #ffffff;
background: #4EBFAC;
padding: 3px;
padding-right: 10px;
padding-left: 10px;
.green {
color: rgba(255, 255, 255, 1.000);
background: rgba(78, 191, 172, 1.000);
border-radius: 4px;
display: inline-block;
border: none;
padding: 10px;
width: 100px;
cursor: pointer;
font-size: 1rem;
font-weight: bold;
transition: background-color 0.2s ease;
text-decoration: none;
text-align: center;
}
.green:hover {
background-color: rgba(60, 159, 141, 1.000);
color: rgba(255, 255, 255, 1.000);
}
.w-150 {
width: 150px;
}
.w-250 {
width: 250px;
}
.grey {
color: #9E9E9E;
background: #FAFAFA;
border: 1px solid #9E9E9E;
color: rgba(158, 158, 158, 1.000);
background: rgba(250, 250, 250, 1.000);
border: 1px solid rgba(158, 158, 158, 1.000);
}
.setting-row {
@@ -358,36 +394,27 @@ img.server-info-icon {
}
i.open-tab-button {
padding-left: 2px;
padding-left: 5px;
font-size: 19px;
cursor: pointer;
}
.reset-data-button,
.custom-css-button,
.download-folder-button {
display: inline-block;
border: none;
padding: 10px;
width: 120px;
cursor: pointer;
font-size: 13px;
transition: background-color 0.2s ease;
text-decoration: none;
}
#css-delete-action {
display: flex;
align-items: center;
justify-content: center;
box-sizing: border-box;
.css-delete-action {
margin-bottom: 10px;
}
.reset-data-button:hover {
background-color: #3c9f8d;
color: #fff;
/*
.action class will add extra margin to right which
we don't want for a button; the extra margin is intended for radio buttons
*/
margin-right: 0px;
}
.selected-css-path,
.download-folder-path {
background: #eeeeee;
background: rgba(238, 238, 238, 1.000);
padding: 5px 10px;
margin-right: 10px;
display: flex;
@@ -400,7 +427,11 @@ i.open-tab-button {
}
#server-info-container {
min-height: calc(100% - 260px);
/* min-height: calc(100% - 260px); */
}
#new-org-button {
margin: 30px 0px 30px 0px;
}
#create-organization-container {
@@ -423,7 +454,7 @@ i.open-tab-button {
visibility: hidden;
}
.toggle+label {
.toggle + label {
display: block;
position: relative;
cursor: pointer;
@@ -431,16 +462,21 @@ i.open-tab-button {
user-select: none;
}
input.toggle-round+label {
.disallowed:hover {
background-color: rgba(241, 241, 241, 1.000);
cursor: not-allowed;
}
input.toggle-round + label {
padding: 2px;
width: 50px;
height: 25px;
background-color: #dddddd;
background-color: rgba(221, 221, 221, 1.000);
border-radius: 25px;
}
input.toggle-round+label:before,
input.toggle-round+label:after {
input.toggle-round + label::before,
input.toggle-round + label::after {
display: block;
position: absolute;
top: 2px;
@@ -449,28 +485,34 @@ input.toggle-round+label:after {
content: "";
}
input.toggle-round+label:before {
right: 2px;
background-color: #f1f1f1;
input.toggle-round + label::before {
background-color: rgba(241, 241, 241, 1.000);
border-radius: 25px;
top: 0;
right: 0px;
left: 0px;
bottom: 0px;
}
input.toggle-round+label:after {
input.toggle-round + label::after {
width: 25px;
height: 25px;
background-color: #fff;
background-color: rgba(255, 255, 255, 1.000);
border-radius: 100%;
}
input.toggle-round:checked+label:before {
background-color: #4EBFAC;
input.toggle-round:checked + label::before {
background-color: rgba(78, 191, 172, 1.000);
top: 0;
right: 0px;
left: 0px;
bottom: 0px;
}
input.toggle-round:checked+label:after {
input.toggle-round:checked + label::after {
margin-left: 25px;
}
/* Add new server modal */
.add-server-modal {
@@ -484,18 +526,17 @@ input.toggle-round:checked+label:after {
width: 100%;
height: 100%;
/* background: rgba(61, 64, 67, 15); */
background: linear-gradient(35deg, #003b52, #45b59b);
background: linear-gradient(35deg, rgba(0, 59, 82, 1.000), rgba(69, 181, 155, 1.000));
overflow: auto;
}
/* Modal Content */
.modal-container {
background-color: #f4f7f8;
background-color: rgba(244, 247, 248, 1.000);
margin: auto;
padding: 57px;
border: #dae1e3 1px solid;
border: rgba(218, 225, 227, 1.000) 1px solid;
width: 550px;
height: 370px;
border-radius: 4px;
@@ -509,7 +550,7 @@ input.toggle-round:checked+label:after {
.divider {
margin-bottom: 30px;
margin-top: 30px;
color: #7d878a;
color: rgba(125, 135, 138, 1.000);
}
.divider hr {
@@ -540,53 +581,54 @@ input.toggle-round:checked+label:after {
margin: auto;
align-items: center;
text-align: center;
color: #fff;
background: #4EBFAC;
color: rgba(255, 255, 255, 1.000);
background: rgba(78, 191, 172, 1.000);
border-color: none;
border: none;
width: 98%;
height: 46px;
border-radius: 3px;
border-radius: 4px;
cursor: pointer;
}
.server-center button:hover {
background: #329588;
background: rgba(50, 149, 136, 1.000);
}
.server-center button:focus {
background: #329588;
background: rgba(50, 149, 136, 1.000);
}
.certificates-card {
width:70%
width: 80%;
}
.certificate-input {
width:100%;
margin-top: 10px;
display:inline-flex;
width: 100%;
margin-top: 10px;
display: inline-flex;
}
.certificate-input div {
align-self:center;
align-self: center;
}
.certificate-input .setting-input-value {
margin-left:10px;
margin-left: 10px;
max-width: 100%;
}
#add-certificate-button {
width:20%;
margin-right:0px;
height: 35px;
margin: 10px 10px 10px 37px;
}
.tip {
background-color: hsl(46,63%,95%);
border: 1px solid hsl(46,63%,84%);
border-radius: 4px;
background: none;
padding: 0;
}
.tip:hover {
box-shadow: none;
}
.md-14 {
@@ -600,11 +642,31 @@ input.toggle-round:checked+label:after {
cursor: pointer;
}
.server-network-option {
font-weight: bold;
font-size: 1.1rem;
margin-top: 10px;
padding-top: 15px;
align-items: center;
text-align: center;
color: rgb(78, 191, 172);
width: 98%;
height: 46px;
cursor: pointer;
}
i.open-network-button {
font-size: 16px;
cursor: pointer;
padding-left: 5px;
position: absolute;
}
/* responsive grid */
@media (max-width: 650px) {
.selected-css-path,
.download-folder-path {
.download-folder-path {
margin-right: 15px;
}
#css-delete-action {
@@ -654,12 +716,10 @@ input.toggle-round:checked+label:after {
flex-direction: column;
align-items: center;
}
.server-info-right {
flex-direction: column;
align-items: center;
}
.action {
margin-top: 10px;
}

View File

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

View File

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

View File

@@ -1,9 +1,10 @@
'use strict';
const Tab = require(__dirname + '/../components/tab.js');
import Tab = require('./tab');
class FunctionalTab extends Tab {
template() {
$closeButton: Element;
template(): string {
return `<div class="tab functional-tab" data-tab-id="${this.props.tabIndex}">
<div class="server-tab-badge close-button">
<i class="material-icons">close</i>
@@ -14,16 +15,22 @@ class FunctionalTab extends Tab {
</div>`;
}
init() {
// TODO: Typescript - This type for props should be TabProps
constructor(props: any) {
super(props);
this.init();
}
init(): void {
this.$el = this.generateNodeFromTemplate(this.template());
if (this.props.name !== 'Settings') {
this.props.$root.appendChild(this.$el);
this.$closeButton = this.$el.getElementsByClassName('server-tab-badge')[0];
this.props.$root.append(this.$el);
this.$closeButton = this.$el.querySelectorAll('.server-tab-badge')[0];
this.registerListeners();
}
}
registerListeners() {
registerListeners(): void {
super.registerListeners();
this.$el.addEventListener('mouseover', () => {
@@ -34,11 +41,11 @@ class FunctionalTab extends Tab {
this.$closeButton.classList.remove('active');
});
this.$closeButton.addEventListener('click', e => {
this.$closeButton.addEventListener('click', (e: Event) => {
this.props.onDestroy();
e.stopPropagation();
});
}
}
module.exports = FunctionalTab;
export = FunctionalTab;

View File

@@ -1,12 +1,15 @@
const { ipcRenderer } = require('electron');
const { shell, app } = require('electron').remote;
const LinkUtil = require('../utils/link-util');
const DomainUtil = require('../utils/domain-util');
const ConfigUtil = require('../utils/config-util');
import { ipcRenderer, remote } from 'electron';
import LinkUtil = require('../utils/link-util');
import DomainUtil = require('../utils/domain-util');
import ConfigUtil = require('../utils/config-util');
const { shell, app } = remote;
const dingSound = new Audio('../resources/sounds/ding.ogg');
function handleExternalLink(event) {
// TODO: TypeScript - Figure out a way to pass correct type here.
function handleExternalLink(this: any, event: any): void {
const { url } = event;
const domainPrefix = DomainUtil.getDomain(this.props.index).url;
const downloadPath = ConfigUtil.getConfigItem('downloadsPath', `${app.getPath('downloads')}`);
@@ -21,21 +24,23 @@ function handleExternalLink(event) {
if (isWhiteListURL) {
event.preventDefault();
// Code to show pdf in a new BrowserWindow (currently commented out due to bug-upstream)
// Show pdf attachments in a new window
if (LinkUtil.isPDF(url) && isUploadsURL) {
ipcRenderer.send('pdf-view', url);
return;
}
// if (LinkUtil.isPDF(url) && isUploadsURL) {
// ipcRenderer.send('pdf-view', url);
// return;
// }
// download txt, mp3, mp4 etc.. by using downloadURL in the
// main process which allows the user to save the files to their desktop
// and not trigger webview reload while image in webview will
// do nothing and will not save it
if (!LinkUtil.isImage(url) && !LinkUtil.isPDF(url) && isUploadsURL) {
// Code to show pdf in a new BrowserWindow (currently commented out due to bug-upstream)
// if (!LinkUtil.isImage(url) && !LinkUtil.isPDF(url) && isUploadsURL) {
if (!LinkUtil.isImage(url) && isUploadsURL) {
ipcRenderer.send('downloadFile', url, downloadPath);
ipcRenderer.once('downloadFileCompleted', (event, filePath, fileName) => {
ipcRenderer.once('downloadFileCompleted', (_event: Event, filePath: string, fileName: string) => {
const downloadNotification = new Notification('Download Complete', {
body: shouldShowInFolder ? `Click to show ${fileName} in folder` : `Click to open ${fileName}`,
silent: true // We'll play our own sound - ding.ogg
@@ -46,7 +51,7 @@ function handleExternalLink(event) {
dingSound.play();
}
downloadNotification.onclick = () => {
downloadNotification.addEventListener('click', () => {
if (shouldShowInFolder) {
// Reveal file in download folder
shell.showItemInFolder(filePath);
@@ -54,7 +59,7 @@ function handleExternalLink(event) {
// Open file in the default native app
shell.openItem(filePath);
}
};
});
ipcRenderer.removeAllListeners('downloadFileFailed');
});
@@ -75,4 +80,4 @@ function handleExternalLink(event) {
}
}
module.exports = handleExternalLink;
export = handleExternalLink;

View File

@@ -1,14 +1,16 @@
'use strict';
const Tab = require(__dirname + '/../components/tab.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js');
import { ipcRenderer } from 'electron';
const {ipcRenderer} = require('electron');
import Tab = require('./tab');
import SystemUtil = require('../utils/system-util');
class ServerTab extends Tab {
template() {
$badge: Element;
template(): string {
return `<div class="tab" data-tab-id="${this.props.tabIndex}">
<div class="server-tooltip" style="display:none"></div>
<div class="server-tooltip" style="display:none">${this.props.name}</div>
<div class="server-tab-badge"></div>
<div class="server-tab">
<img class="server-icons" src='${this.props.icon}'/>
@@ -17,16 +19,22 @@ class ServerTab extends Tab {
</div>`;
}
init() {
super.init();
this.$badge = this.$el.getElementsByClassName('server-tab-badge')[0];
// TODO: Typescript - This type for props should be TabProps
constructor(props: any) {
super(props);
this.init();
}
updateBadge(count) {
if (count > 0) {
const formattedCount = count > 999 ? '1K+' : count;
init(): void {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.append(this.$el);
this.registerListeners();
this.$badge = this.$el.querySelectorAll('.server-tab-badge')[0];
}
updateBadge(count: number): void {
if (count > 0) {
const formattedCount = count > 999 ? '1K+' : count.toString();
this.$badge.innerHTML = formattedCount;
this.$badge.classList.add('active');
} else {
@@ -34,7 +42,7 @@ class ServerTab extends Tab {
}
}
generateShortcutText() {
generateShortcutText(): string {
// Only provide shortcuts for server [0..10]
if (this.props.index >= 10) {
return '';
@@ -57,4 +65,4 @@ class ServerTab extends Tab {
}
}
module.exports = ServerTab;
export = ServerTab;

View File

@@ -1,48 +1,48 @@
'use strict';
const BaseComponent = require(__dirname + '/../components/base.js');
import WebView = require('./webview');
import BaseComponent = require('./base');
// TODO: TypeScript - Type annotate props
interface TabProps {
[key: string]: any;
}
class Tab extends BaseComponent {
constructor(props) {
props: TabProps;
webview: WebView;
$el: Element;
constructor(props: TabProps) {
super();
this.props = props;
this.webview = this.props.webview;
this.init();
}
init() {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$el);
this.registerListeners();
}
registerListeners() {
registerListeners(): void {
this.$el.addEventListener('click', this.props.onClick);
this.$el.addEventListener('mouseover', this.props.onHover);
this.$el.addEventListener('mouseout', this.props.onHoverOut);
}
isLoading() {
return this.webview.isLoading;
showNetworkError(): void {
this.webview.forceLoad();
}
activate() {
activate(): void {
this.$el.classList.add('active');
this.webview.load();
}
deactivate() {
deactivate(): void {
this.$el.classList.remove('active');
this.webview.hide();
}
destroy() {
destroy(): void {
this.$el.parentNode.removeChild(this.$el);
this.webview.$el.parentNode.removeChild(this.webview.$el);
}
}
module.exports = Tab;
export = Tab;

View File

@@ -1,22 +1,39 @@
'use strict';
import { remote } from 'electron';
const path = require('path');
const fs = require('fs');
import path = require('path');
import fs = require('fs');
import ConfigUtil = require('../utils/config-util');
import SystemUtil = require('../utils/system-util');
import BaseComponent = require('../components/base');
import handleExternalLink = require('../components/handle-external-link');
const ConfigUtil = require(__dirname + '/../utils/config-util.js');
const SystemUtil = require(__dirname + '/../utils/system-util.js');
const { app, dialog } = require('electron').remote;
const BaseComponent = require(__dirname + '/../components/base.js');
const handleExternalLink = require(__dirname + '/../components/handle-external-link.js');
const { app, dialog } = remote;
const shouldSilentWebview = ConfigUtil.getConfigItem('silent');
// TODO: TypeScript - Type annotate WebViewProps.
interface WebViewProps {
[key: string]: any;
}
class WebView extends BaseComponent {
constructor(props) {
props: any;
zoomFactor: number;
badgeCount: number;
loading: boolean;
customCSS: string;
$webviewsContainer: DOMTokenList;
$el: Electron.WebviewTag;
// This is required because in main.js we access WebView.method as
// webview[method].
[key: string]: any;
constructor(props: WebViewProps) {
super();
this.props = props;
this.zoomFactor = 1.0;
this.loading = true;
this.badgeCount = 0;
@@ -24,27 +41,27 @@ class WebView extends BaseComponent {
this.$webviewsContainer = document.querySelector('#webviews-container').classList;
}
template() {
template(): string {
return `<webview
class="disabled"
data-tab-id="${this.props.tabIndex}"
src="${this.props.url}"
${this.props.nodeIntegration ? 'nodeIntegration' : ''}
disablewebsecurity
${this.props.preload ? 'preload="js/preload.js"' : ''}
partition="persist:webviewsession"
name="${this.props.name}"
webpreferences="allowRunningInsecureContent, javascript=yes">
</webview>`;
}
init() {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$el);
init(): void {
this.$el = this.generateNodeFromTemplate(this.template()) as Electron.WebviewTag;
this.props.$root.append(this.$el);
this.registerListeners();
}
registerListeners() {
registerListeners(): void {
this.$el.addEventListener('new-window', event => {
handleExternalLink.call(this, event);
});
@@ -93,19 +110,31 @@ class WebView extends BaseComponent {
this.$el.classList.add('onload');
}
this.loading = false;
this.props.switchLoading(false, this.props.url);
this.show();
// Refocus text boxes after reload
// Remove when upstream issue https://github.com/electron/electron/issues/14474 is fixed
this.$el.blur();
this.$el.focus();
});
this.$el.addEventListener('did-fail-load', event => {
const { errorDescription } = event;
const hasConnectivityErr = (SystemUtil.connectivityERR.indexOf(errorDescription) >= 0);
const hasConnectivityErr = SystemUtil.connectivityERR.includes(errorDescription);
if (hasConnectivityErr) {
console.error('error', errorDescription);
this.props.onNetworkError();
if (!this.props.url.includes('network.html')) {
this.props.onNetworkError(this.props.index);
}
}
});
this.$el.addEventListener('did-start-loading', () => {
const isSettingPage = this.props.url.includes('renderer/preference.html');
if (!isSettingPage) {
this.props.switchLoading(true, this.props.url);
}
let userAgent = SystemUtil.getUserAgent();
if (!userAgent) {
SystemUtil.setUserAgent(this.$el.getUserAgent());
@@ -113,14 +142,22 @@ class WebView extends BaseComponent {
}
this.$el.setUserAgent(userAgent);
});
this.$el.addEventListener('did-stop-loading', () => {
this.props.switchLoading(false, this.props.url);
});
}
getBadgeCount(title) {
const messageCountInTitle = (/\(([0-9]+)\)/).exec(title);
getBadgeCount(title: string): number {
const messageCountInTitle = (/\((\d+)\)/).exec(title);
return messageCountInTitle ? Number(messageCountInTitle[1]) : 0;
}
show() {
showNotificationSettings(): void {
this.$el.executeJavaScript('showNotificationSettings()');
}
show(): void {
// Do not show WebView if another tab was selected and this tab should be in background.
if (!this.props.isActive()) {
return;
@@ -161,21 +198,27 @@ class WebView extends BaseComponent {
}
}
focus() {
focus(): void {
// focus Webview and it's contents when Window regain focus.
const webContents = this.$el.getWebContents();
if (webContents && !webContents.isFocused()) {
// HACK: webContents.isFocused() seems to be true even without the element
// being in focus. So, we check against `document.activeElement`.
if (webContents && this.$el !== document.activeElement) {
// HACK: Looks like blur needs to be called on the previously focused
// element to transfer focus correctly, in Electron v3.0.10
// See https://github.com/electron/electron/issues/15718
(document.activeElement as HTMLElement).blur();
this.$el.focus();
webContents.focus();
}
}
hide() {
hide(): void {
this.$el.classList.add('disabled');
this.$el.classList.remove('active');
}
load() {
load(): void {
if (this.$el) {
this.show();
} else {
@@ -183,40 +226,41 @@ class WebView extends BaseComponent {
}
}
zoomIn() {
zoomIn(): void {
this.zoomFactor += 0.1;
this.$el.setZoomFactor(this.zoomFactor);
}
zoomOut() {
zoomOut(): void {
this.zoomFactor -= 0.1;
this.$el.setZoomFactor(this.zoomFactor);
}
zoomActualSize() {
zoomActualSize(): void {
this.zoomFactor = 1.0;
this.$el.setZoomFactor(this.zoomFactor);
}
logOut() {
logOut(): void {
this.$el.executeJavaScript('logout()');
}
showShortcut() {
showShortcut(): void {
this.$el.executeJavaScript('shortcut()');
}
openDevTools() {
openDevTools(): void {
this.$el.openDevTools();
}
back() {
back(): void {
if (this.$el.canGoBack()) {
this.$el.goBack();
this.focus();
}
}
canGoBackButton() {
canGoBackButton(): void {
const $backButton = document.querySelector('#actions-container #back-action');
if (this.$el.canGoBack()) {
$backButton.classList.remove('disable');
@@ -225,23 +269,28 @@ class WebView extends BaseComponent {
}
}
forward() {
forward(): void {
if (this.$el.canGoForward()) {
this.$el.goForward();
}
}
reload() {
reload(): void {
this.hide();
// Shows the loading indicator till the webview is reloaded
this.$webviewsContainer.remove('loaded');
this.loading = true;
this.props.switchLoading(true, this.props.url);
this.$el.reload();
}
send(...param) {
this.$el.send(...param);
forceLoad(): void {
this.init();
}
send(channel: string, ...param: any[]): void {
this.$el.send(channel, ...param);
}
}
module.exports = WebView;
export = WebView;

View File

@@ -1,39 +0,0 @@
const events = require('events');
const { ipcRenderer } = require('electron');
// we have and will have some non camelcase stuff
// while working with zulip so just turning the rule off
// for the whole file.
/* eslint-disable camelcase */
class ElectronBridge extends events {
send_event(...args) {
this.emit(...args);
}
on_event(...args) {
this.on(...args);
}
}
const electron_bridge = new ElectronBridge();
electron_bridge.on('total_unread_count', (...args) => {
ipcRenderer.send('unread-count', ...args);
});
electron_bridge.on('realm_name', (...args) => {
ipcRenderer.send('realm-name-changed', ...args);
});
electron_bridge.on('realm_icon_url', iconURL => {
const serverURL = location.origin;
iconURL = iconURL.includes('http') ? iconURL : `${serverURL}${iconURL}`;
ipcRenderer.send('realm-icon-changed', serverURL, iconURL);
});
// this follows node's idiomatic implementation of event
// emitters to make event handling more simpler instead of using
// functions zulip side will emit event using ElectronBrigde.send_event
// which is alias of .emit and on this side we can handle the data by adding
// a listener for the event.
module.exports = electron_bridge;

View File

@@ -0,0 +1,59 @@
import { ipcRenderer } from 'electron';
import events = require('events');
type ListenerType = ((...args: any[]) => void);
// we have and will have some non camelcase stuff
// while working with zulip so just turning the rule off
// for the whole file.
/* eslint-disable @typescript-eslint/camelcase */
class ElectronBridge extends events {
send_notification_reply_message_supported: boolean;
idle_on_system: boolean;
last_active_on_system: number;
constructor() {
super();
this.send_notification_reply_message_supported = false;
// Indicates if the user is idle or not
this.idle_on_system = false;
// Indicates the time at which user was last active
this.last_active_on_system = Date.now();
}
send_event(eventName: string | symbol, ...args: any[]): void {
this.emit(eventName, ...args);
}
on_event(eventName: string, listener: ListenerType): void {
this.on(eventName, listener);
}
}
const electron_bridge = new ElectronBridge();
electron_bridge.on('total_unread_count', (...args) => {
ipcRenderer.send('unread-count', ...args);
});
electron_bridge.on('realm_name', realmName => {
const serverURL = location.origin;
ipcRenderer.send('realm-name-changed', serverURL, realmName);
});
electron_bridge.on('realm_icon_url', iconURL => {
const serverURL = location.origin;
iconURL = iconURL.includes('http') ? iconURL : `${serverURL}${iconURL}`;
ipcRenderer.send('realm-icon-changed', serverURL, iconURL);
});
// this follows node's idiomatic implementation of event
// emitters to make event handling more simpler instead of using
// functions zulip side will emit event using ElectronBrigde.send_event
// which is alias of .emit and on this side we can handle the data by adding
// a listener for the event.
export = electron_bridge;
/* eslint-enable @typescript-eslint/camelcase */

View File

@@ -1,7 +1,16 @@
const { app } = require('electron').remote;
const path = require('path');
const fs = require('fs');
const SendFeedback = require('@electron-elements/send-feedback');
import { remote } from 'electron';
import SendFeedback from '@electron-elements/send-feedback';
import path = require('path');
import fs = require('fs');
const { app } = remote;
interface SendFeedback extends HTMLElement {
[key: string]: any;
}
type SendFeedbackType = SendFeedback;
// make the button color match zulip app's theme
SendFeedback.customStyles = `
@@ -22,8 +31,8 @@ button {
`;
customElements.define('send-feedback', SendFeedback);
const sendFeedback = document.querySelector('send-feedback');
const feedbackHolder = sendFeedback.parentElement;
export const sendFeedback: SendFeedbackType = document.querySelector('send-feedback');
export const feedbackHolder = sendFeedback.parentElement;
// customize the fields of custom elements
sendFeedback.title = 'Report Issue';
@@ -38,9 +47,9 @@ sendFeedback.useReporter('emailReporter', {
email: 'akash@zulipchat.com'
});
feedbackHolder.addEventListener('click', e => {
// only remove the class if the grey out faded
// part is clicked and not the feedback element itself
feedbackHolder.addEventListener('click', (e: Event) => {
// only remove the class if the grey out faded
// part is clicked and not the feedback element itself
if (e.target === e.currentTarget) {
feedbackHolder.classList.remove('show');
}
@@ -55,8 +64,3 @@ sendFeedback.addEventListener('feedback-submitted', () => {
const dataDir = app.getPath('userData');
const logsDir = path.join(dataDir, '/Logs');
sendFeedback.logs.push(...fs.readdirSync(logsDir).map(file => path.join(logsDir, file)));
module.exports = {
feedbackHolder,
sendFeedback
};

View File

@@ -1,627 +0,0 @@
'use strict';
const { ipcRenderer, remote } = require('electron');
const isDev = require('electron-is-dev');
const { session, app } = remote;
require(__dirname + '/js/tray.js');
const DomainUtil = require(__dirname + '/js/utils/domain-util.js');
const WebView = require(__dirname + '/js/components/webview.js');
const ServerTab = require(__dirname + '/js/components/server-tab.js');
const FunctionalTab = require(__dirname + '/js/components/functional-tab.js');
const ConfigUtil = require(__dirname + '/js/utils/config-util.js');
const DNDUtil = require(__dirname + '/js/utils/dnd-util.js');
const ReconnectUtil = require(__dirname + '/js/utils/reconnect-util.js');
const Logger = require(__dirname + '/js/utils/logger-util.js');
const CommonUtil = require(__dirname + '/js/utils/common-util.js');
const { feedbackHolder } = require(__dirname + '/js/feedback.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class ServerManagerView {
constructor() {
this.$addServerButton = document.getElementById('add-tab');
this.$tabsContainer = document.getElementById('tabs-container');
const $actionsContainer = document.getElementById('actions-container');
this.$reloadButton = $actionsContainer.querySelector('#reload-action');
this.$settingsButton = $actionsContainer.querySelector('#settings-action');
this.$webviewsContainer = document.getElementById('webviews-container');
this.$backButton = $actionsContainer.querySelector('#back-action');
this.$dndButton = $actionsContainer.querySelector('#dnd-action');
this.$addServerTooltip = document.getElementById('add-server-tooltip');
this.$reloadTooltip = $actionsContainer.querySelector('#reload-tooltip');
this.$settingsTooltip = $actionsContainer.querySelector('#setting-tooltip');
this.$serverIconTooltip = document.getElementsByClassName('server-tooltip');
this.$backTooltip = $actionsContainer.querySelector('#back-tooltip');
this.$dndTooltip = $actionsContainer.querySelector('#dnd-tooltip');
this.$sidebar = document.getElementById('sidebar');
this.$fullscreenPopup = document.getElementById('fullscreen-popup');
this.$fullscreenEscapeKey = process.platform === 'darwin' ? '^⌘F' : 'F11';
this.$fullscreenPopup.innerHTML = `Press ${this.$fullscreenEscapeKey} to exit full screen`;
this.activeTabIndex = -1;
this.tabs = [];
this.functionalTabs = {};
this.tabIndex = 0;
}
init() {
this.loadProxy().then(() => {
this.initSidebar();
this.initTabs();
this.initActions();
this.registerIpcs();
this.initDefaultSettings();
});
}
loadProxy() {
return new Promise(resolve => {
// To change proxyEnable to useManualProxy in older versions
const proxyEnabledOld = ConfigUtil.isConfigItemExists('useProxy');
if (proxyEnabledOld) {
const proxyEnableOldState = ConfigUtil.getConfigItem('useProxy');
if (proxyEnableOldState) {
ConfigUtil.setConfigItem('useManualProxy', true);
}
ConfigUtil.removeConfigItem('useProxy');
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
if (proxyEnabled) {
session.fromPartition('persist:webviewsession').setProxy({
pacScript: ConfigUtil.getConfigItem('proxyPAC', ''),
proxyRules: ConfigUtil.getConfigItem('proxyRules', ''),
proxyBypassRules: ConfigUtil.getConfigItem('proxyBypass', '')
}, resolve);
} else {
session.fromPartition('persist:webviewsession').setProxy({
pacScript: '',
proxyRules: '',
proxyBypassRules: ''
}, resolve);
}
});
}
// Settings are initialized only when user clicks on General/Server/Network section settings
// In case, user doesn't visit these section, those values set to be null automatically
// This will make sure the default settings are correctly set to either true or false
initDefaultSettings() {
// Default settings which should be respected
const settingOptions = {
trayIcon: true,
useManualProxy: false,
useSystemProxy: false,
showSidebar: true,
badgeOption: true,
startAtLogin: false,
startMinimized: false,
enableSpellchecker: true,
showNotification: true,
autoUpdate: true,
betaUpdate: false,
silent: false,
lastActiveTab: 0,
dnd: false,
dndPreviousSettings: {
showNotification: true,
silent: false
},
downloadsPath: `${app.getPath('downloads')}`,
showDownloadFolder: false
};
// Platform specific settings
if (process.platform === 'win32') {
// Only available on Windows
settingOptions.flashTaskbarOnMessage = true;
settingOptions.dndPreviousSettings.flashTaskbarOnMessage = true;
}
if (process.platform === 'darwin') {
// Only available on macOS
settingOptions.dockBouncing = true;
}
for (const i in settingOptions) {
if (ConfigUtil.getConfigItem(i) === null) {
ConfigUtil.setConfigItem(i, settingOptions[i]);
}
}
}
initSidebar() {
const showSidebar = ConfigUtil.getConfigItem('showSidebar', true);
this.toggleSidebar(showSidebar);
}
initTabs() {
const servers = DomainUtil.getDomains();
if (servers.length > 0) {
for (let i = 0; i < servers.length; i++) {
this.initServer(servers[i], i);
DomainUtil.updateSavedServer(servers[i].url, i);
this.activateTab(i);
}
// Open last active tab
this.activateTab(ConfigUtil.getConfigItem('lastActiveTab'));
// Remove focus from the settings icon at sidebar bottom
this.$settingsButton.classList.remove('active');
} else {
this.openSettings('AddServer');
}
}
initServer(server, index) {
const tabIndex = this.getTabIndex();
this.tabs.push(new ServerTab({
role: 'server',
icon: server.icon,
$root: this.$tabsContainer,
onClick: this.activateLastTab.bind(this, index),
index,
tabIndex,
onHover: this.onHover.bind(this, index, server.alias),
onHoverOut: this.onHoverOut.bind(this, index),
webview: new WebView({
$root: this.$webviewsContainer,
index,
tabIndex,
url: server.url,
name: CommonUtil.decodeString(server.alias),
isActive: () => {
return index === this.activeTabIndex;
},
onNetworkError: this.openNetworkTroubleshooting.bind(this),
onTitleChange: this.updateBadge.bind(this),
nodeIntegration: false,
preload: true
})
}));
}
initActions() {
this.initDNDButton();
this.$dndButton.addEventListener('click', () => {
const dndUtil = DNDUtil.toggle();
ipcRenderer.send('forward-message', 'toggle-dnd', dndUtil.dnd, dndUtil.newSettings);
});
this.$reloadButton.addEventListener('click', () => {
this.tabs[this.activeTabIndex].webview.reload();
});
this.$addServerButton.addEventListener('click', () => {
this.openSettings('AddServer');
});
this.$settingsButton.addEventListener('click', () => {
this.openSettings('General');
});
this.$backButton.addEventListener('click', () => {
this.tabs[this.activeTabIndex].webview.back();
});
const $serverImgs = document.querySelectorAll('.server-icons');
$serverImgs.forEach($serverImg => {
$serverImg.addEventListener('error', () => {
$serverImg.src = 'img/icon.png';
});
});
this.sidebarHoverEvent(this.$addServerButton, this.$addServerTooltip, true);
this.sidebarHoverEvent(this.$settingsButton, this.$settingsTooltip);
this.sidebarHoverEvent(this.$reloadButton, this.$reloadTooltip);
this.sidebarHoverEvent(this.$backButton, this.$backTooltip);
this.sidebarHoverEvent(this.$dndButton, this.$dndTooltip);
}
initDNDButton() {
const dnd = ConfigUtil.getConfigItem('dnd', false);
this.toggleDNDButton(dnd);
}
getTabIndex() {
const currentIndex = this.tabIndex;
this.tabIndex++;
return currentIndex;
}
sidebarHoverEvent(SidebarButton, SidebarTooltip, addServer = false) {
SidebarButton.addEventListener('mouseover', () => {
SidebarTooltip.removeAttribute('style');
// To handle position of add server tooltip due to scrolling of list of organizations
// This could not be handled using CSS, hence the top of the tooltip is made same
// as that of its parent element.
// This needs to handled only for the add server tooltip and not others.
if (addServer) {
const { top } = SidebarButton.getBoundingClientRect();
SidebarTooltip.style.top = top + 'px';
}
});
SidebarButton.addEventListener('mouseout', () => {
SidebarTooltip.style.display = 'none';
});
}
onHover(index, serverName) {
this.$serverIconTooltip[index].innerHTML = serverName;
this.$serverIconTooltip[index].removeAttribute('style');
// To handle position of servers' tooltip due to scrolling of list of organizations
// This could not be handled using CSS, hence the top of the tooltip is made same
// as that of its parent element.
const { top } = this.$serverIconTooltip[index].parentElement.getBoundingClientRect();
this.$serverIconTooltip[index].style.top = top + 'px';
}
onHoverOut(index) {
this.$serverIconTooltip[index].style.display = 'none';
}
openFunctionalTab(tabProps) {
if (this.functionalTabs[tabProps.name] !== undefined) {
this.activateTab(this.functionalTabs[tabProps.name]);
return;
}
this.functionalTabs[tabProps.name] = this.tabs.length;
const tabIndex = this.getTabIndex();
this.tabs.push(new FunctionalTab({
role: 'function',
materialIcon: tabProps.materialIcon,
name: tabProps.name,
$root: this.$tabsContainer,
index: this.functionalTabs[tabProps.name],
tabIndex,
onClick: this.activateTab.bind(this, this.functionalTabs[tabProps.name]),
onDestroy: this.destroyTab.bind(this, tabProps.name, this.functionalTabs[tabProps.name]),
webview: new WebView({
$root: this.$webviewsContainer,
index: this.functionalTabs[tabProps.name],
tabIndex,
url: tabProps.url,
name: tabProps.name,
isActive: () => {
return this.functionalTabs[tabProps.name] === this.activeTabIndex;
},
onNetworkError: this.openNetworkTroubleshooting.bind(this),
onTitleChange: this.updateBadge.bind(this),
nodeIntegration: true,
preload: false
})
}));
// To show loading indicator the first time a functional tab is opened, indicator is
// closed when the functional tab DOM is ready, handled in webview.js
this.$webviewsContainer.classList.remove('loaded');
this.activateTab(this.functionalTabs[tabProps.name]);
}
openSettings(nav = 'General') {
this.openFunctionalTab({
name: 'Settings',
materialIcon: 'settings',
url: `file://${__dirname}/preference.html#${nav}`
});
this.$settingsButton.classList.add('active');
this.tabs[this.functionalTabs.Settings].webview.send('switch-settings-nav', nav);
}
openAbout() {
this.openFunctionalTab({
name: 'About',
materialIcon: 'sentiment_very_satisfied',
url: `file://${__dirname}/about.html`
});
}
openNetworkTroubleshooting() {
this.openFunctionalTab({
name: 'Network Troubleshooting',
materialIcon: 'network_check',
url: `file://${__dirname}/network.html`
});
}
activateLastTab(index) {
// Open all the tabs in background, also activate the tab based on the index
this.activateTab(index);
// Save last active tab
ConfigUtil.setConfigItem('lastActiveTab', index);
}
activateTab(index, hideOldTab = true) {
if (!this.tabs[index]) {
return;
}
if (this.activeTabIndex !== -1) {
if (this.activeTabIndex === index) {
return;
} else if (hideOldTab) {
// If old tab is functional tab Settings, remove focus from the settings icon at sidebar bottom
if (this.tabs[this.activeTabIndex].props.role === 'function' && this.tabs[this.activeTabIndex].props.name === 'Settings') {
this.$settingsButton.classList.remove('active');
}
this.tabs[this.activeTabIndex].deactivate();
}
}
try {
this.tabs[index].webview.canGoBackButton();
} catch (err) {
}
this.activeTabIndex = index;
this.tabs[index].activate();
ipcRenderer.send('update-menu', {
tabs: this.tabs,
activeTabIndex: this.activeTabIndex
});
ipcRenderer.on('toggle-sidebar', (event, state) => {
const selector = 'webview:not([class*=disabled])';
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggle-sidebar', state);
});
ipcRenderer.on('toggle-silent', (event, state) => {
const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => {
try {
webview.setAudioMuted(state);
} catch (err) {
// webview is not ready yet
webview.addEventListener('dom-ready', () => {
webview.isAudioMuted();
});
}
});
});
ipcRenderer.on('toggle-dnd', (event, state, newSettings) => {
this.toggleDNDButton(state);
ipcRenderer.send('forward-message', 'toggle-silent', newSettings.silent);
const selector = 'webview:not([class*=disabled])';
const webview = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggle-dnd', state, newSettings);
});
ipcRenderer.on('update-realm-icon', (event, serverURL, iconURL) => {
DomainUtil.getDomains().forEach((domain, index) => {
if (domain.url.includes(serverURL)) {
DomainUtil.saveServerIcon(iconURL).then(localIconUrl => {
const serverImgsSelector = `.tab .server-icons`;
const serverImgs = document.querySelectorAll(serverImgsSelector);
serverImgs[index].src = localIconUrl;
domain.icon = localIconUrl;
DomainUtil.db.push(`/domains[${index}]`, domain, true);
DomainUtil.reloadDB();
});
}
});
});
}
destroyTab(name, index) {
if (this.tabs[index].webview.loading) {
return;
}
this.tabs[index].destroy();
delete this.tabs[index];
delete this.functionalTabs[name];
// Issue #188: If the functional tab was not focused, do not activate another tab.
if (this.activeTabIndex === index) {
this.activateTab(0, false);
}
}
destroyView() {
// Show loading indicator
this.$webviewsContainer.classList.remove('loaded');
// Clear global variables
this.activeTabIndex = -1;
this.tabs = [];
this.functionalTabs = {};
// Clear DOM elements
this.$tabsContainer.innerHTML = '';
this.$webviewsContainer.innerHTML = '';
}
reloadView() {
// Save and remember the index of last active tab so that we can use it later
const lastActiveTab = this.tabs[this.activeTabIndex].props.index;
ConfigUtil.setConfigItem('lastActiveTab', lastActiveTab);
// Destroy the current view and re-initiate it
this.destroyView();
this.initTabs();
}
// This will trigger when pressed CTRL/CMD + R [WIP]
// It won't reload the current view properly when you add/delete a server.
reloadCurrentView() {
this.$reloadButton.click();
}
updateBadge() {
let messageCountAll = 0;
for (let i = 0; i < this.tabs.length; i++) {
if (this.tabs[i] && this.tabs[i].updateBadge) {
const count = this.tabs[i].webview.badgeCount;
messageCountAll += count;
this.tabs[i].updateBadge(count);
}
}
ipcRenderer.send('update-badge', messageCountAll);
}
toggleSidebar(show) {
if (show) {
this.$sidebar.classList.remove('sidebar-hide');
} else {
this.$sidebar.classList.add('sidebar-hide');
}
}
// Toggles the dnd button icon.
toggleDNDButton(alert) {
this.$dndTooltip.textContent = (alert ? 'Turn Off' : 'Enable') + ' Do Not Disturb';
this.$dndButton.querySelector('i').textContent = alert ? 'notifications_off' : 'notifications';
}
registerIpcs() {
const webviewListeners = {
'webview-reload': 'reload',
back: 'back',
focus: 'focus',
forward: 'forward',
zoomIn: 'zoomIn',
zoomOut: 'zoomOut',
zoomActualSize: 'zoomActualSize',
'log-out': 'logOut',
shortcut: 'showShortcut',
'tab-devtools': 'openDevTools'
};
for (const key in webviewListeners) {
ipcRenderer.on(key, () => {
const activeWebview = this.tabs[this.activeTabIndex].webview;
if (activeWebview) {
activeWebview[webviewListeners[key]]();
}
});
}
ipcRenderer.on('open-settings', (event, settingNav) => {
this.openSettings(settingNav);
});
ipcRenderer.on('open-about', this.openAbout.bind(this));
ipcRenderer.on('reload-viewer', this.reloadView.bind(this, this.tabs[this.activeTabIndex].props.index));
ipcRenderer.on('reload-current-viewer', this.reloadCurrentView.bind(this));
ipcRenderer.on('hard-reload', () => {
ipcRenderer.send('reload-full-app');
});
ipcRenderer.on('clear-app-data', () => {
ipcRenderer.send('clear-app-settings');
});
ipcRenderer.on('switch-server-tab', (event, index) => {
this.activateLastTab(index);
});
ipcRenderer.on('reload-proxy', (event, showAlert) => {
this.loadProxy().then(() => {
if (showAlert) {
alert('Proxy settings saved!');
ipcRenderer.send('reload-full-app');
}
});
});
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('focus-webview-with-id', (event, webviewId) => {
const webviews = document.querySelectorAll('webview');
webviews.forEach(webview => {
const currentId = webview.getWebContents().id;
const tabId = webview.getAttribute('data-tab-id');
const concurrentTab = document.querySelector(`div[data-tab-id="${tabId}"]`);
if (currentId === webviewId) {
concurrentTab.click();
}
});
});
ipcRenderer.on('render-taskbar-icon', (event, messageCount) => {
// Create a canvas from unread messagecounts
function createOverlayIcon(messageCount) {
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));
});
ipcRenderer.on('open-feedback-modal', () => {
feedbackHolder.classList.add('show');
});
}
}
window.onload = () => {
const serverManagerView = new ServerManagerView();
const reconnectUtil = new ReconnectUtil(serverManagerView);
serverManagerView.init();
window.addEventListener('online', () => {
reconnectUtil.pollInternetAndReload();
});
window.addEventListener('offline', () => {
reconnectUtil.clearState();
logger.log('No internet connection, you are offline.');
});
// only start electron-connect (auto reload on change) when its ran
// from `npm run dev` or `gulp dev` and not from `npm start` when
// app is started `npm start` main process's proces.argv will have
// `--no-electron-connect`
const mainProcessArgv = remote.getGlobal('process').argv;
if (isDev && !mainProcessArgv.includes('--no-electron-connect')) {
const electronConnect = require('electron-connect');
electronConnect.client.create();
}
};

1030
app/renderer/js/main.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,18 +1,28 @@
'use strict';
const { ipcRenderer } = require('electron');
const url = require('url');
const MacNotifier = require('node-mac-notifier');
const ConfigUtil = require('../utils/config-util');
const {
import { ipcRenderer } from 'electron';
import {
appId, customReply, focusCurrentServer, parseReply, setupReply
} = require('./helpers');
} from './helpers';
import url = require('url');
import MacNotifier = require('node-mac-notifier');
import ConfigUtil = require('../utils/config-util');
type ReplyHandler = (response: string) => void;
type ClickHandler = () => void;
let replyHandler: ReplyHandler;
let clickHandler: ClickHandler;
declare const window: ZulipWebWindow;
interface NotificationHandlerArgs {
response: string;
}
let replyHandler;
let clickHandler;
class DarwinNotification {
constructor(title, opts) {
const silent = ConfigUtil.getConfigItem('silent') || false;
tag: string;
constructor(title: string, opts: NotificationOptions) {
const silent: boolean = ConfigUtil.getConfigItem('silent') || false;
const { host, protocol } = location;
const { icon } = opts;
const profilePic = url.resolve(`${protocol}//${host}`, icon);
@@ -39,49 +49,53 @@ class DarwinNotification {
notification.addEventListener('reply', this.notificationHandler);
}
static requestPermission() {
static requestPermission(): void {
return; // eslint-disable-line no-useless-return
}
// Override default Notification permission
static get permission() {
static get permission(): NotificationPermission {
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
}
set onreply(handler) {
set onreply(handler: ReplyHandler) {
replyHandler = handler;
}
get onreply() {
get onreply(): ReplyHandler {
return replyHandler;
}
set onclick(handler) {
set onclick(handler: ClickHandler) {
clickHandler = handler;
}
get onclick() {
get onclick(): ClickHandler {
return clickHandler;
}
// not something that is common or
// used by zulip server but added to be
// future proff.
addEventListener(event, handler) {
addEventListener(event: string, handler: ClickHandler | ReplyHandler): void {
if (event === 'click') {
clickHandler = handler;
clickHandler = handler as ClickHandler;
}
if (event === 'reply') {
replyHandler = handler;
replyHandler = handler as ReplyHandler;
}
}
notificationHandler({ response }) {
notificationHandler({ response }: NotificationHandlerArgs): void {
response = parseReply(response);
focusCurrentServer();
setupReply(this.tag);
if (window.electron_bridge.send_notification_reply_message_supported) {
window.electron_bridge.send_event('send_notification_reply_message', this.tag, response);
return;
}
setupReply(this.tag);
if (replyHandler) {
replyHandler(response);
return;
@@ -92,7 +106,7 @@ class DarwinNotification {
// method specific to notification api
// used by zulip
close() {
close(): void {
return; // eslint-disable-line no-useless-return
}
}

View File

@@ -1,12 +1,13 @@
'use strict';
const { ipcRenderer } = require('electron');
const ConfigUtil = require('../utils/config-util');
const { focusCurrentServer } = require('./helpers');
import { ipcRenderer } from 'electron';
import { focusCurrentServer } from './helpers';
import ConfigUtil = require('../utils/config-util');
const NativeNotification = window.Notification;
class BaseNotification extends NativeNotification {
constructor(title, opts) {
constructor(title: string, opts: NotificationOptions) {
opts.silent = true;
super(title, opts);
@@ -18,14 +19,14 @@ class BaseNotification extends NativeNotification {
});
}
static requestPermission() {
static requestPermission(): void {
return; // eslint-disable-line no-useless-return
}
// Override default Notification permission
static get permission() {
static get permission(): NotificationPermission {
return ConfigUtil.getConfigItem('showNotification') ? 'granted' : 'denied';
}
}
module.exports = BaseNotification;
export = BaseNotification;

View File

@@ -1,5 +1,6 @@
const { remote } = require('electron');
const Logger = require('../utils/logger-util.js');
import { remote } from 'electron';
import Logger = require('../utils/logger-util');
const logger = new Logger({
file: 'errors.log',
@@ -7,24 +8,25 @@ const logger = new Logger({
});
// Do not change this
const appId = 'org.zulip.zulip-electron';
export const appId = 'org.zulip.zulip-electron';
const botsList = [];
export type BotListItem = [string, string];
const botsList: BotListItem[] = [];
let botsListLoaded = false;
// this function load list of bots from the server
// sync=True for a synchronous getJSON request
// in case botsList isn't already completely loaded when required in parseRely
function loadBots(sync = false) {
export function loadBots(sync = false): void {
const { $ } = window;
botsList.length = 0;
if (sync) {
$.ajaxSetup({async: false});
}
$.getJSON('/json/users')
.done(data => {
const members = data.members;
members.forEach(membersRow => {
.done((data: any) => {
const { members } = data;
members.forEach((membersRow: any) => {
if (membersRow.is_bot) {
const bot = `@${membersRow.full_name}`;
const mention = `@**${bot.replace(/^@/, '')}**`;
@@ -33,7 +35,7 @@ function loadBots(sync = false) {
});
botsListLoaded = true;
})
.fail(error => {
.fail((error: any) => {
logger.log('Load bots request failed: ', error.responseText);
logger.log('Load bots request status: ', error.statusText);
});
@@ -42,7 +44,7 @@ function loadBots(sync = false) {
}
}
function checkElements(...elements) {
export function checkElements(...elements: any[]): boolean {
let status = true;
elements.forEach(element => {
if (element === null || element === undefined) {
@@ -52,13 +54,13 @@ function checkElements(...elements) {
return status;
}
function customReply(reply) {
export function customReply(reply: string): void {
// server does not support notification reply yet.
const buttonSelector = '.messagebox #send_controls button[type=submit]';
const messageboxSelector = '.selected_message .messagebox .messagebox-border .messagebox-content';
const textarea = document.querySelector('#compose-textarea');
const messagebox = document.querySelector(messageboxSelector);
const sendButton = document.querySelector(buttonSelector);
const textarea: HTMLInputElement = document.querySelector('#compose-textarea');
const messagebox: HTMLButtonElement = document.querySelector(messageboxSelector);
const sendButton: HTMLButtonElement = document.querySelector(buttonSelector);
// sanity check for old server versions
const elementsExists = checkElements(textarea, messagebox, sendButton);
@@ -77,19 +79,24 @@ const webContentsId = webContents.id;
// this function will focus the server that sent
// the notification. Main function implemented in main.js
function focusCurrentServer() {
currentWindow.send('focus-webview-with-id', webContentsId);
export function focusCurrentServer(): void {
// TODO: TypeScript: currentWindow of type BrowserWindow doesn't
// have a .send() property per typescript.
(currentWindow as any).send('focus-webview-with-id', webContentsId);
}
// this function parses the reply from to notification
// making it easier to reply from notification eg
// @username in reply will be converted to @**username**
// #stream in reply will be converted to #**stream**
// bot mentions are not yet supported
function parseReply(reply) {
export function parseReply(reply: string): string {
const usersDiv = document.querySelectorAll('#user_presences li');
const streamHolder = document.querySelectorAll('#stream_filters li');
const users = [];
const streams = [];
type UsersItem = BotListItem;
type StreamsItem = BotListItem;
const users: UsersItem[] = [];
const streams: StreamsItem[] = [];
usersDiv.forEach(userRow => {
const anchor = userRow.querySelector('span a');
@@ -139,17 +146,8 @@ function parseReply(reply) {
return reply;
}
function setupReply(id) {
export function setupReply(id: string): void {
const { narrow } = window;
narrow.by_subject(id, { trigger: 'notification' });
const narrowByTopic = narrow.by_topic || narrow.by_subject;
narrowByTopic(id, { trigger: 'notification' });
}
module.exports = {
appId,
checkElements,
customReply,
parseReply,
setupReply,
focusCurrentServer,
loadBots
};

View File

@@ -1,12 +1,11 @@
'use strict';
const {
remote: { app }
} = require('electron');
import { remote } from 'electron';
import * as params from '../utils/params-util';
import { appId, loadBots } from './helpers';
const params = require('../utils/params-util.js');
const DefaultNotification = require('./default-notification');
const { appId, loadBots } = require('./helpers');
import DefaultNotification = require('./default-notification');
const { app } = remote;
// From https://github.com/felixrieseberg/electron-windows-notifications#appusermodelid
// On windows 8 we have to explicitly set the appUserModelId otherwise notification won't work.
@@ -15,12 +14,11 @@ app.setAppUserModelId(appId);
window.Notification = DefaultNotification;
if (process.platform === 'darwin') {
const DarwinNotification = require('./darwin-notifications');
window.Notification = DarwinNotification;
window.Notification = require('./darwin-notifications');
}
window.addEventListener('load', () => {
// eslint-disable-next-line no-undef, camelcase
// eslint-disable-next-line no-undef, @typescript-eslint/camelcase
if (params.isPageParams() && page_params.realm_uri) {
loadBots();
}

View File

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

View File

@@ -0,0 +1,16 @@
'use strict';
import { ipcRenderer } from 'electron';
class NetworkTroubleshootingView {
init($reconnectButton: Element, $settingsButton: Element): void {
$reconnectButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
$settingsButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'open-settings');
});
}
}
export = new NetworkTroubleshootingView();

View File

@@ -1,47 +1,57 @@
'use-strict';
const { dialog } = require('electron').remote;
import { remote, OpenDialogOptions } from 'electron';
const BaseComponent = require(__dirname + '/../../components/base.js');
const CertificateUtil = require(__dirname + '/../../utils/certificate-util.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
import path = require('path');
import BaseComponent = require('../../components/base');
import CertificateUtil = require('../../utils/certificate-util');
import DomainUtil = require('../../utils/domain-util');
import t = require('../../utils/translation-util');
const { dialog } = remote;
class AddCertificate extends BaseComponent {
constructor(props) {
// TODO: TypeScript - Here props should be object type
props: any;
_certFile: string;
$addCertificate: Element | null;
addCertificateButton: Element | null;
serverUrl: HTMLInputElement | null;
constructor(props: any) {
super();
this.props = props;
this._certFile = '';
}
template() {
return `
<div class="settings-card server-center certificates-card">
template(): string {
return `
<div class="settings-card certificates-card">
<div class="certificate-input">
<div>Organization URL :</div>
<div>${t.__('Organization URL')}</div>
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/>
</div>
<div class="certificate-input">
<div>Custom CA's certificate file :</div>
<button id="add-certificate-button">Add</button>
<div>${t.__('Certificate file')}</div>
<button class="green" id="add-certificate-button">${t.__('Upload')}</button>
</div>
</div>
`;
}
init() {
init(): void {
this.$addCertificate = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$addCertificate);
this.props.$root.append(this.$addCertificate);
this.addCertificateButton = this.$addCertificate.querySelector('#add-certificate-button');
this.serverUrl = this.$addCertificate.querySelectorAll('input.setting-input-value')[0];
this.serverUrl = this.$addCertificate.querySelectorAll('input.setting-input-value')[0] as HTMLInputElement;
this.initListeners();
}
validateAndAdd() {
validateAndAdd(): void {
const certificate = this._certFile;
const serverUrl = this.serverUrl.value;
if (certificate !== '' && serverUrl !== '') {
const server = encodeURIComponent(DomainUtil.formatUrl(serverUrl));
const fileName = certificate.substring(certificate.lastIndexOf('/') + 1);
const fileName = path.basename(certificate);
const copy = CertificateUtil.copyCertificate(server, certificate, fileName);
if (!copy) {
return;
@@ -54,14 +64,13 @@ class AddCertificate extends BaseComponent {
this.serverUrl.value = '';
} else {
dialog.showErrorBox('Error', `Please, ${serverUrl === '' ?
'Enter an Organization URL' : 'Choose certificate file'}`);
'Enter an Organization URL' : 'Choose certificate file'}`);
}
}
addHandler() {
const showDialogOptions = {
addHandler(): void {
const showDialogOptions: OpenDialogOptions = {
title: 'Select file',
defaultId: 1,
properties: ['openFile'],
filters: [{ name: 'crt, pem', extensions: ['crt', 'pem'] }]
};
@@ -73,7 +82,7 @@ class AddCertificate extends BaseComponent {
});
}
initListeners() {
initListeners(): void {
this.addCertificateButton.addEventListener('click', () => {
this.addHandler();
});
@@ -88,4 +97,4 @@ class AddCertificate extends BaseComponent {
}
}
module.exports = AddCertificate;
export = AddCertificate;

View File

@@ -1,10 +1,10 @@
'use strict';
const electron = require('electron');
const { app } = require('electron');
import { app } from 'electron';
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
import electron = require('electron');
import ConfigUtil = require('../../utils/config-util');
let instance = null;
let instance: BadgeSettings | any = null;
class BadgeSettings {
constructor() {
@@ -17,16 +17,16 @@ class BadgeSettings {
return instance;
}
showBadgeCount(messageCount, mainWindow) {
if (process.platform === 'darwin') {
app.setBadgeCount(messageCount);
}
showBadgeCount(messageCount: number, mainWindow: electron.BrowserWindow): void {
if (process.platform === 'win32') {
this.updateOverlayIcon(messageCount, mainWindow);
} else {
// This should work on both macOS and Linux
app.setBadgeCount(messageCount);
}
}
hideBadgeCount(mainWindow) {
hideBadgeCount(mainWindow: electron.BrowserWindow): void {
if (process.platform === 'darwin') {
app.setBadgeCount(0);
}
@@ -35,7 +35,7 @@ class BadgeSettings {
}
}
updateBadge(badgeCount, mainWindow) {
updateBadge(badgeCount: number, mainWindow: electron.BrowserWindow): void {
if (ConfigUtil.getConfigItem('badgeOption', true)) {
this.showBadgeCount(badgeCount, mainWindow);
} else {
@@ -43,7 +43,7 @@ class BadgeSettings {
}
}
updateOverlayIcon(messageCount, mainWindow) {
updateOverlayIcon(messageCount: number, mainWindow: electron.BrowserWindow): void {
if (!mainWindow.isFocused()) {
mainWindow.flashFrame(ConfigUtil.getConfigItem('flashTaskbarOnMessage'));
}
@@ -54,10 +54,10 @@ class BadgeSettings {
}
}
updateTaskbarIcon(data, text, mainWindow) {
updateTaskbarIcon(data: string, text: string, mainWindow: electron.BrowserWindow): void {
const img = electron.nativeImage.createFromDataURL(data);
mainWindow.setOverlayIcon(img, text);
}
}
module.exports = new BadgeSettings();
export = new BadgeSettings();

View File

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

View File

@@ -0,0 +1,50 @@
'use strict';
import { ipcRenderer } from 'electron';
import BaseComponent = require('../../components/base');
class BaseSection extends BaseComponent {
// TODO: TypeScript - Here props should be object type
generateSettingOption(props: any): void {
const {$element, disabled, value, clickHandler} = props;
$element.innerHTML = '';
const $optionControl = this.generateNodeFromTemplate(this.generateOptionTemplate(value, disabled));
$element.append($optionControl);
if (!disabled) {
$optionControl.addEventListener('click', clickHandler);
}
}
generateOptionTemplate(settingOption: boolean, disabled: boolean): string {
const label = disabled ? `<label class="disallowed" title="Setting locked by system administrator."/>` : `<label/>`;
if (settingOption) {
return `
<div class="action">
<div class="switch">
<input class="toggle toggle-round" type="checkbox" checked disabled>
${label}
</div>
</div>
`;
} else {
return `
<div class="action">
<div class="switch">
<input class="toggle toggle-round" type="checkbox">
${label}
</div>
</div>
`;
}
}
reloadApp(): void {
ipcRenderer.send('forward-message', 'reload-viewer');
}
}
export = BaseSection;

View File

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

View File

@@ -0,0 +1,90 @@
'use strict';
import { ipcRenderer } from 'electron';
import BaseSection = require('./base-section');
import DomainUtil = require('../../utils/domain-util');
import ServerInfoForm = require('./server-info-form');
import AddCertificate = require('./add-certificate');
import FindAccounts = require('./find-accounts');
import t = require('../../utils/translation-util');
class ConnectedOrgSection extends BaseSection {
// TODO: TypeScript - Here props should be object type
props: any;
$serverInfoContainer: Element | null;
$existingServers: Element | null;
$newOrgButton: HTMLButtonElement | null;
$addCertificateContainer: Element | null;
$findAccountsContainer: Element | null;
constructor(props: any) {
super();
this.props = props;
}
template(): string {
return `
<div class="settings-pane" id="server-settings-pane">
<div class="page-title">${t.__('Connected organizations')}</div>
<div class="title" id="existing-servers">${t.__('All the connected orgnizations will appear here.')}</div>
<div id="server-info-container"></div>
<div id="new-org-button"><button class="green sea w-250">${t.__('Connect to another organization')}</button></div>
<div class="page-title">${t.__('Add Custom Certificates')}</div>
<div id="add-certificate-container"></div>
<div class="page-title">${t.__('Find accounts by email')}</div>
<div id="find-accounts-container"></div>
</div>
`;
}
init(): void {
this.initServers();
}
initServers(): void {
this.props.$root.innerHTML = '';
const servers = DomainUtil.getDomains();
this.props.$root.innerHTML = this.template();
this.$serverInfoContainer = document.querySelector('#server-info-container');
this.$existingServers = document.querySelector('#existing-servers');
this.$newOrgButton = document.querySelector('#new-org-button');
this.$addCertificateContainer = document.querySelector('#add-certificate-container');
this.$findAccountsContainer = document.querySelector('#find-accounts-container');
const noServerText = t.__('All the connected orgnizations will appear here');
// Show noServerText if no servers are there otherwise hide it
this.$existingServers.innerHTML = servers.length === 0 ? noServerText : '';
for (let i = 0; i < servers.length; i++) {
new ServerInfoForm({
$root: this.$serverInfoContainer,
server: servers[i],
index: i,
onChange: this.reloadApp
}).init();
}
this.$newOrgButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'open-org-tab');
});
this.initAddCertificate();
this.initFindAccounts();
}
initAddCertificate(): void {
new AddCertificate({
$root: this.$addCertificateContainer
}).init();
}
initFindAccounts(): void {
new FindAccounts({
$root: this.$findAccountsContainer
}).init();
}
}
export = ConnectedOrgSection;

View File

@@ -0,0 +1,78 @@
'use-strict';
import { shell } from 'electron';
import BaseComponent = require('../../components/base');
import t = require('../../utils/translation-util');
class FindAccounts extends BaseComponent {
// TODO: TypeScript - Here props should be object type
props: any;
$findAccounts: Element | null;
$findAccountsButton: Element | null;
$serverUrlField: HTMLInputElement | null;
constructor(props: any) {
super();
this.props = props;
}
template(): string {
return `
<div class="settings-card certificate-card">
<div class="certificate-input">
<div>${t.__('Organization URL')}</div>
<input class="setting-input-value" value="zulipchat.com"/>
</div>
<div class="certificate-input">
<button class="green w-150" id="find-accounts-button">${t.__('Find accounts')}</button>
</div>
</div>
`;
}
init(): void {
this.$findAccounts = this.generateNodeFromTemplate(this.template());
this.props.$root.append(this.$findAccounts);
this.$findAccountsButton = this.$findAccounts.querySelector('#find-accounts-button');
this.$serverUrlField = this.$findAccounts.querySelectorAll('input.setting-input-value')[0] as HTMLInputElement;
this.initListeners();
}
findAccounts(url: string): void {
if (!url) {
return;
}
if (!url.startsWith('http')) {
url = 'https://' + url;
}
shell.openExternal(url + '/accounts/find');
}
initListeners(): void {
this.$findAccountsButton.addEventListener('click', () => {
this.findAccounts(this.$serverUrlField.value);
});
this.$serverUrlField.addEventListener('click', () => {
if (this.$serverUrlField.value === 'zulipchat.com') {
this.$serverUrlField.setSelectionRange(0, 0);
}
});
this.$serverUrlField.addEventListener('keypress', event => {
if (event.keyCode === 13) {
this.findAccounts(this.$serverUrlField.value);
}
});
this.$serverUrlField.addEventListener('input', () => {
if (this.$serverUrlField.value) {
this.$serverUrlField.classList.remove('invalid-input-value');
} else {
this.$serverUrlField.classList.add('invalid-input-value');
}
});
}
}
export = FindAccounts;

View File

@@ -1,111 +1,126 @@
'use strict';
const path = require('path');
const { ipcRenderer, remote } = require('electron');
const fs = require('fs-extra');
import { ipcRenderer, remote, OpenDialogOptions } from 'electron';
import path = require('path');
import fs = require('fs-extra');
const { app, dialog } = remote;
const currentBrowserWindow = remote.getCurrentWindow();
const BaseSection = require(__dirname + '/base-section.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
import BaseSection = require('./base-section');
import ConfigUtil = require('../../utils/config-util');
import EnterpriseUtil = require('./../../utils/enterprise-util');
import t = require('../../utils/translation-util');
class GeneralSection extends BaseSection {
constructor(props) {
// TODO: TypeScript - Here props should be object type
props: any;
constructor(props: any) {
super();
this.props = props;
}
template() {
template(): string {
return `
<div class="settings-pane">
<div class="title">Appearance</div>
<div class="title">${t.__('Appearance')}</div>
<div id="appearance-option-settings" class="settings-card">
<div class="setting-row" id="tray-option">
<div class="setting-description">Show app icon in system tray</div>
<div class="setting-description">${t.__('Show app icon in system tray')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="menubar-option" style= "display:${process.platform === 'darwin' ? 'none' : ''}">
<div class="setting-description">${t.__('Auto hide menu bar (Press Alt key to display)')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="sidebar-option">
<div class="setting-description">Show sidebar (<span class="code">Cmd Or Ctrl+Shift+S</span>)</div>
<div class="setting-description">${t.__('Show sidebar')} (<span class="code">${process.platform === 'darwin' ? 'Cmd+Shift+S' : 'Ctrl+Shift+S'}</span>)</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="badge-option">
<div class="setting-description">Show app unread badge</div>
<div class="setting-description">${t.__('Show app unread badge')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="dock-bounce-option" style= "display:${process.platform === 'darwin' ? '' : 'none'}">
<div class="setting-description">Bounce dock on new private message</div>
<div class="setting-description">${t.__('Bounce dock on new private message')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="flash-taskbar-option" style= "display:${process.platform === 'win32' ? '' : 'none'}">
<div class="setting-description">Flash taskbar on new message</div>
<div class="setting-description">${t.__('Flash taskbar on new message')}</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Desktop Notification</div>
<div class="title">${t.__('Desktop Notifications')}</div>
<div class="settings-card">
<div class="setting-row" id="show-notification-option">
<div class="setting-description">Show Desktop Notifications</div>
<div class="setting-description">${t.__('Show desktop notifications')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="silent-option">
<div class="setting-description">Mute all sounds from Zulip</div>
<div class="setting-description">${t.__('Mute all sounds from Zulip')}</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">App Updates</div>
<div class="title">${t.__('App Updates')}</div>
<div class="settings-card">
<div class="setting-row" id="autoupdate-option">
<div class="setting-description">Enable auto updates</div>
<div class="setting-description">${t.__('Enable auto updates')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="betaupdate-option">
<div class="setting-description">Get beta updates</div>
<div class="setting-description">${t.__('Get beta updates')}</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Functionality</div>
<div class="title">${t.__('Functionality')}</div>
<div class="settings-card">
<div class="setting-row" id="startAtLogin-option">
<div class="setting-description">Start app at login</div>
<div class="setting-description">${t.__('Start app at login')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="start-minimize-option">
<div class="setting-description">Always start minimized</div>
<div class="setting-description">${t.__('Always start minimized')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="quitOnClose-option">
<div class="setting-description">${t.__('Quit when the window is closed')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="enable-spellchecker-option">
<div class="setting-description">Enable Spellchecker (requires restart)</div>
<div class="setting-description">${t.__('Enable spellchecker (requires restart)')}</div>
<div class="setting-control"></div>
</div>
</div>
<div class="title">Add custom CSS</div>
<div class="title">${t.__('Advanced')}</div>
<div class="settings-card">
<div class="setting-row" id="add-custom-css">
<div class="setting-description">
This will inject the selected css stylesheet in all the added accounts
</div>
<button class="custom-css-button blue">Add</button>
</div>
<div class="setting-row" id="remove-custom-css">
<div class="setting-description">
<div class="selected-css-path" id="custom-css-path">${ConfigUtil.getConfigItem('customCSS')}</div>
</div>
<div class="action red" id="css-delete-action">
<i class="material-icons">indeterminate_check_box</i>
<span>Delete</span>
</div>
</div>
</div>
<div class="title">Advanced</div>
<div class="settings-card">
<div class="setting-row" id="show-download-folder">
<div class="setting-description">Show downloaded file in the file manager</div>
<div class="setting-row" id="enable-error-reporting">
<div class="setting-description">${t.__('Enable error reporting (requires restart)')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="show-download-folder">
<div class="setting-description">${t.__('Show downloaded files in file manager')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="add-custom-css">
<div class="setting-description">
${t.__('Add custom CSS')}
</div>
<button class="custom-css-button green">${t.__('Upload')}</button>
</div>
<div class="setting-row" id="remove-custom-css">
<div class="setting-description">
<div class="selected-css-path" id="custom-css-path">${ConfigUtil.getConfigItem('customCSS')}</div>
</div>
<div class="action red" id="css-delete-action">
<i class="material-icons">indeterminate_check_box</i>
<span>${t.__('Delete')}</span>
</div>
</div>
<div class="setting-row" id="download-folder">
<div class="setting-description">
Default download location
${t.__('Default download location')}
</div>
<button class="download-folder-button blue">Choose</button>
<button class="download-folder-button green">${t.__('Change')}</button>
</div>
<div class="setting-row">
<div class="setting-description">
@@ -114,19 +129,19 @@ class GeneralSection extends BaseSection {
</div>
</div>
<div class="title">Reset Application Data</div>
<div class="title">${t.__('Reset Application Data')}</div>
<div class="settings-card">
<div class="setting-row" id="resetdata-option">
<div class="setting-description">This will delete all application data including all added accounts and preferences
<div class="setting-description">${t.__('This will delete all application data including all added accounts and preferences')}
</div>
<button class="reset-data-button blue">Reset App Data</button>
<button class="reset-data-button red w-150">${t.__('Reset App Data')}</button>
</div>
</div>
</div>
`;
}
init() {
init(): void {
this.props.$root.innerHTML = this.template();
this.updateTrayOption();
this.updateBadgeOption();
@@ -144,6 +159,8 @@ class GeneralSection extends BaseSection {
this.removeCustomCSS();
this.downloadFolder();
this.showDownloadFolder();
this.updateQuitOnCloseOption();
this.enableErrorReporting();
// Platform specific settings
@@ -155,9 +172,14 @@ class GeneralSection extends BaseSection {
if (process.platform === 'darwin') {
this.updateDockBouncing();
}
// Auto hide menubar on Windows and Linux
if (process.platform !== 'darwin') {
this.updateMenubarOption();
}
}
updateTrayOption() {
updateTrayOption(): void {
this.generateSettingOption({
$element: document.querySelector('#tray-option .setting-control'),
value: ConfigUtil.getConfigItem('trayIcon', true),
@@ -170,7 +192,20 @@ class GeneralSection extends BaseSection {
});
}
updateBadgeOption() {
updateMenubarOption(): void {
this.generateSettingOption({
$element: document.querySelector('#menubar-option .setting-control'),
value: ConfigUtil.getConfigItem('autoHideMenubar', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('autoHideMenubar');
ConfigUtil.setConfigItem('autoHideMenubar', newValue);
ipcRenderer.send('toggle-menubar', newValue);
this.updateMenubarOption();
}
});
}
updateBadgeOption(): void {
this.generateSettingOption({
$element: document.querySelector('#badge-option .setting-control'),
value: ConfigUtil.getConfigItem('badgeOption', true),
@@ -183,7 +218,7 @@ class GeneralSection extends BaseSection {
});
}
updateDockBouncing() {
updateDockBouncing(): void {
this.generateSettingOption({
$element: document.querySelector('#dock-bounce-option .setting-control'),
value: ConfigUtil.getConfigItem('dockBouncing', true),
@@ -195,7 +230,7 @@ class GeneralSection extends BaseSection {
});
}
updateFlashTaskbar() {
updateFlashTaskbar(): void {
this.generateSettingOption({
$element: document.querySelector('#flash-taskbar-option .setting-control'),
value: ConfigUtil.getConfigItem('flashTaskbarOnMessage', true),
@@ -207,31 +242,38 @@ class GeneralSection extends BaseSection {
});
}
autoUpdateOption() {
autoUpdateOption(): void {
this.generateSettingOption({
$element: document.querySelector('#autoupdate-option .setting-control'),
disabled: EnterpriseUtil.configItemExists('autoUpdate'),
value: ConfigUtil.getConfigItem('autoUpdate', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('autoUpdate');
ConfigUtil.setConfigItem('autoUpdate', newValue);
if (!newValue) {
ConfigUtil.setConfigItem('betaUpdate', false);
this.betaUpdateOption();
}
this.autoUpdateOption();
}
});
}
betaUpdateOption() {
betaUpdateOption(): void {
this.generateSettingOption({
$element: document.querySelector('#betaupdate-option .setting-control'),
value: ConfigUtil.getConfigItem('betaUpdate', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('betaUpdate');
ConfigUtil.setConfigItem('betaUpdate', newValue);
this.betaUpdateOption();
if (ConfigUtil.getConfigItem('autoUpdate')) {
ConfigUtil.setConfigItem('betaUpdate', newValue);
this.betaUpdateOption();
}
}
});
}
updateSilentOption() {
updateSilentOption(): void {
this.generateSettingOption({
$element: document.querySelector('#silent-option .setting-control'),
value: ConfigUtil.getConfigItem('silent', false),
@@ -239,12 +281,14 @@ class GeneralSection extends BaseSection {
const newValue = !ConfigUtil.getConfigItem('silent', true);
ConfigUtil.setConfigItem('silent', newValue);
this.updateSilentOption();
currentBrowserWindow.send('toggle-silent', newValue);
// TODO: TypeScript: currentWindow of type BrowserWindow doesn't
// have a .send() property per typescript.
(currentBrowserWindow as any).send('toggle-silent', newValue);
}
});
}
showDesktopNotification() {
showDesktopNotification(): void {
this.generateSettingOption({
$element: document.querySelector('#show-notification-option .setting-control'),
value: ConfigUtil.getConfigItem('showNotification', true),
@@ -256,7 +300,7 @@ class GeneralSection extends BaseSection {
});
}
updateSidebarOption() {
updateSidebarOption(): void {
this.generateSettingOption({
$element: document.querySelector('#sidebar-option .setting-control'),
value: ConfigUtil.getConfigItem('showSidebar', true),
@@ -269,7 +313,7 @@ class GeneralSection extends BaseSection {
});
}
updateStartAtLoginOption() {
updateStartAtLoginOption(): void {
this.generateSettingOption({
$element: document.querySelector('#startAtLogin-option .setting-control'),
value: ConfigUtil.getConfigItem('startAtLogin', false),
@@ -282,7 +326,19 @@ class GeneralSection extends BaseSection {
});
}
enableSpellchecker() {
updateQuitOnCloseOption(): void {
this.generateSettingOption({
$element: document.querySelector('#quitOnClose-option .setting-control'),
value: ConfigUtil.getConfigItem('quitOnClose', false),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('quitOnClose');
ConfigUtil.setConfigItem('quitOnClose', newValue);
this.updateQuitOnCloseOption();
}
});
}
enableSpellchecker(): void {
this.generateSettingOption({
$element: document.querySelector('#enable-spellchecker-option .setting-control'),
value: ConfigUtil.getConfigItem('enableSpellchecker', true),
@@ -294,7 +350,19 @@ class GeneralSection extends BaseSection {
});
}
clearAppDataDialog() {
enableErrorReporting(): void {
this.generateSettingOption({
$element: document.querySelector('#enable-error-reporting .setting-control'),
value: ConfigUtil.getConfigItem('errorReporting', true),
clickHandler: () => {
const newValue = !ConfigUtil.getConfigItem('errorReporting');
ConfigUtil.setConfigItem('errorReporting', newValue);
this.enableErrorReporting();
}
});
}
clearAppDataDialog(): void {
const clearAppDataMessage = 'By clicking proceed you will be removing all added accounts and preferences from Zulip. When the application restarts, it will be as if you are starting Zulip for the first time.';
const getAppPath = path.join(app.getPath('appData'), app.getName());
@@ -312,10 +380,9 @@ class GeneralSection extends BaseSection {
});
}
customCssDialog() {
const showDialogOptions = {
customCssDialog(): void {
const showDialogOptions: OpenDialogOptions = {
title: 'Select file',
defaultId: 1,
properties: ['openFile'],
filters: [{ name: 'CSS file', extensions: ['css'] }]
};
@@ -328,14 +395,14 @@ class GeneralSection extends BaseSection {
});
}
updateResetDataOption() {
updateResetDataOption(): void {
const resetDataButton = document.querySelector('#resetdata-option .reset-data-button');
resetDataButton.addEventListener('click', () => {
this.clearAppDataDialog();
});
}
minimizeOnStart() {
minimizeOnStart(): void {
this.generateSettingOption({
$element: document.querySelector('#start-minimize-option .setting-control'),
value: ConfigUtil.getConfigItem('startMinimized', false),
@@ -347,51 +414,50 @@ class GeneralSection extends BaseSection {
});
}
addCustomCSS() {
addCustomCSS(): void {
const customCSSButton = document.querySelector('#add-custom-css .custom-css-button');
customCSSButton.addEventListener('click', () => {
this.customCssDialog();
});
}
showCustomCSSPath() {
showCustomCSSPath(): void {
if (!ConfigUtil.getConfigItem('customCSS')) {
const cssPATH = document.getElementById('remove-custom-css');
const cssPATH: HTMLElement = document.querySelector('#remove-custom-css');
cssPATH.style.display = 'none';
}
}
removeCustomCSS() {
const removeCSSButton = document.getElementById('css-delete-action');
removeCustomCSS(): void {
const removeCSSButton = document.querySelector('#css-delete-action');
removeCSSButton.addEventListener('click', () => {
ConfigUtil.setConfigItem('customCSS');
ConfigUtil.setConfigItem('customCSS', "");
ipcRenderer.send('forward-message', 'hard-reload');
});
}
downloadFolderDialog() {
const showDialogOptions = {
downloadFolderDialog(): void {
const showDialogOptions: OpenDialogOptions = {
title: 'Select Download Location',
defaultId: 1,
properties: ['openDirectory']
};
dialog.showOpenDialog(showDialogOptions, selectedFolder => {
if (selectedFolder) {
ConfigUtil.setConfigItem('downloadsPath', selectedFolder[0]);
const downloadFolderPath = document.querySelector('.download-folder-path');
const downloadFolderPath: HTMLElement = document.querySelector('.download-folder-path');
downloadFolderPath.innerText = selectedFolder[0];
}
});
}
downloadFolder() {
downloadFolder(): void {
const downloadFolder = document.querySelector('#download-folder .download-folder-button');
downloadFolder.addEventListener('click', () => {
this.downloadFolderDialog();
});
}
showDownloadFolder() {
showDownloadFolder(): void {
this.generateSettingOption({
$element: document.querySelector('#show-download-folder .setting-control'),
value: ConfigUtil.getConfigItem('showDownloadFolder', false),
@@ -402,7 +468,6 @@ class GeneralSection extends BaseSection {
}
});
}
}
module.exports = GeneralSection;
export = GeneralSection;

View File

@@ -1,49 +1,50 @@
'use strict';
const BaseComponent = require(__dirname + '/../../components/base.js');
import BaseComponent = require('../../components/base');
import t = require('../../utils/translation-util');
class PreferenceNav extends BaseComponent {
constructor(props) {
// TODO: TypeScript - Here props should be object type
props: any;
navItems: string[];
$el: Element;
constructor(props: any) {
super();
this.props = props;
this.navItems = ['General', 'Network', 'AddServer', 'Organizations', 'Shortcuts'];
this.init();
}
template() {
template(): string {
let navItemsTemplate = '';
for (const navItem of this.navItems) {
navItemsTemplate += `<div class="nav" id="nav-${navItem}">${navItem}</div>`;
navItemsTemplate += `<div class="nav" id="nav-${navItem}">${t.__(navItem)}</div>`;
}
return `
<div>
<div id="settings-header">Settings</div>
<div id="settings-header">${t.__('Settings')}</div>
<div id="nav-container">${navItemsTemplate}</div>
</div>
`;
}
init() {
init(): void {
this.$el = this.generateNodeFromTemplate(this.template());
this.props.$root.appendChild(this.$el);
this.props.$root.append(this.$el);
this.registerListeners();
}
registerListeners() {
registerListeners(): void {
for (const navItem of this.navItems) {
const $item = document.getElementById(`nav-${navItem}`);
const $item = document.querySelector(`#nav-${navItem}`);
$item.addEventListener('click', () => {
this.props.onItemSelected(navItem);
});
}
}
select(navItemToSelect) {
select(navItemToSelect: string): void {
for (const navItem of this.navItems) {
if (navItem === navItemToSelect) {
this.activate(navItem);
@@ -53,15 +54,15 @@ class PreferenceNav extends BaseComponent {
}
}
activate(navItem) {
const $item = document.getElementById(`nav-${navItem}`);
activate(navItem: string): void {
const $item = document.querySelector(`#nav-${navItem}`);
$item.classList.add('active');
}
deactivate(navItem) {
const $item = document.getElementById(`nav-${navItem}`);
deactivate(navItem: string): void {
const $item = document.querySelector(`#nav-${navItem}`);
$item.classList.remove('active');
}
}
module.exports = PreferenceNav;
export = PreferenceNav;

View File

@@ -1,46 +1,53 @@
'use strict';
const {ipcRenderer} = require('electron');
import { ipcRenderer } from 'electron';
const BaseSection = require(__dirname + '/base-section.js');
const ConfigUtil = require(__dirname + '/../../utils/config-util.js');
import BaseSection = require('./base-section');
import ConfigUtil = require('../../utils/config-util');
import t = require('../../utils/translation-util');
class NetworkSection extends BaseSection {
constructor(props) {
// TODO: TypeScript - Here props should be object type
props: any;
$proxyPAC: HTMLInputElement;
$proxyRules: HTMLInputElement;
$proxyBypass: HTMLInputElement;
$proxySaveAction: Element;
$manualProxyBlock: Element;
constructor(props: any) {
super();
this.props = props;
}
template() {
template(): string {
return `
<div class="settings-pane">
<div class="title">Proxy</div>
<div class="title">${t.__('Proxy')}</div>
<div id="appearance-option-settings" class="settings-card">
<div class="setting-row" id="use-system-settings">
<div class="setting-description">Use system proxy settings (requires restart)</div>
<div class="setting-description">${t.__('Use system proxy settings (requires restart)')}</div>
<div class="setting-control"></div>
</div>
<div class="setting-row" id="use-manual-settings">
<div class="setting-description">Manual proxy configuration</div>
<div class="setting-description">${t.__('Manual proxy configuration')}</div>
<div class="setting-control"></div>
</div>
<div class="manual-proxy-block">
<div class="setting-row" id="proxy-pac-option">
<span class="setting-input-key">PAC script</span>
<span class="setting-input-key">PAC ${t.__('script')}</span>
<input class="setting-input-value" placeholder="e.g. foobar.com/pacfile.js"/>
</div>
<div class="setting-row" id="proxy-rules-option">
<span class="setting-input-key">Proxy rules</span>
<span class="setting-input-key">${t.__('Proxy rules')}</span>
<input class="setting-input-value" placeholder="e.g. http=foopy:80;ftp=foopy2"/>
</div>
<div class="setting-row" id="proxy-bypass-option">
<span class="setting-input-key">Proxy bypass rules</span>
<span class="setting-input-key">${t.__('Proxy bypass rules')}</span>
<input class="setting-input-value" placeholder="e.g. foobar.com"/>
</div>
<div class="setting-row">
<div class="action blue" id="proxy-save-action">
<i class="material-icons">check_box</i>
<span>Save</span>
<div class="action green" id="proxy-save-action">
<span>${t.__('Save')}</span>
</div>
</div>
</div>
@@ -49,12 +56,12 @@ class NetworkSection extends BaseSection {
`;
}
init() {
init(): void {
this.props.$root.innerHTML = this.template();
this.$proxyPAC = document.querySelector('#proxy-pac-option .setting-input-value');
this.$proxyRules = document.querySelector('#proxy-rules-option .setting-input-value');
this.$proxyBypass = document.querySelector('#proxy-bypass-option .setting-input-value');
this.$proxySaveAction = document.getElementById('proxy-save-action');
this.$proxySaveAction = document.querySelector('#proxy-save-action');
this.$manualProxyBlock = this.props.$root.querySelector('.manual-proxy-block');
this.initProxyOption();
@@ -71,14 +78,14 @@ class NetworkSection extends BaseSection {
});
}
initProxyOption() {
initProxyOption(): void {
const manualProxyEnabled = ConfigUtil.getConfigItem('useManualProxy', false);
this.toggleManualProxySettings(manualProxyEnabled);
this.updateProxyOption();
}
toggleManualProxySettings(option) {
toggleManualProxySettings(option: boolean): void {
if (option) {
this.$manualProxyBlock.classList.remove('hidden');
} else {
@@ -86,7 +93,7 @@ class NetworkSection extends BaseSection {
}
}
updateProxyOption() {
updateProxyOption(): void {
this.generateSettingOption({
$element: document.querySelector('#use-system-settings .setting-control'),
value: ConfigUtil.getConfigItem('useSystemProxy', false),
@@ -100,7 +107,7 @@ class NetworkSection extends BaseSection {
if (newValue === false) {
// Remove proxy system proxy settings
ConfigUtil.setConfigItem('proxyRules', '');
ipcRenderer.send('forward-message', 'reload-proxy', true);
ipcRenderer.send('forward-message', 'reload-proxy', false);
}
ConfigUtil.setConfigItem('useSystemProxy', newValue);
this.updateProxyOption();
@@ -126,4 +133,4 @@ class NetworkSection extends BaseSection {
}
}
module.exports = NetworkSection;
export = NetworkSection;

View File

@@ -1,89 +0,0 @@
'use strict';
const BaseComponent = require(__dirname + '/../../components/base.js');
const DomainUtil = require(__dirname + '/../../utils/domain-util.js');
const shell = require('electron').shell;
class NewServerForm extends BaseComponent {
constructor(props) {
super();
this.props = props;
}
template() {
return `
<div class="server-input-container">
<div class="title">Organization URL</div>
<div class="add-server-info-row">
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/>
</div>
<div class="server-center">
<div class="server-save-action">
<button id="connect">Connect</button>
</div>
</div>
<div class="server-center">
<div class="divider">
<hr class="left"/>OR<hr class="right" />
</div>
</div>
<div class="server-center">
<div class="server-save-action">
<button id="open-create-org-link">Create a new organization</button>
</div>
</div>
</div>
`;
}
init() {
this.initForm();
this.initActions();
}
initForm() {
this.$newServerForm = this.generateNodeFromTemplate(this.template());
this.$saveServerButton = this.$newServerForm.getElementsByClassName('server-save-action')[0];
this.props.$root.innerHTML = '';
this.props.$root.appendChild(this.$newServerForm);
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.setting-input-value')[0];
}
submitFormHandler() {
this.$saveServerButton.children[0].innerHTML = 'Connecting...';
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
DomainUtil.addDomain(serverConf).then(() => {
this.props.onChange(this.props.index);
});
}, errorMessage => {
this.$saveServerButton.children[0].innerHTML = 'Connect';
alert(errorMessage);
});
}
openCreateNewOrgExternalLink() {
const link = 'https://zulipchat.com/new/';
const externalCreateNewOrgEl = document.getElementById('open-create-org-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
initActions() {
this.$saveServerButton.addEventListener('click', () => {
this.submitFormHandler();
});
this.$newServerUrl.addEventListener('keypress', event => {
const EnterkeyCode = event.keyCode;
// Submit form when Enter key is pressed
if (EnterkeyCode === 13) {
this.submitFormHandler();
}
});
// open create new org link in default browser
this.openCreateNewOrgExternalLink();
}
}
module.exports = NewServerForm;

View File

@@ -0,0 +1,107 @@
'use strict';
import { shell, ipcRenderer } from 'electron';
import BaseComponent = require('../../components/base');
import DomainUtil = require('../../utils/domain-util');
import t = require('../../utils/translation-util');
class NewServerForm extends BaseComponent {
// TODO: TypeScript - Here props should be object type
props: any;
$newServerForm: Element;
$saveServerButton: HTMLButtonElement;
$newServerUrl: HTMLInputElement;
constructor(props: any) {
super();
this.props = props;
}
template(): string {
return `
<div class="server-input-container">
<div class="title">${t.__('Organization URL')}</div>
<div class="add-server-info-row">
<input class="setting-input-value" autofocus placeholder="your-organization.zulipchat.com or zulip.your-organization.com"/>
</div>
<div class="server-center">
<div class="server-save-action">
<button id="connect">${t.__('Connect')}</button>
</div>
</div>
<div class="server-center">
<div class="divider">
<hr class="left"/>${t.__('OR')}<hr class="right" />
</div>
</div>
<div class="server-center">
<div class="server-save-action">
<button id="open-create-org-link">${t.__('Create a new organization')}</button>
</div>
</div>
<div class="server-center">
<div class="server-network-option">
<span id="open-network-settings">${t.__('Network and Proxy Settings')}</span>
<i class="material-icons open-network-button">open_in_new</i>
</div>
</div>
</div>
`;
}
init(): void {
this.initForm();
this.initActions();
}
initForm(): void {
this.$newServerForm = this.generateNodeFromTemplate(this.template());
this.$saveServerButton = this.$newServerForm.querySelectorAll('.server-save-action')[0] as HTMLButtonElement;
this.props.$root.innerHTML = '';
this.props.$root.append(this.$newServerForm);
this.$newServerUrl = this.$newServerForm.querySelectorAll('input.setting-input-value')[0] as HTMLInputElement;
}
submitFormHandler(): void {
this.$saveServerButton.children[0].innerHTML = 'Connecting...';
DomainUtil.checkDomain(this.$newServerUrl.value).then(serverConf => {
DomainUtil.addDomain(serverConf).then(() => {
this.props.onChange(this.props.index);
});
}, errorMessage => {
this.$saveServerButton.children[0].innerHTML = 'Connect';
alert(errorMessage);
});
}
openCreateNewOrgExternalLink(): void {
const link = 'https://zulipchat.com/new/';
const externalCreateNewOrgEl = document.querySelector('#open-create-org-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
networkSettingsLink(): void {
const networkSettingsId = document.querySelectorAll('.server-network-option')[0];
networkSettingsId.addEventListener('click', () => ipcRenderer.send('forward-message', 'open-network-settings'));
}
initActions(): void {
this.$saveServerButton.addEventListener('click', () => {
this.submitFormHandler();
});
this.$newServerUrl.addEventListener('keypress', event => {
const EnterkeyCode = event.keyCode;
// Submit form when Enter key is pressed
if (EnterkeyCode === 13) {
this.submitFormHandler();
}
});
// open create new org link in default browser
this.openCreateNewOrgExternalLink();
this.networkSettingsLink();
}
}
export = NewServerForm;

View File

@@ -1,24 +1,29 @@
'use strict';
const BaseComponent = require(__dirname + '/js/components/base.js');
const { ipcRenderer } = require('electron');
import { ipcRenderer } from 'electron';
const Nav = require(__dirname + '/js/pages/preference/nav.js');
const ServersSection = require(__dirname + '/js/pages/preference/servers-section.js');
const GeneralSection = require(__dirname + '/js/pages/preference/general-section.js');
const NetworkSection = require(__dirname + '/js/pages/preference/network-section.js');
const ConnectedOrgSection = require(__dirname + '/js/pages/preference/connected-org-section.js');
const ShortcutsSection = require(__dirname + '/js/pages/preference/shortcuts-section.js');
import BaseComponent = require('../../components/base');
import Nav = require('./nav');
import ServersSection = require('./servers-section');
import GeneralSection = require('./general-section');
import NetworkSection = require('./network-section');
import ConnectedOrgSection = require('./connected-org-section');
import ShortcutsSection = require('./shortcuts-section');
type Section = ServersSection | GeneralSection | NetworkSection | ConnectedOrgSection | ShortcutsSection;
class PreferenceView extends BaseComponent {
$sidebarContainer: Element;
$settingsContainer: Element;
nav: Nav;
section: Section;
constructor() {
super();
this.$sidebarContainer = document.getElementById('sidebar');
this.$settingsContainer = document.getElementById('settings-container');
this.$sidebarContainer = document.querySelector('#sidebar');
this.$settingsContainer = document.querySelector('#settings-container');
}
init() {
init(): void {
this.nav = new Nav({
$root: this.$sidebarContainer,
onItemSelected: this.handleNavigation.bind(this)
@@ -28,7 +33,7 @@ class PreferenceView extends BaseComponent {
this.registerIpcs();
}
setDefaultView() {
setDefaultView(): void {
let nav = 'General';
const hasTag = window.location.hash;
if (hasTag) {
@@ -37,7 +42,7 @@ class PreferenceView extends BaseComponent {
this.handleNavigation(nav);
}
handleNavigation(navItem) {
handleNavigation(navItem: string): void {
this.nav.select(navItem);
switch (navItem) {
case 'AddServer': {
@@ -77,28 +82,32 @@ class PreferenceView extends BaseComponent {
}
// Handle toggling and reflect changes in preference page
handleToggle(elementName, state) {
handleToggle(elementName: string, state: boolean): void {
const inputSelector = `#${elementName} .action .switch input`;
const input = document.querySelector(inputSelector);
const input: HTMLInputElement = document.querySelector(inputSelector);
if (input) {
input.checked = state;
}
}
registerIpcs() {
ipcRenderer.on('switch-settings-nav', (event, navItem) => {
registerIpcs(): void {
ipcRenderer.on('switch-settings-nav', (_event: Event, navItem: string) => {
this.handleNavigation(navItem);
});
ipcRenderer.on('toggle-sidebar', (event, state) => {
ipcRenderer.on('toggle-sidebar-setting', (_event: Event, state: boolean) => {
this.handleToggle('sidebar-option', state);
});
ipcRenderer.on('toggletray', (event, state) => {
ipcRenderer.on('toggle-menubar-setting', (_event: Event, state: boolean) => {
this.handleToggle('menubar-option', state);
});
ipcRenderer.on('toggletray', (_event: Event, state: boolean) => {
this.handleToggle('tray-option', state);
});
ipcRenderer.on('toggle-dnd', (event, state, newSettings) => {
ipcRenderer.on('toggle-dnd', (_event: Event, _state: boolean, newSettings: any) => {
this.handleToggle('show-notification-option', newSettings.showNotification);
this.handleToggle('silent-option', newSettings.silent);
@@ -109,7 +118,9 @@ class PreferenceView extends BaseComponent {
}
}
window.onload = () => {
window.addEventListener('load', () => {
const preferenceView = new PreferenceView();
preferenceView.init();
};
});
export = PreferenceView;

View File

@@ -1,17 +1,28 @@
'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');
import { remote, ipcRenderer } from 'electron';
import BaseComponent = require('../../components/base');
import DomainUtil = require('../../utils/domain-util');
import Messages = require('./../../../../resources/messages');
import t = require('../../utils/translation-util');
const { dialog } = remote;
class ServerInfoForm extends BaseComponent {
constructor(props) {
// TODO: TypeScript - Here props should be object type
props: any;
$serverInfoForm: Element;
$serverInfoAlias: Element;
$serverIcon: Element;
$deleteServerButton: Element;
$openServerButton: Element;
constructor(props: any) {
super();
this.props = props;
}
template() {
template(): string {
return `
<div class="settings-card">
<div class="server-info-left">
@@ -27,7 +38,7 @@ class ServerInfoForm extends BaseComponent {
</div>
<div class="server-info-row">
<div class="action red server-delete-action">
<span>Disconnect</span>
<span>${t.__('Disconnect')}</span>
</div>
</div>
</div>
@@ -35,31 +46,35 @@ class ServerInfoForm extends BaseComponent {
`;
}
init() {
init(): void {
this.initForm();
this.initActions();
}
initForm() {
initForm(): void {
this.$serverInfoForm = this.generateNodeFromTemplate(this.template());
this.$serverInfoAlias = this.$serverInfoForm.getElementsByClassName('server-info-alias')[0];
this.$serverIcon = this.$serverInfoForm.getElementsByClassName('server-info-icon')[0];
this.$deleteServerButton = this.$serverInfoForm.getElementsByClassName('server-delete-action')[0];
this.$openServerButton = this.$serverInfoForm.getElementsByClassName('open-tab-button')[0];
this.props.$root.appendChild(this.$serverInfoForm);
this.$serverInfoAlias = this.$serverInfoForm.querySelectorAll('.server-info-alias')[0];
this.$serverIcon = this.$serverInfoForm.querySelectorAll('.server-info-icon')[0];
this.$deleteServerButton = this.$serverInfoForm.querySelectorAll('.server-delete-action')[0];
this.$openServerButton = this.$serverInfoForm.querySelectorAll('.open-tab-button')[0];
this.props.$root.append(this.$serverInfoForm);
}
initActions() {
initActions(): void {
this.$deleteServerButton.addEventListener('click', () => {
dialog.showMessageBox({
type: 'warning',
buttons: ['YES', 'NO'],
buttons: [t.__('YES'), t.__('NO')],
defaultId: 0,
message: 'Are you sure you want to disconnect this organization?'
message: t.__('Are you sure you want to disconnect this organization?')
}, response => {
if (response === 0) {
DomainUtil.removeDomain(this.props.index);
this.props.onChange(this.props.index);
if (DomainUtil.removeDomain(this.props.index)) {
ipcRenderer.send('reload-full-app');
} else {
const { title, content } = Messages.orgRemovalError(DomainUtil.getDomain(this.props.index).url);
dialog.showErrorBox(title, content);
}
}
});
});
@@ -76,7 +91,6 @@ class ServerInfoForm extends BaseComponent {
ipcRenderer.send('forward-message', 'switch-server-tab', this.props.index);
});
}
}
module.exports = ServerInfoForm;
export = ServerInfoForm;

View File

@@ -1,20 +1,24 @@
'use strict';
const BaseSection = require(__dirname + '/base-section.js');
const NewServerForm = require(__dirname + '/new-server-form.js');
import BaseSection = require('./base-section');
import NewServerForm = require('./new-server-form');
import t = require('../../utils/translation-util');
class ServersSection extends BaseSection {
constructor(props) {
// TODO: TypeScript - Here props should be object type
props: any;
$newServerContainer: Element;
constructor(props: any) {
super();
this.props = props;
}
template() {
template(): string {
return `
<div class="add-server-modal">
<div class="modal-container">
<div class="settings-pane" id="server-settings-pane">
<div class="page-title">Add a Zulip organization</div>
<div class="page-title">${t.__('Add a Zulip organization')}</div>
<div id="new-server-container"></div>
</div>
</div>
@@ -22,20 +26,20 @@ class ServersSection extends BaseSection {
`;
}
init() {
init(): void {
this.initServers();
}
initServers() {
initServers(): void {
this.props.$root.innerHTML = '';
this.props.$root.innerHTML = this.template();
this.$newServerContainer = document.getElementById('new-server-container');
this.$newServerContainer = document.querySelector('#new-server-container');
this.initNewServerForm();
}
initNewServerForm() {
initNewServerForm(): void {
new NewServerForm({
$root: this.$newServerContainer,
onChange: this.reloadApp
@@ -43,4 +47,4 @@ class ServersSection extends BaseSection {
}
}
module.exports = ServersSection;
export = ServersSection;

View File

@@ -1,322 +0,0 @@
'use strict';
const BaseSection = require(__dirname + '/base-section.js');
const shell = require('electron').shell;
class ShortcutsSection extends BaseSection {
constructor(props) {
super();
this.props = props;
}
templateMac() {
const userOSKey = '⌘';
return `
<div class="settings-pane">
<div class="title">Application Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>,</kbd></td>
<td>Settings</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>K</kbd></td>
<td>Keyboard Shortcuts</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>Toggle Do Not Disturb</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>D</kbd></td>
<td>Reset App Settings</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>L</kbd></td>
<td>Log Out</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
<td>Hide Zulip</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
<td>Hide Others</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>Q</kbd></td>
<td>Quit Zulip</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">Edit Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
<td>Undo</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
<td>Redo</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>X</kbd></td>
<td>Cut</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>C</kbd></td>
<td>Copy</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
<td>Paste</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
<td>Paste and Match Style</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>A</kbd></td>
<td>Select All</td>
</tr>
<tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>Space</kbd></td>
<td>Emoji & Symbols</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">View Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
<td>Reload</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
<td>Hard Reload</td>
</tr>
<tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>F</kbd></td>
<td>Enter Full Screen</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>+</kbd></td>
<td>Zoom In</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>-</kbd></td>
<td>Zoom Out</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>0</kbd></td>
<td>Actual Size</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td>Toggle Sidebar</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>I</kbd></td>
<td>Toggle DevTools for Zulip App</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>U</kbd></td>
<td>Toggle DevTools for Active Tab</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">History Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>←</kbd></td>
<td>Back</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>→</kbd></td>
<td>Forward</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">Window Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>M</kbd></td>
<td>Minimize</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>W</kbd></td>
<td>Close</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="settings-card tip"><b><i class="material-icons md-14">settings</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link">keyboard shortcuts</span>.</div>
</div>
`;
}
templateWinLin() {
const userOSKey = 'Ctrl';
return `
<div class="settings-pane">
<div class="title">Application Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>,</kbd></td>
<td>Settings</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></td>
<td>Keyboard Shortcuts</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>Toggle Do Not Disturb</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>L</kbd></td>
<td>Log Out</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Q</kbd></td>
<td>Quit Zulip</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">Edit Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Z</kbd></td>
<td>Undo</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Y</kbd></td>
<td>Redo</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>X</kbd></td>
<td>Cut</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>C</kbd></td>
<td>Copy</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>V</kbd></td>
<td>Paste</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd></td>
<td>Paste and Match Style</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>A</kbd></td>
<td>Select All</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">View Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>R</kbd></td>
<td>Reload</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>R</kbd></td>
<td>Hard Reload</td>
</tr>
<tr>
<td><kbd>F11</kbd></td>
<td>Toggle Full Screen</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>=</kbd></td>
<td>Zoom In</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>-</kbd></td>
<td>Zoom Out</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>0</kbd></td>
<td>Actual Size</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td>Toggle Sidebar</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd></td>
<td>Toggle DevTools for Zulip App</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd></td>
<td>Toggle DevTools for Active Tab</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">History Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>Alt</kbd> + <kbd>←</kbd></td>
<td>Back</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>→</kbd></td>
<td>Forward</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">Window Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>M</kbd></td>
<td>Minimize</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>W</kbd></td>
<td>Close</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="tip"><b><i class="material-icons md-14">lightbulb_outline</i>Tip: </b>These desktop app shortcuts extend the Zulip webapp's <span id="open-hotkeys-link">keyboard shortcuts</span>.</div>
</div>
`;
}
openHotkeysExternalLink() {
const link = 'https://zulipchat.com/help/keyboard-shortcuts';
const externalCreateNewOrgEl = document.getElementById('open-hotkeys-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
init() {
this.props.$root.innerHTML = (process.platform === 'darwin') ?
this.templateMac() : this.templateWinLin();
this.openHotkeysExternalLink();
}
}
module.exports = ShortcutsSection;

View File

@@ -0,0 +1,345 @@
'use strict';
import { shell } from 'electron';
import BaseSection = require('./base-section');
import t = require('../../utils/translation-util');
class ShortcutsSection extends BaseSection {
// TODO: TypeScript - Here props should be object type
props: any;
constructor(props: any) {
super();
this.props = props;
}
// TODO - Deduplicate templateMac and templateWinLin functions. In theory
// they both should be the same the only thing different should be the userOSKey
// variable but there seems to be inconsistences between both function, one has more
// lines though one may just be using more new lines and other thing is the use of +.
templateMac(): string {
const userOSKey = '⌘';
return `
<div class="settings-pane">
<div class="settings-card tip"><p><b><i class="material-icons md-14">settings</i>${t.__('Tip')}: </b>${t.__('These desktop app shortcuts extend the Zulip webapp\'s')} <span id="open-hotkeys-link"> ${t.__('keyboard shortcuts')}</span>.</p></div>
<div class="title">${t.__('Application Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>,</kbd></td>
<td>${t.__('Settings')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>K</kbd></td>
<td>${t.__('Keyboard Shortcuts')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>${t.__('Toggle Do Not Disturb')}</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>D</kbd></td>
<td>${t.__('Reset App Settings')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>L</kbd></td>
<td>${t.__('Log Out')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
<td>${t.__('Hide Zulip')}</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>H</kbd></td>
<td>${t.__('Hide Others')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>Q</kbd></td>
<td>${t.__('Quit Zulip')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('Edit Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
<td>${t.__('Undo')}</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>Z</kbd></td>
<td>${t.__('Redo')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>X</kbd></td>
<td>${t.__('Cut')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>C</kbd></td>
<td>${t.__('Copy')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
<td>${t.__('Paste')}</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>V</kbd></td>
<td>${t.__('Paste and Match Style')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>A</kbd></td>
<td>${t.__('Select All')}</td>
</tr>
<tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>Space</kbd></td>
<td>${t.__('Emoji & Symbols')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('View Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
<td>${t.__('Reload')}</td>
</tr>
<tr>
<td><kbd>Shift</kbd><kbd>${userOSKey}</kbd><kbd>R</kbd></td>
<td>${t.__('Hard Reload')}</td>
</tr>
<tr>
<td><kbd>Control</kbd><kbd>${userOSKey}</kbd><kbd>F</kbd></td>
<td>${t.__('Enter Full Screen')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>+</kbd></td>
<td>${t.__('Zoom In')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>-</kbd></td>
<td>${t.__('Zoom Out')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>0</kbd></td>
<td>${t.__('Actual Size')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td>${t.__('Toggle Sidebar')}</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>I</kbd></td>
<td>${t.__('Toggle DevTools for Zulip App')}</td>
</tr>
<tr>
<td><kbd>Option</kbd><kbd>${userOSKey}</kbd><kbd>U</kbd></td>
<td>${t.__('Toggle DevTools for Active Tab')}</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Tab</kbd></td>
<td>${t.__('Switch to Next Organization')}</td>
</tr>
<tr>
<td><kbd>Ctrl</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
<td>${t.__('Switch to Previous Organization')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('History Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>←</kbd></td>
<td>${t.__('Back')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>→</kbd></td>
<td>${t.__('Forward')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">Window Shortcuts</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>M</kbd></td>
<td>${t.__('Minimize')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd><kbd>W</kbd></td>
<td>${t.__('Close')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
</div>
`;
}
templateWinLin(): string {
const userOSKey = 'Ctrl';
return `
<div class="settings-pane">
<div class="settings-card tip"><p><b><i class="material-icons md-14">settings</i>${t.__('Tip')}: </b>${t.__('These desktop app shortcuts extend the Zulip webapp\'s')} <span id="open-hotkeys-link"> ${t.__('keyboard shortcuts')}</span>.</p></div>
<div class="title">${t.__('Application Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>,</kbd></td>
<td>${t.__('Settings')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>K</kbd></td>
<td>${t.__('Keyboard Shortcuts')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>M</kbd></td>
<td>${t.__('Toggle Do Not Disturb')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>L</kbd></td>
<td>${t.__('Log Out')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Q</kbd></td>
<td>${t.__('Quit Zulip')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('Edit Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Z</kbd></td>
<td>${t.__('Undo')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Y</kbd></td>
<td>${t.__('Redo')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>X</kbd></td>
<td>${t.__('Cut')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>C</kbd></td>
<td>${t.__('Copy')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>V</kbd></td>
<td>${t.__('Paste')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>V</kbd></td>
<td>${t.__('Paste and Match Style')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>A</kbd></td>
<td>${t.__('Select All')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('View Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>R</kbd></td>
<td>${t.__('Reload')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>R</kbd></td>
<td>${t.__('Hard Reload')}</td>
</tr>
<tr>
<td><kbd>F11</kbd></td>
<td>${t.__('Toggle Full Screen')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>=</kbd></td>
<td>${t.__('Zoom In')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>-</kbd></td>
<td>${t.__('Zoom Out')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>0</kbd></td>
<td>${t.__('Actual Size')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>S</kbd></td>
<td>${t.__('Toggle Sidebar')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>I</kbd></td>
<td>${t.__('Toggle DevTools for Zulip App')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>U</kbd></td>
<td>${t.__('Toggle DevTools for Active Tab')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Tab</kbd></td>
<td>${t.__('Switch to Next Organization')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>Shift</kbd> + <kbd>Tab</kbd></td>
<td>${t.__('Switch to Previous Organization')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('History Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>Alt</kbd> + <kbd>←</kbd></td>
<td>${t.__('Back')}</td>
</tr>
<tr>
<td><kbd>Alt</kbd> + <kbd>→</kbd></td>
<td>${t.__('Forward')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
<div class="title">${t.__('Window Shortcuts')}</div>
<div class="settings-card">
<table>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>M</kbd></td>
<td>${t.__('Minimize')}</td>
</tr>
<tr>
<td><kbd>${userOSKey}</kbd> + <kbd>W</kbd></td>
<td>${t.__('Close')}</td>
</tr>
</table>
<div class="setting-control"></div>
</div>
</div>
`;
}
openHotkeysExternalLink(): void {
const link = 'https://zulipchat.com/help/keyboard-shortcuts';
const externalCreateNewOrgEl = document.querySelector('#open-hotkeys-link');
externalCreateNewOrgEl.addEventListener('click', () => {
shell.openExternal(link);
});
}
init(): void {
this.props.$root.innerHTML = (process.platform === 'darwin') ?
this.templateMac() : this.templateWinLin();
this.openHotkeysExternalLink();
}
}
export = ShortcutsSection;

View File

@@ -1,98 +0,0 @@
'use strict';
const { ipcRenderer, shell } = require('electron');
const SetupSpellChecker = require('./spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const LinkUtil = require(__dirname + '/utils/link-util.js');
const params = require(__dirname + '/utils/params-util.js');
// eslint-disable-next-line import/no-unassigned-import
require('./notification');
// Prevent drag and drop event in main process which prevents remote code executaion
require(__dirname + '/shared/preventdrag.js');
// eslint-disable-next-line camelcase
window.electron_bridge = require('./electron-bridge');
const logout = () => {
// Create the menu for the below
document.querySelector('.dropdown-toggle').click();
const nodes = document.querySelectorAll('.dropdown-menu li:last-child a');
nodes[nodes.length - 1].click();
};
const shortcut = () => {
// Create the menu for the below
const node = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
// Additional check
if (node.text.trim().toLowerCase() === 'keyboard shortcuts (?)') {
node.click();
} else {
// Atleast click the dropdown
document.querySelector('.dropdown-toggle').click();
}
};
process.once('loaded', () => {
global.logout = logout;
global.shortcut = shortcut;
});
// To prevent failing this script on linux we need to load it after the document loaded
document.addEventListener('DOMContentLoaded', () => {
if (params.isPageParams()) {
// Get the default language of the server
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef, camelcase
if (serverLanguage) {
// Set spellcheker language
ConfigUtil.setConfigItem('spellcheckerLanguage', serverLanguage);
// Init spellchecker
SetupSpellChecker.init();
}
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
if (getRestartButton) {
getRestartButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
// Open image attachment link in the lightbox instead of opening in the default browser
const { $, lightbox } = window;
$('#main_div').on('click', '.message_content p a', function (e) {
const url = $(this).attr('href');
if (LinkUtil.isImage(url)) {
const $img = $(this).parent().siblings('.message_inline_image').find('img');
// prevent the image link from opening in a new page.
e.preventDefault();
// prevent the message compose dialog from happening.
e.stopPropagation();
// Open image in the default browser if image preview is unavailable
if (!$img[0]) {
shell.openExternal(window.location.origin + url);
}
// Open image in lightbox
lightbox.open($img);
}
});
}
});
// Clean up spellchecker events after you navigate away from this page;
// otherwise, you may experience errors
window.addEventListener('beforeunload', () => {
SetupSpellChecker.unsubscribeSpellChecker();
});
// electron's globalShortcut can cause unexpected results
// so adding the reload shortcut in the old-school way
document.addEventListener('keydown', event => {
if (event.code === 'F5') {
ipcRenderer.send('forward-message', 'hard-reload');
}
});

165
app/renderer/js/preload.ts Normal file
View File

@@ -0,0 +1,165 @@
// we have and will have some non camelcase stuff
// while working with zulip and electron bridge
// so turning the rule off for the whole file.
/* eslint-disable @typescript-eslint/camelcase */
'use strict';
import { ipcRenderer, shell } from 'electron';
import SetupSpellChecker from './spellchecker';
import isDev = require('electron-is-dev');
import LinkUtil = require('./utils/link-util');
import params = require('./utils/params-util');
import NetworkError = require('./pages/network');
interface PatchedGlobal extends NodeJS.Global {
logout: () => void;
shortcut: () => void;
showNotificationSettings: () => void;
}
const globalPatched = global as PatchedGlobal;
// eslint-disable-next-line import/no-unassigned-import
require('./notification');
// Prevent drag and drop event in main process which prevents remote code executaion
require(__dirname + '/shared/preventdrag.js');
declare let window: ZulipWebWindow;
window.electron_bridge = require('./electron-bridge');
const logout = (): void => {
// Create the menu for the below
const dropdown: HTMLElement = document.querySelector('.dropdown-toggle');
dropdown.click();
const nodes: NodeListOf<HTMLElement> = document.querySelectorAll('.dropdown-menu li:last-child a');
nodes[nodes.length - 1].click();
};
const shortcut = (): void => {
// Create the menu for the below
const node: HTMLElement = document.querySelector('a[data-overlay-trigger=keyboard-shortcuts]');
// Additional check
if (node.textContent.trim().toLowerCase() === 'keyboard shortcuts (?)') {
node.click();
} else {
// Atleast click the dropdown
const dropdown: HTMLElement = document.querySelector('.dropdown-toggle');
dropdown.click();
}
};
const showNotificationSettings = (): void => {
// Create the menu for the below
const dropdown: HTMLElement = document.querySelector('.dropdown-toggle');
dropdown.click();
const nodes: NodeListOf<HTMLElement> = document.querySelectorAll('.dropdown-menu li a');
nodes[2].click();
const notificationItem: NodeListOf<HTMLElement> = document.querySelectorAll('.normal-settings-list li div');
// wait until the notification dom element shows up
setTimeout(() => {
notificationItem[2].click();
}, 100);
};
process.once('loaded', (): void => {
globalPatched.logout = logout;
globalPatched.shortcut = shortcut;
globalPatched.showNotificationSettings = showNotificationSettings;
});
// To prevent failing this script on linux we need to load it after the document loaded
document.addEventListener('DOMContentLoaded', (): void => {
if (params.isPageParams()) {
// Get the default language of the server
const serverLanguage = page_params.default_language; // eslint-disable-line no-undef
if (serverLanguage) {
// Init spellchecker
SetupSpellChecker.init(serverLanguage);
}
// redirect users to network troubleshooting page
const getRestartButton = document.querySelector('.restart_get_events_button');
if (getRestartButton) {
getRestartButton.addEventListener('click', () => {
ipcRenderer.send('forward-message', 'reload-viewer');
});
}
// Open image attachment link in the lightbox instead of opening in the default browser
const { $, lightbox } = window;
$('#main_div').on('click', '.message_content p a', function (this: HTMLElement, e: Event) {
const url = $(this).attr('href');
if (LinkUtil.isImage(url)) {
const $img = $(this).parent().siblings('.message_inline_image').find('img');
// prevent the image link from opening in a new page.
e.preventDefault();
// prevent the message compose dialog from happening.
e.stopPropagation();
// Open image in the default browser if image preview is unavailable
if (!$img[0]) {
shell.openExternal(window.location.origin + url);
}
// Open image in lightbox
lightbox.open($img);
}
});
}
});
// Clean up spellchecker events after you navigate away from this page;
// otherwise, you may experience errors
window.addEventListener('beforeunload', (): void => {
SetupSpellChecker.unsubscribeSpellChecker();
});
window.addEventListener('load', (event: any): void => {
if (!event.target.URL.includes('app/renderer/network.html')) {
return;
}
const $reconnectButton = document.querySelector('#reconnect');
const $settingsButton = document.querySelector('#settings');
NetworkError.init($reconnectButton, $settingsButton);
});
// electron's globalShortcut can cause unexpected results
// so adding the reload shortcut in the old-school way
// Zoom from numpad keys is not supported by electron, so adding it through listeners.
document.addEventListener('keydown', event => {
const cmdOrCtrl = event.ctrlKey || event.metaKey;
if (event.code === 'F5') {
ipcRenderer.send('forward-message', 'hard-reload');
} else if (cmdOrCtrl && (event.code === 'NumpadAdd' || event.code === 'Equal')) {
ipcRenderer.send('forward-message', 'zoomIn');
} else if (cmdOrCtrl && event.code === 'NumpadSubtract') {
ipcRenderer.send('forward-message', 'zoomOut');
} else if (cmdOrCtrl && event.code === 'Numpad0') {
ipcRenderer.send('forward-message', 'zoomActualSize');
}
});
// Set user as active and update the time of last activity
ipcRenderer.on('set-active', () => {
if (isDev) {
console.log('active');
}
window.electron_bridge.idle_on_system = false;
window.electron_bridge.last_active_on_system = Date.now();
});
// Set user as idle and time of last activity is left unchanged
ipcRenderer.on('set-idle', () => {
if (isDev) {
console.log('idle');
}
window.electron_bridge.idle_on_system = true;
});

View File

@@ -5,7 +5,7 @@
// It doesn't affect the compose box so that users can still
// use drag and drop event to share files etc
const preventDragAndDrop = () => {
const preventDragAndDrop = (): void => {
const preventEvents = ['dragover', 'drop'];
preventEvents.forEach(dragEvents => {
document.addEventListener(dragEvents, event => {

View File

@@ -1,62 +0,0 @@
'use strict';
const { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } = require('electron-spellchecker');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
const Logger = require(__dirname + '/utils/logger-util.js');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class SetupSpellChecker {
init() {
if (ConfigUtil.getConfigItem('enableSpellchecker')) {
this.enableSpellChecker();
}
this.enableContextMenu();
}
enableSpellChecker() {
try {
this.SpellCheckHandler = new SpellCheckHandler();
} catch (err) {
logger.error(err);
}
}
enableContextMenu() {
if (this.SpellCheckHandler) {
this.SpellCheckHandler.attachToInput();
const userLanguage = ConfigUtil.getConfigItem('spellcheckerLanguage');
// eslint-disable-next-line no-unused-expressions
process.platform === 'darwin' ?
// On macOS, spellchecker fails to auto-detect the lanugage user is typing in
// that's why we need to mention it explicitly
this.SpellCheckHandler.switchLanguage(userLanguage) :
// On Linux and Windows, spellchecker can automatically detects the language the user is typing in
// and silently switches on the fly; thus we can start off as US English
this.SpellCheckHandler.switchLanguage('en-US');
}
const contextMenuBuilder = new ContextMenuBuilder(this.SpellCheckHandler);
this.contextMenuListener = new ContextMenuListener(info => {
contextMenuBuilder.showPopupMenu(info);
});
}
unsubscribeSpellChecker() {
// eslint-disable-next-line no-undef
if (this.SpellCheckHandler) {
this.SpellCheckHandler.unsubscribe();
}
if (this.contextMenuListener) {
this.contextMenuListener.unsubscribe();
}
}
}
module.exports = new SetupSpellChecker();

View File

@@ -0,0 +1,56 @@
'use strict';
import { SpellCheckHandler, ContextMenuListener, ContextMenuBuilder } from 'electron-spellchecker';
import ConfigUtil = require('./utils/config-util');
import Logger = require('./utils/logger-util');
const logger = new Logger({
file: 'errors.log',
timestamp: true
});
class SetupSpellChecker {
SpellCheckHandler: typeof SpellCheckHandler;
contextMenuListener: typeof ContextMenuListener;
init(serverLanguage: string): void {
if (ConfigUtil.getConfigItem('enableSpellchecker')) {
this.enableSpellChecker();
}
this.enableContextMenu(serverLanguage);
}
enableSpellChecker(): void {
try {
this.SpellCheckHandler = new SpellCheckHandler();
} catch (err) {
logger.error(err);
}
}
enableContextMenu(serverLanguage: string): void {
if (this.SpellCheckHandler) {
this.SpellCheckHandler.attachToInput();
this.SpellCheckHandler.switchLanguage(serverLanguage);
this.SpellCheckHandler.currentSpellcheckerChanged.subscribe(() => {
this.SpellCheckHandler.switchLanguage(this.SpellCheckHandler.currentSpellcheckerLanguage);
});
}
const contextMenuBuilder = new ContextMenuBuilder(this.SpellCheckHandler);
this.contextMenuListener = new ContextMenuListener((info: object) => {
contextMenuBuilder.showPopupMenu(info);
});
}
unsubscribeSpellChecker(): void {
if (this.SpellCheckHandler) {
this.SpellCheckHandler.unsubscribe();
}
if (this.contextMenuListener) {
this.contextMenuListener.unsubscribe();
}
}
}
export = new SetupSpellChecker();

View File

@@ -1,17 +1,15 @@
'use strict';
const path = require('path');
import { ipcRenderer, remote, WebviewTag, NativeImage } from 'electron';
const electron = require('electron');
const {ipcRenderer, remote} = electron;
const {Tray, Menu, nativeImage, BrowserWindow} = remote;
import path = require('path');
import ConfigUtil = require('./utils/config-util.js');
const { Tray, Menu, nativeImage, BrowserWindow } = remote;
const APP_ICON = path.join(__dirname, '../../resources/tray', 'tray');
const ConfigUtil = require(__dirname + '/utils/config-util.js');
declare let window: ZulipWebWindow;
const iconPath = () => {
const iconPath = (): string => {
if (process.platform === 'linux') {
return APP_ICON + 'linux.png';
}
@@ -20,7 +18,7 @@ const iconPath = () => {
let unread = 0;
const trayIconSize = () => {
const trayIconSize = (): number => {
switch (process.platform) {
case 'darwin':
return 20;
@@ -45,7 +43,7 @@ const config = {
thick: process.platform === 'win32'
};
const renderCanvas = function (arg) {
const renderCanvas = function (arg: number): Promise<HTMLCanvasElement> {
config.unreadCount = arg;
return new Promise(resolve => {
@@ -79,10 +77,10 @@ const renderCanvas = function (arg) {
ctx.fillText('99+', CENTER, CENTER + (SIZE * 0.15));
} else if (config.unreadCount < 10) {
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.5}px Helvetica`;
ctx.fillText(config.unreadCount, CENTER, CENTER + (SIZE * 0.20));
ctx.fillText(String(config.unreadCount), CENTER, CENTER + (SIZE * 0.20));
} else {
ctx.font = `${config.thick ? 'bold ' : ''}${SIZE * 0.5}px Helvetica`;
ctx.fillText(config.unreadCount, CENTER, CENTER + (SIZE * 0.15));
ctx.fillText(String(config.unreadCount), CENTER, CENTER + (SIZE * 0.15));
}
resolve(canvas);
@@ -94,16 +92,23 @@ const renderCanvas = function (arg) {
* @param arg: Unread count
* @return the native image
*/
const renderNativeImage = function (arg) {
const renderNativeImage = function (arg: number): Promise<NativeImage> {
return Promise.resolve()
.then(() => renderCanvas(arg))
.then(canvas => {
const pngData = nativeImage.createFromDataURL(canvas.toDataURL('image/png')).toPNG();
return Promise.resolve(nativeImage.createFromBuffer(pngData, config.pixelRatio));
// TODO: Fix the function to correctly use Promise correctly.
// the Promise.resolve().then(...) above is useless we should
// start with renderCanvas(arg).then
// eslint-disable-next-line promise/no-return-wrap
return Promise.resolve(nativeImage.createFromBuffer(pngData, {
scaleFactor: config.pixelRatio
}));
});
};
function sendAction(action) {
function sendAction(action: string): void {
const win = BrowserWindow.getAllWindows()[0];
if (process.platform === 'darwin') {
@@ -113,57 +118,43 @@ function sendAction(action) {
win.webContents.send(action);
}
const createTray = function () {
const createTray = function (): void {
window.tray = new Tray(iconPath());
const contextMenu = Menu.buildFromTemplate([{
label: 'About',
click() {
// We need to focus the main window first
ipcRenderer.send('focus-app');
sendAction('open-about');
const contextMenu = Menu.buildFromTemplate([
{
label: 'Zulip',
click() {
ipcRenderer.send('focus-app');
}
},
{
label: 'Settings',
click() {
ipcRenderer.send('focus-app');
sendAction('open-settings');
}
},
{
type: 'separator'
},
{
label: 'Quit',
click() {
ipcRenderer.send('quit-app');
}
}
},
{
type: 'separator'
},
{
label: 'Focus',
click() {
ipcRenderer.send('focus-app');
}
},
{
type: 'separator'
},
{
label: 'Settings',
click() {
ipcRenderer.send('focus-app');
sendAction('open-settings');
}
},
{
type: 'separator'
},
{
label: 'Quit',
click() {
ipcRenderer.send('quit-app');
}
}
]);
window.tray.setContextMenu(contextMenu);
window.tray.on('click', () => {
// Click event only works on Windows
if (process.platform === 'win32') {
if (process.platform === 'linux' || process.platform === 'win32') {
window.tray.on('click', () => {
ipcRenderer.send('toggle-app');
}
});
});
}
};
ipcRenderer.on('destroytray', event => {
ipcRenderer.on('destroytray', (event: Event): Event => {
if (!window.tray) {
return;
return undefined;
}
window.tray.destroy();
@@ -176,7 +167,7 @@ ipcRenderer.on('destroytray', event => {
return event;
});
ipcRenderer.on('tray', (event, arg) => {
ipcRenderer.on('tray', (_event: Event, arg: number): void => {
if (!window.tray) {
return;
}
@@ -196,7 +187,7 @@ ipcRenderer.on('tray', (event, arg) => {
}
});
function toggleTray() {
function toggleTray(): void {
let state;
if (window.tray) {
state = false;
@@ -217,7 +208,7 @@ function toggleTray() {
ConfigUtil.setConfigItem('trayIcon', true);
}
const selector = 'webview:not([class*=disabled])';
const webview = document.querySelector(selector);
const webview: WebviewTag = document.querySelector(selector);
const webContents = webview.getWebContents();
webContents.send('toggletray', state);
}

View File

@@ -1,11 +1,14 @@
'use strict';
const { app, dialog } = require('electron').remote;
const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
const { initSetUp } = require('./default-util');
import { remote } from 'electron';
import JsonDB from 'node-json-db';
import { initSetUp } from './default-util';
import fs = require('fs');
import path = require('path');
import Logger = require('./logger-util');
const { app, dialog } = remote;
initSetUp();
@@ -14,10 +17,12 @@ const logger = new Logger({
timestamp: true
});
let instance = null;
let instance: null | CertificateUtil = null;
const certificatesDir = `${app.getPath('userData')}/certificates`;
class CertificateUtil {
db: JsonDB;
constructor() {
if (instance) {
return instance;
@@ -28,7 +33,8 @@ class CertificateUtil {
this.reloadDB();
return instance;
}
getCertificate(server, defaultValue = null) {
getCertificate(server: string, defaultValue: any = null): any {
this.reloadDB();
const value = this.db.getData('/')[server];
if (value === undefined) {
@@ -37,8 +43,9 @@ class CertificateUtil {
return value;
}
}
// Function to copy the certificate to userData folder
copyCertificate(server, location, fileName) {
copyCertificate(_server: string, location: string, fileName: string): boolean {
let copied = false;
const filePath = `${certificatesDir}/${fileName}`;
try {
@@ -54,16 +61,19 @@ class CertificateUtil {
}
return copied;
}
setCertificate(server, fileName) {
const filePath = `${certificatesDir}/${fileName}`;
setCertificate(server: string, fileName: string): void {
const filePath = `${fileName}`;
this.db.push(`/${server}`, filePath, true);
this.reloadDB();
}
removeCertificate(server) {
removeCertificate(server: string): void {
this.db.delete(`/${server}`);
this.reloadDB();
}
reloadDB() {
reloadDB(): void {
const settingsJsonPath = path.join(app.getPath('userData'), '/config/certificates.json');
try {
const file = fs.readFileSync(settingsJsonPath, 'utf8');
@@ -83,4 +93,4 @@ class CertificateUtil {
}
}
module.exports = new CertificateUtil();
export = new CertificateUtil();

View File

@@ -1,6 +1,6 @@
'use strict';
let instance = null;
let instance: null | CommonUtil = null;
class CommonUtil {
constructor() {
@@ -13,13 +13,13 @@ class CommonUtil {
}
// unescape already encoded/escaped strings
decodeString(string) {
decodeString(stringInput: string): string {
const parser = new DOMParser();
const dom = parser.parseFromString(
'<!doctype html><body>' + string,
'<!doctype html><body>' + stringInput,
'text/html');
return dom.body.textContent;
}
}
module.exports = new CommonUtil();
export = new CommonUtil();

View File

@@ -1,32 +1,34 @@
'use strict';
import JsonDB from 'node-json-db';
const fs = require('fs');
const path = require('path');
const process = require('process');
const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
import fs = require('fs');
import path = require('path');
import electron = require('electron');
import Logger = require('./logger-util');
import EnterpriseUtil = require('./enterprise-util');
const logger = new Logger({
file: 'config-util.log',
timestamp: true
});
let instance = null;
let dialog = null;
let app = null;
let instance: null | ConfigUtil = null;
let dialog: Electron.Dialog = null;
let app: Electron.App = null;
/* To make the util runnable in both main and renderer process */
if (process.type === 'renderer') {
const remote = require('electron').remote;
const { remote } = electron;
dialog = remote.dialog;
app = remote.app;
} else {
const electron = require('electron');
dialog = electron.dialog;
app = electron.app;
}
class ConfigUtil {
db: JsonDB;
constructor() {
if (instance) {
return instance;
@@ -38,8 +40,13 @@ class ConfigUtil {
return instance;
}
getConfigItem(key, defaultValue = null) {
this.reloadDB();
getConfigItem(key: string, defaultValue: any = null): any {
try {
this.db.reload();
} catch (err) {
logger.error('Error while reloading settings.json: ');
logger.error(err);
}
const value = this.db.getData('/')[key];
if (value === undefined) {
this.setConfigItem(key, defaultValue);
@@ -48,24 +55,34 @@ class ConfigUtil {
return value;
}
}
// This function returns whether a key exists in the configuration file (settings.json)
isConfigItemExists(key) {
this.reloadDB();
isConfigItemExists(key: string): boolean {
try {
this.db.reload();
} catch (err) {
logger.error('Error while reloading settings.json: ');
logger.error(err);
}
const value = this.db.getData('/')[key];
return (value !== undefined);
}
setConfigItem(key, value) {
setConfigItem(key: string, value: any, override? : boolean): void {
if (EnterpriseUtil.configItemExists(key) && !override) {
// if item is in global config and we're not trying to override
return;
}
this.db.push(`/${key}`, value, true);
this.reloadDB();
this.db.save();
}
removeConfigItem(key) {
removeConfigItem(key: string): void {
this.db.delete(`/${key}`);
this.reloadDB();
this.db.save();
}
reloadDB() {
reloadDB(): void {
const settingsJsonPath = path.join(app.getPath('userData'), '/config/settings.json');
try {
const file = fs.readFileSync(settingsJsonPath, 'utf8');
@@ -86,4 +103,4 @@ class ConfigUtil {
}
}
module.exports = new ConfigUtil();
export = new ConfigUtil();

View File

@@ -1,6 +1,6 @@
const fs = require('fs');
import fs = require('fs');
let app = null;
let app: Electron.App = null;
let setupCompleted = false;
if (process.type === 'renderer') {
app = require('electron').remote.app;
@@ -12,7 +12,7 @@ const zulipDir = app.getPath('userData');
const logDir = `${zulipDir}/Logs/`;
const certificatesDir = `${zulipDir}/certificates/`;
const configDir = `${zulipDir}/config/`;
const initSetUp = () => {
export const initSetUp = (): void => {
// if it is the first time the app is running
// create zulip dir in userData folder to
// avoid errors
@@ -72,7 +72,3 @@ const initSetUp = () => {
setupCompleted = true;
}
};
module.exports = {
initSetUp
};

View File

@@ -1,17 +1,30 @@
'use strict';
const ConfigUtil = require(__dirname + '/config-util.js');
import ConfigUtil = require('./config-util');
function toggle() {
// TODO: TypeScript - add to Setting interface
// the list of settings since we have fixed amount of them
// We want to do this by creating a new module that exports
// this interface
interface Setting {
[key: string]: any;
}
interface Toggle {
dnd: boolean;
newSettings: Setting;
}
export function toggle(): Toggle {
const dnd = !ConfigUtil.getConfigItem('dnd', false);
const dndSettingList = ['showNotification', 'silent'];
if (process.platform === 'win32') {
dndSettingList.push('flashTaskbarOnMessage');
}
let newSettings;
let newSettings: Setting;
if (dnd) {
const oldSettings = {};
const oldSettings: Setting = {};
newSettings = {};
// Iterate through the dndSettingList.
@@ -35,7 +48,3 @@ function toggle() {
ConfigUtil.setConfigItem('dnd', dnd);
return {dnd, newSettings};
}
module.exports = {
toggle
};

View File

@@ -1,328 +0,0 @@
'use strict';
const { app, dialog } = require('electron').remote;
const fs = require('fs');
const path = require('path');
const JsonDB = require('node-json-db');
const request = require('request');
const escape = require('escape-html');
const Logger = require('./logger-util');
const CertificateUtil = require(__dirname + '/certificate-util.js');
const ProxyUtil = require(__dirname + '/proxy-util.js');
const ConfigUtil = require(__dirname + '/config-util.js');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
let instance = null;
const defaultIconUrl = '../renderer/img/icon.png';
class DomainUtil {
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.reloadDB();
// Migrate from old schema
if (this.db.getData('/').domain) {
this.addDomain({
alias: 'Zulip',
url: this.db.getData('/domain')
});
this.db.delete('/domain');
}
return instance;
}
getDomains() {
this.reloadDB();
if (this.db.getData('/').domains === undefined) {
return [];
} else {
return this.db.getData('/domains');
}
}
getDomain(index) {
this.reloadDB();
return this.db.getData(`/domains[${index}]`);
}
updateDomain(index, server) {
this.reloadDB();
this.db.push(`/domains[${index}]`, server, true);
}
addDomain(server) {
return new Promise(resolve => {
if (server.icon) {
this.saveServerIcon(server.icon).then(localIconUrl => {
server.icon = localIconUrl;
this.db.push('/domains[]', server, true);
this.reloadDB();
resolve();
});
} else {
server.icon = defaultIconUrl;
this.db.push('/domains[]', server, true);
this.reloadDB();
resolve();
}
});
}
removeDomains() {
this.db.delete('/domains');
this.reloadDB();
}
removeDomain(index) {
this.db.delete(`/domains[${index}]`);
this.reloadDB();
}
// Check if domain is already added
duplicateDomain(domain) {
domain = this.formatUrl(domain);
const servers = this.getDomains();
for (const i in servers) {
if (servers[i].url === domain) {
return true;
}
}
return false;
}
checkDomain(domain, silent = false) {
if (!silent && this.duplicateDomain(domain)) {
// Do not check duplicate in silent mode
return Promise.reject('This server has been added.');
}
domain = this.formatUrl(domain);
const certificate = CertificateUtil.getCertificate(encodeURIComponent(domain));
let certificateLocation = '';
if (certificate) {
// To handle case where certificate has been moved from the location in certificates.json
try {
certificateLocation = fs.readFileSync(certificate);
} catch (err) {
logger.warn('Error while trying to get certificate: ' + err);
}
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
// If certificate for the domain exists add it as a ca key in the request's parameter else consider only domain as the parameter for request
// Add proxy as a parameter if it sbeing used.
const checkDomain = {
url: domain + '/static/audio/zulip.ogg',
ca: (certificateLocation) ? certificateLocation : '',
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : ''
};
const serverConf = {
icon: defaultIconUrl,
url: domain,
alias: domain
};
return new Promise((resolve, reject) => {
request(checkDomain, (error, response) => {
// If the domain contains following strings we just bypass the server
const whitelistDomains = [
'zulipdev.org'
];
// make sure that error is an error or string not undefined
// so validation does not throw error.
error = error || '';
const certsError = error.toString().includes('certificate');
if (!error && response.statusCode < 400) {
// Correct
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
}, () => {
resolve(serverConf);
});
} else if (domain.indexOf(whitelistDomains) >= 0 || certsError) {
if (silent) {
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
}, () => {
resolve(serverConf);
});
} else {
// Report error to sentry to get idea of possible certificate errors
// users get when adding the servers
logger.reportSentry(new Error(error));
const certErrorMessage = `Do you trust certificate from ${domain}? \n ${error}`;
const certErrorDetail = `The organization you're connecting to is either someone impersonating the Zulip server you entered, or the server you're trying to connect to is configured in an insecure way.
\nIf you have a valid certificate please add it from Settings>Organizations and try to add the organization again.
\nUnless you have a good reason to believe otherwise, you should not proceed.
\nYou can click here if you'd like to proceed with the connection.`;
dialog.showMessageBox({
type: 'warning',
buttons: ['Yes', 'No'],
defaultId: 1,
message: certErrorMessage,
detail: certErrorDetail
}, response => {
if (response === 0) {
this.getServerSettings(domain).then(serverSettings => {
resolve(serverSettings);
}, () => {
resolve(serverConf);
});
} else {
reject('Untrusted Certificate.');
}
});
}
} else {
const invalidZulipServerError = `${domain} does not appear to be a valid Zulip server. Make sure that \
\n (1) you can connect to that URL in a web browser and \n (2) if you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings \n (3) its a zulip server \
\n (4) the server has a valid certificate, you can add custom certificates in Settings>Organizations`;
reject(invalidZulipServerError);
}
});
});
}
getServerSettings(domain) {
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const serverSettingsOptions = {
url: domain + '/api/v1/server_settings',
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : ''
};
return new Promise((resolve, reject) => {
request(serverSettingsOptions, (error, response) => {
if (!error && response.statusCode === 200) {
const data = JSON.parse(response.body);
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
resolve({
// Some Zulip Servers use absolute URL for server icon whereas others use relative URL
// Following check handles both the cases
icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon,
url: data.realm_uri,
alias: escape(data.realm_name)
});
}
} else {
reject('Zulip server version < 1.6.');
}
});
});
}
saveServerIcon(url) {
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
const serverIconOptions = {
url,
proxy: proxyEnabled ? ProxyUtil.getProxy(url) : ''
};
// The save will always succeed. If url is invalid, downgrade to default icon.
return new Promise(resolve => {
const filePath = this.generateFilePath(url);
const file = fs.createWriteStream(filePath);
try {
request(serverIconOptions).on('response', response => {
response.on('error', err => {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
resolve(defaultIconUrl);
});
response.pipe(file).on('finish', () => {
resolve(filePath);
});
}).on('error', err => {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
resolve(defaultIconUrl);
});
} catch (err) {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
resolve(defaultIconUrl);
}
});
}
updateSavedServer(url, index) {
// Does not promise successful update
this.checkDomain(url, true).then(newServerConf => {
this.saveServerIcon(newServerConf.icon).then(localIconUrl => {
newServerConf.icon = localIconUrl;
this.updateDomain(index, newServerConf);
this.reloadDB();
});
});
}
reloadDB() {
const domainJsonPath = path.join(app.getPath('userData'), 'config/domain.json');
try {
const file = fs.readFileSync(domainJsonPath, 'utf8');
JSON.parse(file);
} catch (err) {
if (fs.existsSync(domainJsonPath)) {
fs.unlinkSync(domainJsonPath);
dialog.showErrorBox(
'Error saving new organization',
'There seems to be error while saving new organization, ' +
'you may have to re-add your previous organizations back.'
);
logger.error('Error while JSON parsing domain.json: ');
logger.error(err);
logger.reportSentry(err);
}
}
this.db = new JsonDB(domainJsonPath, true, true);
}
generateFilePath(url) {
const dir = `${app.getPath('userData')}/server-icons`;
const extension = path.extname(url).split('?')[0];
let hash = 5381;
let len = url.length;
while (len) {
hash = (hash * 33) ^ url.charCodeAt(--len);
}
// Create 'server-icons' directory if not existed
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
return `${dir}/${hash >>> 0}${extension}`;
}
formatUrl(domain) {
const hasPrefix = (domain.indexOf('http') === 0);
if (hasPrefix) {
return domain;
} else {
return (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`;
}
}
}
module.exports = new DomainUtil();

View File

@@ -0,0 +1,331 @@
'use strict';
import JsonDB from 'node-json-db';
import escape = require('escape-html');
import request = require('request');
import fs = require('fs');
import path = require('path');
import Logger = require('./logger-util');
import electron = require('electron');
import RequestUtil = require('./request-util');
import EnterpriseUtil = require('./enterprise-util');
import Messages = require('../../../resources/messages');
const { ipcRenderer } = electron;
const { app, dialog } = electron.remote;
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
let instance: null | DomainUtil = null;
const defaultIconUrl = '../renderer/img/icon.png';
class DomainUtil {
db: JsonDB;
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.reloadDB();
// Migrate from old schema
if (this.db.getData('/').domain) {
this.addDomain({
alias: 'Zulip',
url: this.db.getData('/domain')
});
this.db.delete('/domain');
}
return instance;
}
getDomains(): any {
this.reloadDB();
if (this.db.getData('/').domains === undefined) {
return [];
} else {
return this.db.getData('/domains');
}
}
getDomain(index: number): any {
this.reloadDB();
return this.db.getData(`/domains[${index}]`);
}
shouldIgnoreCerts(url: string): boolean {
const domains = this.getDomains();
for (const domain of domains) {
if (domain.url === url) {
return domain.ignoreCerts;
}
}
return null;
}
updateDomain(index: number, server: object): void {
this.reloadDB();
this.db.push(`/domains[${index}]`, server, true);
}
addDomain(server: any): Promise<void> {
const { ignoreCerts } = server;
return new Promise(resolve => {
if (server.icon) {
this.saveServerIcon(server, ignoreCerts).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(): void {
this.db.delete('/domains');
this.reloadDB();
}
removeDomain(index: number): boolean {
if (EnterpriseUtil.isPresetOrg(this.getDomain(index).url)) {
return false;
}
this.db.delete(`/domains[${index}]`);
this.reloadDB();
return true;
}
// Check if domain is already added
duplicateDomain(domain: any): boolean {
domain = this.formatUrl(domain);
const servers = this.getDomains();
for (const i in servers) {
if (servers[i].url === domain) {
return true;
}
}
return false;
}
async checkCertError(domain: any, serverConf: any, error: string, silent: boolean): Promise<string | object> {
if (silent) {
// since getting server settings has already failed
return serverConf;
} else {
// Report error to sentry to get idea of possible certificate errors
// users get when adding the servers
logger.reportSentry(new Error(error).toString());
const certErrorMessage = Messages.certErrorMessage(domain, error);
const certErrorDetail = Messages.certErrorDetail();
const response = await (dialog.showMessageBox({
type: 'warning',
buttons: ['Yes', 'No'],
defaultId: 1,
message: certErrorMessage,
detail: certErrorDetail
}) as any); // TODO: TypeScript - Figure this out
if (response === 0) {
// set ignoreCerts parameter to true in case user responds with yes
serverConf.ignoreCerts = true;
try {
return await this.getServerSettings(domain, serverConf.ignoreCerts);
} catch (_) {
if (error === Messages.noOrgsError(domain)) {
throw new Error(error);
}
return serverConf;
}
} else {
throw new Error('Untrusted certificate.');
}
}
}
// ignoreCerts parameter helps in fetching server icon and
// other server details when user chooses to ignore certificate warnings
async checkDomain(domain: any, ignoreCerts = false, silent = false): Promise<any> {
if (!silent && this.duplicateDomain(domain)) {
// Do not check duplicate in silent mode
throw new Error('This server has been added.');
}
domain = this.formatUrl(domain);
const serverConf = {
icon: defaultIconUrl,
url: domain,
alias: domain,
ignoreCerts
};
try {
return await this.getServerSettings(domain, serverConf.ignoreCerts);
} catch (err) {
// If the domain contains following strings we just bypass the server
const whitelistDomains = [
'zulipdev.org'
];
// make sure that error is an error or string not undefined
// so validation does not throw error.
const error = err || '';
const certsError = error.toString().includes('certificate');
if (domain.indexOf(whitelistDomains) >= 0 || certsError) {
return this.checkCertError(domain, serverConf, error, silent);
} else {
throw Messages.invalidZulipServerError(domain);
}
}
}
getServerSettings(domain: any, ignoreCerts = false): Promise<object | string> {
const serverSettingsOptions = {
url: domain + '/api/v1/server_settings',
...RequestUtil.requestOptions(domain, ignoreCerts)
};
return new Promise((resolve, reject) => {
request(serverSettingsOptions, (error: string, response: any) => {
if (!error && response.statusCode === 200) {
const data = JSON.parse(response.body);
if (data.hasOwnProperty('realm_icon') && data.realm_icon) {
resolve({
// Some Zulip Servers use absolute URL for server icon whereas others use relative URL
// Following check handles both the cases
icon: data.realm_icon.startsWith('/') ? data.realm_uri + data.realm_icon : data.realm_icon,
url: data.realm_uri,
alias: escape(data.realm_name),
ignoreCerts
});
} else {
reject(Messages.noOrgsError(domain));
}
} else {
reject(response);
}
});
});
}
saveServerIcon(server: any, ignoreCerts = false): Promise<string> {
const url = server.icon;
const domain = server.url;
const serverIconOptions = {
url,
...RequestUtil.requestOptions(domain, ignoreCerts)
};
// The save will always succeed. If url is invalid, downgrade to default icon.
return new Promise(resolve => {
const filePath = this.generateFilePath(url);
const file = fs.createWriteStream(filePath);
try {
request(serverIconOptions).on('response', (response: any) => {
response.on('error', (err: string) => {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
resolve(defaultIconUrl);
});
response.pipe(file).on('finish', () => {
resolve(filePath);
});
}).on('error', (err: string) => {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
resolve(defaultIconUrl);
});
} catch (err) {
logger.log('Could not get server icon.');
logger.log(err);
logger.reportSentry(err);
resolve(defaultIconUrl);
}
});
}
async updateSavedServer(url: string, index: number): Promise<void> {
// Does not promise successful update
const oldIcon = this.getDomain(index).icon;
const { ignoreCerts } = this.getDomain(index);
try {
const newServerConf = await this.checkDomain(url, ignoreCerts, true);
const localIconUrl = await this.saveServerIcon(newServerConf, ignoreCerts);
if (!oldIcon || localIconUrl !== '../renderer/img/icon.png') {
newServerConf.icon = localIconUrl;
this.updateDomain(index, newServerConf);
this.reloadDB();
}
} catch (err) {
ipcRenderer.send('forward-message', 'show-network-error', index);
}
}
reloadDB(): void {
const domainJsonPath = path.join(app.getPath('userData'), 'config/domain.json');
try {
const file = fs.readFileSync(domainJsonPath, 'utf8');
JSON.parse(file);
} catch (err) {
if (fs.existsSync(domainJsonPath)) {
fs.unlinkSync(domainJsonPath);
dialog.showErrorBox(
'Error saving new organization',
'There seems to be error while saving new organization, ' +
'you may have to re-add your previous organizations back.'
);
logger.error('Error while JSON parsing domain.json: ');
logger.error(err);
logger.reportSentry(err);
}
}
this.db = new JsonDB(domainJsonPath, true, true);
}
generateFilePath(url: string): string {
const dir = `${app.getPath('userData')}/server-icons`;
const extension = path.extname(url).split('?')[0];
let hash = 5381;
let len = url.length;
while (len) {
hash = (hash * 33) ^ url.charCodeAt(--len);
}
// Create 'server-icons' directory if not existed
if (!fs.existsSync(dir)) {
fs.mkdirSync(dir);
}
return `${dir}/${hash >>> 0}${extension}`;
}
formatUrl(domain: any): string {
const hasPrefix = (domain.indexOf('http') === 0);
if (hasPrefix) {
return domain;
} else {
return (domain.indexOf('localhost:') >= 0) ? `http://${domain}` : `https://${domain}`;
}
}
}
export = new DomainUtil();

View File

@@ -0,0 +1,80 @@
import fs = require('fs');
import path = require('path');
import Logger = require('./logger-util');
const logger = new Logger({
file: 'enterprise-util.log',
timestamp: true
});
let instance: null | EnterpriseUtil = null;
class EnterpriseUtil {
// todo: replace enterpriseSettings type with an interface once settings are final
enterpriseSettings: any;
configFile: boolean;
constructor() {
if (instance) {
return instance;
}
instance = this;
this.reloadDB();
}
reloadDB(): void {
let enterpriseFile = '/etc/zulip-desktop-config/global_config.json';
if (process.platform === 'win32') {
enterpriseFile = 'C:\\Program Files\\Zulip-Desktop-Config\\global_config.json';
}
enterpriseFile = path.resolve(enterpriseFile);
if (fs.existsSync(enterpriseFile)) {
this.configFile = true;
try {
const file = fs.readFileSync(enterpriseFile, 'utf8');
this.enterpriseSettings = JSON.parse(file);
} catch (err) {
logger.log('Error while JSON parsing global_config.json: ');
logger.log(err);
}
} else {
this.configFile = false;
}
}
getConfigItem(key: string, defaultValue?: any): any {
this.reloadDB();
if (!this.configFile) {
return defaultValue;
}
if (defaultValue === undefined) {
defaultValue = null;
}
return this.configItemExists(key) ? this.enterpriseSettings[key] : defaultValue;
}
configItemExists(key: string): boolean {
this.reloadDB();
if (!this.configFile) {
return false;
}
return (this.enterpriseSettings[key] !== undefined);
}
isPresetOrg(url: string): boolean {
if (!this.configFile || !this.configItemExists('presetOrganizations')) {
return false;
}
const presetOrgs = this.enterpriseSettings.presetOrganizations;
for (const org of presetOrgs) {
if (url.includes(org)) {
return true;
}
}
return false;
}
}
export = new EnterpriseUtil();

View File

@@ -1,8 +1,14 @@
'use strict';
const wurl = require('wurl');
// TODO: TypeScript - Add @types/
import wurl = require('wurl');
let instance = null;
let instance: null | LinkUtil = null;
interface IsInternalResponse {
isInternalUrl: boolean;
isUploadsUrl: boolean;
}
class LinkUtil {
constructor() {
@@ -15,7 +21,7 @@ class LinkUtil {
return instance;
}
isInternal(currentUrl, newUrl) {
isInternal(currentUrl: string, newUrl: string): IsInternalResponse {
const currentDomain = wurl('hostname', currentUrl);
const newDomain = wurl('hostname', newUrl);
@@ -29,17 +35,17 @@ class LinkUtil {
};
}
isImage(url) {
isImage(url: string): boolean {
// test for images extension as well as urls like .png?s=100
const isImageUrl = /\.(bmp|gif|jpg|jpeg|png|webp)\?*.*$/i;
return isImageUrl.test(url);
}
isPDF(url) {
isPDF(url: string): boolean {
// test for pdf extension
const isPDFUrl = /\.(pdf)\?*.*$/i;
return isPDFUrl.test(url);
}
}
module.exports = new LinkUtil();
export = new LinkUtil();

View File

@@ -1,12 +1,13 @@
'use strict';
import JsonDB from 'node-json-db';
import fs = require('fs');
import path = require('path');
import electron = require('electron');
import Logger = require('./logger-util');
const fs = require('fs');
const path = require('path');
const process = require('process');
const remote =
process.type === 'renderer' ? require('electron').remote : require('electron');
const JsonDB = require('node-json-db');
const Logger = require('./logger-util');
process.type === 'renderer' ? electron.remote : electron;
const logger = new Logger({
file: 'linux-update-util.log',
@@ -15,10 +16,11 @@ const logger = new Logger({
/* To make the util runnable in both main and renderer process */
const { dialog, app } = remote;
let instance = null;
let instance: null | LinuxUpdateUtil = null;
class LinuxUpdateUtil {
db: JsonDB;
constructor() {
if (instance) {
return instance;
@@ -30,7 +32,7 @@ class LinuxUpdateUtil {
return instance;
}
getUpdateItem(key, defaultValue = null) {
getUpdateItem(key: string, defaultValue: any = null): any {
this.reloadDB();
const value = this.db.getData('/')[key];
if (value === undefined) {
@@ -41,17 +43,17 @@ class LinuxUpdateUtil {
}
}
setUpdateItem(key, value) {
setUpdateItem(key: string, value: any): void {
this.db.push(`/${key}`, value, true);
this.reloadDB();
}
removeUpdateItem(key) {
removeUpdateItem(key: string): void {
this.db.delete(`/${key}`);
this.reloadDB();
}
reloadDB() {
reloadDB(): void {
const linuxUpdateJsonPath = path.join(app.getPath('userData'), '/config/updates.json');
try {
const file = fs.readFileSync(linuxUpdateJsonPath, 'utf8');
@@ -71,4 +73,4 @@ class LinuxUpdateUtil {
}
}
module.exports = new LinuxUpdateUtil();
export = new LinuxUpdateUtil();

View File

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

View File

@@ -0,0 +1,155 @@
import { Console as NodeConsole } from 'console'; // eslint-disable-line node/prefer-global/console
import { initSetUp } from './default-util';
import { sentryInit, captureException } from './sentry-util';
import fs = require('fs');
import os = require('os');
import isDev = require('electron-is-dev');
import electron = require('electron');
// this interface adds [key: string]: any so
// we can do console[type] later on in the code
interface PatchedConsole extends Console {
[key: string]: any;
}
interface LoggerOptions {
timestamp?: any;
file?: string;
level?: boolean;
logInDevMode?: boolean;
}
initSetUp();
let app: Electron.App = null;
let reportErrors = true;
if (process.type === 'renderer') {
app = electron.remote.app;
// Report Errors to Sentry only if it is enabled in settings
// Gets the value of reportErrors from config-util for renderer process
// For main process, sentryInit() is handled in index.js
const { ipcRenderer } = electron;
ipcRenderer.send('error-reporting');
ipcRenderer.on('error-reporting-val', (_event: any, errorReporting: boolean) => {
reportErrors = errorReporting;
if (reportErrors) {
sentryInit();
}
});
} else {
app = electron.app;
}
const browserConsole: PatchedConsole = console;
const logDir = `${app.getPath('userData')}/Logs`;
class Logger {
nodeConsole: PatchedConsole;
timestamp: any; // TODO: TypeScript - Figure out how to make this work with string | Function.
level: boolean;
logInDevMode: boolean;
[key: string]: any;
constructor(opts: LoggerOptions = {}) {
let {
timestamp = true,
file = 'console.log',
level = true,
logInDevMode = false
} = opts;
file = `${logDir}/${file}`;
if (timestamp === true) {
timestamp = this.getTimestamp;
}
// Trim log according to type of process
if (process.type === 'renderer') {
requestIdleCallback(() => this.trimLog(file));
} else {
process.nextTick(() => this.trimLog(file));
}
const fileStream = fs.createWriteStream(file, { flags: 'a' });
const nodeConsole = new NodeConsole(fileStream);
this.nodeConsole = nodeConsole;
this.timestamp = timestamp;
this.level = level;
this.logInDevMode = logInDevMode;
this.setUpConsole();
}
_log(type: string, ...args: any[]): void {
const {
nodeConsole, timestamp, level, logInDevMode
} = this;
let nodeConsoleLog;
/* eslint-disable no-fallthrough */
switch (true) {
case typeof timestamp === 'function':
args.unshift(timestamp() + ' |\t');
case (level !== false):
args.unshift(type.toUpperCase() + ' |');
case isDev || logInDevMode:
nodeConsoleLog = nodeConsole[type] || nodeConsole.log;
nodeConsoleLog.apply(null, args); // eslint-disable-line prefer-spread
default: break;
}
/* eslint-enable no-fallthrough */
browserConsole[type].apply(null, args);
}
setUpConsole(): void {
for (const type in browserConsole) {
this.setupConsoleMethod(type);
}
}
setupConsoleMethod(type: string): void {
this[type] = (...args: any[]) => {
const log = this._log.bind(this, type, ...args);
log();
};
}
getTimestamp(): string {
const date = new Date();
const timestamp =
`${date.getMonth()}/${date.getDate()} ` +
`${date.getMinutes()}:${date.getSeconds()}`;
return timestamp;
}
reportSentry(err: string): void {
if (reportErrors) {
captureException(err);
}
}
trimLog(file: string): void{
fs.readFile(file, 'utf8', (err, data) => {
if (err) {
throw err;
}
const MAX_LOG_FILE_LINES = 500;
const logs = data.split(os.EOL);
const logLength = logs.length - 1;
// Keep bottom MAX_LOG_FILE_LINES of each log instance
if (logLength > MAX_LOG_FILE_LINES) {
const trimmedLogs = logs.slice(logLength - MAX_LOG_FILE_LINES);
const toWrite = trimmedLogs.join(os.EOL);
fs.writeFileSync(file, toWrite);
}
});
}
}
export = Logger;

View File

@@ -1,15 +1,11 @@
// This util function returns the page params if they're present else returns null
function isPageParams() {
export function isPageParams(): null | object {
let webpageParams = null;
try {
// eslint-disable-next-line no-undef, camelcase
// eslint-disable-next-line no-undef, @typescript-eslint/camelcase
webpageParams = page_params;
} catch (err) {
} catch (_) {
webpageParams = null;
}
return webpageParams;
}
module.exports = {
isPageParams
};

View File

@@ -1,9 +1,15 @@
'use strict';
const url = require('url');
const ConfigUtil = require('./config-util.js');
import url = require('url');
let instance = null;
import ConfigUtil = require('./config-util');
let instance: null | ProxyUtil = null;
interface ProxyRule {
hostname?: string;
port?: number;
}
class ProxyUtil {
constructor() {
@@ -17,8 +23,13 @@ class ProxyUtil {
}
// Return proxy to be used for a particular uri, to be used for request
getProxy(uri) {
uri = url.parse(uri);
getProxy(_uri: string): ProxyRule | void {
const parsedUri = url.parse(_uri);
if (parsedUri === null) {
return;
}
const uri = parsedUri;
const proxyRules = ConfigUtil.getConfigItem('proxyRules', '').split(';');
// If SPS is on and system uses no proxy then request should not try to use proxy from
// environment. NO_PROXY = '*' makes request ignore all environment proxy variables.
@@ -27,9 +38,9 @@ class ProxyUtil {
return;
}
const proxyRule = {};
const proxyRule: any = {};
if (uri.protocol === 'http:') {
proxyRules.forEach(proxy => {
proxyRules.forEach((proxy: string) => {
if (proxy.includes('http=')) {
proxyRule.hostname = proxy.split('http=')[1].trim().split(':')[0];
proxyRule.port = proxy.split('http=')[1].trim().split(':')[1];
@@ -39,7 +50,7 @@ class ProxyUtil {
}
if (uri.protocol === 'https:') {
proxyRules.forEach(proxy => {
proxyRules.forEach((proxy: string) => {
if (proxy.includes('https=')) {
proxyRule.hostname = proxy.split('https=')[1].trim().split(':')[0];
proxyRule.port = proxy.split('https=')[1].trim().split(':')[1];
@@ -49,14 +60,15 @@ class ProxyUtil {
}
}
resolveSystemProxy(mainWindow) {
// TODO: Refactor to async function
resolveSystemProxy(mainWindow: Electron.BrowserWindow): void {
const page = mainWindow.webContents;
const ses = page.session;
const resolveProxyUrl = 'www.google.com';
const resolveProxyUrl = 'www.example.com';
// Check HTTP Proxy
const httpProxy = new Promise(resolve => {
ses.resolveProxy('http://' + resolveProxyUrl, proxy => {
ses.resolveProxy('http://' + resolveProxyUrl, (proxy: string) => {
let httpString = '';
if (proxy !== 'DIRECT') {
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
@@ -70,12 +82,12 @@ class ProxyUtil {
});
// Check HTTPS Proxy
const httpsProxy = new Promise(resolve => {
ses.resolveProxy('https://' + resolveProxyUrl, proxy => {
ses.resolveProxy('https://' + resolveProxyUrl, (proxy: string) => {
let httpsString = '';
if (proxy !== 'DIRECT' || proxy.includes('HTTPS')) {
// in case of proxy HTTPS url:port, windows gives first word as HTTPS while linux gives PROXY
// for all other HTTP or direct url:port both uses PROXY
if (proxy.includes('PROXY' || proxy.includes('HTTPS'))) {
if (proxy.includes('PROXY') || proxy.includes('HTTPS')) {
httpsString += 'https=' + proxy.split('PROXY')[1] + ';';
}
}
@@ -85,7 +97,7 @@ class ProxyUtil {
// Check FTP Proxy
const ftpProxy = new Promise(resolve => {
ses.resolveProxy('ftp://' + resolveProxyUrl, proxy => {
ses.resolveProxy('ftp://' + resolveProxyUrl, (proxy: string) => {
let ftpString = '';
if (proxy !== 'DIRECT') {
if (proxy.includes('PROXY')) {
@@ -98,7 +110,7 @@ class ProxyUtil {
// Check SOCKS Proxy
const socksProxy = new Promise(resolve => {
ses.resolveProxy('socks4://' + resolveProxyUrl, proxy => {
ses.resolveProxy('socks4://' + resolveProxyUrl, (proxy: string) => {
let socksString = '';
if (proxy !== 'DIRECT') {
if (proxy.includes('SOCKS5')) {
@@ -127,4 +139,4 @@ class ProxyUtil {
}
}
module.exports = new ProxyUtil();
export = new ProxyUtil();

View File

@@ -1,60 +0,0 @@
const isOnline = require('is-online');
const Logger = require('./logger-util');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
class ReconnectUtil {
constructor(serverManagerView) {
this.serverManagerView = serverManagerView;
this.alreadyReloaded = false;
}
clearState() {
this.alreadyReloaded = false;
}
pollInternetAndReload() {
const pollInterval = setInterval(() => {
this._checkAndReload()
.then(status => {
if (status) {
this.alreadyReloaded = true;
clearInterval(pollInterval);
}
});
}, 1500);
}
_checkAndReload() {
return new Promise(resolve => {
if (!this.alreadyReloaded) { // eslint-disable-line no-negated-condition
isOnline()
.then(online => {
if (online) {
if (!this.alreadyReloaded) {
this.serverManagerView.reloadView();
}
logger.log('You\'re back online.');
return resolve(true);
}
logger.log('There is no internet connection, try checking network cables, modem and router.');
const errMsgHolder = document.querySelector('#description');
if (errMsgHolder) {
errMsgHolder.innerHTML = `
<div>You internet connection does't seem to work properly!</div>
</div>Verify that it works and then click try again.</div>`;
}
return resolve(false);
});
} else {
return resolve(true);
}
});
}
}
module.exports = ReconnectUtil;

View File

@@ -0,0 +1,102 @@
import { ipcRenderer } from 'electron';
import backoff = require('backoff');
import request = require('request');
import Logger = require('./logger-util');
import RequestUtil = require('./request-util');
import DomainUtil = require('./domain-util');
const logger = new Logger({
file: `domain-util.log`,
timestamp: true
});
class ReconnectUtil {
// TODO: TypeScript - Figure out how to annotate webview
// it should be WebView; maybe make it a generic so we can
// pass the class from main.ts
webview: any;
url: string;
alreadyReloaded: boolean;
fibonacciBackoff: any;
constructor(webview: any) {
this.webview = webview;
this.url = webview.props.url;
this.alreadyReloaded = false;
this.clearState();
}
clearState(): void {
this.fibonacciBackoff = backoff.fibonacci({
initialDelay: 5000,
maxDelay: 300000
});
}
isOnline(): Promise<boolean> {
return new Promise(resolve => {
try {
const ignoreCerts = DomainUtil.shouldIgnoreCerts(this.url);
if (ignoreCerts === null) {
return;
}
request(
{
url: `${this.url}/static/favicon.ico`,
...RequestUtil.requestOptions(this.url, ignoreCerts)
},
(error: Error, response: any) => {
const isValidResponse =
!error && response.statusCode >= 200 && response.statusCode < 400;
resolve(isValidResponse);
}
);
} catch (err) {
logger.log(err);
}
});
}
pollInternetAndReload(): void {
this.fibonacciBackoff.backoff();
this.fibonacciBackoff.on('ready', () => {
this._checkAndReload().then(status => {
if (status) {
this.fibonacciBackoff.reset();
} else {
this.fibonacciBackoff.backoff();
}
});
});
}
// TODO: Make this a async function
_checkAndReload(): Promise<boolean> {
return new Promise(resolve => {
if (!this.alreadyReloaded) { // eslint-disable-line no-negated-condition
this.isOnline()
.then((online: boolean) => {
if (online) {
ipcRenderer.send('forward-message', 'reload-viewer');
logger.log('You\'re back online.');
return resolve(true);
}
logger.log('There is no internet connection, try checking network cables, modem and router.');
const errMsgHolder = document.querySelector('#description');
if (errMsgHolder) {
errMsgHolder.innerHTML = `
<div>Your internet connection doesn't seem to work properly!</div>
<div>Verify that it works and then click try again.</div>`;
}
return resolve(false);
});
} else {
return resolve(true);
}
});
}
}
export = ReconnectUtil;

View File

@@ -0,0 +1,87 @@
import { remote } from 'electron';
import fs = require('fs');
import path = require('path');
import ConfigUtil = require('./config-util');
import Logger = require('./logger-util');
import ProxyUtil = require('./proxy-util');
import CertificateUtil = require('./certificate-util');
import SystemUtil = require('./system-util');
const { app } = remote;
const logger = new Logger({
file: `request-util.log`,
timestamp: true
});
let instance: null | RequestUtil = null;
// TODO: TypeScript - Use ProxyRule for the proxy property
// we can do this now since we use export = ProxyUtil syntax
interface RequestUtilResponse {
ca: string;
proxy: string | void | object;
ecdhCurve: 'auto';
headers: { 'User-Agent': string };
rejectUnauthorized: boolean;
}
class RequestUtil {
constructor() {
if (!instance) {
instance = this;
}
return instance;
}
// ignoreCerts parameter helps in fetching server icon and
// other server details when user chooses to ignore certificate warnings
requestOptions(domain: string, ignoreCerts: boolean): RequestUtilResponse {
domain = this.formatUrl(domain);
const certificate = CertificateUtil.getCertificate(
encodeURIComponent(domain)
);
let certificateFile = null;
if (certificate && certificate.includes('/')) {
// certificate saved using old app version
certificateFile = certificate;
} else if (certificate) {
certificateFile = path.join(`${app.getPath('userData')}/certificates`, certificate);
}
let certificateLocation = '';
if (certificate) {
// To handle case where certificate has been moved from the location in certificates.json
try {
certificateLocation = fs.readFileSync(certificateFile, 'utf8');
} catch (err) {
logger.warn(`Error while trying to get certificate: ${err}`);
}
}
const proxyEnabled = ConfigUtil.getConfigItem('useManualProxy') || ConfigUtil.getConfigItem('useSystemProxy');
// If certificate for the domain exists add it as a ca key in the request's parameter else consider only domain as the parameter for request
// Add proxy as a parameter if it is being used.
return {
ca: certificateLocation ? certificateLocation : '',
proxy: proxyEnabled ? ProxyUtil.getProxy(domain) : '',
ecdhCurve: 'auto',
headers: { 'User-Agent': SystemUtil.getUserAgent() },
rejectUnauthorized: !ignoreCerts
};
}
formatUrl(domain: string): string {
const hasPrefix = domain.startsWith('http', 0);
if (hasPrefix) {
return domain;
} else {
return domain.includes('localhost:') ? `http://${domain}` : `https://${domain}`;
}
}
}
const requestUtil = new RequestUtil();
export = requestUtil;

View File

@@ -1,16 +0,0 @@
const { init, captureException } = require('@sentry/electron');
const isDev = require('electron-is-dev');
const sentryInit = () => {
if (!isDev) {
init({
dsn: 'SENTRY_DSN',
sendTimeout: 30 // wait 30 seconds before considering the sending capture to have failed, default is 1 second
});
}
};
module.exports = {
sentryInit,
captureException
};

View File

@@ -0,0 +1,21 @@
import { init } from '@sentry/electron';
import isDev = require('electron-is-dev');
import path = require('path');
import dotenv = require('dotenv');
dotenv.config({ path: path.resolve(__dirname, '/../../../../.env') });
export const sentryInit = (): void => {
if (!isDev) {
init({
dsn: process.env.SENTRY_DSN,
// We should ignore this error since it's harmless and we know the reason behind this
// This error mainly comes from the console logs.
// This is a temp solution until Sentry supports disabling the console logs
ignoreErrors: ['does not appear to be a valid Zulip server']
// sendTimeout: 30 // wait 30 seconds before considering the sending capture to have failed, default is 1 second
});
}
};
export { captureException } from '@sentry/electron';

View File

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

View File

@@ -0,0 +1,64 @@
'use strict';
import { remote } from 'electron';
import os = require('os');
import ConfigUtil = require('./config-util');
const { app } = remote;
let instance: null | SystemUtil = null;
class SystemUtil {
connectivityERR: string[];
userAgent: string | null;
constructor() {
if (instance) {
return instance;
} else {
instance = this;
}
this.connectivityERR = [
'ERR_INTERNET_DISCONNECTED',
'ERR_PROXY_CONNECTION_FAILED',
'ERR_CONNECTION_RESET',
'ERR_NOT_CONNECTED',
'ERR_NAME_NOT_RESOLVED',
'ERR_NETWORK_CHANGED'
];
this.userAgent = null;
return instance;
}
getOS(): string {
const platform = os.platform();
if (platform === 'darwin') {
return 'Mac';
} else if (platform === 'linux') {
return 'Linux';
} else if (platform === 'win32') {
if (parseFloat(os.release()) < 6.2) {
return 'Windows 7';
} else {
return 'Windows 10';
}
} else {
return '';
}
}
setUserAgent(webViewUserAgent: string): void {
this.userAgent = `ZulipElectron/${app.getVersion()} ${webViewUserAgent}`;
}
getUserAgent(): string | null {
if (!this.userAgent) {
this.setUserAgent(ConfigUtil.getConfigItem('userAgent', null));
}
return this.userAgent;
}
}
export = new SystemUtil();

View File

@@ -0,0 +1,35 @@
'use strict';
import path = require('path');
import electron = require('electron');
import i18n = require('i18n');
let instance: TranslationUtil = null;
let app: Electron.App = null;
/* To make the util runnable in both main and renderer process */
if (process.type === 'renderer') {
app = electron.remote.app;
} else {
app = electron.app;
}
class TranslationUtil {
constructor() {
if (instance) {
return this;
}
instance = this;
i18n.configure({
directory: path.join(__dirname, '../../../translations/'),
register: this
});
}
__(phrase: string): string {
return i18n.__({ phrase, locale: app.getLocale() ? app.getLocale() : 'en' });
}
}
export = new TranslationUtil();

View File

@@ -1,56 +1,65 @@
<!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>
<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" class="toggle-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>
<body>
<div id="content">
<div class="popup">
<span class="popuptext hidden" id="fullscreen-popup"></span>
</div>
<div id="sidebar" class="toggle-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>
<span id="add-server-tooltip" style="display:none">Add organization</span>
</div>
</div>
<div id="actions-container">
<div class="action-button" id="dnd-action">
<i class="material-icons md-48">notifications</i>
<span id="dnd-tooltip" style="display:none">Do Not Disturb</span>
</div>
<div class="action-button" id="reload-action">
<i class="material-icons md-48">refresh</i>
<span id="reload-tooltip" style="display:none">Reload</span>
</div>
<div class="action-button disable" id="loading-action">
<i class="refresh material-icons md-48">loop</i>
<span id="loading-tooltip" style="display:none">Loading</span>
</div>
<div class="action-button disable" id="back-action">
<i class="material-icons md-48">arrow_back</i>
<span id="back-tooltip" style="display:none">Go Back</span>
</div>
<div class="action-button" id="settings-action">
<i class="material-icons md-48">settings</i>
<span id="setting-tooltip" style="display:none">Settings</span>
</div>
</div>
</div>
<div id="main-container">
<div id="webviews-container"></div>
</div>
</div>
<span id="add-server-tooltip" style="display:none">Add organization</span>
</div>
</div>
<div id="actions-container">
<div class="action-button" id="dnd-action">
<i class="material-icons md-48">notifications</i>
<span id="dnd-tooltip" style="display:none">Do Not Disturb</span>
</div>
<div class="action-button" id="reload-action">
<i class="material-icons md-48">refresh</i>
<span id="reload-tooltip" style="display:none">Reload</span>
</div>
<div class="action-button disable" id="back-action">
<i class="material-icons md-48">arrow_back</i>
<span id="back-tooltip" style="display:none">Go Back</span>
</div>
<div class="action-button" id="settings-action">
<i class="material-icons md-48">settings</i>
<span id="setting-tooltip" style="display:none">Settings</span>
</div>
</div>
</div>
<div id="main-container">
<div id="webviews-container"></div>
</div>
</div>
<div id="feedback-modal">
<send-feedback></send-feedback>
</div>
</body>
<script src="js/main.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html>
<div id="feedback-modal">
<send-feedback></send-feedback>
</div>
</body>
<script>
// we don't use src='./js/main' in the script tag because
// it messes up require module path resolution
require('./js/main');
</script>
<script>require('./js/shared/preventdrag.js')</script>
</html>

View File

@@ -1,22 +1,29 @@
<!DOCTYPE html>
<html lang="en" class="responsive desktop">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width">
<title>Zulip - Network Troubleshooting</title>
<link rel="stylesheet" href="css/network.css" type="text/css" media="screen">
</head>
<body>
<div id="content">
<div id="picture"><img src="img/zulip_network.png"></div>
<div id="title">Zulip can't connect</div>
<div id="description">
<div>Your computer seems to be offline.</div>
<div>We will keep trying to reconnect, or you can try now.</div>
</div>
<div id="reconnect">Try now</div>
</div>
</body>
<script src="js/pages/network.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width">
<title>Zulip - Network Troubleshooting</title>
<link rel="stylesheet" href="css/network.css" type="text/css" media="screen">
</head>
<body>
<div id="content">
<div id="picture"><img src="img/zulip_network.png"></div>
<div id="title">We can't connect to this organization</div>
<div id="subtitle">This could be because</div>
<ul id="description">
<li>You're not online or your proxy is misconfigured.</li>
<li>There is no Zulip organization hosted at this URL.</li>
<li>This Zulip organization is temporarily unavailable.</li>
<li>This Zulip organization has been moved or deleted.</li>
</ul>
<div id="buttons">
<div id="reconnect" class="button">Reconnect</div>
<div id="settings" class="button">Settings</div>
</div>
</div>
</body>
<script>var exports = {};</script>
<script src="js/preload.js"></script>
<script>require('./js/shared/preventdrag.js')</script>
</html>

View File

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

52
app/resources/messages.ts Normal file
View File

@@ -0,0 +1,52 @@
interface DialogBoxError {
title: string;
content: string;
}
class Messages {
invalidZulipServerError(domain: string): string {
return `${domain} does not appear to be a valid Zulip server. Make sure that
\n • You can connect to that URL in a web browser.\
\n • If you need a proxy to connect to the Internet, that you've configured your proxy in the Network settings.\
\n • It's a Zulip server. (The oldest supported version is 1.6).\
\n • The server has a valid certificate. (You can add custom certificates in Settings > Organizations). \
\n • The SSL is correctly configured for the certificate. Check out the SSL troubleshooting guide -
\n https://zulip.readthedocs.io/en/stable/production/ssl-certificates.html`;
}
noOrgsError(domain: string): string {
return `${domain} does not have any organizations added.\
\nPlease contact your server administrator.`;
}
certErrorMessage(domain: string, error: string): string {
return `Do you trust certificate from ${domain}? \n ${error}`;
}
certErrorDetail(): string {
return `The organization you're connecting to is either someone impersonating the Zulip server you entered, or the server you're trying to connect to is configured in an insecure way.
\nIf you have a valid certificate please add it from Settings>Organizations and try to add the organization again.
\nUnless you have a good reason to believe otherwise, you should not proceed.
\nYou can click here if you'd like to proceed with the connection.`;
}
enterpriseOrgError(length: number, domains: string[]): DialogBoxError {
let domainList = '';
for (const domain of domains) {
domainList += `${domain}\n`;
}
return {
title: `Could not add the following ${length === 1 ? `organization` : `organizations`}`,
content: `${domainList}\nPlease contact your system administrator.`
};
}
orgRemovalError(url: string): DialogBoxError {
return {
title: `Removing ${url} is a restricted operation.`,
content: `Please contact your system administrator.`
};
}
}
export = new Messages();

View File

@@ -0,0 +1,20 @@
# How to help translate Zulip Desktop
These are *generated* files (*) that contain translations of the strings in
the app.
You can help translate Zulip Desktop into your language! We do our
translations in Transifex, which is a nice web app for collaborating on
translations; a maintainer then syncs those translations into this repo.
To help out, [join the Zulip project on
Transifex](https://www.transifex.com/zulip/zulip/) and enter translations
there. More details in the [Zulip contributor docs](https://zulip.readthedocs.io/en/latest/translating/translating.html#translators-workflow).
Within that Transifex project, if you'd like to focus on Zulip Desktop, look
at `desktop.json`. The other resources there are for the Zulip web/mobile
app, where translations are also very welcome.
(*) One file is an exception: `en.json` is manually maintained as a
list of (English) messages in the source code, and is used when we upload to
Transifex a list of strings to be translated. It doesn't contain any
translations.

View File

@@ -1,39 +1,117 @@
{
"About Zulip": "За Зулип",
"Actual Size": "Реален размер",
"About Zulip": "За Zulip",
"Actual Size": "Действителен размер",
"Add Custom Certificates": "Добавяне на персонализирани сертификати",
"Add Organization": "Добавяне на организация",
"Add a Zulip organization": "Добавете организация Zulip",
"Add custom CSS": "Добавете персонализиран CSS",
"Advanced": "напреднал",
"All the connected organizations will appear here": "Всички свързани организации ще се появят тук",
"Always start minimized": "Винаги започвайте да минимизирате",
"App Updates": "Актуализации на приложения",
"Appearance": "Външен вид",
"Application Shortcuts": "Клавишни комбинации за приложения",
"Are you sure you want to disconnect this organization?": "Наистина ли искате да прекъснете връзката с тази организация?",
"Auto hide Menu bar": "Автоматично скриване на лентата с менюта",
"Auto hide menu bar (Press Alt key to display)": "Автоматично скриване на лентата с менюта (натиснете клавиша Alt за показване)",
"Back": "обратно",
"Bounce dock on new private message": "Прескочи док в новото лично съобщение",
"Certificate file": "Файл за сертификат",
"Change": "промяна",
"Check for Updates": "Провери за обновления",
"Close": "Близо",
"Connect": "Свържете",
"Connect to another organization": "Свържете се с друга организация",
"Connected organizations": "Свързани организации",
"Copy": "копие",
"Copy Zulip URL": "Копирайте URL адреса на Zulip",
"Create a new organization": "Създайте нова организация",
"Cut": "Разрез",
"Default download location": "Място на изтегляне по подразбиране",
"Delete": "Изтрий",
"Desktop App Settings": "Настройки за настолни приложения",
"Desktop App Settings": "Настройки на приложението за работния плот",
"Desktop Notifications": "Известия за работния плот",
"Desktop Settings": "Настройки на работния плот",
"Disconnect": "Прекъсване на връзката",
"Download App Logs": "Изтеглете регистрационни файлове на приложенията",
"Edit": "редактиране",
"Edit Shortcuts": "Редактиране на преки пътища",
"Enable auto updates": "Активиране на автоматичните актуализации",
"Enable error reporting (requires restart)": "Активиране на отчитането за грешки (изисква се рестартиране)",
"Enable spellchecker (requires restart)": "Активиране на проверката на правописа (изисква се рестартиране)",
"Factory Reset": "Фабрично нулиране",
"File": "досие",
"Find accounts": "Намерете профили",
"Find accounts by email": "Намерете профили по имейл",
"Flash taskbar on new message": "Flash лентата на задачите в новото съобщение",
"Forward": "напред",
"Hard Reload": "Трудно презареждане",
"Functionality": "Функционалност",
"General": "Общ",
"Get beta updates": "Изтеглете бета актуализации",
"Hard Reload": "Hard Reload",
"Help": "Помогне",
"Help Center": "Помощен център",
"History": "история",
"History Shortcuts": "Преки пътища в историята",
"Keyboard Shortcuts": "Комбинация от клавиши",
"Log Out": "Излез от профила си",
"Log Out of Organization": "Излезте от организацията",
"Manual proxy configuration": "Ръчна конфигурация на прокси",
"Minimize": "Минимизиране",
"Mute all sounds from Zulip": "Заглуши всички звуци от Zulip",
"NO": "НЕ",
"Network": "мрежа",
"OR": "ИЛИ",
"Organization URL": "URL адрес на организацията",
"Organizations": "организации",
"Paste": "паста",
"Paste and Match Style": "Поставяне и съвпадение на стила",
"Paste and Match Style": "Поставяне и стил на съвпадение",
"Proxy": "пълномощник",
"Proxy bypass rules": "Правила за заобикаляне на прокси",
"Proxy rules": "Прокси правила",
"Quit": "напускам",
"Quit Zulip": "Прекрати Zulip",
"Redo": "ремонтирам",
"Release Notes": "Бележки към изданието",
"Reload": "Презареди",
"Report an issue...": "Подаване на сигнал за проблем ...",
"Reset App Settings": "Нулирайте настройките на приложението",
"Report an Issue": "Подаване на сигнал за проблем",
"Reset App Data": "Нулиране на данни за приложения",
"Reset App Settings": "Нулиране на настройките на приложението",
"Reset Application Data": "Нулиране на данните на приложението",
"Save": "Запази",
"Select All": "Избери всички",
"Show App Logs": "Показване на регистрационните файлове на приложения",
"Toggle DevTools for Active Tab": "Превключете DevTools за активен раздел",
"Settings": "Настройки",
"Shortcuts": "Shortcuts",
"Show App Logs": "Показване на регистрационните файлове на приложенията",
"Show app icon in system tray": "Показване на иконата на приложението в системната област",
"Show app unread badge": "Показване на непрочетената значка на приложението",
"Show desktop notifications": "Показване на известията на работния плот",
"Show downloaded files in file manager": "Показване на изтеглените файлове във файловия мениджър",
"Show sidebar": "Показване на страничната лента",
"Start app at login": "Стартирайте приложението при влизане",
"Switch to Next Organization": "Превключване към следваща организация",
"Switch to Previous Organization": "Превключване към предишна организация",
"These desktop app shortcuts extend the Zulip webapp's": "Тези клавишни комбинации за настолни приложения разширяват webapp на Zulip",
"This will delete all application data including all added accounts and preferences": "Това ще изтрие всички данни за приложения, включително всички добавени акаунти и предпочитания",
"Tip": "Бакшиш",
"Toggle DevTools for Active Tab": "Превключете DevTools за Active Tab",
"Toggle DevTools for Zulip App": "Превключете DevTools за Zulip App",
"Toggle Do Not Disturb": "Превключване Не безпокойте",
"Toggle Full Screen": "Превключване на цял екран",
"Toggle Sidebar": "Превключване на страничната лента",
"Toggle Tray Icon": "Превключване на иконата на тавата",
"Toggle Tray Icon": "Превключете иконата на тава",
"Tools": "Инструменти",
"Undo": "премахвам",
"Upload": "Качи",
"Use system proxy settings (requires restart)": "Използване на системните прокси настройки (изисква рестартиране)",
"View": "изглед",
"View Shortcuts": "Преглед на преки пътища",
"Window": "прозорец",
"Window Shortcuts": "Клавишни комбинации",
"YES": "ДА",
"Zoom In": "Увеличавам",
"Zoom Out": "Отдалечавам",
"Zulip Help": "Помощ за Zulip"
"Zulip Help": "Помощ за Zulip",
"keyboard shortcuts": "комбинация от клавиши",
"script": "писменост"
}

View File

@@ -1,39 +1,117 @@
{
"About Zulip": "O společnosti Zulip",
"About Zulip": "O uživateli Zulip",
"Actual Size": "Aktuální velikost",
"Add Custom Certificates": "Přidat vlastní certifikáty",
"Add Organization": "Přidat organizaci",
"Add a Zulip organization": "Přidejte organizaci Zulip",
"Add custom CSS": "Přidat vlastní CSS",
"Advanced": "Pokročilý",
"All the connected organizations will appear here": "Zde se zobrazí všechny připojené organizace",
"Always start minimized": "Vždy začněte minimalizovat",
"App Updates": "Aktualizace aplikací",
"Appearance": "Vzhled",
"Application Shortcuts": "Zkratky aplikací",
"Are you sure you want to disconnect this organization?": "Opravdu chcete tuto organizaci odpojit?",
"Auto hide Menu bar": "Auto hide Menu bar",
"Auto hide menu bar (Press Alt key to display)": "Automaticky skrýt lištu menu (stiskněte klávesu Alt pro zobrazení)",
"Back": "Zadní",
"Bounce dock on new private message": "Bounce dock na nové soukromé zprávy",
"Certificate file": "Soubor certifikátu",
"Change": "Změna",
"Check for Updates": "Kontrola aktualizací",
"Close": "Zavřít",
"Connect": "Připojit",
"Connect to another organization": "Připojte se k jiné organizaci",
"Connected organizations": "Připojené organizace",
"Copy": "kopírovat",
"Copy Zulip URL": "Kopírovat adresu URL aplikace Zulip",
"Create a new organization": "Vytvořit novou organizaci",
"Cut": "Střih",
"Delete": "Vymazat",
"Default download location": "Výchozí umístění stahování",
"Delete": "Smazat",
"Desktop App Settings": "Nastavení aplikace Desktop",
"Desktop Notifications": "Oznámení o pracovní ploše",
"Desktop Settings": "Nastavení plochy",
"Disconnect": "Odpojit",
"Download App Logs": "Stáhnout App Logs",
"Edit": "Upravit",
"Edit Shortcuts": "Upravit zkratky",
"Enable auto updates": "Povolit automatické aktualizace",
"Enable error reporting (requires restart)": "Povolit hlášení chyb (vyžaduje restart)",
"Enable spellchecker (requires restart)": "Povolit kontrolu pravopisu (vyžaduje restartování)",
"Factory Reset": "Tovární nastavení",
"File": "Soubor",
"Find accounts": "Najít účty",
"Find accounts by email": "Najít účty e-mailem",
"Flash taskbar on new message": "Hlavní panel Flash na nové zprávě",
"Forward": "Vpřed",
"Functionality": "Funkčnost",
"General": "Všeobecné",
"Get beta updates": "Získejte aktualizace beta",
"Hard Reload": "Hard Reload",
"Help": "Pomoc",
"Help Center": "Centrum nápovědy",
"History": "Dějiny",
"History Shortcuts": "Zkratky historie",
"Keyboard Shortcuts": "Klávesové zkratky",
"Log Out": "Odhlásit se",
"Minimize": "Minimalizujte",
"Log Out of Organization": "Odhlásit se z organizace",
"Manual proxy configuration": "Ruční konfigurace proxy",
"Minimize": "Minimalizovat",
"Mute all sounds from Zulip": "Vypněte všechny zvuky ze Zulipu",
"NO": "NE",
"Network": "Síť",
"OR": "NEBO",
"Organization URL": "URL organizace",
"Organizations": "Organizace",
"Paste": "Vložit",
"Paste and Match Style": "Vložit a přizpůsobit styl",
"Paste and Match Style": "Vložit a shodit styl",
"Proxy": "Proxy",
"Proxy bypass rules": "Proxy bypass pravidla",
"Proxy rules": "Proxy pravidla",
"Quit": "Přestat",
"Quit Zulip": "Ukončete Zulip",
"Redo": "Předělat",
"Release Notes": "Poznámky k vydání",
"Reload": "Znovu načíst",
"Report an issue...": "Nahlásit problém...",
"Report an Issue": "Nahlásit problém",
"Reset App Data": "Resetovat data aplikace",
"Reset App Settings": "Obnovit nastavení aplikace",
"Reset Application Data": "Resetovat data aplikace",
"Save": "Uložit",
"Select All": "Vybrat vše",
"Show App Logs": "Zobrazit protokoly aplikace",
"Toggle DevTools for Active Tab": "Přepínač DevTools pro aktivní kartu",
"Toggle DevTools for Zulip App": "Přepnout nástroj DevTools pro aplikaci Zulip App",
"Settings": "Nastavení",
"Shortcuts": "Zkratky",
"Show App Logs": "Zobrazit protokoly aplikací",
"Show app icon in system tray": "Zobrazit ikonu aplikace v systémové liště",
"Show app unread badge": "Zobrazit nepřečtený odznak aplikace",
"Show desktop notifications": "Zobrazit oznámení na ploše",
"Show downloaded files in file manager": "Zobrazit stažené soubory ve správci souborů",
"Show sidebar": "Zobrazit postranní panel",
"Start app at login": "Spuštění aplikace při přihlášení",
"Switch to Next Organization": "Přepnout na další organizaci",
"Switch to Previous Organization": "Přepněte na předchozí organizaci",
"These desktop app shortcuts extend the Zulip webapp's": "Tyto klávesové zkratky pro aplikaci na ploše rozšiřují aplikaci Zulip webapp",
"This will delete all application data including all added accounts and preferences": "Tím odstraníte všechna data aplikace včetně všech přidaných účtů a předvoleb",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Přepnout DevTools pro Active Tab",
"Toggle DevTools for Zulip App": "Přepnout DevTools pro Zulip App",
"Toggle Do Not Disturb": "Přepnout Do Nerušit",
"Toggle Full Screen": "Přepnout na celou obrazovku",
"Toggle Sidebar": "Přepnout postranní panel",
"Toggle Tray Icon": "Přepnout ikonu zásobníku",
"Tools": "Nástroje",
"Undo": "vrátit",
"Upload": "nahrát",
"Use system proxy settings (requires restart)": "Použít nastavení proxy serveru (vyžaduje restart)",
"View": "Pohled",
"View Shortcuts": "Zobrazit zástupce",
"Window": "Okno",
"Window Shortcuts": "Klávesové zkratky",
"YES": "ANO",
"Zoom In": "Přiblížit",
"Zoom Out": "Oddálit",
"Zulip Help": "Zulip Nápověda"
"Zulip Help": "Nápověda Zulip",
"keyboard shortcuts": "klávesové zkratky",
"script": "skript"
}

View File

@@ -1,39 +1,117 @@
{
"About Zulip": "Über Zulip",
"Actual Size": "Tatsächliche Größe",
"Add Custom Certificates": "Benutzerdefinierte Zertifikate hinzufügen",
"Add Organization": "Organisation hinzufügen",
"Add a Zulip organization": "Fügen Sie eine Zulip-Organisation hinzu",
"Add custom CSS": "Fügen Sie benutzerdefiniertes CSS hinzu",
"Advanced": "Fortgeschritten",
"All the connected organizations will appear here": "Alle verbundenen Organisationen werden hier angezeigt",
"Always start minimized": "Immer minimiert anfangen",
"App Updates": "App-Updates",
"Appearance": "Aussehen",
"Application Shortcuts": "Anwendungsverknüpfungen",
"Are you sure you want to disconnect this organization?": "Möchten Sie diese Organisation wirklich trennen?",
"Auto hide Menu bar": "Menüleiste automatisch ausblenden",
"Auto hide menu bar (Press Alt key to display)": "Menüleiste automatisch ausblenden (Drücken Sie die Alt-Taste, um anzuzeigen)",
"Back": "Zurück",
"Bounce dock on new private message": "Bounce-Dock für neue private Nachricht",
"Certificate file": "Zertifikatsdatei",
"Change": "Veränderung",
"Check for Updates": "Auf Updates prüfen",
"Close": "Schließen",
"Connect": "Verbinden",
"Connect to another organization": "Stellen Sie eine Verbindung zu einer anderen Organisation her",
"Connected organizations": "Verbundene Organisationen",
"Copy": "Kopieren",
"Copy Zulip URL": "Zulip-URL kopieren",
"Create a new organization": "Erstellen Sie eine neue Organisation",
"Cut": "Schnitt",
"Default download location": "Standard-Download-Speicherort",
"Delete": "Löschen",
"Desktop App Settings": "Desktop App Einstellungen",
"Desktop App Settings": "Desktop App-Einstellungen",
"Desktop Notifications": "Desktop-Benachrichtigungen",
"Desktop Settings": "Desktop-Einstellungen",
"Disconnect": "Trennen",
"Download App Logs": "App-Protokolle herunterladen",
"Edit": "Bearbeiten",
"Edit Shortcuts": "Verknüpfungen bearbeiten",
"Enable auto updates": "Automatische Updates aktivieren",
"Enable error reporting (requires restart)": "Fehlerberichterstattung aktivieren (Neustart erforderlich)",
"Enable spellchecker (requires restart)": "Rechtschreibprüfung aktivieren (Neustart erforderlich)",
"Factory Reset": "Werkseinstellungen zurückgesetzt",
"File": "Datei",
"Find accounts": "Konten suchen",
"Find accounts by email": "Finden Sie Konten per E-Mail",
"Flash taskbar on new message": "Flash-Taskleiste bei neuer Nachricht",
"Forward": "Nach vorne",
"Hard Reload": "Hard Reload",
"Functionality": "Funktionalität",
"General": "Allgemeines",
"Get beta updates": "Erhalten Sie Beta-Updates",
"Hard Reload": "Festes Nachladen",
"Help": "Hilfe",
"Help Center": "Hilfezentrum",
"History": "Geschichte",
"History Shortcuts": "Verlaufsverknüpfungen",
"Keyboard Shortcuts": "Tastatürkürzel",
"Log Out": "Ausloggen",
"Log Out of Organization": "Aus Organisation ausloggen",
"Manual proxy configuration": "Manuelle Proxy-Konfiguration",
"Minimize": "Minimieren",
"Mute all sounds from Zulip": "Schaltet alle Sounds von Zulip stumm",
"NO": "NEIN",
"Network": "Netzwerk",
"OR": "ODER",
"Organization URL": "Organisations-URL",
"Organizations": "Organisationen",
"Paste": "Einfügen",
"Paste and Match Style": "Einfügen und Anpassen von Stilen",
"Paste and Match Style": "Einfügen und Format anpassen",
"Proxy": "Proxy",
"Proxy bypass rules": "Proxy-Umgehungsregeln",
"Proxy rules": "Proxy-Regeln",
"Quit": "Verlassen",
"Redo": "Wiederholen",
"Quit Zulip": "Beenden Sie Zulip",
"Redo": "Redo",
"Release Notes": "Versionshinweise",
"Reload": "Neu laden",
"Report an issue...": "Ein Problem melden...",
"Report an Issue": "Ein Problem melden",
"Reset App Data": "App-Daten zurücksetzen",
"Reset App Settings": "App-Einstellungen zurücksetzen",
"Reset Application Data": "Anwendungsdaten zurücksetzen",
"Save": "sparen",
"Select All": "Wählen Sie Alle",
"Show App Logs": "App-Logs anzeigen",
"Toggle DevTools for Active Tab": "Schalten Sie DevTools für Active Tab ein",
"Toggle DevTools for Zulip App": "Umschalt DevTools für Zulip App",
"Settings": "die Einstellungen",
"Shortcuts": "Verknüpfungen",
"Show App Logs": "App-Protokolle anzeigen",
"Show app icon in system tray": "App-Symbol in der Taskleiste anzeigen",
"Show app unread badge": "Zeige App ungelesenen Badge",
"Show desktop notifications": "Desktop-Benachrichtigungen anzeigen",
"Show downloaded files in file manager": "Heruntergeladene Dateien im Dateimanager anzeigen",
"Show sidebar": "Seitenleiste anzeigen",
"Start app at login": "App beim Login starten",
"Switch to Next Organization": "Wechseln Sie zur nächsten Organisation",
"Switch to Previous Organization": "Zur vorherigen Organisation wechseln",
"These desktop app shortcuts extend the Zulip webapp's": "Diese Desktop-App-Verknüpfungen erweitern die Zulip-Webanwendungen",
"This will delete all application data including all added accounts and preferences": "Dadurch werden alle Anwendungsdaten einschließlich aller hinzugefügten Konten und Einstellungen gelöscht",
"Tip": "Spitze",
"Toggle DevTools for Active Tab": "DevTools für aktive Registerkarte umschalten",
"Toggle DevTools for Zulip App": "DevTools für Zulip App umschalten",
"Toggle Do Not Disturb": "Nicht stören umschalten",
"Toggle Full Screen": "Vollbild umschalten",
"Toggle Sidebar": "Toggle Seitenleiste",
"Toggle Tray Icon": "Toggle Tray-Symbol",
"Toggle Sidebar": "Seitenleiste umschalten",
"Toggle Tray Icon": "Taskleistensymbol umschalten",
"Tools": "Werkzeuge",
"Undo": "Rückgängig machen",
"Upload": "Hochladen",
"Use system proxy settings (requires restart)": "System-Proxy-Einstellungen verwenden (Neustart erforderlich)",
"View": "Aussicht",
"View Shortcuts": "Verknüpfungen anzeigen",
"Window": "Fenster",
"Window Shortcuts": "Fensterverknüpfungen",
"YES": "JA",
"Zoom In": "Hineinzoomen",
"Zoom Out": "Rauszoomen",
"Zulip Help": "Zulip Hilfe"
"Zulip Help": "Zulip-Hilfe",
"keyboard shortcuts": "Tastatürkürzel",
"script": "Skript"
}

118
app/translations/en-GB.json Normal file
View File

@@ -0,0 +1,118 @@
{
"Add Organization": "Add Organization",
"Toggle Do Not Disturb": "Toggle Do Not Disturb",
"Desktop Settings": "Desktop Settings",
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Copy Zulip URL": "Copy Zulip URL",
"Log Out of Organization": "Log Out of Organization",
"Services": "Services",
"Hide": "Hide",
"Hide Others": "Hide Others",
"Unhide": "Unhide",
"Minimize": "Minimize",
"Close": "Close",
"Quit": "Quit",
"Edit": "Edit",
"Undo": "Undo",
"Redo": "Redo",
"Cut": "Cut",
"Copy": "Copy",
"Paste": "Paste",
"Paste and Match Style": "Paste and Match Style",
"Select All": "Select All",
"View": "View",
"Reload": "Reload",
"Hard Reload": "Hard Reload",
"Toggle Full Screen": "Toggle Full Screen",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Actual Size": "Actual Size",
"Toggle Tray Icon": "Toggle Tray Icon",
"Toggle Sidebar": "Toggle Sidebar",
"Auto hide Menu bar": "Auto hide Menu bar",
"History": "History",
"Back": "Back",
"Forward": "Forward",
"Window": "Window",
"Tools": "Tools",
"Check for Updates": "Check for Updates",
"Release Notes": "Release Notes",
"Factory Reset": "Factory Reset",
"Download App Logs": "Download App Logs",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Help": "Help",
"About Zulip": "About Zulip",
"Help Center": "Help Center",
"Report an Issue": "Report an Issue",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"General": "General",
"Network": "Network",
"AddServer": "AddServer",
"Organizations": "Organizations",
"Shortcuts": "Shortcuts",
"Settings": "Settings",
"Add a Zulip organization": "Add a Zulip organization",
"Organization URL": "Organization URL",
"Connect": "Connect",
"OR": "OR",
"Create a new organization": "Create a new organization",
"Proxy": "Proxy",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"Manual proxy configuration": "Manual proxy configuration",
"script": "script",
"Proxy rules": "Proxy rules",
"Proxy bypass rules": "Proxy bypass rules",
"Save": "Save",
"Appearance": "Appearance",
"Show app icon in system tray": "Show app icon in system tray",
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
"Show sidebar": "Show sidebar",
"Show app unread badge": "Show app unread badge",
"Bounce dock on new private message": "Bounce dock on new private message",
"Flash taskbar on new message": "Flash taskbar on new message",
"Desktop Notifications": "Desktop Notifications",
"Show desktop notifications": "Show desktop notifications",
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"App Updates": "App Updates",
"Enable auto updates": "Enable auto updates",
"Get beta updates": "Get beta updates",
"Functionality": "Functionality",
"Start app at login": "Start app at login",
"Always start minimized": "Always start minimized",
"Enable spellchecker (requires restart)": "Enable spellchecker (requires restart)",
"Advanced": "Advanced",
"Enable error reporting (requires restart)": "Enable error reporting (requires restart)",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Add custom CSS": "Add custom CSS",
"Upload": "Upload",
"Delete": "Delete",
"Default download location": "Default download location",
"Change": "Change",
"Reset Application Data": "Reset Application Data",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Reset App Data": "Reset App Data",
"Connected organizations": "Connected organizations",
"All the connected orgnizations will appear here.": "All the connected orgnizations will appear here.",
"Connect to another organization": "Connect to another organization",
"Add Custom Certificates": "Add Custom Certificates",
"Find accounts by email": "Find accounts by email",
"All the connected orgnizations will appear here": "All the connected orgnizations will appear here",
"Disconnect": "Disconnect",
"Certificate file": "Certificate file",
"Find accounts": "Find accounts",
"Tip": "Tip",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"keyboard shortcuts": "keyboard shortcuts",
"Application Shortcuts": "Application Shortcuts",
"Reset App Settings": "Reset App Settings",
"Log Out": "Log Out",
"Hide Zulip": "Hide Zulip",
"Quit Zulip": "Quit Zulip",
"Edit Shortcuts": "Edit Shortcuts",
"Emoji & Symbols": "Emoji & Symbols",
"View Shortcuts": "View Shortcuts",
"Enter Full Screen": "Enter Full Screen",
"History Shortcuts": "History Shortcuts"
}

108
app/translations/en-US.json Normal file
View File

@@ -0,0 +1,108 @@
{
"File": "File",
"Add Organization": "Add Organization",
"Toggle Do Not Disturb": "Toggle Do Not Disturb",
"Desktop Settings": "Desktop Settings",
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Copy Zulip URL": "Copy Zulip URL",
"Log Out of Organization": "Log Out of Organization",
"Minimize": "Minimize",
"Close": "Close",
"Quit": "Quit",
"Edit": "Edit",
"Undo": "Undo",
"Redo": "Redo",
"Cut": "Cut",
"Copy": "Copy",
"Paste": "Paste",
"Paste and Match Style": "Paste and Match Style",
"Select All": "Select All",
"View": "View",
"Reload": "Reload",
"Hard Reload": "Hard Reload",
"Toggle Full Screen": "Toggle Full Screen",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Actual Size": "Actual Size",
"Toggle Tray Icon": "Toggle Tray Icon",
"Toggle Sidebar": "Toggle Sidebar",
"Auto hide Menu bar": "Auto hide Menu bar",
"History": "History",
"Back": "Back",
"Forward": "Forward",
"Window": "Window",
"Tools": "Tools",
"Check for Updates": "Check for Updates",
"Release Notes": "Release Notes",
"Factory Reset": "Factory Reset",
"Download App Logs": "Download App Logs",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Help": "Help",
"About Zulip": "About Zulip",
"Help Center": "Help Center",
"Report an Issue": "Report an Issue",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"General": "General",
"Network": "Network",
"AddServer": "AddServer",
"Organizations": "Organizations",
"Shortcuts": "Shortcuts",
"Appearance": "Appearance",
"Show app icon in system tray": "Show app icon in system tray",
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
"Show sidebar": "Show sidebar",
"Show app unread badge": "Show app unread badge",
"Bounce dock on new private message": "Bounce dock on new private message",
"Flash taskbar on new message": "Flash taskbar on new message",
"Desktop Notifications": "Desktop Notifications",
"Show desktop notifications": "Show desktop notifications",
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"App Updates": "App Updates",
"Enable auto updates": "Enable auto updates",
"Get beta updates": "Get beta updates",
"Functionality": "Functionality",
"Start app at login": "Start app at login",
"Always start minimized": "Always start minimized",
"Enable spellchecker (requires restart)": "Enable spellchecker (requires restart)",
"Advanced": "Advanced",
"Enable error reporting (requires restart)": "Enable error reporting (requires restart)",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Add custom CSS": "Add custom CSS",
"Upload": "Upload",
"Delete": "Delete",
"Default download location": "Default download location",
"Change": "Change",
"Reset Application Data": "Reset Application Data",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Reset App Data": "Reset App Data",
"Proxy": "Proxy",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"Manual proxy configuration": "Manual proxy configuration",
"script": "script",
"Proxy rules": "Proxy rules",
"Proxy bypass rules": "Proxy bypass rules",
"Save": "Save",
"Connected organizations": "Connected organizations",
"All the connected orgnizations will appear here.": "All the connected orgnizations will appear here.",
"Connect to another organization": "Connect to another organization",
"Add Custom Certificates": "Add Custom Certificates",
"Find accounts by email": "Find accounts by email",
"All the connected orgnizations will appear here": "All the connected orgnizations will appear here",
"Disconnect": "Disconnect",
"Organization URL": "Organization URL",
"Certificate file": "Certificate file",
"Find accounts": "Find accounts",
"Tip": "Tip",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"keyboard shortcuts": "keyboard shortcuts",
"Application Shortcuts": "Application Shortcuts",
"Settings": "Settings",
"Log Out": "Log Out",
"Quit Zulip": "Quit Zulip",
"Edit Shortcuts": "Edit Shortcuts",
"View Shortcuts": "View Shortcuts",
"History Shortcuts": "History Shortcuts",
"Window Shortcuts": "Window Shortcuts"
}

View File

@@ -1,39 +1,118 @@
{
"About Zulip": "About Zulip",
"Actual Size": "Actual Size",
"Add Custom Certificates": "Add Custom Certificates",
"Add Organization": "Add Organization",
"Add a Zulip organization": "Add a Zulip organization",
"Add custom CSS": "Add custom CSS",
"Advanced": "Advanced",
"All the connected organizations will appear here": "All the connected organizations will appear here",
"Always start minimized": "Always start minimized",
"App Updates": "App Updates",
"Appearance": "Appearance",
"Application Shortcuts": "Application Shortcuts",
"Are you sure you want to disconnect this organization?": "Are you sure you want to disconnect this organization?",
"Auto hide Menu bar": "Auto hide Menu bar",
"Auto hide menu bar (Press Alt key to display)": "Auto hide menu bar (Press Alt key to display)",
"Back": "Back",
"Bounce dock on new private message": "Bounce dock on new private message",
"Certificate file": "Certificate file",
"Change": "Change",
"Check for Updates": "Check for Updates",
"Close": "Close",
"Connect": "Connect",
"Connect to another organization": "Connect to another organization",
"Connected organizations": "Connected organizations",
"Copy": "Copy",
"Copy Zulip URL": "Copy Zulip URL",
"Create a new organization": "Create a new organization",
"Cut": "Cut",
"Default download location": "Default download location",
"Delete": "Delete",
"Desktop App Settings": "Desktop App Settings",
"Desktop Notifications": "Desktop Notifications",
"Desktop Settings": "Desktop Settings",
"Disconnect": "Disconnect",
"Download App Logs": "Download App Logs",
"Edit": "Edit",
"Edit Shortcuts": "Edit Shortcuts",
"Enable auto updates": "Enable auto updates",
"Enable error reporting (requires restart)": "Enable error reporting (requires restart)",
"Enable spellchecker (requires restart)": "Enable spellchecker (requires restart)",
"Factory Reset": "Factory Reset",
"File": "File",
"Find accounts": "Find accounts",
"Find accounts by email": "Find accounts by email",
"Flash taskbar on new message": "Flash taskbar on new message",
"Forward": "Forward",
"Functionality": "Functionality",
"General": "General",
"Get beta updates": "Get beta updates",
"Hard Reload": "Hard Reload",
"Help": "Help",
"Help Center": "Help Center",
"History": "History",
"History Shortcuts": "History Shortcuts",
"Keyboard Shortcuts": "Keyboard Shortcuts",
"Log Out": "Log Out",
"Log Out of Organization": "Log Out of Organization",
"Manual proxy configuration": "Manual proxy configuration",
"Minimize": "Minimize",
"Mute all sounds from Zulip": "Mute all sounds from Zulip",
"NO": "NO",
"Network": "Network",
"OR": "OR",
"Organization URL": "Organization URL",
"Organizations": "Organizations",
"Paste": "Paste",
"Paste and Match Style": "Paste and Match Style",
"Proxy": "Proxy",
"Proxy bypass rules": "Proxy bypass rules",
"Proxy rules": "Proxy rules",
"Quit": "Quit",
"Quit Zulip": "Quit Zulip",
"Redo": "Redo",
"Release Notes": "Release Notes",
"Reload": "Reload",
"Report an issue...": "Report an issue...",
"Report an Issue": "Report an Issue",
"Reset App Data": "Reset App Data",
"Reset App Settings": "Reset App Settings",
"Reset Application Data": "Reset Application Data",
"Save": "Save",
"Select All": "Select All",
"Settings": "Settings",
"Shortcuts": "Shortcuts",
"Show App Logs": "Show App Logs",
"Show app icon in system tray": "Show app icon in system tray",
"Show app unread badge": "Show app unread badge",
"Show desktop notifications": "Show desktop notifications",
"Show downloaded files in file manager": "Show downloaded files in file manager",
"Show sidebar": "Show sidebar",
"Start app at login": "Start app at login",
"Switch to Next Organization": "Switch to Next Organization",
"Switch to Previous Organization": "Switch to Previous Organization",
"These desktop app shortcuts extend the Zulip webapp's": "These desktop app shortcuts extend the Zulip webapp's",
"This will delete all application data including all added accounts and preferences": "This will delete all application data including all added accounts and preferences",
"Tip": "Tip",
"Toggle DevTools for Active Tab": "Toggle DevTools for Active Tab",
"Toggle DevTools for Zulip App": "Toggle DevTools for Zulip App",
"Toggle Do Not Disturb": "Toggle Do Not Disturb",
"Toggle Full Screen": "Toggle Full Screen",
"Toggle Sidebar": "Toggle Sidebar",
"Toggle Tray Icon": "Toggle Tray Icon",
"Tools": "Tools",
"Undo": "Undo",
"Upload": "Upload",
"Use system proxy settings (requires restart)": "Use system proxy settings (requires restart)",
"View": "View",
"View Shortcuts": "View Shortcuts",
"Window": "Window",
"Window Shortcuts": "Window Shortcuts",
"YES": "YES",
"Zoom In": "Zoom In",
"Zoom Out": "Zoom Out",
"Zulip Help": "Zulip Help"
"Zulip Help": "Zulip Help",
"keyboard shortcuts": "keyboard shortcuts",
"script": "script",
"Quit when the window is closed": "Quit when the window is closed"
}

View File

@@ -1,39 +1,117 @@
{
"About Zulip": "Acerca de Zulip",
"Actual Size": "Tamaño real",
"Add Custom Certificates": "Añadir certificados personalizados",
"Add Organization": "Añadir organización",
"Add a Zulip organization": "Añadir una organización Zulip",
"Add custom CSS": "Añadir CSS personalizado",
"Advanced": "Avanzado",
"All the connected organizations will appear here": "Todas las organizaciones conectadas aparecerán aquí.",
"Always start minimized": "Siempre empezar minimizado",
"App Updates": "Actualizaciones de la aplicación",
"Appearance": "Apariencia",
"Application Shortcuts": "Atajos de aplicación",
"Are you sure you want to disconnect this organization?": "¿Estás seguro de que quieres desconectar esta organización?",
"Auto hide Menu bar": "Ocultar automáticamente la barra de menú",
"Auto hide menu bar (Press Alt key to display)": "Ocultar automáticamente la barra de menú (presionar la tecla Alt para mostrar)",
"Back": "Espalda",
"Close": "Cerca",
"Bounce dock on new private message": "Bounce dock en nuevo mensaje privado",
"Certificate file": "Archivo de certificado",
"Change": "Cambio",
"Check for Updates": "Buscar actualizaciones",
"Close": "Cerrar",
"Connect": "Conectar",
"Connect to another organization": "Conectarse a otra organización",
"Connected organizations": "Organizaciones conectadas",
"Copy": "Dupdo",
"Copy Zulip URL": "Copiar URL de Zulip",
"Create a new organization": "Crear una nueva organización",
"Cut": "Cortar",
"Default download location": "Ubicación de descarga predeterminada",
"Delete": "Borrar",
"Desktop App Settings": "Configuración de la aplicación de escritorio",
"Desktop Notifications": "Notificaciones de escritorio",
"Desktop Settings": "Configuraciones de escritorio",
"Disconnect": "Desconectar",
"Download App Logs": "Descargar App Logs",
"Edit": "Editar",
"File": "Archivo",
"Edit Shortcuts": "Editar accesos directos",
"Enable auto updates": "Habilitar actualizaciones automáticas",
"Enable error reporting (requires restart)": "Habilitar informes de errores (requiere reinicio)",
"Enable spellchecker (requires restart)": "Habilitar el corrector ortográfico (requiere reinicio)",
"Factory Reset": "Restablecimiento de fábrica",
"File": "Expediente",
"Find accounts": "Encontrar cuentas",
"Find accounts by email": "Encuentra cuentas por correo electrónico",
"Flash taskbar on new message": "Flash barra de tareas en nuevo mensaje",
"Forward": "Adelante",
"Hard Reload": "Recargar duro",
"Functionality": "Funcionalidad",
"General": "General",
"Get beta updates": "Recibe actualizaciones beta",
"Hard Reload": "Recarga dura",
"Help": "Ayuda",
"Help Center": "Centro de ayuda",
"History": "Historia",
"History Shortcuts": "Atajos de historia",
"Keyboard Shortcuts": "Atajos de teclado",
"Log Out": "Cerrar sesión",
"Log Out of Organization": "Salir de la organización",
"Manual proxy configuration": "Configuración de proxy manual",
"Minimize": "Minimizar",
"Mute all sounds from Zulip": "Silencia todos los sonidos de Zulip",
"NO": "NO",
"Network": "Red",
"OR": "O",
"Organization URL": "URL de la organización",
"Organizations": "Organizaciones",
"Paste": "Pegar",
"Paste and Match Style": "Pegar y combinar estilo",
"Proxy": "Apoderado",
"Proxy bypass rules": "Reglas de omisión de proxy",
"Proxy rules": "Reglas de proxy",
"Quit": "Dejar",
"Quit Zulip": "Dejar Zulip",
"Redo": "Rehacer",
"Release Notes": "Notas de lanzamiento",
"Reload": "Recargar",
"Report an issue...": "Reportar un problema...",
"Reset App Settings": "Restablecer configuraciones",
"Report an Issue": "Reportar un problema",
"Reset App Data": "Restablecer datos de la aplicación",
"Reset App Settings": "Restablecer la configuración de la aplicación",
"Reset Application Data": "Restablecer datos de aplicación",
"Save": "Salvar",
"Select All": "Seleccionar todo",
"Settings": "Ajustes",
"Shortcuts": "Atajos",
"Show App Logs": "Mostrar registros de aplicaciones",
"Show app icon in system tray": "Mostrar icono de la aplicación en la bandeja del sistema",
"Show app unread badge": "Mostrar la aplicación de placa sin leer",
"Show desktop notifications": "Mostrar notificaciones de escritorio",
"Show downloaded files in file manager": "Mostrar los archivos descargados en el administrador de archivos",
"Show sidebar": "Mostrar barra lateral",
"Start app at login": "Iniciar la aplicación al iniciar sesión",
"Switch to Next Organization": "Cambiar a la siguiente organización",
"Switch to Previous Organization": "Cambiar a la organización anterior",
"These desktop app shortcuts extend the Zulip webapp's": "Estos accesos directos de aplicaciones de escritorio extienden las aplicaciones web de Zulip.",
"This will delete all application data including all added accounts and preferences": "Esto eliminará todos los datos de la aplicación, incluidas todas las cuentas y preferencias agregadas.",
"Tip": "Propina",
"Toggle DevTools for Active Tab": "Alternar DevTools para Active Tab",
"Toggle DevTools for Zulip App": "Alternar DevTools para la aplicación Zulip",
"Toggle DevTools for Zulip App": "Alternar DevTools para Zulip App",
"Toggle Do Not Disturb": "Alternar No molestar",
"Toggle Full Screen": "Alternar pantalla completa",
"Toggle Sidebar": "Alternar barra lateral",
"Toggle Tray Icon": "Alternar el icono de la bandeja",
"Toggle Tray Icon": "Icono de bandeja de palanca",
"Tools": "Herramientas",
"Undo": "Deshacer",
"Upload": "Subir",
"Use system proxy settings (requires restart)": "Usar la configuración del proxy del sistema (requiere reinicio)",
"View": "Ver",
"View Shortcuts": "Ver accesos directos",
"Window": "Ventana",
"Window Shortcuts": "Atajos de ventana",
"YES": "SÍ",
"Zoom In": "Acercarse",
"Zoom Out": "Disminuir el zoom",
"Zulip Help": "Ayuda de Zulip"
"Zulip Help": "Ayuda de Zulip",
"keyboard shortcuts": "atajos de teclado",
"script": "guión"
}

View File

@@ -1,39 +1,117 @@
{
"About Zulip": "À propos de Zulip",
"Actual Size": "Taille actuelle",
"Back": "Arrière",
"Add Custom Certificates": "Ajouter des certificats personnalisés",
"Add Organization": "Ajouter une organisation",
"Add a Zulip organization": "Ajouter une organisation Zulip",
"Add custom CSS": "Ajouter un CSS personnalisé",
"Advanced": "Avancée",
"All the connected organizations will appear here": "Toutes les organisations connectées apparaîtront ici",
"Always start minimized": "Toujours commencer minimisé",
"App Updates": "Mises à jour de l'application",
"Appearance": "Apparence",
"Application Shortcuts": "Raccourcis d'application",
"Are you sure you want to disconnect this organization?": "Êtes-vous sûr de vouloir déconnecter cette organisation?",
"Auto hide Menu bar": "Masquer automatiquement la barre de menus",
"Auto hide menu bar (Press Alt key to display)": "Masquer automatiquement la barre de menu (appuyez sur la touche Alt pour afficher)",
"Back": "Retour",
"Bounce dock on new private message": "Bounce Dock sur un nouveau message privé",
"Certificate file": "Dossier de certificat",
"Change": "Changement",
"Check for Updates": "Vérifier les mises à jour",
"Close": "Fermer",
"Connect": "Relier",
"Connect to another organization": "Se connecter à une autre organisation",
"Connected organizations": "Organisations connectées",
"Copy": "Copie",
"Copy Zulip URL": "Copier l'URL Zulip",
"Create a new organization": "Créer une nouvelle organisation",
"Cut": "Couper",
"Default download location": "Emplacement de téléchargement par défaut",
"Delete": "Effacer",
"Desktop App Settings": "Paramètres de l'application de bureau",
"Desktop Notifications": "Notifications de bureau",
"Desktop Settings": "Paramètres du bureau",
"Disconnect": "Déconnecter",
"Download App Logs": "Télécharger les journaux d'application",
"Edit": "modifier",
"Edit Shortcuts": "Modifier les raccourcis",
"Enable auto updates": "Activer les mises à jour automatiques",
"Enable error reporting (requires restart)": "Activer le signalement des erreurs (nécessite un redémarrage)",
"Enable spellchecker (requires restart)": "Activer le correcteur orthographique (nécessite un redémarrage)",
"Factory Reset": "Retour aux paramètres d'usine",
"File": "Fichier",
"Find accounts": "Trouver des comptes",
"Find accounts by email": "Trouver des comptes par email",
"Flash taskbar on new message": "Barre de tâches Flash sur un nouveau message",
"Forward": "Vers l'avant",
"Functionality": "La fonctionnalité",
"General": "Général",
"Get beta updates": "Obtenir les mises à jour bêta",
"Hard Reload": "Rechargement dur",
"Help": "Aidez-moi",
"History": "Histoire",
"Help Center": "Centre d'aide",
"History": "L'histoire",
"History Shortcuts": "Raccourcis Histoire",
"Keyboard Shortcuts": "Raccourcis clavier",
"Log Out": "Connectez - Out",
"Log Out of Organization": "Déconnexion de l'organisation",
"Manual proxy configuration": "Configuration manuelle du proxy",
"Minimize": "Minimiser",
"Mute all sounds from Zulip": "Couper tous les sons de Zulip",
"NO": "NON",
"Network": "Réseau",
"OR": "OU",
"Organization URL": "URL de l'organisation",
"Organizations": "Les organisations",
"Paste": "Coller",
"Paste and Match Style": "Coller et assortir le style",
"Proxy": "Procuration",
"Proxy bypass rules": "Règles de contournement des procurations",
"Proxy rules": "Règles de procuration",
"Quit": "Quitter",
"Quit Zulip": "Quittez Zulip",
"Redo": "Refaire",
"Release Notes": "Notes de version",
"Reload": "Recharger",
"Report an issue...": "Signaler un problème...",
"Reset App Settings": "Réinitialiser les paramètres",
"Report an Issue": "Signaler un problème",
"Reset App Data": "Réinitialiser les données de l'application",
"Reset App Settings": "Réinitialiser les paramètres de l'application",
"Reset Application Data": "Réinitialiser les données d'application",
"Save": "sauvegarder",
"Select All": "Tout sélectionner",
"Show App Logs": "Afficher les journaux d'applications",
"Settings": "Réglages",
"Shortcuts": "Raccourcis",
"Show App Logs": "Afficher les journaux d'application",
"Show app icon in system tray": "Afficher l'icône de l'application dans la barre d'état système",
"Show app unread badge": "Afficher le badge non lu de l'application",
"Show desktop notifications": "Afficher les notifications du bureau",
"Show downloaded files in file manager": "Afficher les fichiers téléchargés dans le gestionnaire de fichiers",
"Show sidebar": "Afficher la barre latérale",
"Start app at login": "Lancer l'application à la connexion",
"Switch to Next Organization": "Passer à l'organisation suivante",
"Switch to Previous Organization": "Passer à l'organisation précédente",
"These desktop app shortcuts extend the Zulip webapp's": "Ces raccourcis dapplications de bureau étendent les applications Web de Zulip.",
"This will delete all application data including all added accounts and preferences": "Cela supprimera toutes les données de l'application, y compris tous les comptes et préférences ajoutés.",
"Tip": "Pointe",
"Toggle DevTools for Active Tab": "Basculer DevTools pour l'onglet actif",
"Toggle DevTools for Zulip App": "Basculer DevTools pour l'application Zulip",
"Toggle DevTools for Zulip App": "Basculer DevTools pour Zulip App",
"Toggle Do Not Disturb": "Basculer ne pas déranger",
"Toggle Full Screen": "Basculer en plein écran",
"Toggle Sidebar": "Basculer la barre latérale",
"Toggle Tray Icon": "Toggle Tray Icône",
"Toggle Tray Icon": "Basculer l'icône du plateau",
"Tools": "Outils",
"Undo": "annuler",
"Upload": "Télécharger",
"Use system proxy settings (requires restart)": "Utiliser les paramètres proxy du système (nécessite un redémarrage)",
"View": "Vue",
"Window": "Fenêtre",
"View Shortcuts": "Afficher les raccourcis",
"Window": "La fenêtre",
"Window Shortcuts": "Raccourcis de la fenêtre",
"YES": "OUI",
"Zoom In": "Agrandir",
"Zoom Out": "Dézoomer",
"Zulip Help": "Aide Zulip"
"Zulip Help": "Aide Zulip",
"keyboard shortcuts": "Raccourcis clavier",
"script": "scénario"
}

View File

@@ -1,39 +1,118 @@
{
"About Zulip": "Zulip के बारे में",
"About Zulip": "जूलिप के बारे में",
"Actual Size": "वास्तविक आकार",
"Add Custom Certificates": "कस्टम प्रमाण पत्र जोड़ें",
"Add Organization": "संगठन जोड़ें",
"Add a Zulip organization": "एक जूलिप संगठन जोड़ें",
"Add custom CSS": "कस्टम CSS जोड़ें",
"Advanced": "उन्नत",
"All the connected organizations will appear here": "सभी जुड़े संगठन यहां दिखाई देंगे",
"Always start minimized": "हमेशा कम से कम शुरू करें",
"App Updates": "ऐप अपडेट",
"Appearance": "दिखावट",
"Application Shortcuts": "आवेदन शॉर्टकट",
"Are you sure you want to disconnect this organization?": "क्या आप वाकई इस संगठन को डिस्कनेक्ट करना चाहते हैं?",
"Auto hide Menu bar": "ऑटो मेनू मेनू छुपाएँ",
"Auto hide menu bar (Press Alt key to display)": "ऑटो छिपाने मेनू बार (प्रेस Alt कुंजी प्रदर्शित करने के लिए)",
"Back": "वापस",
"Bounce dock on new private message": "नए निजी संदेश पर बाउंस डॉक",
"Certificate file": "प्रमाणपत्र फ़ाइल",
"Change": "परिवर्तन",
"Check for Updates": "अद्यतन के लिए जाँच",
"Close": "बंद करे",
"Connect": "जुडिये",
"Connect to another organization": "किसी अन्य संगठन से कनेक्ट करें",
"Connected organizations": "जुड़े हुए संगठन",
"Copy": "प्रतिलिपि",
"Copy Zulip URL": "Zulip URL को कॉपी करें",
"Create a new organization": "एक नया संगठन बनाएं",
"Cut": "कट गया",
"Default download location": "डिफ़ॉल्ट डाउनलोड स्थान",
"Delete": "हटाना",
"Desktop App Settings": "डेस्कटॉप ऐप सेटिंग्स",
"Desktop Notifications": "डेस्कटॉप सूचनाएं",
"Desktop Settings": "डेस्कटॉप सेटिंग्स",
"Disconnect": "डिस्कनेक्ट",
"Download App Logs": "ऐप लॉग डाउनलोड करें",
"Edit": "संपादित करें",
"Edit Shortcuts": "शॉर्टकट संपादित करें",
"Enable auto updates": "ऑटो अपडेट सक्षम करें",
"Enable error reporting (requires restart)": "त्रुटि रिपोर्टिंग सक्षम करें (पुनरारंभ की आवश्यकता है)",
"Enable spellchecker (requires restart)": "वर्तनी जाँचक सक्षम करें (पुनः आरंभ करने की आवश्यकता है)",
"Factory Reset": "नए यंत्र जैसी सेटिंग",
"File": "फ़ाइल",
"Find accounts": "खाते ढूंढे",
"Find accounts by email": "ईमेल द्वारा खाते ढूंढें",
"Flash taskbar on new message": "नए संदेश पर फ्लैश टास्कबार",
"Forward": "आगे",
"Hard Reload": "कठिन पुन: लोड करें",
"Functionality": "कार्यक्षमता",
"General": "सामान्य",
"Get beta updates": "बीटा अपडेट प्राप्त करें",
"Hard Reload": "हार्ड रीलोड",
"Help": "मदद",
"Help Center": "सहायता केंद्र",
"History": "इतिहास",
"History Shortcuts": "इतिहास शॉर्टकट",
"Keyboard Shortcuts": "कुंजीपटल अल्प मार्ग",
"Log Out": "लोग आउट",
"Log Out of Organization": "संगठन से बाहर प्रवेश करें",
"Manual proxy configuration": "मैनुअल प्रॉक्सी कॉन्फ़िगरेशन",
"Minimize": "छोटा करना",
"Mute all sounds from Zulip": "ज़ूलिप से सभी ध्वनियों को म्यूट करें",
"NO": "नहीं",
"Network": "नेटवर्क",
"OR": "या",
"Organization URL": "संगठन का URL",
"Organizations": "संगठन",
"Paste": "चिपकाएं",
"Paste and Match Style": "चिपकाएं और शैली का मिलान करें",
"Paste and Match Style": "पेस्ट और मैच स्टाइल",
"Proxy": "प्रतिनिधि",
"Proxy bypass rules": "प्रॉक्सी बायपास नियम",
"Proxy rules": "प्रॉक्सी नियम",
"Quit": "छोड़ना",
"Quit Zulip": "ज़ुल्फ़ छोड़ो",
"Redo": "फिर से करना",
"Release Notes": "रिलीज नोट्स",
"Reload": "सीमा से अधिक लादना",
"Report an issue...": "मामले की रिपोर्ट करें...",
"Reset App Settings": "ऐप ेटिंग्स रीसेट करें",
"Report an Issue": "मामले की रिपोर्ट करें",
"Reset App Data": "ऐप ेट रीसेट करें",
"Reset App Settings": "ऐप सेटिंग रीसेट करें",
"Reset Application Data": "एप्लिकेशन डेटा रीसेट करें",
"Save": "बचाना",
"Select All": "सभी का चयन करे",
"Settings": "सेटिंग्स",
"Shortcuts": "शॉर्टकट",
"Show App Logs": "ऐप लॉग दिखाएं",
"Show app icon in system tray": "सिस्टम ट्रे में ऐप आइकन दिखाएं",
"Show app unread badge": "ऐप अपठित बैज दिखाएं",
"Show desktop notifications": "डेस्कटॉप सूचनाएं दिखाएं",
"Show downloaded files in file manager": "फ़ाइल प्रबंधक में डाउनलोड की गई फ़ाइलें दिखाएं",
"Show sidebar": "साइडबार दिखाओ",
"Start app at login": "लॉगिन पर ऐप शुरू करें",
"Switch to Next Organization": "अगला संगठन पर स्विच करें",
"Switch to Previous Organization": "पिछले संगठन पर स्विच करें",
"These desktop app shortcuts extend the Zulip webapp's": "ये डेस्कटॉप ऐप शॉर्टकट Zulip webapp's का विस्तार करते हैं",
"This will delete all application data including all added accounts and preferences": "यह सभी जोड़े गए खातों और वरीयताओं सहित सभी एप्लिकेशन डेटा को हटा देगा",
"Tip": "टिप",
"Toggle DevTools for Active Tab": "सक्रिय टैब के लिए DevTools टॉगल करें",
"Toggle DevTools for Zulip App": "Zulip ऐप के लिए DevTools टॉगल करें",
"Toggle DevTools for Zulip App": "Zulip App के लिए DevTools टॉगल करें",
"Toggle Do Not Disturb": "टॉगल डू नॉट डिस्टर्ब",
"Toggle Full Screen": "पूर्णस्क्रीन चालू करें",
"Toggle Sidebar": "साइडबार टॉगल करें",
"Toggle Tray Icon": "ट्रे आइकन टॉगल करें",
"Toggle Sidebar": "टॉगल साइडबार",
"Toggle Tray Icon": "टॉगल ट्रे आइकन",
"Tools": "उपकरण",
"Undo": "पूर्ववत करें",
"Upload": "अपलोड",
"Use system proxy settings (requires restart)": "सिस्टम प्रॉक्सी सेटिंग्स का उपयोग करें (पुनः आरंभ करने की आवश्यकता है)",
"View": "राय",
"View Shortcuts": "शॉर्टकट देखें",
"Window": "खिड़की",
"Window Shortcuts": "विंडो शॉर्टकट",
"YES": "हाँ",
"Zoom In": "ज़ूम इन",
"Zoom Out": "ज़ूम आउट",
"Zulip Help": "Zulip सहायता"
}
"Zulip Help": "Zulip मदद",
"keyboard shortcuts": "कुंजीपटल अल्प मार्ग",
"script": "लिपि",
"AddServer": "AddServer"
}

View File

@@ -1,39 +1,117 @@
{
"About Zulip": "A Zulipról",
"About Zulip": "Zulipról",
"Actual Size": "Valódi méret",
"Add Custom Certificates": "Egyéni tanúsítványok hozzáadása",
"Add Organization": "Szervezet hozzáadása",
"Add a Zulip organization": "Adjon hozzá egy Zulip szervezetet",
"Add custom CSS": "Egyéni CSS hozzáadása",
"Advanced": "Fejlett",
"All the connected organizations will appear here": "Itt megjelenik az összes kapcsolódó szervezet",
"Always start minimized": "Mindig kezdjen minimálisra",
"App Updates": "Alkalmazásfrissítések",
"Appearance": "Megjelenés",
"Application Shortcuts": "Alkalmazás parancsikonok",
"Are you sure you want to disconnect this organization?": "Biztosan le szeretné szüntetni ezt a szervezetet?",
"Auto hide Menu bar": "Automatikus elrejtése Menüsor",
"Auto hide menu bar (Press Alt key to display)": "Automatikus elrejtés a menüsorban (nyomja meg az Alt billentyűt a megjelenítéshez)",
"Back": "Hát",
"Bounce dock on new private message": "Ugrás a dokkolót az új privát üzenetre",
"Certificate file": "Tanúsítványfájl",
"Change": "változás",
"Check for Updates": "Frissítések keresése",
"Close": "Bezárás",
"Connect": "Csatlakozás",
"Connect to another organization": "Csatlakozás egy másik szervezethez",
"Connected organizations": "Kapcsolódó szervezetek",
"Copy": "Másolat",
"Copy Zulip URL": "Zulip URL másolása",
"Create a new organization": "Hozzon létre egy új szervezetet",
"Cut": "Vágott",
"Default download location": "Alapértelmezett letöltési hely",
"Delete": "Töröl",
"Desktop App Settings": "Asztali alkalmazások beállításai",
"Desktop App Settings": "Asztali alkalmazás beállításai",
"Desktop Notifications": "Asztali értesítések",
"Desktop Settings": "Asztali beállítások",
"Disconnect": "szétkapcsol",
"Download App Logs": "Alkalmazásnaplók letöltése",
"Edit": "szerkesztése",
"Edit Shortcuts": "Gyorsbillentyűk szerkesztése",
"Enable auto updates": "Automatikus frissítések engedélyezése",
"Enable error reporting (requires restart)": "Hibajelentés engedélyezése (újraindítás szükséges)",
"Enable spellchecker (requires restart)": "Helyesírás-ellenőrző engedélyezése (újraindítás szükséges)",
"Factory Reset": "Gyári beállítások visszaállítása",
"File": "fájl",
"Find accounts": "Fiókok keresése",
"Find accounts by email": "Fiókok keresése e-mailben",
"Flash taskbar on new message": "Flash-tálca az új üzeneten",
"Forward": "Előre",
"Functionality": "funkcionalitás",
"General": "Tábornok",
"Get beta updates": "Béta frissítéseket kap",
"Hard Reload": "Hard Reload",
"Help": "Segítség",
"Help Center": "Segítség Központ",
"History": "Történelem",
"History Shortcuts": "Előzmények parancsikonjai",
"Keyboard Shortcuts": "Gyorsbillentyűket",
"Log Out": "Kijelentkezés",
"Log Out of Organization": "Jelentkezzen ki a szervezetből",
"Manual proxy configuration": "Manuális proxy konfiguráció",
"Minimize": "Kis méret",
"Mute all sounds from Zulip": "A Zulip összes hangjának elnémítása",
"NO": "NEM",
"Network": "Hálózat",
"OR": "VAGY",
"Organization URL": "A szervezet URL-címe",
"Organizations": "szervezetek",
"Paste": "Paszta",
"Paste and Match Style": "Beillesztés és stílusok illesztése",
"Paste and Match Style": "Beillesztés és egyezés stílusa",
"Proxy": "Meghatalmazott",
"Proxy bypass rules": "Proxy bypass szabályok",
"Proxy rules": "Proxy szabályok",
"Quit": "quit",
"Quit Zulip": "Lépjen ki Zulipból",
"Redo": "Újra",
"Release Notes": "Kiadási megjegyzések",
"Reload": "Reload",
"Report an issue...": "Hibajelentés ...",
"Reset App Settings": "Az alkalmazás beállításainak visszaállítása",
"Report an Issue": "Jelentés bejelentése",
"Reset App Data": "Az App Data visszaállítása",
"Reset App Settings": "Alkalmazásbeállítások visszaállítása",
"Reset Application Data": "Alkalmazásadatok visszaállítása",
"Save": "Mentés",
"Select All": "Mindet kiválaszt",
"Settings": "Beállítások",
"Shortcuts": "parancsikonok",
"Show App Logs": "Alkalmazásnaplók megjelenítése",
"Toggle DevTools for Active Tab": "A DevTools aktiválása az Aktív laphoz",
"Toggle DevTools for Zulip App": "Kapcsolja a DevTools-ot a Zulip App-hoz",
"Show app icon in system tray": "Az alkalmazás ikonjának megjelenítése a tálcán",
"Show app unread badge": "Az alkalmazás olvasatlan jelvényének megjelenítése",
"Show desktop notifications": "Az asztali értesítések megjelenítése",
"Show downloaded files in file manager": "A letöltött fájlok megjelenítése a fájlkezelőben",
"Show sidebar": "Az oldalsáv megjelenítése",
"Start app at login": "Indítsa el az alkalmazást bejelentkezéskor",
"Switch to Next Organization": "Váltás a következő szervezetre",
"Switch to Previous Organization": "Váltás az előző szervezetre",
"These desktop app shortcuts extend the Zulip webapp's": "Ezek az asztali alkalmazás gyorsbillentyűk kiterjesztik a Zulip webapp-ot",
"This will delete all application data including all added accounts and preferences": "Ez törli az összes alkalmazásadatot, beleértve az összes hozzáadott fiókot és beállítást",
"Tip": "Tipp",
"Toggle DevTools for Active Tab": "Váltás DevTools az aktív fülre",
"Toggle DevTools for Zulip App": "A DevTools váltása a Zulip App számára",
"Toggle Do Not Disturb": "Váltás Ne zavarja",
"Toggle Full Screen": "Teljes képernyőre váltás",
"Toggle Sidebar": "Oldalsáv átkapcsolása",
"Toggle Tray Icon": "Tálcaikon átkapcsolása",
"Toggle Sidebar": "Az oldalsáv átkapcsolása",
"Toggle Tray Icon": "Átváltás a tálca ikonjára",
"Tools": "Eszközök",
"Undo": "kibont",
"Upload": "Feltöltés",
"Use system proxy settings (requires restart)": "Rendszer proxy beállításainak használata (újraindítás szükséges)",
"View": "Kilátás",
"View Shortcuts": "Gyorsbillentyűk megtekintése",
"Window": "Ablak",
"Zoom In": "Nagyítás",
"Window Shortcuts": "Ablak gyorsbillentyűk",
"YES": "IGEN",
"Zoom In": "Ráközelíteni",
"Zoom Out": "Kicsinyítés",
"Zulip Help": "Zulip Súgó"
"Zulip Help": "Zulip Súgó",
"keyboard shortcuts": "gyorsbillentyűket",
"script": "forgatókönyv"
}

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