Merge pull request #1 from RocketChat/develop

asdf
This commit is contained in:
gerbsen 2018-09-25 18:13:57 +02:00 committed by GitHub
commit e1865861de
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
74 changed files with 4894 additions and 3242 deletions

View file

@ -8,9 +8,12 @@ charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.{js,coffee,html,less,json}]
[*.{js,coffee,html,less,css,json}]
indent_style = tab
[*.i18n.json]
indent_style = space
indent_size = 4
indent_size = 2
[*.md]
trim_trailing_whitespace = false

View file

@ -1,6 +1,5 @@
node_modules
app
e2e
src/public/lib/bugsnag.js
src/public/vendor/*
scripts/istanbul-reporter.js
/node_modules
/app
/src/public/lib/bugsnag.js
/src/public/vendor/*
/scripts/istanbul-reporter.js

9
.eslintrc Normal file
View file

@ -0,0 +1,9 @@
{
"extends": [
"@rocket.chat/eslint-config"
],
"globals": {
"_": false,
"Bugsnag": false
}
}

View file

@ -1,82 +0,0 @@
{
"parserOptions": {
"sourceType": "module"
},
"env": {
"browser": true,
"commonjs": true,
"es6": true,
"node": true,
"jquery": true
},
"rules": {
"no-var": 2,
"prefer-const": 2,
"no-multi-spaces": 2,
"no-eval": 2,
"no-extend-native": 2,
"no-multi-str": 2,
"no-use-before-define": 2,
"no-const-assign": 2,
"no-cond-assign": 2,
"no-constant-condition": 2,
"no-control-regex": 2,
"no-debugger": 2,
"no-delete-var": 2,
"no-dupe-keys": 2,
"no-dupe-args": 2,
"no-duplicate-case": 2,
"no-empty": 2,
"no-empty-character-class": 2,
"no-ex-assign": 2,
"no-extra-boolean-cast": 2,
"no-extra-semi": 2,
"no-fallthrough": 2,
"no-func-assign": 2,
"no-inner-declarations": [2, "functions"],
"no-invalid-regexp": 2,
"no-irregular-whitespace": 2,
"no-mixed-spaces-and-tabs": 2,
"no-sparse-arrays": 2,
"no-negated-in-lhs": 2,
"no-obj-calls": 2,
"no-octal": 2,
"no-redeclare": 2,
"no-regex-spaces": 2,
"no-undef": 2,
"no-unreachable": 2,
"no-unused-vars": [2, {
"vars": "all",
"args": "after-used"
}],
"no-lonely-if": 2,
"no-trailing-spaces": 2,
"complexity": [1, 31],
"space-in-parens": [2, "never"],
"space-before-function-paren": [2, "always"],
"space-before-blocks": [2, "always"],
"indent": [2, 4, {"SwitchCase": 1}],
"keyword-spacing": 2,
"block-spacing": 2,
"brace-style": [2, "1tbs", { "allowSingleLine": true }],
"computed-property-spacing": 2,
"comma-spacing": 2,
"comma-style": 2,
"guard-for-in": 2,
"wrap-iife": 2,
"block-scoped-var": 2,
"curly": [2, "all"],
"eqeqeq": [2, "allow-null"],
"new-cap": [2, {
"capIsNewExceptions": ["Match.Optional", "Match.Maybe", "Match.ObjectIncluding"]
}],
"use-isnan": 2,
"valid-typeof": 2,
"linebreak-style": [2, "unix"],
"semi": [2, "always"]
},
"globals": {
"_" : false,
"Bugsnag" : false
}
}

View file

@ -10,6 +10,7 @@ Thanks for opening an issue! A few things to keep in mind:
## My Setup
- Operating System:
- App Version:
- Installation type: <!-- exe, dmg, package manager etc. -->
<!-- Answer questions by putting x in box, e.g. [x] -->
- [ ] I have tested with the latest version

View file

@ -1,10 +1,11 @@
<!-- INSTRUCTION: Your Pull Request name should start with one of the following tags -->
<!-- [NEW] For new features -->
<!-- [FIX] For bug fixes -->
<!-- [IMPROVE] For non-breaking changes that enhance some existing feature -->
<!-- [BREAK] For pull requests including breaking changes -->
<!-- INSTRUCTION: Keep the line below to notify all core developers about this new PR -->
@RocketChat/desktopapp
@RocketChat/electron
<!-- INSTRUCTION: Inform the issue number that this PR closes, or remove the line below -->
Closes #ISSUE_NUMBER

31
.github/changelog.js vendored
View file

@ -3,14 +3,10 @@ const { resolve } = require('path');
const gitUrl = 'https://github.com/RocketChat/Rocket.Chat.Electron';
const parserOpts = {
mergePattern: /^(Merge pull request #(\d+) from (.*)|(.*) \(#(\d+)\))$/,
mergeCorrespondence: ['_', 'pr', 'source', 'subject_squashed', 'pr_squashed'],
headerPattern: /^(\[([A-z]+)\] )?(.*)$/m,
headerCorrespondence: [
'stype',
'type',
'subject'
],
mergePattern: /^Merge pull request #(.*) from .*$/,
mergeCorrespondence: ['pr']
headerCorrespondence: ['stype', 'type', 'subject'],
};
const LABELS = {
@ -22,6 +18,10 @@ const LABELS = {
title: 'New Features',
collapse: false
},
IMPROVE: {
title: 'Improvements',
collapse: false
},
FIX: {
title: 'Bug Fixes',
collapse: false
@ -40,10 +40,25 @@ const sort = Object.keys(LABELS);
const writerOpts = {
transform: (commit) => {
if (!commit.pr) {
if (!commit.pr && !commit.pr_squashed) {
return;
}
if (commit.pr_squashed) {
commit.pr = commit.pr_squashed;
const matches = /^(\[([A-z]+)\] )?(.*)$/m.exec(commit.subject_squashed);
if (matches) {
commit.stype = matches[1];
commit.type = matches[2];
commit.subject = matches[3];
} else {
commit.subject = commit.subject_squashed;
}
delete commit.pr_squashed;
delete commit.subject_squashed;
}
commit.type = (commit.type || 'OTHER').toUpperCase();
if (LABELS[commit.type] == null) {
return;

9
.gitignore vendored
View file

@ -1,14 +1,13 @@
node_modules
.DS_Store
Thumbs.db
*.log
*.autogenerated
# ignore everything in 'app' folder what had been generated from 'src' folder
/node_modules
/package-lock.json
/app
/coverage
/dist
/releases
# We are using YARN
package-lock.json
/Development.provisionprofile

View file

@ -20,23 +20,33 @@ matrix:
- os: osx
osx_image: xcode8.3
before_install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo snap install snapcraft --classic; fi
install:
- yarn
script:
- if [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then export CSC_IDENTITY_AUTO_DISCOVERY=false; fi
- yarn release
branches:
only:
- master
- develop
- hotfix
- /^hotfix\/.*$/
- /^release\/.*$/
notifications:
email:
on_success: never
on_failure: change
before_install:
- if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then sudo snap install snapcraft --classic; fi
install:
- yarn
- yarn list
script:
- if [[ "$TRAVIS_PULL_REQUEST" != "false" ]]; then export CSC_IDENTITY_AUTO_DISCOVERY=false; fi
# e2e tests should be performed only on Linux
- |
if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then
export DISPLAY=:99.0
sh -e /etc/init.d/xvfb start
sleep 3
yarn e2e
fi
- yarn release

View file

@ -1,3 +1,67 @@
<a name="2.13.3"></a>
## 2.13.3 (2018-09-18)
### Improvements
- [#881](https://github.com/RocketChat/Rocket.Chat.Electron/pull/881) End-to-end tests
- [#882](https://github.com/RocketChat/Rocket.Chat.Electron/pull/882) Set new DMG background
### Bug Fixes
- [#884](https://github.com/RocketChat/Rocket.Chat.Electron/pull/884) Show tray icon status again
- [#875](https://github.com/RocketChat/Rocket.Chat.Electron/pull/875) Toggled tray icon notifications
- [#880](https://github.com/RocketChat/Rocket.Chat.Electron/pull/880) Tray icon toggle crashes in MacOS
- [#869](https://github.com/RocketChat/Rocket.Chat.Electron/pull/869) Window state errors on save when antivirus software is present
<a name="2.13.2"></a>
## 2.13.2 (2018-09-10)
### Bug Fixes
- Dependencies updated
- Window state persistency triggering redefined
- AppId for Windows setups recovered
- Linux package names fixed
- Fixed multiple issues in provisioning profiles and entitlements for MacOS builds
<a name="2.13.1"></a>
## 2.13.1 (2018-08-30)
Fixes for MacOS and Windows builds.
<a name="2.13.0"></a>
# 2.13.0 (2018-08-27)
### New Features
- [#838](https://github.com/RocketChat/Rocket.Chat.Electron/pull/838) Russian translation
- [#837](https://github.com/RocketChat/Rocket.Chat.Electron/pull/837) Auto update fixes and settings enforcement
### Improvements
- [#821](https://github.com/RocketChat/Rocket.Chat.Electron/pull/821) Always force download of uploaded files
- [#824](https://github.com/RocketChat/Rocket.Chat.Electron/pull/824) Background process rearranged
### Bug Fixes
- [#817](https://github.com/RocketChat/Rocket.Chat.Electron/pull/817) Disabled update in builds for Mac App Store
- [#836](https://github.com/RocketChat/Rocket.Chat.Electron/pull/836) Window state persistency
- [#825](https://github.com/RocketChat/Rocket.Chat.Electron/pull/825) macOS menubar icon extra space removed
- [#835](https://github.com/RocketChat/Rocket.Chat.Electron/pull/835) Support On-Premise Jitsi screen sharing
- [#818](https://github.com/RocketChat/Rocket.Chat.Electron/pull/818) Fixed dock icon badge counter showing zero
<a name="2.12.1"></a>
## 2.12.1 (2018-08-14)

View file

@ -1,15 +1,20 @@
# Rocket.Chat Desktop App [![Build Status](https://img.shields.io/travis/RocketChat/Rocket.Chat.Electron/master.svg)](https://travis-ci.org/RocketChat/Rocket.Chat.Electron) [![Build status](https://ci.appveyor.com/api/projects/status/k72eq3gm42wt4j8b?svg=true)](https://ci.appveyor.com/project/RocketChat/rocket-chat-electron) [![Project Dependencies](https://david-dm.org/RocketChat/Rocket.Chat.Electron.svg)](https://david-dm.org/RocketChat/Rocket.Chat.Electron) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/54ebf39732d14cb19a1a992b46bd0da6)](https://www.codacy.com/app/RocketChat/Rocket-Chat-Electron?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=RocketChat/Rocket.Chat.Electron&amp;utm_campaign=Badge_Grade)
Rocket.Chat Desktop App [![Build Status](https://img.shields.io/travis/RocketChat/Rocket.Chat.Electron/master.svg)](https://travis-ci.org/RocketChat/Rocket.Chat.Electron) [![Build status](https://ci.appveyor.com/api/projects/status/k72eq3gm42wt4j8b/branch/master?svg=true)](https://ci.appveyor.com/project/RocketChat/rocket-chat-electron) [![Project Dependencies](https://david-dm.org/RocketChat/Rocket.Chat.Electron.svg)](https://david-dm.org/RocketChat/Rocket.Chat.Electron) [![Codacy Badge](https://api.codacy.com/project/badge/Grade/54ebf39732d14cb19a1a992b46bd0da6)](https://www.codacy.com/app/RocketChat/Rocket-Chat-Electron?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=RocketChat/Rocket.Chat.Electron&amp;utm_campaign=Badge_Grade)
===============================================================================
Desktop application for [Rocket.Chat](https://github.com/RocketChat/Rocket.Chat) available for macOS, Windows and Linux using [Electron](https://electronjs.org/).
Desktop application for [Rocket.Chat](https://github.com/RocketChat/Rocket.Chat) available for macOS, Windows and Linux using [Electron](http://electron.atom.io).
# Download
You can download the latest version from the [Releases](https://github.com/RocketChat/Rocket.Chat.Electron/releases/latest) page.
# Install
Launch the installer and follow the instructions to install.
## Windows Options
On Windows you can run a silent install by adding the `/S` flag. You can also add the options below:
- `/S` - Silent install
@ -49,12 +54,12 @@ The build process compiles all stuff from the `src` folder and puts it into the
Build process is founded upon [gulp](https://github.com/gulpjs/gulp) task runner and [rollup](https://github.com/rollup/rollup) bundler. There are two entry files for your code: `src/background.js` and `src/app.js`. Rollup will follow all `import` statements starting from those files and compile code of the whole dependency tree into one `.js` file for each entry point.
## Adding node modules
### Adding node modules
Remember to respect the split between `dependencies` and `devDependencies` in `package.json` file. Only modules listed in `dependencies` will be included into distributable app.
## Working with modules
### Working with modules
Thanks to [rollup](https://github.com/rollup/rollup) you can (and should) use ES6 modules for most code in `src` folder.
@ -70,32 +75,44 @@ const myStuff = require('./my_lib/my_stuff');
const { myFunction } = require('./App');
```
## Issues with Install
## Troubleshooting
### node-gyp
Follow the installation instruction on [node-gyp readme](https://github.com/nodejs/node-gyp#installation).
#### Ubuntu Install
You will need to install:
### Ubuntu
You will need to install the following packages:
```sh
build-essential
libevas-dev
libxss-dev
```
### Fedora Install
You will need to install:
### Fedora
You will need to install the following packages:
```sh
libX11
libXScrnSaver-devel
gcc-c++
```
#### Windows 7
### Windows 7
On Windows 7 you may have to follow option 2 of the [node-gyp install guide](https://github.com/nodejs/node-gyp#installation) and install Visual Studio
# Testing
## Unit tests
## Testing
### Unit tests
```
yarn test
@ -103,15 +120,18 @@ yarn test
Using [electron-mocha](https://github.com/jprichardson/electron-mocha) test runner with the [chai](http://chaijs.com/api/assert/) assertion library. This task searches for all files in `src` directory which respect pattern `*.spec.js`.
## End to end tests
### End to end tests
```
yarn e2e
```
Using [mocha](https://mochajs.org/) test runner and [spectron](http://electron.atom.io/spectron/). This task searches for all files in `e2e` directory which respect pattern `*.e2e.js`.
Using [mocha](https://mochajs.org/) test runner and [spectron](http://electron.atom.io/spectron/).
This task searches for all files in `src/e2e` directory which respect pattern `*.e2e.js`.
## Code coverage
### Code coverage
```
yarn coverage
@ -121,7 +141,8 @@ Using [istanbul](http://gotwarlost.github.io/istanbul/) code coverage tool.
You can set the reporter(s) by setting `ISTANBUL_REPORTERS` environment variable (defaults to `text-summary` and `html`). The report directory can be set with `ISTANBUL_REPORT_DIR` (defaults to `coverage`).
# Making a release
## Making a release
To package your app into an installer use command:
@ -131,13 +152,9 @@ yarn release
It will start the packaging process for operating system you are running this command on. Ready for distribution file will be outputted to `dist` directory.
Right now you can only create Windows installer when running Windows, the same is true for macOS. For Linux builds, you can use our [Docker image](https://hub.docker.com/r/rocketchat/electron.builder/) with the following commands:
```
docker run --rm -ti -v ${PWD}:/project -v ${PWD##*/}-node-modules:/project/node_modules -v ~/.electron:/root/.electron rocketchat/electron.builder /bin/bash -l -c "yarn && yarn release -- --x64 --ia32 --p never"
```
All packaging actions are handled by [electron-builder](https://github.com/electron-userland/electron-builder). It has a lot of [customization options](https://github.com/electron-userland/electron-builder/wiki/Options), which you can declare under ["build" key in package.json file](https://github.com/szwacz/electron-boilerplate/blob/master/package.json#L2).
# Default servers
The `servers.json` file will define what servers the client will connect to and will populate the server list in the sidebar, it contains a list of default servers which will be added the first time the user runs the app (or when all servers are removed from the list).
@ -149,33 +166,37 @@ The file syntax is as follows:
}
```
## Pre-Release Configuration
You can bundle a `servers.json` with the install package, the file should be located in the root of the project application (same level as the `package.json`). If the file is found, the initial "Connect to server" screen will be skipped and it will attempt to connect to the first server in the array that has been defined and drop the user right at the login screen. Note that the `servers.json` will only be checked if no other servers have already be added, even if you uninstall the app without removing older preferences, it will not be triggered again.
## Post-Install Configuration
If you can't (or don't want to) bundle the file inside the app, you can create a `servers.json` in the user preferences folder which will overwrite the packaged one. The file should be located in the `%APPDATA%/Rocket.Chat/` folder or the installation folder in case of a installation for all users (Windows only).
For Windows the full paths are:
```
~\Users\<username>\AppData\Roaming\Rocket.Chat\
~\Program Files\Rocket.Chat\Resources\
```
On MacOS the full path is:
```
~/Users/<username>/Library/Application Support/Rocket.Chat/
~/Applications/Rocket.Chat.app/Contents/Resources/
```
On Linux the full path is:
```
/home/<username>/.config/Rocket.Chat/
/opt/Rocket.Chat/resources/
```
# Useful links
http://developerthing.blogspot.com.br/2017/01/awesome-electron.html
# License

View file

@ -1,39 +1,27 @@
# build: off
image: Visual Studio 2017
environment:
nodejs_version: "8"
platform:
- x64
skip_tags: true
shallow_clone: true
clone_depth: 1
cache:
- "%LOCALAPPDATA%/Yarn"
- node_modules -> package.json
branches:
only:
- master
- develop
skip_tags: true
environment:
nodejs_version: "8"
cache:
- "%LOCALAPPDATA%/Yarn"
- node_modules -> package.json
- app/node_modules -> app/package.json
shallow_clone: true
clone_depth: 1
- /^hotfix\/.*$/
- /^release\/.*$/
install:
- ps: Install-Product node $env:nodejs_version $env:platform
- set CI=true
- yarn
test_script:
- node --version
- yarn --version
# - yarn test
# - yarn e2e
- yarn list
build_script:
- yarn release

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 151 KiB

View file

@ -14,5 +14,7 @@
<true/>
<key>com.apple.security.device.microphone</key>
<true/>
<key>com.apple.security.personal-information.location</key>
<true/>
</dict>
</plist>

View file

@ -3,7 +3,6 @@
CreateShortCut "$SMSTARTUP\Rocket.Chat.lnk" "$INSTDIR\Rocket.Chat.exe" ""
FunctionEnd
; Using the read me setting as an easy way to add an add to startup option
!define MUI_FINISHPAGE_SHOWREADME
!define MUI_FINISHPAGE_SHOWREADME_TEXT "Run at startup"
!define MUI_FINISHPAGE_SHOWREADME_FUNCTION AddToStartup
@ -22,12 +21,17 @@
!endif
!macro customInstall
; Required as electron-builder does not provide a way to specify it as of version 11.2.4
WriteRegStr SHCTX "${UNINSTALL_REGISTRY_KEY}" "DisplayIcon" '"$INSTDIR\resources\build\icon.ico"'
; Remove dangling reference of version 2.13.1
${If} $installMode == "all"
DeleteRegKey HKLM "Software\Microsoft\Windows\CurrentVersion\Uninstall\66bed7da-e601-54e6-b2e8-7be611d82556"
${Else}
DeleteRegKey HKCU "Software\Microsoft\Windows\CurrentVersion\Uninstall\66bed7da-e601-54e6-b2e8-7be611d82556"
${EndIf}
Delete "$SMSTARTUP\Rocket.Chat+.lnk"
!macroend
!macro customUnInstall
${IfNot} ${Silent}
Delete "$SMSTARTUP\Rocket.Chat.lnk"
${endif}
${EndIf}
!macroend

BIN
build/installerIcon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
build/installerSidebar.bmp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

BIN
build/uninstallerIcon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

View file

@ -1,14 +0,0 @@
import { expect } from 'chai';
import testUtils from './utils';
describe('application launch', function () {
beforeEach(testUtils.beforeEach);
afterEach(testUtils.afterEach);
it.skip('shows hello world text on screen after launch', function () {
return this.app.client.getText('#greet').then(function (text) {
expect(text).to.equal('Hello World!');
});
});
});

View file

@ -1,25 +0,0 @@
import electron from 'electron';
import { Application } from 'spectron';
var beforeEach = function () {
this.timeout(10000);
this.app = new Application({
path: electron,
args: ['.'],
startTimeout: 10000,
waitTimeout: 10000,
});
return this.app.start();
};
var afterEach = function () {
this.timeout(10000);
if (this.app && this.app.isRunning()) {
return this.app.stop();
}
};
export default {
beforeEach: beforeEach,
afterEach: afterEach,
};

99
electron-builder.json Normal file
View file

@ -0,0 +1,99 @@
{
"files": [
"app/**/*",
"node_modules/**/*",
"package.json"
],
"extraResources": [
"dictionaries/**/*",
"build/icon.ico",
"servers.json"
],
"appId": "chat.rocket",
"mac": {
"category": "public.app-category.productivity",
"target": [
"dmg",
"pkg",
"zip",
"mas"
],
"icon": "build/icon.icns",
"bundleVersion": "38",
"helperBundleId": "chat.rocket.electron.helper",
"type": "distribution",
"artifactName": "rocketchat-${version}.${ext}"
},
"dmg": {
"background": "build/background.png",
"contents": [
{
"type": "dir",
"x": 100,
"y": 211
},
{
"type": "link",
"path": "/Applications",
"x": 500,
"y": 211
}
]
},
"pkg": {
"isRelocatable": false,
"overwriteAction": "upgrade"
},
"mas": {
"entitlements": "build/entitlements.mas.plist",
"entitlementsInherit": "build/entitlements.mas.inherit.plist",
"artifactName": "rocketchat-${version}-mas.${ext}"
},
"win": {
"target": [
"nsis",
"appx"
],
"icon": "build/icon.ico"
},
"nsis": {
"oneClick": false,
"perMachine": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true,
"artifactName": "rocketchat-setup-${version}.${ext}"
},
"appx": {
"backgroundColor": "#2f343d",
"displayName": "Rocket.Chat",
"publisherDisplayName": "Rocket.Chat",
"languages": [
"en-US",
"en-GB",
"pt-BR"
],
"artifactName": "rocketchat-${version}-${arch}.${ext}"
},
"linux": {
"target": [
"tar.gz",
"deb",
"rpm",
"snap"
],
"executableName": "rocketchat-desktop",
"category": "GNOME;GTK;Network;InstantMessaging",
"desktop": {
"StartupWMClass": "Rocket.Chat",
"MimeType": "x-scheme-handler/rocketchat"
}
},
"publish": [
{
"provider": "github",
"owner": "RocketChat",
"repo": "Rocket.Chat.Electron",
"vPrefixedTagName": false
}
]
}

View file

@ -2,7 +2,7 @@
"name": "rocketchat",
"productName": "Rocket.Chat",
"description": "Rocket.Chat Native Cross-Platform Desktop Application via Electron.",
"version": "2.13.0-develop",
"version": "2.14.0-develop",
"author": "Rocket.Chat Support <support@rocket.chat>",
"copyright": "© 2018, Rocket.Chat",
"homepage": "https://rocket.chat",
@ -12,75 +12,15 @@
"type": "git",
"url": "https://github.com/RocketChat/Rocket.Chat.Electron.git"
},
"build": {
"appId": "chat.rocket",
"files": [
"app/**/*",
"node_modules/**/*",
"package.json"
],
"extraResources": [
"dictionaries/**/*",
"build/icon.ico",
"servers.json"
],
"mac": {
"bundleVersion": "30",
"helperBundleId": "chat.rocket.electron.helper",
"category": "public.app-category.productivity",
"target": [
"dmg",
"pkg",
"zip",
"mas"
]
},
"nsis": {
"include": "build/win/installer.nsh",
"oneClick": false,
"perMachine": false,
"allowElevation": true,
"allowToChangeInstallationDirectory": true
},
"appx": {
"backgroundColor": "#2f343d",
"displayName": "Rocket.Chat",
"publisherDisplayName": "Rocket.Chat",
"languages": [
"en-US",
"en-GB",
"pt-BR"
]
},
"linux": {
"desktop": {
"StartupWMClass": "Rocket.Chat",
"MimeType": "x-scheme-handler/rocketchat"
},
"category": "GNOME;GTK;Network;InstantMessaging",
"target": [
"tar.gz",
"deb",
"rpm",
"snap"
]
},
"publish": [
{
"provider": "github",
"owner": "RocketChat",
"repo": "Rocket.Chat.Electron",
"vPrefixedTagName": false
}
]
},
"scripts": {
"postinstall": "electron-builder install-app-deps",
"start": "gulp start",
"build": "gulp build-app",
"changelog": "conventional-changelog --config .github/changelog.js -i HISTORY.md -s",
"release": "gulp release --env=production",
"lint": "eslint .",
"release-dev": "gulp release --env=development",
"release-mas-dev": "build --publish never --mac mas-dev --c.mac.provisioningProfile=Development.provisionprofile",
"lint": "eslint src",
"pretest": "gulp build-unit-tests --env=test",
"test": "electron-mocha app/specs.js.autogenerated --renderer --require source-map-support/register",
"coverage": "npm test -- -R scripts/istanbul-reporter",
@ -89,35 +29,37 @@
},
"dependencies": {
"@paulcbetts/system-idle-time": "^1.0.4",
"electron-updater": "^3.1.1",
"fs-jetpack": "^2.1.0",
"electron-updater": "^3.1.2",
"fs-jetpack": "^2.1.1",
"lodash": "^4.17.10",
"spellchecker": "^3.4.4"
"spellchecker": "^3.5.0"
},
"optionalDependencies": {
"node-mac-notifier": "^1.0.1"
"node-mac-notifier": "^1.1.0"
},
"devDependencies": {
"@rocket.chat/eslint-config": "^0.1.2",
"chai": "^4.1.2",
"conventional-changelog-cli": "^2.0.5",
"electron": "^2.0.7",
"electron-builder": "^20.28.2",
"electron": "^2.0.10",
"electron-builder": "^20.28.4",
"electron-mocha": "^6.0.4",
"eslint": "^5.4.0",
"eslint": "^5.6.0",
"gulp": "^3.9.1",
"gulp-batch": "^1.0.5",
"gulp-less": "^4.0.1",
"gulp-plumber": "^1.2.0",
"gulp-rename": "^1.4.0",
"gulp-sequence": "^1.0.0",
"gulp-util": "^3.0.8",
"gulp-watch": "^5.0.1",
"istanbul": "^0.4.5",
"minimist": "^1.2.0",
"mocha": "^5.2.0",
"rollup": "^0.64.1",
"rollup": "^0.66.1",
"rollup-plugin-istanbul": "^2.0.1",
"spectron": "^3.8.0"
"rollup-plugin-json": "^3.1.0",
"run-sequence": "^2.2.1",
"spectron": "^4.0.0"
},
"devEngines": {
"node": ">=7.x",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 50 KiB

View file

@ -1,9 +0,0 @@
[Desktop Entry]
Name=Rocket.Chat
Comment=Rocket.Chat Native Cross-Platform Desktop Application via Electron.
Exec=rocketchat-desktop
Terminal=false
Type=Application
Icon=${SNAP}/meta/gui/icon.png
Categories=GNOME;GTK;Network;InstantMessaging
StartupWMClass=Rocket.Chat

View file

@ -1,38 +0,0 @@
name: rocketchat-desktop
version: 2.13.0-develop
summary: Rocket.Chat
description: Rocket.Chat Native Cross-Platform Desktop Application via Electron.
confinement: strict
grade: stable
icon: snap/gui/icon.png
apps:
rocketchat-desktop:
command: env TMPDIR=$XDG_RUNTIME_DIR desktop-launch $SNAP/rocketchat-desktop
plugs:
- home
- x11
- unity7
- browser-support
- network
- gsettings
- pulseaudio
- opengl
- camera
parts:
app:
plugin: dump
stage-packages:
- libnotify4
- libappindicator1
- libxtst6
- libnss3
- libxss1
- fontconfig-config
- gconf2
- libasound2
- pulseaudio
organize:
rocketchat: rocketchat-desktop
source: dist/linux-unpacked
after:
- desktop-glib-only

View file

@ -1,15 +1,15 @@
import './branding/branding.js';
import { start } from './scripts/start';
import { remote } from 'electron';
const app = remote.app;
const { app } = remote;
Bugsnag.metaData = {
// platformId: app.process.platform,
// platformArch: app.process.arch,
// electronVersion: app.process.versions.electron,
version: app.getVersion()
// platformVersion: cordova.platformVersion
// build: appInfo.build
// platformId: app.process.platform,
// platformArch: app.process.arch,
// electronVersion: app.process.versions.electron,
version: app.getVersion(),
// platformVersion: cordova.platformVersion
// build: appInfo.build
};
Bugsnag.appVersion = app.getVersion();

View file

@ -3,9 +3,9 @@ import querystring from 'querystring';
import url from 'url';
import jetpack from 'fs-jetpack';
import idle from '@paulcbetts/system-idle-time';
import { app, ipcMain, Menu } from 'electron';
import { app, ipcMain, BrowserWindow, Menu } from 'electron';
import { canUpdate, checkForUpdates } from './background/autoUpdate';
import autoUpdate from './background/autoUpdate';
import certificate from './background/certificate';
import { addServer, createMainWindow, getMainWindow } from './background/mainWindow';
import './background/screenshare';
@ -21,92 +21,110 @@ process.env.GOOGLE_API_KEY = 'AIzaSyADqUh_c1Qhji3Cp1NE43YrcpuPkmhXD-c';
const isMacOS = process.platform === 'darwin';
const unsetDefaultApplicationMenu = () => {
if (!isMacOS) {
Menu.setApplicationMenu(null);
return;
}
if (!isMacOS) {
Menu.setApplicationMenu(null);
return;
}
const emptyMenuTemplate = [{
submenu: [
{
label: i18n.__('Quit_App', app.getName()),
accelerator: 'CommandOrControl+Q',
click () {
app.quit();
}
}
]
}];
Menu.setApplicationMenu(Menu.buildFromTemplate(emptyMenuTemplate));
const emptyMenuTemplate = [{
submenu: [
{
label: i18n.__('Quit_App', app.getName()),
accelerator: 'CommandOrControl+Q',
click() {
app.quit();
},
},
],
}];
Menu.setApplicationMenu(Menu.buildFromTemplate(emptyMenuTemplate));
};
const setUserDataPath = () => {
const appName = app.getName();
const dirName = env.name === 'production' ? appName : `${ appName } (${ env.name })`;
const appName = app.getName();
const dirName = env.name === 'production' ? appName : `${ appName } (${ env.name })`;
app.setPath('userData', path.join(app.getPath('appData'), dirName));
app.setPath('userData', path.join(app.getPath('appData'), dirName));
};
const migrateOlderVersionUserData = () => {
const olderAppName = 'Rocket.Chat+';
const dirName = env.name === 'production' ? olderAppName : `${ olderAppName } (${ env.name })`;
const olderUserDataPath = path.join(app.getPath('appData'), dirName);
const olderAppName = 'Rocket.Chat+';
const dirName = env.name === 'production' ? olderAppName : `${ olderAppName } (${ env.name })`;
const olderUserDataPath = path.join(app.getPath('appData'), dirName);
try {
jetpack.copy(olderUserDataPath, app.getPath('userData'), { overwrite: true });
jetpack.remove(olderUserDataPath);
} catch (e) {
return;
}
try {
jetpack.copy(olderUserDataPath, app.getPath('userData'), { overwrite: true });
jetpack.remove(olderUserDataPath);
} catch (e) {
return;
}
};
const parseProtocolUrls = (args) =>
args.filter(arg => /^rocketchat:\/\/./.test(arg))
.map(uri => url.parse(uri))
.map(({ hostname, pathname, query }) => {
const { insecure } = querystring.parse(query);
return `${ insecure === 'true' ? 'http' : 'https' }://${ hostname }${ pathname || '' }`;
});
args.filter((arg) => /^rocketchat:\/\/./.test(arg))
.map((uri) => url.parse(uri))
.map(({ hostname, pathname, query }) => {
const { insecure } = querystring.parse(query);
return `${ insecure === 'true' ? 'http' : 'https' }://${ hostname }${ pathname || '' }`;
});
const addServers = (protocolUrls) => parseProtocolUrls(protocolUrls)
.forEach(serverUrl => addServer(serverUrl));
.forEach((serverUrl) => addServer(serverUrl));
const isSecondInstance = app.makeSingleInstance((argv) => {
addServers(argv.slice(2));
addServers(argv.slice(2));
});
if (isSecondInstance) {
app.quit();
if (isSecondInstance && !process.mas) {
app.quit();
}
// macOS only
app.on('open-url', (event, url) => {
event.preventDefault();
addServers([ url ]);
event.preventDefault();
addServers([url]);
});
app.on('ready', () => {
unsetDefaultApplicationMenu();
setUserDataPath();
migrateOlderVersionUserData();
unsetDefaultApplicationMenu();
setUserDataPath();
migrateOlderVersionUserData();
if (!app.isDefaultProtocolClient('rocketchat')) {
app.setAsDefaultProtocolClient('rocketchat');
}
if (!app.isDefaultProtocolClient('rocketchat')) {
app.setAsDefaultProtocolClient('rocketchat');
}
createMainWindow();
createMainWindow();
getMainWindow().then(mainWindow => certificate.initWindow(mainWindow));
getMainWindow().then((mainWindow) => certificate.initWindow(mainWindow));
if (canUpdate()) {
checkForUpdates();
}
autoUpdate();
});
app.on('window-all-closed', () => {
app.quit();
app.quit();
});
ipcMain.on('getSystemIdleTime', (event) => {
event.returnValue = idle.getIdleTime();
event.returnValue = idle.getIdleTime();
});
ipcMain.on('show-about-dialog', () => {
getMainWindow().then((mainWindow) => {
const win = new BrowserWindow({
title: i18n.__('About', app.getName()),
parent: mainWindow,
width: 400,
height: 300,
type: 'toolbar',
resizable: false,
maximizable: false,
minimizable: false,
center: true,
show: false,
});
win.setMenuBarVisibility(false);
win.once('ready-to-show', () => win.show());
win.loadURL(`file://${ __dirname }/public/about.html`);
});
});

View file

@ -3,139 +3,178 @@ import { autoUpdater } from 'electron-updater';
import jetpack from 'fs-jetpack';
import i18n from '../i18n/index.js';
const installDir = jetpack.cwd(app.getAppPath());
const appDir = jetpack.cwd(app.getAppPath());
const userDataDir = jetpack.cwd(app.getPath('userData'));
const updateStoreFile = 'update.json';
const updateSettingsFileName = 'update.json';
const loadUpdateSettings = (dir) => {
try {
return dir.read(updateSettingsFileName, 'json') || {};
} catch (error) {
console.error(error);
return {};
}
};
const appUpdateSettings = loadUpdateSettings(appDir);
const userUpdateSettings = loadUpdateSettings(userDataDir);
const updateSettings = (() => {
const defaultUpdateSettings = { autoUpdate: true };
if (appUpdateSettings.forced) {
return Object.assign({}, defaultUpdateSettings, appUpdateSettings);
} else {
return Object.assign({}, defaultUpdateSettings, appUpdateSettings, userUpdateSettings);
}
})();
delete updateSettings.forced;
const saveUpdateSettings = () => {
if (appUpdateSettings.forced) {
return;
}
userDataDir.write(updateSettingsFileName, userUpdateSettings, { atomic: true });
};
let checkForUpdatesEvent;
autoUpdater.autoDownload = false;
let updateFile = {};
try {
const installUpdateFile = installDir.read(updateStoreFile, 'json');
const userUpdateFile = userDataDir.read(updateStoreFile, 'json');
updateFile = Object.assign({}, installUpdateFile, userUpdateFile);
} catch (err) {
console.error(err);
function updateDownloaded() {
dialog.showMessageBox({
title: i18n.__('Update_ready'),
message: i18n.__('Update_ready_message'),
buttons: [
i18n.__('Update_Install_Later'),
i18n.__('Update_Install_Now'),
],
defaultId: 1,
}, (response) => {
if (response === 0) {
dialog.showMessageBox({
title: i18n.__('Update_installing_later'),
message: i18n.__('Update_installing_later_message'),
});
} else {
autoUpdater.quitAndInstall();
setTimeout(() => app.quit(), 1000);
}
});
}
function updateDownloaded () {
dialog.showMessageBox({
title: i18n.__('Update_ready'),
message: i18n.__('Update_ready_message'),
buttons: [
i18n.__('Update_Install_Later'),
i18n.__('Update_Install_Now')
],
defaultId: 1
}, (response) => {
if (response === 0) {
dialog.showMessageBox({
title: i18n.__('Update_installing_later'),
message: i18n.__('Update_installing_later_message')
});
} else {
autoUpdater.quitAndInstall();
setTimeout(() => app.quit(), 1000);
}
});
function updateNotAvailable() {
if (checkForUpdatesEvent) {
checkForUpdatesEvent.sender.send('update-result', false);
checkForUpdatesEvent = null;
}
}
function updateNotAvailable () {
if (checkForUpdatesEvent) {
checkForUpdatesEvent.sender.send('update-result', false);
checkForUpdatesEvent = null;
}
function updateAvailable({ version }) {
if (checkForUpdatesEvent) {
checkForUpdatesEvent.sender.send('update-result', true);
checkForUpdatesEvent = null;
} else if (updateSettings.skip === version) {
return;
}
let window = new BrowserWindow({
title: i18n.__('Update_Available'),
width: 600,
height: 330,
show : false,
center: true,
resizable: false,
maximizable: false,
minimizable: false,
});
window.loadURL(`file://${ __dirname }/public/update.html`);
window.setMenuBarVisibility(false);
window.webContents.on('did-finish-load', () => {
window.webContents.send('new-version', version);
window.show();
});
ipcMain.once('update-response', (e, type) => {
switch (type) {
case 'skip':
userUpdateSettings.skip = version;
saveUpdateSettings();
dialog.showMessageBox({
title: i18n.__('Update_skip'),
message: i18n.__('Update_skip_message'),
}, () => window.close());
break;
case 'remind':
dialog.showMessageBox({
title: i18n.__('Update_remind'),
message: i18n.__('Update_remind_message'),
}, () => window.close());
break;
case 'update':
dialog.showMessageBox({
title: i18n.__('Update_downloading'),
message: i18n.__('Update_downloading_message'),
}, () => window.close());
autoUpdater.downloadUpdate();
break;
}
});
window.on('closed', () => {
window = null;
ipcMain.removeAllListeners('update-response');
});
}
function updateAvailable ({version}) {
if (checkForUpdatesEvent) {
checkForUpdatesEvent.sender.send('update-result', true);
checkForUpdatesEvent = null;
} else if (updateFile.skip === version) {
return;
}
export const canUpdate = () =>
(process.platform === 'linux' && Boolean(process.env.APPIMAGE)) ||
(process.platform === 'win32' && !process.windowsStore) ||
(process.platform === 'darwin' && !process.mas);
let window = new BrowserWindow({
title: i18n.__('Update_Available'),
width: 600,
height: 330,
show : false,
center: true,
resizable: false,
maximizable: false,
minimizable: false
});
export const canAutoUpdate = () => updateSettings.autoUpdate !== false;
window.loadURL(`file://${__dirname}/public/update.html`);
window.setMenuBarVisibility(false);
export const canSetAutoUpdate = () => !appUpdateSettings.forced || appUpdateSettings.autoUpdate !== false;
window.webContents.on('did-finish-load', () => {
window.webContents.send('new-version', version);
window.show();
});
export const setAutoUpdate = (canAutoUpdate) => {
if (!canSetAutoUpdate()) {
return;
}
ipcMain.once('update-response', (e, type) => {
switch (type) {
case 'skip':
updateFile.skip = version;
userDataDir.write(updateStoreFile, updateFile, { atomic: true });
dialog.showMessageBox({
title: i18n.__('Update_skip'),
message: i18n.__('Update_skip_message')
}, () => window.close());
break;
case 'remind':
dialog.showMessageBox({
title: i18n.__('Update_remind'),
message: i18n.__('Update_remind_message')
}, () => window.close());
break;
case 'update':
dialog.showMessageBox({
title: i18n.__('Update_downloading'),
message: i18n.__('Update_downloading_message')
}, () => window.close());
autoUpdater.downloadUpdate();
break;
}
});
window.on('closed', () => {
window = null;
ipcMain.removeAllListeners('update-response');
});
}
function canUpdate () {
return !process.mas;
}
function checkForUpdates () {
autoUpdater.on('update-available', updateAvailable);
autoUpdater.on('update-not-available', updateNotAvailable);
autoUpdater.on('update-downloaded', updateDownloaded);
// Event from about window
ipcMain.on('check-for-updates', (e, autoUpdate) => {
if (autoUpdate === true || autoUpdate === false) {
updateFile.autoUpdate = autoUpdate;
userDataDir.write(updateStoreFile, updateFile, { atomic: true });
} else if (autoUpdate === 'auto') {
e.returnValue = updateFile.autoUpdate !== false;
} else {
checkForUpdatesEvent = e;
autoUpdater.checkForUpdates();
}
});
if (updateFile.autoUpdate !== false) {
autoUpdater.checkForUpdates();
}
}
export {
canUpdate,
checkForUpdates
updateSettings.autoUpdate = userUpdateSettings.autoUpdate = Boolean(canAutoUpdate);
saveUpdateSettings();
};
ipcMain.on('can-update', (event) => {
event.returnValue = canUpdate();
});
ipcMain.on('can-auto-update', (event) => {
event.returnValue = canAutoUpdate();
});
ipcMain.on('can-set-auto-update', (event) => {
event.returnValue = canSetAutoUpdate();
});
ipcMain.on('set-auto-update', (event, canAutoUpdate) => {
setAutoUpdate(canAutoUpdate);
});
autoUpdater.autoDownload = false;
autoUpdater.on('update-available', updateAvailable);
autoUpdater.on('update-not-available', updateNotAvailable);
autoUpdater.on('update-downloaded', updateDownloaded);
ipcMain.on('check-for-updates', (event) => {
if (canAutoUpdate() && canUpdate()) {
checkForUpdatesEvent = event;
autoUpdater.checkForUpdates();
}
});
export default () => {
if (canAutoUpdate() && canUpdate()) {
autoUpdater.checkForUpdates();
}
};

View file

@ -4,108 +4,108 @@ import url from 'url';
import i18n from '../i18n/index.js';
class CertificateStore {
initWindow (win) {
this.storeFileName = 'certificate.json';
this.userDataDir = jetpack.cwd(app.getPath('userData'));
initWindow(win) {
this.storeFileName = 'certificate.json';
this.userDataDir = jetpack.cwd(app.getPath('userData'));
this.load();
this.load();
// Don't ask twice for same cert if loading multiple urls
this.queued = {};
// Don't ask twice for same cert if loading multiple urls
this.queued = {};
this.window = win;
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
event.preventDefault();
if (this.isTrusted(url, certificate)) {
callback(true);
return;
}
this.window = win;
app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
event.preventDefault();
if (this.isTrusted(url, certificate)) {
callback(true);
return;
}
if (this.queued[certificate.fingerprint]) {
this.queued[certificate.fingerprint].push(callback);
// Call the callback after approved/rejected
return;
} else {
this.queued[certificate.fingerprint] = [callback];
}
if (this.queued[certificate.fingerprint]) {
this.queued[certificate.fingerprint].push(callback);
// Call the callback after approved/rejected
return;
} else {
this.queued[certificate.fingerprint] = [callback];
}
let detail = `URL: ${url}\nError: ${error}`;
if (this.isExisting(url)) {
detail = i18n.__('Certificate_error_different', detail);
}
let detail = `URL: ${ url }\nError: ${ error }`;
if (this.isExisting(url)) {
detail = i18n.__('Certificate_error_different', detail);
}
dialog.showMessageBox(this.window, {
title: i18n.__('Certificate_error'),
message: i18n.__('Certificate_error_message', certificate.issuerName),
detail: detail,
type: 'warning',
buttons: [
i18n.__('Yes'),
i18n.__('No')
],
cancelId: 1
}, (response) => {
if (response === 0) {
this.add(url, certificate);
this.save();
if (webContents.getURL().indexOf('file://') === 0) {
webContents.send('certificate-reload', url);
}
}
//Call all queued callbacks with result
this.queued[certificate.fingerprint].forEach(cb => cb(response === 0));
delete this.queued[certificate.fingerprint];
});
});
}
dialog.showMessageBox(this.window, {
title: i18n.__('Certificate_error'),
message: i18n.__('Certificate_error_message', certificate.issuerName),
detail,
type: 'warning',
buttons: [
i18n.__('Yes'),
i18n.__('No'),
],
cancelId: 1,
}, (response) => {
if (response === 0) {
this.add(url, certificate);
this.save();
if (webContents.getURL().indexOf('file://') === 0) {
webContents.send('certificate-reload', url);
}
}
// Call all queued callbacks with result
this.queued[certificate.fingerprint].forEach((cb) => cb(response === 0));
delete this.queued[certificate.fingerprint];
});
});
}
load () {
try {
this.data = this.userDataDir.read(this.storeFileName, 'json');
} catch (e) {
console.error(e);
this.data = {};
}
load() {
try {
this.data = this.userDataDir.read(this.storeFileName, 'json');
} catch (e) {
console.error(e);
this.data = {};
}
if (this.data === undefined) {
this.clear();
}
}
if (this.data === undefined) {
this.clear();
}
}
clear () {
this.data = {};
this.save();
}
clear() {
this.data = {};
this.save();
}
save () {
this.userDataDir.write(this.storeFileName, this.data, { atomic: true });
}
save() {
this.userDataDir.write(this.storeFileName, this.data, { atomic: true });
}
parseCertificate (certificate) {
return certificate.issuerName + '\n' + certificate.data.toString();
}
parseCertificate(certificate) {
return `${ certificate.issuerName }\n${ certificate.data.toString() }`;
}
getHost (certUrl) {
return url.parse(certUrl).host;
}
getHost(certUrl) {
return url.parse(certUrl).host;
}
add (certUrl, certificate) {
const host = this.getHost(certUrl);
this.data[host] = this.parseCertificate(certificate);
}
add(certUrl, certificate) {
const host = this.getHost(certUrl);
this.data[host] = this.parseCertificate(certificate);
}
isExisting (certUrl) {
const host = this.getHost(certUrl);
return this.data.hasOwnProperty(host);
}
isExisting(certUrl) {
const host = this.getHost(certUrl);
return this.data.hasOwnProperty(host);
}
isTrusted (certUrl, certificate) {
const host = this.getHost(certUrl);
if (!this.isExisting(certUrl)) {
return false;
}
return this.data[host] === this.parseCertificate(certificate);
}
isTrusted(certUrl, certificate) {
const host = this.getHost(certUrl);
if (!this.isExisting(certUrl)) {
return false;
}
return this.data[host] === this.parseCertificate(certificate);
}
}
const certificateStore = new CertificateStore();

View file

@ -13,115 +13,115 @@ import env from '../env';
let mainWindow = null;
const mainWindowOptions = {
width: 1000,
height: 600,
minWidth: 600,
minHeight: 400,
titleBarStyle: 'hidden',
show: false
width: 1000,
height: 600,
minWidth: 600,
minHeight: 400,
titleBarStyle: 'hidden',
show: false,
};
const attachWindowStateHandling = (mainWindow) => {
const mainWindowState = windowStateKeeper('main', mainWindowOptions);
const mainWindowState = windowStateKeeper('main', mainWindowOptions);
mainWindow.once('ready-to-show', () => mainWindowState.loadState(mainWindow));
mainWindow.once('ready-to-show', () => mainWindowState.loadState(mainWindow));
// macOS only
app.on('activate', () => {
mainWindowState.saveState(mainWindow);
mainWindow.show();
});
// macOS only
app.on('activate', () => {
mainWindow.show();
});
app.on('before-quit', () => {
mainWindowState.saveState(mainWindow);
mainWindow.forceClose = true;
});
app.on('before-quit', () => {
mainWindowState.saveState(mainWindow);
mainWindowState.saveState.flush();
mainWindow = null;
});
mainWindow.on('show', () => {
mainWindowState.saveState(mainWindow);
});
mainWindow.on('show', () => {
mainWindowState.saveState(mainWindow);
});
mainWindow.on('close', function (event) {
if (mainWindow.forceClose) {
mainWindowState.saveState(mainWindow);
return;
}
event.preventDefault();
if (mainWindow.isFullScreen()) {
mainWindow.once('leave-full-screen', () => {
mainWindow.hide();
});
mainWindow.setFullScreen(false);
} else {
mainWindow.hide();
}
mainWindowState.saveState(mainWindow);
});
mainWindow.on('close', function(event) {
if (!mainWindow) {
return;
}
mainWindow.on('resize', function () {
mainWindowState.saveState(mainWindow);
});
event.preventDefault();
if (mainWindow.isFullScreen()) {
mainWindow.once('leave-full-screen', () => {
mainWindow.hide();
});
mainWindow.setFullScreen(false);
} else {
mainWindow.hide();
}
mainWindowState.saveState(mainWindow);
});
mainWindow.on('move', function () {
mainWindowState.saveState(mainWindow);
});
mainWindow.on('resize', () => {
mainWindowState.saveState(mainWindow);
});
mainWindow.on('move', () => {
mainWindowState.saveState(mainWindow);
});
};
const attachIpcMessageHandling = (mainWindow) => {
ipcMain.on('focus', () => {
mainWindow.show();
});
ipcMain.on('focus', () => {
mainWindow.show();
});
ipcMain.on('update-taskbar-icon', (event, data, text) => {
const img = nativeImage.createFromDataURL(data);
mainWindow.setOverlayIcon(img, text);
});
ipcMain.on('update-taskbar-icon', (event, data, text) => {
const img = nativeImage.createFromDataURL(data);
mainWindow.setOverlayIcon(img, text);
});
};
export const createMainWindow = (cb) => {
if (mainWindow) {
cb && cb(mainWindow);
return;
}
if (mainWindow) {
cb && cb(mainWindow);
return;
}
mainWindow = new BrowserWindow(mainWindowOptions);
attachWindowStateHandling(mainWindow);
attachIpcMessageHandling(mainWindow);
mainWindow = new BrowserWindow(mainWindowOptions);
attachWindowStateHandling(mainWindow);
attachIpcMessageHandling(mainWindow);
mainWindow.webContents.on('will-navigate', (event) => {
event.preventDefault();
});
mainWindow.webContents.on('will-navigate', (event) => {
event.preventDefault();
});
const appUrl = url.format({
pathname: path.join(__dirname, 'public', 'app.html'),
protocol: 'file:',
slashes: true
});
const appUrl = url.format({
pathname: path.join(__dirname, 'public', 'app.html'),
protocol: 'file:',
slashes: true,
});
mainWindow.loadURL(appUrl);
mainWindow.loadURL(appUrl);
if (env.name === 'development') {
mainWindow.openDevTools();
}
if (env.name === 'development') {
mainWindow.openDevTools();
}
cb && cb(mainWindow);
cb && cb(mainWindow);
};
export const getMainWindow = () => new Promise((resolve) => {
if (app.isReady()) {
createMainWindow(resolve);
return;
}
if (app.isReady()) {
createMainWindow(resolve);
return;
}
app.on('ready', () => createMainWindow(resolve));
app.on('ready', () => createMainWindow(resolve));
});
export const addServer = (serverUrl) => getMainWindow().then((mainWindow) => {
mainWindow.send('add-host', serverUrl);
mainWindow.send('add-host', serverUrl);
mainWindow.show();
mainWindow.show();
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
if (mainWindow.isMinimized()) {
mainWindow.restore();
}
});

View file

@ -2,34 +2,34 @@ import { BrowserWindow, ipcMain } from 'electron';
let screenshareEvent;
ipcMain.on('screenshare', (event, sources) => {
screenshareEvent = event;
let mainWindow = new BrowserWindow({
width: 776,
height: 600,
show : false,
skipTaskbar: false
});
screenshareEvent = event;
let mainWindow = new BrowserWindow({
width: 776,
height: 600,
show : false,
skipTaskbar: false,
});
mainWindow.loadURL('file://'+__dirname+'/../public/screenshare.html');
mainWindow.loadURL(`file://${ __dirname }/../public/screenshare.html`);
//window.openDevTools();
mainWindow.webContents.on('did-finish-load', () => {
mainWindow.webContents.send('sources', sources);
mainWindow.show();
});
// window.openDevTools();
mainWindow.webContents.on('did-finish-load', () => {
mainWindow.webContents.send('sources', sources);
mainWindow.show();
});
mainWindow.on('closed', () => {
mainWindow = null;
if (screenshareEvent) {
screenshareEvent.sender.send('screenshare-result', 'PermissionDeniedError');
screenshareEvent = null;
}
});
mainWindow.on('closed', () => {
mainWindow = null;
if (screenshareEvent) {
screenshareEvent.sender.send('screenshare-result', 'PermissionDeniedError');
screenshareEvent = null;
}
});
});
ipcMain.on('source-result', (e, sourceId) => {
if (screenshareEvent) {
screenshareEvent.sender.send('screenshare-result', sourceId);
screenshareEvent = null;
}
if (screenshareEvent) {
screenshareEvent.sender.send('screenshare-result', sourceId);
screenshareEvent = null;
}
});

View file

@ -3,21 +3,21 @@ import { app } from 'electron';
let servers = {};
export default {
loadServers (s) {
servers = s;
},
loadServers(s) {
servers = s;
},
getServers () {
return servers;
}
getServers() {
return servers;
},
};
app.on('login', function (event, webContents, request, authInfo, callback) {
for (const url of Object.keys(servers)) {
const server = servers[url];
if (request.url.indexOf(url) === 0 && server.username) {
callback(server.username, server.password);
break;
}
}
app.on('login', function(event, webContents, request, authInfo, callback) {
for (const url of Object.keys(servers)) {
const server = servers[url];
if (request.url.indexOf(url) === 0 && server.username) {
callback(server.username, server.password);
break;
}
}
});

View file

@ -6,59 +6,63 @@ import { app } from 'electron';
import jetpack from 'fs-jetpack';
import { debounce } from 'lodash';
export default function (name, defaults) {
export default function(name, defaults) {
let state = {
width: defaults.width,
height: defaults.height
};
let state = {
width: defaults.width,
height: defaults.height,
};
const userDataDir = jetpack.cwd(app.getPath('userData'));
const stateStoreFile = `window-state-${name}.json`;
const userDataDir = jetpack.cwd(app.getPath('userData'));
const stateStoreFile = `window-state-${ name }.json`;
try {
state = userDataDir.read(stateStoreFile, 'json') || state;
} catch (err) {
console.error(`Failed to load "${ name }" window state`);
console.error(err);
}
try {
state = userDataDir.read(stateStoreFile, 'json') || state;
} catch (err) {
console.error(`Failed to load "${ name }" window state`);
console.error(err);
}
const saveState = function (window) {
state.isMaximized = window.isMaximized();
state.isMinimized = window.isMinimized();
state.isHidden = !window.isMinimized() && !window.isVisible();
const saveState = function(window) {
if (window.isDestroyed()) {
return;
}
if (!state.isMaximized && !state.isHidden) {
[ state.x, state.y ] = window.getPosition();
[ state.width, state.height ] = window.getSize();
}
state.isMaximized = window.isMaximized();
state.isMinimized = window.isMinimized();
state.isHidden = !window.isMinimized() && !window.isVisible();
userDataDir.write(stateStoreFile, state, { atomic: true });
};
if (!state.isMaximized && !state.isHidden) {
[state.x, state.y] = window.getPosition();
[state.width, state.height] = window.getSize();
}
const loadState = function (window) {
if (this.x !== undefined && this.y !== undefined) {
window.setPosition(this.x, this.y, false);
}
userDataDir.write(stateStoreFile, state, { atomic: true });
};
if (this.width !== undefined && this.height !== undefined) {
window.setSize(this.width, this.height, false);
}
const loadState = function(window) {
if (this.x !== undefined && this.y !== undefined) {
window.setPosition(this.x, this.y, false);
}
this.isMaximized ? window.maximize() : window.unmaximize();
this.isMinimized ? window.minimize() : window.restore();
this.isHidden ? window.hide() : window.show();
};
if (this.width !== undefined && this.height !== undefined) {
window.setSize(this.width, this.height, false);
}
return {
get x () { return state.x && Math.floor(state.x); },
get y () { return state.y && Math.floor(state.y); },
get width () { return state.width && Math.floor(state.width); },
get height () { return state.height && Math.floor(state.height); },
get isMaximized () { return state.isMaximized; },
get isMinimized () { return state.isMinimized; },
get isHidden () { return state.isHidden; },
saveState: debounce(saveState, 1000),
loadState
};
this.isMaximized ? window.maximize() : window.unmaximize();
this.isMinimized ? window.minimize() : window.restore();
this.isHidden ? window.hide() : window.show();
};
return {
get x() { return state.x && Math.floor(state.x); },
get y() { return state.y && Math.floor(state.y); },
get width() { return state.width && Math.floor(state.width); },
get height() { return state.height && Math.floor(state.height); },
get isMaximized() { return state.isMaximized; },
get isMinimized() { return state.isMinimized; },
get isHidden() { return state.isHidden; },
saveState: debounce(saveState, 1000), // see https://github.com/RocketChat/Rocket.Chat.Electron/issues/181
loadState,
};
}

8
src/e2e/.eslintrc Normal file
View file

@ -0,0 +1,8 @@
{
"extends": [
"@rocket.chat/eslint-config"
],
"env": {
"mocha": true
}
}

14
src/e2e/app.e2e.js Normal file
View file

@ -0,0 +1,14 @@
import { expect } from 'chai';
import { app, startApp, stopApp } from './utils';
import appManifest from '../../package.json';
describe('application', function() {
before(startApp);
after(stopApp);
it('shows the main window', async function() {
expect(await app.browserWindow.isVisible()).to.be.true;
expect(await app.browserWindow.getTitle()).to.be.equal(appManifest.productName);
});
});

87
src/e2e/utils.js Normal file
View file

@ -0,0 +1,87 @@
import path from 'path';
import electron from 'electron';
import { Application } from 'spectron';
export let app = null;
let logFetchInterval = null;
const fetchLogs = async() => {
const logs = await app.client.getMainProcessLogs();
logs.forEach((log) => console.log(log));
};
export async function startApp() {
this.timeout(10000);
app = new Application({
path: electron,
cwd: process.cwd(),
args: [path.join(__dirname, '..')],
quitTimeout: 5000,
startTimeout: 5000,
waitTimeout: 5000,
});
await app.start();
await app.client.waitUntilWindowLoaded();
logFetchInterval = setInterval(fetchLogs, 100);
}
export async function stopApp() {
this.timeout(10000);
if (app && app.isRunning()) {
clearInterval(logFetchInterval);
fetchLogs();
await app.stop();
app = null;
}
}
export const menuItem = (menuId) => ({
get exists() {
return app.client.execute((menuId) => {
const { Menu } = require('electron').remote;
const appMenu = Menu.getApplicationMenu();
const menuItem = appMenu.getMenuItemById(menuId);
return !!menuItem;
}, menuId).then(({ value }) => value);
},
get enabled() {
return app.client.execute((menuId) => {
const { Menu } = require('electron').remote;
const appMenu = Menu.getApplicationMenu();
const menuItem = appMenu.getMenuItemById(menuId);
return menuItem.enabled;
}, menuId).then(({ value }) => value);
},
get visible() {
return app.client.execute((menuId) => {
const { Menu } = require('electron').remote;
const appMenu = Menu.getApplicationMenu();
const menuItem = appMenu.getMenuItemById(menuId);
return menuItem.visible;
}, menuId).then(({ value }) => value);
},
get label() {
return app.client.execute((menuId) => {
const { Menu } = require('electron').remote;
const appMenu = Menu.getApplicationMenu();
const menuItem = appMenu.getMenuItemById(menuId);
return menuItem.label;
}, menuId).then(({ value }) => value);
},
click() {
return app.client.execute((menuId) => {
const { Menu } = require('electron').remote;
const appMenu = Menu.getApplicationMenu();
const menuItem = appMenu.getMenuItemById(menuId);
menuItem.click();
}, menuId);
},
});

View file

@ -6,75 +6,75 @@
import { app, BrowserWindow, screen } from 'electron';
import jetpack from 'fs-jetpack';
export default function (name, options) {
const userDataDir = jetpack.cwd(app.getPath('userData'));
const stateStoreFile = 'window-state-' + name +'.json';
const defaultSize = {
width: options.width,
height: options.height
};
let state = {};
const win = new BrowserWindow(Object.assign({}, options, state));
export default function(name, options) {
const userDataDir = jetpack.cwd(app.getPath('userData'));
const stateStoreFile = `window-state-${ name }.json`;
const defaultSize = {
width: options.width,
height: options.height,
};
let state = {};
const win = new BrowserWindow(Object.assign({}, options, state));
const restore = function () {
let restoredState = {};
try {
restoredState = userDataDir.read(stateStoreFile, 'json');
} catch (err) {
// For some reason json can't be read (might be corrupted).
// No worries, we have defaults.
}
return Object.assign({}, defaultSize, restoredState);
};
const restore = function() {
let restoredState = {};
try {
restoredState = userDataDir.read(stateStoreFile, 'json');
} catch (err) {
// For some reason json can't be read (might be corrupted).
// No worries, we have defaults.
}
return Object.assign({}, defaultSize, restoredState);
};
const getCurrentPosition = function () {
const position = win.getPosition();
const size = win.getSize();
return {
x: Math.floor(position[0]),
y: Math.floor(position[1]),
width: Math.floor(size[0]),
height: Math.floor(size[1])
};
};
const getCurrentPosition = function() {
const position = win.getPosition();
const size = win.getSize();
return {
x: Math.floor(position[0]),
y: Math.floor(position[1]),
width: Math.floor(size[0]),
height: Math.floor(size[1]),
};
};
const windowWithinBounds = function (windowState, bounds) {
return windowState.x >= bounds.x &&
const windowWithinBounds = function(windowState, bounds) {
return windowState.x >= bounds.x &&
windowState.y >= bounds.y &&
windowState.x + windowState.width <= bounds.x + bounds.width &&
windowState.y + windowState.height <= bounds.y + bounds.height;
};
};
const resetToDefaults = function (/*windowState*/) {
const bounds = screen.getPrimaryDisplay().bounds;
return Object.assign({}, defaultSize, {
x: (bounds.width - defaultSize.width) / 2,
y: (bounds.height - defaultSize.height) / 2
});
};
const resetToDefaults = function(/* windowState*/) {
const { bounds } = screen.getPrimaryDisplay();
return Object.assign({}, defaultSize, {
x: (bounds.width - defaultSize.width) / 2,
y: (bounds.height - defaultSize.height) / 2,
});
};
const ensureVisibleOnSomeDisplay = function (windowState) {
const visible = screen.getAllDisplays().some(function (display) {
return windowWithinBounds(windowState, display.bounds);
});
if (!visible) {
// Window is partially or fully not visible now.
// Reset it to safe defaults.
return resetToDefaults(windowState);
}
return windowState;
};
const ensureVisibleOnSomeDisplay = function(windowState) {
const visible = screen.getAllDisplays().some(function(display) {
return windowWithinBounds(windowState, display.bounds);
});
if (!visible) {
// Window is partially or fully not visible now.
// Reset it to safe defaults.
return resetToDefaults(windowState);
}
return windowState;
};
const saveState = function () {
if (!win.isMinimized() && !win.isMaximized()) {
Object.assign(state, getCurrentPosition());
}
userDataDir.write(stateStoreFile, state, { atomic: true });
};
const saveState = function() {
if (!win.isMinimized() && !win.isMaximized()) {
Object.assign(state, getCurrentPosition());
}
userDataDir.write(stateStoreFile, state, { atomic: true });
};
state = ensureVisibleOnSomeDisplay(restore());
state = ensureVisibleOnSomeDisplay(restore());
win.on('close', saveState);
win.on('close', saveState);
return win;
return win;
}

View file

@ -13,61 +13,70 @@ let loadedLanguage = [];
* @param {number} chount Count to check for singular / plural (0-1,2-n)
* @returns {string} Translation in user language
*/
function loadTranslation (phrase = '', count) {
const loadedLanguageTranslation = loadedLanguage[phrase];
let translation = loadedLanguageTranslation;
if (loadedLanguageTranslation === undefined) {
translation = phrase;
} else if (loadedLanguageTranslation instanceof Object) {
translation = loadedLanguageTranslation['one'];
if (count > 1) {
translation = loadedLanguageTranslation['multi'];
}
}
return translation;
function loadTranslation(phrase = '', count) {
const loadedLanguageTranslation = loadedLanguage[phrase];
let translation = loadedLanguageTranslation;
if (loadedLanguageTranslation === undefined) {
translation = phrase;
} else if (loadedLanguageTranslation instanceof Object) {
translation = loadedLanguageTranslation.one;
if (count > 1) {
translation = loadedLanguageTranslation.multi;
}
}
return translation;
}
class I18n {
/**
/**
* Load users language if available, and fallback to english for any missing strings
* @constructor
*/
constructor () {
let dir = path.join(__dirname, '../i18n/lang');
if (!fs.existsSync(dir)) {
dir = path.join(__dirname, 'i18n/lang');
}
const defaultLocale = path.join(dir, 'en.i18n.json');
loadedLanguage = JSON.parse(fs.readFileSync(defaultLocale, 'utf8'));
const locale = path.join(dir, `${eApp.getLocale()}.i18n.json`);
if (fs.existsSync(locale)) {
const lang = JSON.parse(fs.readFileSync(locale, 'utf8'));
loadedLanguage = Object.assign(loadedLanguage, lang);
}
}
constructor() {
const load = () => {
let dir = path.join(__dirname, '../i18n/lang');
if (!fs.existsSync(dir)) {
dir = path.join(__dirname, 'i18n/lang');
}
const defaultLocale = path.join(dir, 'en.i18n.json');
loadedLanguage = JSON.parse(fs.readFileSync(defaultLocale, 'utf8'));
const locale = path.join(dir, `${ eApp.getLocale() }.i18n.json`);
if (fs.existsSync(locale)) {
const lang = JSON.parse(fs.readFileSync(locale, 'utf8'));
loadedLanguage = Object.assign(loadedLanguage, lang);
}
};
/**
if (eApp.isReady()) {
load();
return;
}
eApp.once('ready', load);
}
/**
* Get translation string
* @param {string} phrase The key for the translation string
* @param {...string|number} replacements List of replacements in template strings
* @return {string} Translation in users language
*/
__ (phrase, ...replacements) {
const translation = loadTranslation(phrase, 0);
return util.format(translation, ...replacements);
}
__(phrase, ...replacements) {
const translation = loadTranslation(phrase, 0);
return util.format(translation, ...replacements);
}
/**
/**
* Get translation string
* @param {string} phrase The key for the translation string
* @param {number} count Count to check for singular / plural (0-1,2-n)
* @param {...string|number} replacements List of replacements in template strings
* @return {string} Translation in users language
*/
pluralize (phrase, count, ...replacements) {
const translation = loadTranslation(phrase, count);
return util.format(translation, ...replacements);
}
pluralize(phrase, count, ...replacements) {
const translation = loadTranslation(phrase, count);
return util.format(translation, ...replacements);
}
}
export default new I18n();

View file

@ -47,6 +47,7 @@
"New_Version": "New Version:",
"No": "No",
"No_suggestions": "No suggestions",
"No updates are available.": "No updates are available.",
"No_valid_server_found": "No valid server found at the URL",
"Open_DevTools": "Open DevTools",
"Open_Language_Dictionary": "Open Language Dictionary",

View file

@ -44,6 +44,7 @@
"New_Version": "Nova Versão:",
"No": "Não",
"No_suggestions": "Sem sugestões",
"No updates are available.": "Não há atualizações disponíveis.",
"No_valid_server_found": "Nenhum servidor válido encontrado neste endereço",
"Open_DevTools": "Abrir DevTools",
"Open_Language_Dictionary": "Abrir Dicionário Idioma",

102
src/i18n/lang/ru.i18n.json Normal file
View file

@ -0,0 +1,102 @@
{
"About": "О %s",
"Add": "Добавить",
"Add_host_to_servers": "Вы хотите добавить \"%s\" в Ваш список серверов?",
"Add_new_server": "Добавить новый сервер",
"Add_Server": "Добавить сервер",
"Application_Reload": "Приложение - Перезагрузка",
"Application_Toggle_DevTools": "Приложение - Открыть DevTools",
"Auth_needed_try": "Требуется авторизация, попробуйте %s",
"Back": "Назад",
"Bring_All_to_Front": "Переместить все вперед",
"Browse_for_language": "Поиск языков...",
"Cancel": "Отменить",
"Certificate_error": "Ошибка сертификата",
"Certificate_error_different": "Сертификат отличается от предыдущего.\n\n %s",
"Certificate_error_message": "Вы доверяете сертификату от \"%s\"?",
"Check_connection": "Проверьте Ваше интернет-соединение!",
"Check_for_Updates": "Проверить обновления",
"Check_for_Updates_on_Start": "Проверять обновления при запуске",
"Clear": "Очистить",
"Clear_Trusted_Certificates": "Очистить Доверенные Сертификаты",
"Close": "Закрыть",
"Connect": "Подключиться",
"Copy": "Копировать",
"Copyright": "Copyright %s",
"Current_Server_Reload": "Текущий сервер - Перезагрузить вкладку",
"Current_Server_Toggle_DevTools": "Текущий сервер - Открыть DevTools",
"Current_Version": "Текущая версия:",
"Cut": "Вырезать",
"Development": "Разработка",
"Dictionary_install_error": "Ошибка при установке словаря",
"Edit": "Правка",
"Enter_your_server_URL": "Введите URL Вашего сервера",
"Error": "Ошибка",
"Forward": "Вперед",
"Help": "Справка",
"Help_Name": "Документация %s",
"Hide": "Скрыть",
"History": "История",
"Host_not_validated": "Не удалось проверить Хост \"%s\", поэтому он не был добавлен.",
"Houston_we_have_a_problem": "Хьюстон, у нас проблемы",
"Invalid_Host": "Не корректный Хост",
"Invalid_url": "Не корректный url",
"Learn_More": "Узнать больше",
"Minimize": "Свернуть",
"More_spelling_suggestions": "Другие варианты написания",
"New_Version": "Новая версия:",
"No": "Нет",
"No_suggestions": "Нет предложений",
"No_valid_server_found": "По заданному URL не найден подходящий сервер",
"Open_DevTools": "Открыть DevTools",
"Open_Language_Dictionary": "Открыть словарь",
"Open_Link": "Открыть ссылку",
"Original_Zoom": "Восстановить масштаб",
"Paste": "Вставить",
"Quit": "Выйти",
"Quit_App": "Выйти из %s",
"Redo": "Повторить",
"Reload": "Перезагрузить",
"Reload_server": "Перезагрузить вкладку сервера",
"Remove_server": "Удалить сервер",
"Report_Issue": "Сообщить о проблеме",
"Reset_App_Data": "Сбросить данные приложения",
"Select_a_screen_to_share": "Выберите экран для совместного использования",
"Select_All": "Выделить все",
"Server_Failed_to_Load": "Не удалось загрузить сервер",
"Share_Your_Screen": "Демонстрация Вашего экрана",
"Show": "Показать",
"Spelling_languages": "Правописание",
"Timeout_trying_to_connect": "Превышено время ожидания при попытке подключения",
"Toggle_DevTools": "Открыть DevTools",
"Toggle_Full_Screen": "Полноэкранный режим",
"Toggle_Menu_Bar": "Показывать главное меню",
"Toggle_Server_List": "Показывать список серверов",
"Toggle_Tray_Icon": "Показать значок в трее",
"Undo": "Отменить",
"Update_Available": "Доступно обновление",
"Update_Available_message": "Доступна новая версия приложения Rocket.Chat!",
"Update_Available_New": "Доступно новое обновление",
"Update_downloading": "Загрузка обновления",
"Update_downloading_message": "Вы будете оповещены, когда обновление будет готово к установке",
"Update_Install": "Установить обновление",
"Update_Install_Later": "Установить позже",
"Update_Install_Now": "Уставить сейчас",
"Update_installing_later": "Обновление будет установлено позже",
"Update_installing_later_message": "Обновление будет установлено, когда Вы закроете приложение",
"Update_ready": "Обновление готово к установке",
"Update_ready_message": "Обновление загружено",
"Update_remind": "Напомнить позже",
"Update_remind_message": "Мы напомним Вам при следующем запуске приложения",
"Update_skip": "Пропустить обновление",
"Update_skip_message": "Мы оповестим Вас, когда следующее обновление будет доступно.\nЕсли Вы передумаете, Вы можете проверить наличие обновлений в меню \"О Rocket.Chat\".",
"Update_skip_remind": "Напомнить мне позже",
"Update_skip_version": "Пропустить эту версию",
"Validating": "Проверяем...",
"Version": "Версия",
"View": "Вид",
"Window": "Окна",
"Yes": "Да",
"Zoom_In": "Увеличить",
"Zoom_Out": "Уменьшить"
}

View file

@ -1,115 +1,103 @@
<!doctype html>
<html>
<head>
<title></title>
<meta charset="utf-8" />
<style>
html {
background-color: #ececec;
font-family: helvetica;
padding: 10px;
}
body {
margin: 0px;
text-align: center;
}
img {
height: 60px;
margin-bottom: 10px;
}
.app-name {
font-size: 14px;
font-weight: bold;
}
.app-version {
margin-top: 15px;
font-size: 11px;
}
.app-version .version {
font-weight: bold;
}
.update {
margin-top:10px;
}
.copyright {
font-size: 10px;
position: absolute;
bottom: 0;
margin-left: auto;
margin-right: auto;
left: 0;
right: 0;
}
.update-container {
display: none;
}
.update-container.update-container--enabled {
display: block;
}
.auto-update-container {
margin: 5px 0;
}
</style>
<script>
const i18n = require('../i18n/index');
</script>
</head>
<body>
<img src="images/icon.png">
<div class="app-name">
</div>
<div class="app-version"></div>
<div class="update-container">
<span class="update-spin icon-spin3 animate-spin" style="display:none;"></span>
<button class="update"><script>document.write(i18n.__('Check_for_Updates'))</script></button>
<p class="auto-update-container"><input type="checkbox" id="auto-update" checked /> <script>document.write(i18n.__('Check_for_Updates_on_Start'))</script></p>
</div>
<p class="copyright"><script>document.write(i18n.__('Copyright', require('../../package.json').copyright))</script></p>
<script>
<title>About %s</title>
<link rel="stylesheet" href="../stylesheets/main.css" />
<script>
document.addEventListener('DOMContentLoaded', () => {
const { remote, ipcRenderer } = require('electron');
const i18n = require('../i18n/index');
const { copyright } = require('../../package.json');
document.querySelector('.app-name').innerHTML = remote.app.getName();
document.title = i18n.__('About', remote.app.getName());
document.querySelector('.app-version').innerHTML = `${i18n.__('Version')} <span class="version">${remote.app.getVersion()}</span>`;
document.querySelector('.check-for-updates').innerHTML = i18n.__('Check_for_Updates');
document.querySelector('.check-for-updates-on-start + span').innerHTML = i18n.__('Check_for_Updates_on_Start');
document.querySelector('.copyright').innerHTML = i18n.__('Copyright', copyright);
const canUpdate = !process.mas;
const canUpdate = ipcRenderer.sendSync('can-update');
if (canUpdate) {
const autoUpdate = ipcRenderer.sendSync('check-for-updates', 'auto');
if (!autoUpdate) {
document.querySelector('#auto-update').removeAttribute('checked');
}
document.querySelector('.update').onclick = function(e) {
document.querySelector('.update-spin').setAttribute('style', '');
document.querySelector('.update').setAttribute('disabled', 'disabled');
ipcRenderer.send('check-for-updates');
};
const canAutoUpdate = ipcRenderer.sendSync('can-auto-update');
document.querySelector('#auto-update').onclick = function(e) {
ipcRenderer.send('check-for-updates', e.target.checked);
};
if (canAutoUpdate) {
document.querySelector('.check-for-updates-on-start').setAttribute('checked', 'checked');
} else {
document.querySelector('.check-for-updates-on-start').removeAttribute('checked');
}
const canSetAutoUpdate = ipcRenderer.sendSync('can-set-auto-update');
if (canSetAutoUpdate) {
document.querySelector('.check-for-updates-on-start').addEventListener('change', (event) => {
ipcRenderer.send('set-auto-update', event.target.checked);
});
} else {
document.querySelector('.check-for-updates-on-start').setAttribute('disabled', 'disabled');
}
document.querySelector('.check-for-updates').addEventListener('click', (e) => {
e.preventDefault();
document.querySelector('.check-for-updates').setAttribute('disabled', 'disabled');
document.querySelector('.check-for-updates').classList.add('hidden');
document.querySelector('.checking-for-updates').classList.remove('hidden');
ipcRenderer.send('check-for-updates');
}, false);
ipcRenderer.on('update-result', (e, updateAvailable) => {
document.querySelector('.update-spin').setAttribute('style', 'display:none');
document.querySelector('.update').removeAttribute('disabled');
if (!updateAvailable) {
alert('No updates are available.');
const resetUpdatesSection = () => {
document.querySelector('.check-for-updates').removeAttribute('disabled');
document.querySelector('.check-for-updates').classList.remove('hidden');
document.querySelector('.checking-for-updates').classList.add('hidden');
};
if (updateAvailable) {
resetUpdatesSection();
return;
}
document.querySelector('.checking-for-updates .message').innerHTML = i18n.__('No updates are available.');
document.querySelector('.checking-for-updates').classList.add('message-shown');
setTimeout(() => {
resetUpdatesSection();
document.querySelector('.checking-for-updates .message').innerHTML = '';
document.querySelector('.checking-for-updates').classList.remove('message-shown');
}, 5000);
});
document.querySelector('.update-container').classList.add('update-container--enabled');
document.querySelector('.updates').classList.remove('hidden');
}
</script>
</body>
}, false);
</script>
</head>
<body class="about-page">
<section class="app-info">
<div class="app-logo">
<img src="./images/logo.svg">
</div>
<div class="app-version">
Version <span class="version">%s</span>
</div>
</section>
<section class="updates hidden">
<button class="check-for-updates button primary">
Check for Updates
</button>
<div class="checking-for-updates hidden">
<span class="dot"></span>
<span class="dot"></span>
<span class="dot"></span>
<span class="message"></span>
</div>
<label class="check-for-updates-on-start__label">
<input class="check-for-updates-on-start" type="checkbox" checked /> <span>Check for Updates on Start</span>
</label>
</section>
<div class="copyright"></div>
</body>
</html>

View file

@ -28,7 +28,7 @@
<section class="landing-page">
<div class="wrapper">
<header>
<img class="logo" src="./images/logo.svg" />
<img class="logo" src="./images/logo-dark.svg" />
</header>
<div class="loading-animation" id="loading">
<div class="bounce1"></div>

View file

@ -2,59 +2,58 @@
// in all input fields and textareas across your app.
const i18n = require('../../i18n');
(function () {
'use strict';
(function() {
'use strict';
const remote = require('electron').remote;
const Menu = remote.Menu;
const MenuItem = remote.MenuItem;
const { remote } = require('electron');
const { Menu, MenuItem } = remote;
const isAnyTextSelected = function () {
return window.getSelection().toString() !== '';
};
const isAnyTextSelected = function() {
return window.getSelection().toString() !== '';
};
const cut = new MenuItem({
label: i18n.__('Cut'),
click: function () {
document.execCommand("cut");
}
});
const cut = new MenuItem({
label: i18n.__('Cut'),
click() {
document.execCommand('cut');
},
});
const copy = new MenuItem({
label: i18n.__('Copy'),
click: function () {
document.execCommand("copy");
}
});
const copy = new MenuItem({
label: i18n.__('Copy'),
click() {
document.execCommand('copy');
},
});
const paste = new MenuItem({
label: i18n.__('Paste'),
click: function () {
document.execCommand("paste");
}
});
const paste = new MenuItem({
label: i18n.__('Paste'),
click() {
document.execCommand('paste');
},
});
const normalMenu = new Menu();
normalMenu.append(copy);
const normalMenu = new Menu();
normalMenu.append(copy);
const textEditingMenu = new Menu();
textEditingMenu.append(cut);
textEditingMenu.append(copy);
textEditingMenu.append(paste);
const textEditingMenu = new Menu();
textEditingMenu.append(cut);
textEditingMenu.append(copy);
textEditingMenu.append(paste);
document.addEventListener('contextmenu', function (e) {
switch (e.target.nodeName) {
case 'TEXTAREA':
case 'INPUT':
e.preventDefault();
textEditingMenu.popup(remote.getCurrentWindow());
break;
default:
if (isAnyTextSelected()) {
e.preventDefault();
normalMenu.popup(remote.getCurrentWindow());
}
}
}, false);
document.addEventListener('contextmenu', function(e) {
switch (e.target.nodeName) {
case 'TEXTAREA':
case 'INPUT':
e.preventDefault();
textEditingMenu.popup(remote.getCurrentWindow());
break;
default:
if (isAnyTextSelected()) {
e.preventDefault();
normalMenu.popup(remote.getCurrentWindow());
}
}
}, false);
}());

View file

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg width="1000px" height="182px" viewBox="0 0 1000 182" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="logo" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="type" transform="translate(245.154709, 29.581775)" fill="#9ea2a8" fill-rule="nonzero">
<path d="M62.5717573,58.8485839 C62.5717573,69.0064127 58.7804613,75.6774374 51.5006054,78.8592939 L61.9662852,118.580494 C62.4203893,120.403088 61.5121812,121.308475 59.8424026,121.308475 L44.0670163,121.308475 C42.5509709,121.308475 41.7941308,120.552016 41.4890296,119.185662 L31.3260858,80.6795239 L20.8604059,80.6795239 L20.8604059,118.885442 C20.8604059,120.403088 19.9521978,121.308475 18.4337873,121.308475 L2.65840094,121.308475 C1.14235556,121.308475 0.231782288,120.39836 0.231782288,118.885442 L0.231782288,2.44982204 C0.231782288,0.934539684 1.14235556,0.0244246988 2.65840094,0.0244246988 L40.7298244,0.0244246988 C54.9868002,0.0244246988 62.5717573,7.60556433 62.5717573,21.8553647 L62.5717573,58.8485839 Z M35.8765871,60.6664499 C39.8192511,60.6664499 41.9431337,58.5436363 41.9431337,54.6029566 L41.9431337,26.100992 C41.9431337,22.1603123 39.8192511,20.0398626 35.8765871,20.0398626 L20.8604059,20.0398626 L20.8604059,60.6688139 L35.8765871,60.6664499 Z" id="Shape"></path>
<path d="M74.6977552,21.8553647 C74.6977552,7.60556433 82.2803472,0.0244246988 96.5396882,0.0244246988 L113.528384,0.0244246988 C127.78536,0.0244246988 135.370317,7.60556433 135.370317,21.8553647 L135.370317,99.4775352 C135.370317,113.724972 127.78536,121.306111 113.528384,121.306111 L96.5396882,121.306111 C82.2803472,121.306111 74.6977552,113.724972 74.6977552,99.4775352 L74.6977552,21.8553647 Z M109.734723,101.749277 C113.677387,101.749277 115.801269,99.7777549 115.801269,95.6857834 L115.801269,25.6471165 C115.801269,21.7064368 113.677387,19.5836231 109.734723,19.5836231 L100.938821,19.5836231 C96.9961574,19.5836231 94.8722748,21.7064368 94.8722748,25.6471165 L94.8722748,95.6881474 C94.8722748,99.7777549 96.9961574,101.749277 100.938821,101.749277 L109.734723,101.749277 Z" id="Shape"></path>
<path d="M211.352335,38.2279787 C211.352335,39.7456249 210.441762,40.653376 208.928082,40.653376 L193.760533,40.653376 C192.090754,40.653376 191.333914,39.7456249 191.333914,38.2279787 L191.333914,26.100992 C191.333914,22.1603123 189.212396,20.0398626 185.269732,20.0398626 L177.382039,20.0398626 C173.288007,20.0398626 171.315493,22.1603123 171.315493,26.100992 L171.315493,95.2366358 C171.315493,99.3286073 173.43701,101.295401 177.382039,101.295401 L185.269732,101.295401 C189.212396,101.295401 191.333914,99.3262434 191.333914,95.2366358 L191.333914,83.1049212 C191.333914,81.5872749 192.090754,80.6795239 193.760533,80.6795239 L208.928082,80.6795239 C210.448857,80.6795239 211.352335,81.5872749 211.352335,83.1049212 L211.352335,99.4798991 C211.352335,113.727336 203.61601,121.308475 189.512767,121.308475 L172.524072,121.308475 C158.267096,121.308475 150.530771,113.727336 150.530771,99.4798991 L150.530771,21.8553647 C150.530771,7.60556433 158.267096,0.0244246988 172.524072,0.0244246988 L189.512767,0.0244246988 C203.618375,0.0244246988 211.352335,7.60556433 211.352335,21.8553647 L211.352335,38.2279787 Z" id="Shape"></path>
<path d="M275.352628,121.308475 C273.529117,121.308475 272.471905,120.552016 271.861703,119.036734 L252.753855,74.0084992 L247.139047,84.7738593 L247.139047,118.275547 C247.139047,120.247068 246.079471,121.308475 244.106957,121.308475 L229.54488,121.308475 C227.572365,121.308475 226.510424,120.247068 226.510424,118.275547 L226.510424,3.05735334 C226.510424,1.08819546 227.572365,0.0244246988 229.54488,0.0244246988 L244.106957,0.0244246988 C246.077106,0.0244246988 247.139047,1.08583153 247.139047,3.05735334 L247.139047,50.0547456 L270.497026,2.29853019 C271.256231,0.783247842 272.320537,0.0244246988 273.987951,0.0244246988 L289.91234,0.0244246988 C292.187591,0.0244246988 293.098164,1.54207099 292.033858,3.6625207 L266.251626,56.5721145 L293.701271,117.821671 C294.763212,119.793193 293.852639,121.308475 291.428386,121.308475 L275.352628,121.308475 Z" id="Shape"></path>
<path d="M365.293617,17.6121013 C365.293617,19.1273836 364.685779,20.1887905 362.866998,20.1887905 L324.946943,20.1887905 L324.946943,50.5086211 L353.917363,50.5086211 C355.438139,50.5086211 356.343982,51.4187361 356.343982,53.0853103 L356.343982,68.0939338 C356.343982,69.7628719 355.433409,70.6729869 353.917363,70.6729869 L324.946943,70.6729869 L324.946943,101.295401 L362.866998,101.295401 C364.69051,101.295401 365.293617,102.054224 365.293617,103.720799 L365.293617,118.883078 C365.293617,120.39836 364.685779,121.303747 362.866998,121.303747 L307.050039,121.303747 C305.682996,121.303747 304.772423,120.393632 304.772423,118.883078 L304.772423,2.44982204 C304.772423,0.934539684 305.682996,0.0244246988 307.050039,0.0244246988 L362.866998,0.0244246988 C364.69051,0.0244246988 365.293617,0.934539684 365.293617,2.44982204 L365.293617,17.6121013 Z" id="Shape"></path>
<path d="M435.665557,0.0244246988 C437.332971,0.0244246988 438.092176,0.934539684 438.092176,2.44982204 L438.092176,17.6121013 C438.092176,19.1273836 437.332971,20.0374986 435.665557,20.0374986 L418.07139,20.0374986 L418.07139,118.883078 C418.07139,120.549652 417.31455,121.303747 415.647136,121.303747 L399.87175,121.303747 C398.350974,121.303747 397.445131,120.549652 397.445131,118.883078 L397.445131,20.0374986 L379.853328,20.0374986 C378.332553,20.0374986 377.42671,19.1273836 377.42671,17.6121013 L377.42671,2.44982204 C377.42671,0.934539684 378.337283,0.0244246988 379.853328,0.0244246988 L435.665557,0.0244246988 Z" id="Shape"></path>
<path d="M436.586195,104.356644 C436.586195,102.385122 437.648136,101.32608 439.620651,101.32608 L453.423523,101.32608 C455.396037,101.32608 456.455614,102.385122 456.455614,104.356644 L456.455614,118.301497 C456.455614,120.275383 455.396037,121.334426 453.423523,121.334426 L439.620651,121.334426 C437.648136,121.334426 436.586195,120.275383 436.586195,118.301497 L436.586195,104.356644 Z" id="Shape"></path>
<path d="M532.422837,38.2279787 C532.422837,39.7456249 531.509899,40.653376 529.996219,40.653376 L514.828669,40.653376 C513.161256,40.653376 512.404416,39.7456249 512.404416,38.2279787 L512.404416,26.100992 C512.404416,22.1603123 510.282898,20.0398626 506.340234,20.0398626 L498.452541,20.0398626 C494.356144,20.0398626 492.385995,22.1603123 492.385995,26.100992 L492.385995,95.2366358 C492.385995,99.3286073 494.507512,101.295401 498.452541,101.295401 L506.340234,101.295401 C510.282898,101.295401 512.404416,99.3262434 512.404416,95.2366358 L512.404416,83.1049212 C512.404416,81.5872749 513.161256,80.6795239 514.828669,80.6795239 L529.996219,80.6795239 C531.516994,80.6795239 532.422837,81.5872749 532.422837,83.1049212 L532.422837,99.4798991 C532.422837,113.727336 524.686512,121.308475 510.580904,121.308475 L493.592209,121.308475 C479.335233,121.308475 471.598908,113.727336 471.598908,99.4798991 L471.598908,21.8553647 C471.598908,7.60556433 479.335233,0.0244246988 493.592209,0.0244246988 L510.580904,0.0244246988 C524.688877,0.0244246988 532.422837,7.60556433 532.422837,21.8553647 L532.422837,38.2279787 Z" id="Shape"></path>
<path d="M587.627229,2.44982204 C587.627229,0.934539684 588.535437,0.0244246988 590.051482,0.0244246988 L605.673136,0.0244246988 C607.494282,0.0244246988 608.248757,0.934539684 608.248757,2.44982204 L608.248757,118.883078 C608.248757,120.39836 607.491917,121.303747 605.673136,121.303747 L590.051482,121.303747 C588.530707,121.303747 587.627229,120.393632 587.627229,118.883078 L587.627229,70.6729869 L568.211915,70.6729869 L568.211915,118.885442 C568.211915,120.403088 567.301341,121.308475 565.785296,121.308475 L550.166008,121.308475 C548.342496,121.308475 547.585656,120.39836 547.585656,118.885442 L547.585656,2.44982204 C547.585656,0.934539684 548.342496,0.0244246988 550.166008,0.0244246988 L565.785296,0.0244246988 C567.306072,0.0244246988 568.211915,0.934539684 568.211915,2.44982204 L568.211915,50.659913 L587.627229,50.659913 L587.627229,2.44982204 Z" id="Shape"></path>
<path d="M677.416849,121.308475 C676.052172,121.308475 675.295332,120.552016 674.990231,119.185662 L670.744831,97.0545018 L643.744559,97.0545018 L639.652892,119.185662 C639.347791,120.552016 638.590951,121.308475 637.226273,121.308475 L620.999148,121.308475 C619.331735,121.308475 618.57253,120.39836 619.024268,118.731786 L644.203393,2.14723835 C644.508495,0.631956001 645.416703,0.0244246988 646.78138,0.0244246988 L667.866473,0.0244246988 C669.23115,0.0244246988 670.141724,0.631956001 670.44446,2.14723835 L695.623585,118.731786 C695.928686,120.39836 695.320848,121.308475 693.502067,121.308475 L677.416849,121.308475 Z M657.24233,26.5548675 L647.535855,79.4644613 L666.951169,79.4644613 L657.24233,26.5548675 Z" id="Shape"></path>
<path d="M752.339291,0.0244246988 C754.006705,0.0244246988 754.76591,0.934539684 754.76591,2.44982204 L754.76591,17.6121013 C754.76591,19.1273836 754.006705,20.0374986 752.339291,20.0374986 L734.747489,20.0374986 L734.747489,118.883078 C734.747489,120.549652 733.988283,121.303747 732.32087,121.303747 L716.545484,121.303747 C715.029438,121.303747 714.12123,120.549652 714.12123,118.883078 L714.12123,20.0374986 L696.529428,20.0374986 C695.011017,20.0374986 694.105174,19.1273836 694.105174,17.6121013 L694.105174,2.44982204 C694.105174,0.934539684 695.015747,0.0244246988 696.529428,0.0244246988 L752.339291,0.0244246988 Z" id="Shape"></path>
</g>
<g id="icon">
<path d="M180.330283,70.2108641 L180.333892,70.2165073 C180.333291,70.2155668 180.332689,70.2146264 180.332087,70.213686 C180.331486,70.2127454 180.330884,70.2118047 180.330283,70.2108641 Z M61.954152,7.64436327 C68.2928921,11.164898 74.2846134,15.6191491 79.4016009,20.5699674 C87.6499712,19.0760214 96.1550122,18.3226821 104.783255,18.3226821 C130.612543,18.3226821 155.100983,25.0984914 173.733224,37.3980812 C183.38199,43.7706948 191.051311,51.3316745 196.525584,59.8730564 C202.622027,69.3905961 205.712343,79.6253998 205.712343,90.5923218 C205.712343,101.264274 202.622027,111.503322 196.525584,121.01874 C191.051311,129.564366 183.38199,137.123224 173.733224,143.495837 C155.100983,155.795427 130.614597,162.566992 104.783255,162.566992 C96.1550122,162.566992 87.6520245,161.813653 79.4016009,160.321829 C74.28256,165.270525 68.2928921,169.726898 61.954152,173.247433 C28.085937,190.209238 0,173.646385 0,173.646385 C0,173.646385 26.1126525,151.476989 21.8662921,132.042958 C10.1826408,120.068047 3.85211418,105.625154 3.85211418,90.3015965 C3.85211418,75.2666424 10.1846942,60.8237493 21.8662921,48.846716 C26.1119493,29.4180247 0.00864693756,7.25275357 2.14766577e-06,7.24541359 C0.00835435701,7.24048522 28.0909741,-9.31704081 61.954152,7.64436327 Z" id="rocket" fill="#DB2323" fill-rule="nonzero"></path>
<path d="M41.6969487,124.026456 C30.0889845,114.878649 23.1213873,103.17255 23.1213873,90.4136809 C23.1213873,61.1362728 59.8093762,37.4022441 105.066304,37.4022441 C150.323232,37.4022441 187.011221,61.1362728 187.011221,90.4136809 C187.011221,119.691089 150.323232,143.425118 105.066304,143.425118 C93.912316,143.425118 83.2788238,141.983462 73.5867827,139.372472 L66.5009556,146.206206 C62.6508003,149.919383 58.1381119,153.279632 53.4342869,155.927536 C47.1994165,158.989423 41.0426011,160.659947 34.9512744,161.16967 C35.2948967,160.544948 35.6113103,159.912092 35.9509881,159.286387 C43.0509631,146.207835 44.9662833,134.454525 41.6969487,124.026456 Z" id="bubble" fill="#FFF"></path>
<path d="M65.7703624,103.026182 C59.1169113,103.026182 53.7232234,97.5458181 53.7232234,90.7854471 C53.7232234,84.0250762 59.1169113,78.5447127 65.7703624,78.5447127 C72.4238136,78.5447127 77.8175014,84.0250762 77.8175014,90.7854471 C77.8175014,97.5458181 72.4238136,103.026182 65.7703624,103.026182 Z M104.556273,103.026182 C97.9028222,103.026182 92.5091344,97.5458181 92.5091344,90.7854471 C92.5091344,84.0250762 97.9028222,78.5447127 104.556273,78.5447127 C111.209725,78.5447127 116.603412,84.0250762 116.603412,90.7854471 C116.603412,97.5458181 111.209725,103.026182 104.556273,103.026182 Z M143.342184,103.026182 C136.688733,103.026182 131.295045,97.5458181 131.295045,90.7854471 C131.295045,84.0250762 136.688733,78.5447127 143.342184,78.5447127 C149.995636,78.5447127 155.389323,84.0250762 155.389323,90.7854471 C155.389323,97.5458181 149.995636,103.026182 143.342184,103.026182 Z" id="circles" fill="#DB2323" fill-rule="nonzero"></path>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -5,8 +5,8 @@ const { remote } = require('electron');
const selfBrowserWindow = remote.getCurrentWindow();
selfBrowserWindow.webContents.once('dom-ready', () => {
window.JitsiMeetElectron = {
/**
window.JitsiMeetElectron = {
/**
* Get sources available for screensharing. The callback is invoked
* with an array of DesktopCapturerSources.
*
@ -21,16 +21,16 @@ selfBrowserWindow.webContents.once('dom-ready', () => {
* default electron will return images with height and width of
* 150px.
*/
obtainDesktopStreams (callback, errorCallback, options = {}) {
electron.desktopCapturer.getSources(options,
(error, sources) => {
if (error) {
errorCallback(error);
return;
}
obtainDesktopStreams(callback, errorCallback, options = {}) {
electron.desktopCapturer.getSources(options,
(error, sources) => {
if (error) {
errorCallback(error);
return;
}
callback(sources);
});
}
};
callback(sources);
});
},
};
});

View file

@ -1,39 +1,39 @@
const { ipcRenderer, remote } = require('electron');
if (process.platform === 'darwin') {
const NodeNotification = require('node-mac-notifier');
window.Notification = class Notification extends NodeNotification {
constructor (title, options) {
options.bundleId = `chat.rocket`;
super(title, options);
this.addEventListener('click', (/*notification*/) => this.onclick());
}
const NodeNotification = require('node-mac-notifier');
window.Notification = class Notification extends NodeNotification {
constructor(title, options) {
options.bundleId = 'chat.rocket';
super(title, options);
this.addEventListener('click', (/* notification*/) => this.onclick());
}
static requestPermission () {
return;
}
static requestPermission() {
return;
}
static get permission () {
return 'granted';
}
};
static get permission() {
return 'granted';
}
};
}
class Notification extends window.Notification {
constructor (title, options) {
super(title, options);
ipcRenderer.send('notification-shim', title, options);
constructor(title, options) {
super(title, options);
ipcRenderer.send('notification-shim', title, options);
// Handle correct notification using unique tag
ipcRenderer.once(`clicked-${options.tag}`, () => this.onclick());
}
// Handle correct notification using unique tag
ipcRenderer.once(`clicked-${ options.tag }`, () => this.onclick());
}
get onclick () {
return super.onclick;
}
get onclick() {
return super.onclick;
}
/*
/*
set onclick (fn) {
const result = super.onclick = () => {
ipcRenderer.send('focus');
@ -44,28 +44,28 @@ class Notification extends window.Notification {
}
*/
set onclick (fn) {
const result = super.onclick = () => {
const currentWindow = remote.getCurrentWindow();
if (process.platform === 'win32') {
if (currentWindow.isVisible()) {
currentWindow.focus();
} else if (currentWindow.isMinimized()) {
currentWindow.restore();
} else {
currentWindow.show();
}
} else if (currentWindow.isMinimized()) {
currentWindow.restore();
} else {
currentWindow.show();
}
set onclick(fn) {
const result = super.onclick = (...args) => {
const currentWindow = remote.getCurrentWindow();
if (process.platform === 'win32') {
if (currentWindow.isVisible()) {
currentWindow.focus();
} else if (currentWindow.isMinimized()) {
currentWindow.restore();
} else {
currentWindow.show();
}
} else if (currentWindow.isMinimized()) {
currentWindow.restore();
} else {
currentWindow.show();
}
ipcRenderer.sendToHost('focus');
fn.apply(this, arguments);
};
return result;
}
ipcRenderer.sendToHost('focus');
fn.apply(this, args);
};
return result;
}
}
module.exports = Notification;

View file

@ -13,371 +13,375 @@ const isWindows = ['win32', 'win64'].indexOf(os.platform()) !== -1;
class SpellCheck {
get userLanguage () {
const lang = localStorage.getItem('userLanguage');
if (lang) {
return lang.replace('-', '_');
}
}
get userLanguage() {
const lang = localStorage.getItem('userLanguage');
if (lang) {
return lang.replace('-', '_');
}
get dictionaries () {
const dictionaries = localStorage.getItem('spellcheckerDictionaries');
if (dictionaries) {
const result = JSON.parse(dictionaries);
if (Array.isArray(result)) {
return result;
}
}
}
return undefined;
}
constructor () {
this.enabledDictionaries = [];
this.contractions = this.getContractions();
this.loadAvailableDictionaries();
this.setEnabledDictionaries();
get dictionaries() {
const dictionaries = localStorage.getItem('spellcheckerDictionaries');
if (dictionaries) {
const result = JSON.parse(dictionaries);
if (Array.isArray(result)) {
return result;
}
}
this.languagesMenu = {
label: i18n.__('Spelling_languages'),
submenu: this.availableDictionaries.map((dictionary) => {
const menu = {
label: dictionary,
type: 'checkbox',
checked: this.enabledDictionaries.includes(dictionary),
click: (menuItem) => {
menu.checked = menuItem.checked;
// If not using os dictionary then limit to only 1 language
if (!this.multiLanguage && this.languagesMenu.submenu) {
this.languagesMenu.submenu.forEach((m) => {
if (m.label !== menuItem.label) {
m.checked = false;
}
});
}
if (menuItem.checked) {
this.setEnabled(dictionary);
} else {
this.disable(dictionary);
}
this.saveEnabledDictionaries();
}
};
return menu;
})
};
return undefined;
}
this.browseForLanguageMenu = new MenuItem({
label: i18n.__('Browse_for_language'),
click: () => {
dialog.showOpenDialog({
title: i18n.__('Open_Language_Dictionary'),
defaultPath: this.dictionariesPath,
filters: {name: 'Dictionaries', extensions: ['aff', 'dic']},
properties: ['openFile', 'multiSelections']
},
(filePaths) => { this.installDictionariesFromPaths(filePaths); }
);
}
});
}
constructor() {
this.enabledDictionaries = [];
this.contractions = this.getContractions();
this.loadAvailableDictionaries();
this.setEnabledDictionaries();
/**
this.languagesMenu = {
label: i18n.__('Spelling_languages'),
submenu: this.availableDictionaries.map((dictionary) => {
const menu = {
label: dictionary,
type: 'checkbox',
checked: this.enabledDictionaries.includes(dictionary),
click: (menuItem) => {
menu.checked = menuItem.checked;
// If not using os dictionary then limit to only 1 language
if (!this.multiLanguage && this.languagesMenu.submenu) {
this.languagesMenu.submenu.forEach((m) => {
if (m.label !== menuItem.label) {
m.checked = false;
}
});
}
if (menuItem.checked) {
this.setEnabled(dictionary);
} else {
this.disable(dictionary);
}
this.saveEnabledDictionaries();
},
};
return menu;
}),
};
this.browseForLanguageMenu = new MenuItem({
label: i18n.__('Browse_for_language'),
click: () => {
dialog.showOpenDialog({
title: i18n.__('Open_Language_Dictionary'),
defaultPath: this.dictionariesPath,
filters: { name: 'Dictionaries', extensions: ['aff', 'dic'] },
properties: ['openFile', 'multiSelections'],
},
(filePaths) => { this.installDictionariesFromPaths(filePaths); }
);
},
});
}
/**
* Set enabled dictionaries on load
* Either sets enabled dictionaries to saved preferences, or enables the first
* dictionary that is valid based on system (defaults to en_US)
*/
setEnabledDictionaries () {
const dictionaries = this.dictionaries;
if (dictionaries) {
// Dictionary disabled
if (dictionaries.length === 0) {
return;
}
if (this.setEnabled(dictionaries)) {
return;
}
}
setEnabledDictionaries() {
const { dictionaries } = this;
if (dictionaries) {
// Dictionary disabled
if (dictionaries.length === 0) {
return;
}
if (this.setEnabled(dictionaries)) {
return;
}
}
if (this.userLanguage) {
if (this.setEnabled(this.userLanguage)) {
return;
}
if (this.userLanguage.includes('_') && this.setEnabled(this.userLanguage.split('_')[0])) {
return;
}
}
if (this.userLanguage) {
if (this.setEnabled(this.userLanguage)) {
return;
}
if (this.userLanguage.includes('_') && this.setEnabled(this.userLanguage.split('_')[0])) {
return;
}
}
const navigatorLanguage = navigator.language.replace('-', '_');
if (this.setEnabled(navigatorLanguage)) {
return;
}
const navigatorLanguage = navigator.language.replace('-', '_');
if (this.setEnabled(navigatorLanguage)) {
return;
}
if (navigatorLanguage.includes('_') && this.setEnabled(navigatorLanguage.split('_')[0])) {
return;
}
if (navigatorLanguage.includes('_') && this.setEnabled(navigatorLanguage.split('_')[0])) {
return;
}
if (this.setEnabled('en_US')) {
return;
}
if (this.setEnabled('en_US')) {
return;
}
if (!this.setEnabled('en')) {
console.info('Unable to set a language for the spell checker - Spell checker is disabled');
}
if (!this.setEnabled('en')) {
console.info('Unable to set a language for the spell checker - Spell checker is disabled');
}
}
}
loadAvailableDictionaries () {
this.availableDictionaries = checker.getAvailableDictionaries().sort();
if (this.availableDictionaries.length === 0) {
this.multiLanguage = false;
// Dictionaries path is correct for build
this.dictionariesPath = path.join(remote.app.getAppPath(), '../dictionaries');
this.getDictionariesFromInstallDirectory();
} else {
this.multiLanguage = !isWindows;
this.availableDictionaries = this.availableDictionaries.map((dict) => dict.replace('-', '_'));
}
}
loadAvailableDictionaries() {
this.availableDictionaries = checker.getAvailableDictionaries().sort();
if (this.availableDictionaries.length === 0) {
this.multiLanguage = false;
// Dictionaries path is correct for build
this.dictionariesPath = path.join(remote.app.getAppPath(), '../dictionaries');
this.getDictionariesFromInstallDirectory();
} else {
this.multiLanguage = !isWindows;
this.availableDictionaries = this.availableDictionaries.map((dict) => dict.replace('-', '_'));
}
}
/**
/**
* Installs all of the dictionaries specified in filePaths
* Copies dicts into our dictionary path and adds them to availableDictionaries
*/
installDictionariesFromPaths (dictionaryPaths) {
for (const dictionaryPath of dictionaryPaths) {
const dictionaryFileName = dictionaryPath.split(path.sep).pop();
const dictionaryName = dictionaryFileName.slice(0, -4);
const newDictionaryPath = path.join(this.dictionariesPath, dictionaryFileName);
installDictionariesFromPaths(dictionaryPaths) {
for (const dictionaryPath of dictionaryPaths) {
const dictionaryFileName = dictionaryPath.split(path.sep).pop();
const dictionaryName = dictionaryFileName.slice(0, -4);
const newDictionaryPath = path.join(this.dictionariesPath, dictionaryFileName);
this.copyDictionaryToInstallDirectory(dictionaryName, dictionaryPath, newDictionaryPath);
}
}
this.copyDictionaryToInstallDirectory(dictionaryName, dictionaryPath, newDictionaryPath);
}
}
copyDictionaryToInstallDirectory (dictionaryName, oldPath, newPath) {
fs.createReadStream(oldPath).pipe(fs.createWriteStream(newPath)
.on('error', (errorMessage) => {
dialog.showErrorBox(i18n.__('Error'), i18n.__('Error copying dictionary file') + `: ${dictionaryName}`);
console.error(errorMessage);
})
.on('finish', () => {
if (!this.availableDictionaries.includes(dictionaryName)) {
this.availableDictionaries.push(dictionaryName);
}
}));
}
copyDictionaryToInstallDirectory(dictionaryName, oldPath, newPath) {
fs.createReadStream(oldPath).pipe(fs.createWriteStream(newPath)
.on('error', (errorMessage) => {
dialog.showErrorBox(i18n.__('Error'), `${ i18n.__('Error copying dictionary file') }: ${ dictionaryName }`);
console.error(errorMessage);
})
.on('finish', () => {
if (!this.availableDictionaries.includes(dictionaryName)) {
this.availableDictionaries.push(dictionaryName);
}
}));
}
getDictionariesFromInstallDirectory () {
if (this.dictionariesPath) {
const fileNames = fs.readdirSync(this.dictionariesPath);
for (const fileName of fileNames) {
const dictionaryExtension = fileName.slice(-3);
const dictionaryName = fileName.slice(0, -4);
if (!this.availableDictionaries.includes(dictionaryName)
getDictionariesFromInstallDirectory() {
if (this.dictionariesPath) {
const fileNames = fs.readdirSync(this.dictionariesPath);
for (const fileName of fileNames) {
const dictionaryExtension = fileName.slice(-3);
const dictionaryName = fileName.slice(0, -4);
if (!this.availableDictionaries.includes(dictionaryName)
&& (dictionaryExtension === 'aff' || dictionaryExtension === 'dic')) {
this.availableDictionaries.push(dictionaryName);
}
}
}
}
this.availableDictionaries.push(dictionaryName);
}
}
}
}
setEnabled (dictionaries) {
dictionaries = [].concat(dictionaries);
let result = false;
for (let i = 0; i < dictionaries.length; i++) {
if (this.availableDictionaries.includes(dictionaries[i])) {
result = true;
this.enabledDictionaries.push(dictionaries[i]);
// If using Hunspell or Windows then only allow 1 language for performance reasons
if (!this.multiLanguage) {
this.enabledDictionaries = [dictionaries[i]];
checker.setDictionary(dictionaries[i], this.dictionariesPath);
return true;
}
}
}
return result;
}
setEnabled(dictionaries) {
dictionaries = [].concat(dictionaries);
let result = false;
for (let i = 0; i < dictionaries.length; i++) {
if (this.availableDictionaries.includes(dictionaries[i])) {
result = true;
this.enabledDictionaries.push(dictionaries[i]);
// If using Hunspell or Windows then only allow 1 language for performance reasons
if (!this.multiLanguage) {
this.enabledDictionaries = [dictionaries[i]];
checker.setDictionary(dictionaries[i], this.dictionariesPath);
return true;
}
}
}
return result;
}
disable (dictionary) {
const pos = this.enabledDictionaries.indexOf(dictionary);
if (pos !== -1) {
this.enabledDictionaries.splice(pos, 1);
}
}
disable(dictionary) {
const pos = this.enabledDictionaries.indexOf(dictionary);
if (pos !== -1) {
this.enabledDictionaries.splice(pos, 1);
}
}
getContractions () {
const contractions = [
"ain't", "aren't", "can't", "could've", "couldn't", "couldn't've", "didn't", "doesn't", "don't", "hadn't",
"hadn't've", "hasn't", "haven't", "he'd", "he'd've", "he'll", "he's", "how'd", "how'll", "how's", "I'd",
"I'd've", "I'll", "I'm", "I've", "isn't", "it'd", "it'd've", "it'll", "it's", "let's", "ma'am", "mightn't",
"mightn't've", "might've", "mustn't", "must've", "needn't", "not've", "o'clock", "shan't", "she'd", "she'd've",
"she'll", "she's", "should've", "shouldn't", "shouldn't've", "that'll", "that's", "there'd", "there'd've",
"there're", "there's", "they'd", "they'd've", "they'll", "they're", "they've", "wasn't", "we'd", "we'd've",
"we'll", "we're", "we've", "weren't", "what'll", "what're", "what's", "what've", "when's", "where'd",
"where's", "where've", "who'd", "who'll", "who're", "who's", "who've", "why'll", "why're", "why's", "won't",
"would've", "wouldn't", "wouldn't've", "y'all", "y'all'd've", "you'd", "you'd've", "you'll", "you're", "you've"
];
getContractions() {
const contractions = [
"ain't", "aren't", "can't", "could've", "couldn't", "couldn't've", "didn't", "doesn't", "don't", "hadn't",
"hadn't've", "hasn't", "haven't", "he'd", "he'd've", "he'll", "he's", "how'd", "how'll", "how's", "I'd",
"I'd've", "I'll", "I'm", "I've", "isn't", "it'd", "it'd've", "it'll", "it's", "let's", "ma'am", "mightn't",
"mightn't've", "might've", "mustn't", "must've", "needn't", "not've", "o'clock", "shan't", "she'd", "she'd've",
"she'll", "she's", "should've", "shouldn't", "shouldn't've", "that'll", "that's", "there'd", "there'd've",
"there're", "there's", "they'd", "they'd've", "they'll", "they're", "they've", "wasn't", "we'd", "we'd've",
"we'll", "we're", "we've", "weren't", "what'll", "what're", "what's", "what've", "when's", "where'd",
"where's", "where've", "who'd", "who'll", "who're", "who's", "who've", "why'll", "why're", "why's", "won't",
"would've", "wouldn't", "wouldn't've", "y'all", "y'all'd've", "you'd", "you'd've", "you'll", "you're", "you've",
];
const contractionMap = contractions.reduce((acc, word) => {
acc[word.replace(/'.*/, '')] = true;
return acc;
}, {});
const contractionMap = contractions.reduce((acc, word) => {
acc[word.replace(/'.*/, '')] = true;
return acc;
}, {});
return contractionMap;
}
return contractionMap;
}
enable () {
webFrame.setSpellCheckProvider('', false, {
spellCheck: (text) => this.isCorrect(text)
});
enable() {
webFrame.setSpellCheckProvider('', false, {
spellCheck: (text) => this.isCorrect(text),
});
this.setupContextMenuListener();
}
this.setupContextMenuListener();
}
getMenu () {
return [
{
label: i18n.__('Undo'),
role: 'undo'
},
{
label: i18n.__('Redo'),
role: 'redo'
},
{
type: 'separator'
},
{
label: i18n.__('Cut'),
role: 'cut',
accelerator: 'CommandOrControl+X',
},
{
label: i18n.__('Copy'),
role: 'copy',
accelerator: 'CommandOrControl+C',
},
{
label: i18n.__('Paste'),
role: 'paste',
accelerator: 'CommandOrControl+V',
},
{
label: i18n.__('Select_All'),
role: 'selectall',
accelerator: 'CommandOrControl+A',
}
];
}
getMenu() {
return [
{
label: i18n.__('Undo'),
role: 'undo',
},
{
label: i18n.__('Redo'),
role: 'redo',
},
{
type: 'separator',
},
{
label: i18n.__('Cut'),
role: 'cut',
accelerator: 'CommandOrControl+X',
},
{
label: i18n.__('Copy'),
role: 'copy',
accelerator: 'CommandOrControl+C',
},
{
label: i18n.__('Paste'),
role: 'paste',
accelerator: 'CommandOrControl+V',
},
{
label: i18n.__('Select_All'),
role: 'selectall',
accelerator: 'CommandOrControl+A',
},
];
}
saveEnabledDictionaries () {
localStorage.setItem('spellcheckerDictionaries', JSON.stringify(this.enabledDictionaries));
}
saveEnabledDictionaries() {
localStorage.setItem('spellcheckerDictionaries', JSON.stringify(this.enabledDictionaries));
}
isCorrect (text) {
if (!this.enabledDictionaries.length || this.contractions[text.toLocaleLowerCase()]) {
return true;
}
isCorrect(text) {
if (!this.enabledDictionaries.length || this.contractions[text.toLocaleLowerCase()]) {
return true;
}
if (this.multiLanguage) {
for (let i = 0; i < this.enabledDictionaries.length; i++) {
checker.setDictionary(this.enabledDictionaries[i]);
if (!checker.isMisspelled(text)) {
return true;
}
}
} else {
return !checker.isMisspelled(text);
}
return false;
}
if (this.multiLanguage) {
for (let i = 0; i < this.enabledDictionaries.length; i++) {
checker.setDictionary(this.enabledDictionaries[i]);
if (!checker.isMisspelled(text)) {
return true;
}
}
} else {
return !checker.isMisspelled(text);
}
return false;
}
getCorrections (text) {
if (!this.multiLanguage) {
return checker.getCorrectionsForMisspelling(text);
}
getCorrections(text) {
if (!this.multiLanguage) {
return checker.getCorrectionsForMisspelling(text);
}
const allCorrections = this.enabledDictionaries.map((dictionary) => {
checker.setDictionary(dictionary);
return checker.getCorrectionsForMisspelling(text);
}).filter((c) => c.length > 0);
const allCorrections = this.enabledDictionaries.map((dictionary) => {
checker.setDictionary(dictionary);
return checker.getCorrectionsForMisspelling(text);
}).filter((c) => c.length > 0);
const length = Math.max(...allCorrections.map((a) => a.length));
const length = Math.max(...allCorrections.map((a) => a.length));
// Get the best suggestions of each language first
const corrections = [];
for (let i = 0; i < length; i++) {
corrections.push(...allCorrections.map((c) => c[i]).filter((c) => c));
}
// Get the best suggestions of each language first
const corrections = [];
for (let i = 0; i < length; i++) {
corrections.push(...allCorrections.map((c) => c[i]).filter((c) => c));
}
// Remove duplicates
return [...new Set(corrections)];
}
// Remove duplicates
return [...new Set(corrections)];
}
setupContextMenuListener () {
window.addEventListener('contextmenu', (event) => {
event.preventDefault();
setupContextMenuListener() {
window.addEventListener('contextmenu', (event) => {
event.preventDefault();
const template = this.getMenu();
const template = this.getMenu();
if (this.languagesMenu && this.browseForLanguageMenu) {
template.unshift({ type: 'separator' });
if (this.dictionariesPath) {
template.unshift(this.browseForLanguageMenu);
}
template.unshift(this.languagesMenu);
}
if (this.languagesMenu && this.browseForLanguageMenu) {
template.unshift({ type: 'separator' });
if (this.dictionariesPath) {
template.unshift(this.browseForLanguageMenu);
}
template.unshift(this.languagesMenu);
}
setTimeout(() => {
if (event.target.nodeName === 'A') {
const targetLink = event.target.href;
setTimeout(() => {
if (event.target.nodeName === 'A') {
const targetLink = event.target.href;
template.unshift({
label: i18n.__('Open_Link'),
click: () => {
shell.openExternal(targetLink);
}
});
}
template.unshift({
label: i18n.__('Open_Link'),
click: () => {
shell.openExternal(targetLink);
},
});
}
if (['TEXTAREA', 'INPUT'].indexOf(event.target.nodeName) > -1) {
const text = window.getSelection().toString().trim();
if (text !== '' && !this.isCorrect(text)) {
const options = this.getCorrections(text);
const maxItems = Math.min(options.length, 6);
if (['TEXTAREA', 'INPUT'].indexOf(event.target.nodeName) > -1) {
const text = window.getSelection().toString().trim();
if (text !== '' && !this.isCorrect(text)) {
const options = this.getCorrections(text);
const maxItems = Math.min(options.length, 6);
if (maxItems > 0) {
const suggestions = [];
const onClick = function (menuItem) {
webContents.replaceMisspelling(menuItem.label);
};
if (maxItems > 0) {
const suggestions = [];
const onClick = function(menuItem) {
webContents.replaceMisspelling(menuItem.label);
};
for (let i = 0; i < options.length; i++) {
const item = options[i];
suggestions.push({ label: item, click: onClick });
}
for (let i = 0; i < options.length; i++) {
const item = options[i];
suggestions.push({ label: item, click: onClick });
}
template.unshift({ type: 'separator' });
template.unshift({ type: 'separator' });
if (suggestions.length > maxItems) {
const moreSuggestions = {
label: i18n.__('More_spelling_suggestions'),
submenu: suggestions.slice(maxItems)
};
template.unshift(moreSuggestions);
}
if (suggestions.length > maxItems) {
const moreSuggestions = {
label: i18n.__('More_spelling_suggestions'),
submenu: suggestions.slice(maxItems),
};
template.unshift(moreSuggestions);
}
template.unshift.apply(template, suggestions.slice(0, maxItems));
} else {
template.unshift({ label: i18n.__('No_suggestions'), enabled: false });
}
}
}
template.unshift.apply(template, suggestions.slice(0, maxItems));
} else {
template.unshift({ label: i18n.__('No_suggestions'), enabled: false });
}
}
}
menu = remote.Menu.buildFromTemplate(template);
menu.popup(remote.getCurrentWindow(), undefined, undefined, 5);
}, 0);
}, false);
}
menu = remote.Menu.buildFromTemplate(template);
menu.popup(remote.getCurrentWindow(), undefined, undefined, 5);
}, 0);
}, false);
}
}
module.exports = SpellCheck;

View file

@ -12,118 +12,119 @@ window.i18n = i18n;
const defaultWindowOpen = window.open;
function customWindowOpen (url, frameName, features) {
if (url.indexOf('meet.jit.si') !== -1) {
features = ((features) ? (features + ",") : "") +
"nodeIntegration=true,preload=" + path.join(__dirname, 'jitsi-preload.js');
return defaultWindowOpen(url, frameName, features);
} else {
return defaultWindowOpen(url, frameName, features);
}
function customWindowOpen(url, frameName, features) {
const jitsiDomain = RocketChat.settings.get('Jitsi_Domain');
if (jitsiDomain && url.indexOf(jitsiDomain) !== -1) {
features = `${ (features) ? (`${ features },`) : ''
}nodeIntegration=true,preload=${ path.join(__dirname, 'jitsi-preload.js') }`;
return defaultWindowOpen(url, frameName, features);
} else {
return defaultWindowOpen(url, frameName, features);
}
}
window.open = customWindowOpen;
const events = ['unread-changed', 'get-sourceId', 'user-status-manually-set'];
events.forEach(function (e) {
window.addEventListener(e, function (event) {
ipcRenderer.sendToHost(e, event.detail);
});
events.forEach(function(e) {
window.addEventListener(e, function(event) {
ipcRenderer.sendToHost(e, event.detail);
});
});
const userPresenceControl = () => {
const INTERVAL = 10000; // 10s
setInterval(() => {
try {
const idleTime = ipcRenderer.sendSync('getSystemIdleTime');
if (idleTime < INTERVAL) {
UserPresence.setOnline();
}
} catch (e) {
console.error(`Error getting system idle time: ${e}`);
}
}, INTERVAL);
const INTERVAL = 10000; // 10s
setInterval(() => {
try {
const idleTime = ipcRenderer.sendSync('getSystemIdleTime');
if (idleTime < INTERVAL) {
UserPresence.setOnline();
}
} catch (e) {
console.error(`Error getting system idle time: ${ e }`);
}
}, INTERVAL);
};
const changeSidebarColor = () => {
const sidebar = document.querySelector('.sidebar');
const fullpage = document.querySelector('.full-page');
if (sidebar) {
const sidebarItem = sidebar.querySelector('.sidebar-item');
let itemColor;
if (sidebarItem) {
itemColor = window.getComputedStyle(sidebarItem);
}
const { color, background } = window.getComputedStyle(sidebar);
ipcRenderer.sendToHost('sidebar-background', {color: itemColor || color, background: background});
} else if (fullpage) {
const { color, background } = window.getComputedStyle(fullpage);
ipcRenderer.sendToHost('sidebar-background', {color: color, background: background});
} else {
window.requestAnimationFrame(changeSidebarColor);
const sidebar = document.querySelector('.sidebar');
const fullpage = document.querySelector('.full-page');
if (sidebar) {
const sidebarItem = sidebar.querySelector('.sidebar-item');
let itemColor;
if (sidebarItem) {
itemColor = window.getComputedStyle(sidebarItem);
}
const { color, background } = window.getComputedStyle(sidebar);
ipcRenderer.sendToHost('sidebar-background', { color: itemColor || color, background });
} else if (fullpage) {
const { color, background } = window.getComputedStyle(fullpage);
ipcRenderer.sendToHost('sidebar-background', { color, background });
} else {
window.requestAnimationFrame(changeSidebarColor);
}
}
};
ipcRenderer.on('request-sidebar-color', () => {
changeSidebarColor();
changeSidebarColor();
});
window.addEventListener('load', function () {
Meteor.startup(function () {
Tracker.autorun(function () {
const siteName = RocketChat.settings.get('Site_Name');
if (siteName) {
ipcRenderer.sendToHost('title-changed', siteName);
}
});
});
userPresenceControl();
window.addEventListener('load', function() {
Meteor.startup(function() {
Tracker.autorun(function() {
const siteName = RocketChat.settings.get('Site_Name');
if (siteName) {
ipcRenderer.sendToHost('title-changed', siteName);
}
});
});
userPresenceControl();
});
window.onload = function () {
const $ = require('./vendor/jquery-3.1.1');
function checkExternalUrl (e) {
const href = $(this).attr('href');
// Check href matching current domain
if (RegExp(`^${location.protocol}\/\/${location.host}`).test(href)) {
return;
}
window.onload = function() {
const $ = require('./vendor/jquery-3.1.1');
function checkExternalUrl(e) {
const href = $(this).attr('href');
// Check href matching current domain
if (RegExp(`^${ location.protocol }\/\/${ location.host }`).test(href)) {
return;
}
// Check if is file upload link
if (/^\/file-upload\//.test(href) && !this.hasAttribute('download')) {
this.setAttribute('download', '');
this.click();
}
// Check if is file upload link
if (/^\/file-upload\//.test(href) && !this.hasAttribute('download')) {
this.setAttribute('download', '');
this.click();
}
// Check href matching relative URL
if (!/^([a-z]+:)?\/\//.test(href)) {
return;
}
// Check href matching relative URL
if (!/^([a-z]+:)?\/\//.test(href)) {
return;
}
if (/^file:\/\/.+/.test(href)) {
const item = href.slice(6);
shell.showItemInFolder(item);
e.preventDefault();
} else {
shell.openExternal(href);
e.preventDefault();
}
}
if (/^file:\/\/.+/.test(href)) {
const item = href.slice(6);
shell.showItemInFolder(item);
e.preventDefault();
} else {
shell.openExternal(href);
e.preventDefault();
}
}
$(document).on('click', 'a', checkExternalUrl);
$(document).on('click', 'a', checkExternalUrl);
$('#reload').click(function () {
ipcRenderer.sendToHost('reload-server');
$(this).hide();
$(this).parent().find('.loading-animation').show();
});
$('#reload').click(function() {
ipcRenderer.sendToHost('reload-server');
$(this).hide();
$(this).parent().find('.loading-animation').show();
});
};
// Prevent redirect to url when dragging in
document.addEventListener('dragover', e => e.preventDefault());
document.addEventListener('drop', e => e.preventDefault());
document.addEventListener('dragover', (e) => e.preventDefault());
document.addEventListener('drop', (e) => e.preventDefault());
const spellChecker = new SpellCheck();
spellChecker.enable();

View file

@ -1 +1 @@
Bugsnag.apiKey = "2fa5f5a7b728b84ae273015995b42a6e";
Bugsnag.apiKey = '2fa5f5a7b728b84ae273015995b42a6e';

View file

@ -10,85 +10,85 @@ import historyMenu from './menus/history';
import windowMenu from './menus/window';
import helpMenu from './menus/help';
const Menu = remote.Menu;
const { Menu } = remote;
const APP_NAME = remote.app.getName();
const isMac = process.platform === 'darwin';
document.title = APP_NAME;
const menuTemplate = [
{
label: '&' + APP_NAME,
submenu: appMenu
},
{
label: '&' + i18n.__('Edit'),
submenu: editMenu
},
{
label: '&' + i18n.__('View'),
submenu: viewMenu
},
{
label: '&' + i18n.__('History'),
submenu: historyMenu
},
{
label: '&' + i18n.__('Window'),
id: 'window',
role: 'window',
submenu: windowMenu
},
{
label: '&' + i18n.__('Help'),
role: 'help',
submenu: helpMenu
}
{
label: `&${ APP_NAME }`,
submenu: appMenu,
},
{
label: `&${ i18n.__('Edit') }`,
submenu: editMenu,
},
{
label: `&${ i18n.__('View') }`,
submenu: viewMenu,
},
{
label: `&${ i18n.__('History') }`,
submenu: historyMenu,
},
{
label: `&${ i18n.__('Window') }`,
id: 'window',
role: 'window',
submenu: windowMenu,
},
{
label: `&${ i18n.__('Help') }`,
role: 'help',
submenu: helpMenu,
},
];
function createMenu () {
const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
function createMenu() {
const menu = Menu.buildFromTemplate(menuTemplate);
Menu.setApplicationMenu(menu);
}
function addServer (host, position) {
const index = windowMenu.findIndex((i) => i.id === 'server-list-separator');
windowMenu[index].visible = true;
function addServer(host, position) {
const index = windowMenu.findIndex((i) => i.id === 'server-list-separator');
windowMenu[index].visible = true;
const menuItem = {
label: '&' + host.title,
accelerator: `CmdOrCtrl+ ${position}`,
position: 'before=server-list-separator',
id: host.url,
click: () => {
const mainWindow = remote.getCurrentWindow();
mainWindow.show();
servers.setActive(host.url);
}
};
const menuItem = {
label: `&${ host.title }`,
accelerator: `CmdOrCtrl+ ${ position }`,
position: 'before=server-list-separator',
id: host.url,
click: () => {
const mainWindow = remote.getCurrentWindow();
mainWindow.show();
servers.setActive(host.url);
},
};
windowMenu.push(menuItem);
windowMenu.push(menuItem);
createMenu();
createMenu();
}
function removeServer (server) {
const index = windowMenu.findIndex((i) => i.id === server);
windowMenu.splice(index, 1);
createMenu();
function removeServer(server) {
const index = windowMenu.findIndex((i) => i.id === server);
windowMenu.splice(index, 1);
createMenu();
}
function autoHideMenu () {
remote.getCurrentWindow().setAutoHideMenuBar(true);
function autoHideMenu() {
remote.getCurrentWindow().setAutoHideMenuBar(true);
}
if (!isMac && localStorage.getItem('autohideMenu') === 'true') {
autoHideMenu();
autoHideMenu();
}
createMenu();
export {
addServer,
removeServer
addServer,
removeServer,
};

View file

@ -1,67 +1,51 @@
import { remote } from 'electron';
import { remote, ipcRenderer } from 'electron';
import i18n from '../../i18n/index.js';
const APP_NAME = remote.app.getName();
const appName = remote.app.getName();
const isMac = process.platform === 'darwin';
const appTemplate = [
{
label: i18n.__('About', APP_NAME),
click: function () {
const win = new remote.BrowserWindow({
width: 310,
height: 240,
resizable: false,
show: false,
center: true,
maximizable: false,
minimizable: false,
title: 'About Rocket.Chat'
});
win.loadURL('file://' + __dirname + '/about.html');
win.setMenuBarVisibility(false);
win.show();
}
},
{
type: 'separator',
id: 'about-sep'
},
{
label: i18n.__('Quit_App', APP_NAME),
accelerator: 'CommandOrControl+Q',
click: function () {
remote.app.quit();
}
}
{
label: i18n.__('About', appName),
click: () => ipcRenderer.send('show-about-dialog'),
},
{
type: 'separator',
id: 'about-sep',
},
{
label: i18n.__('Quit_App', appName),
accelerator: 'CommandOrControl+Q',
click: () => remote.app.quit(),
},
];
if (isMac) {
const macAppExtraTemplate = [
{
role: 'services',
submenu: [],
position: 'after=about-sep'
},
{
type: 'separator'
},
{
accelerator: 'Command+H',
role: 'hide'
},
{
accelerator: 'Command+Alt+H',
role: 'hideothers'
},
{
role: 'unhide'
},
{
type: 'separator'
}
];
appTemplate.push(...macAppExtraTemplate);
const macAppExtraTemplate = [
{
role: 'services',
submenu: [],
position: 'after=about-sep',
},
{
type: 'separator',
},
{
accelerator: 'Command+H',
role: 'hide',
},
{
accelerator: 'Command+Alt+H',
role: 'hideothers',
},
{
role: 'unhide',
},
{
type: 'separator',
},
];
appTemplate.push(...macAppExtraTemplate);
}
export default appTemplate;

View file

@ -1,39 +1,39 @@
import i18n from '../../i18n/index.js';
const editTemplate = [
{
label: i18n.__('Undo'),
accelerator: 'CommandOrControl+Z',
role: 'undo'
},
{
label: i18n.__('Redo'),
accelerator: 'CommandOrControl+Shift+Z',
role: 'redo'
},
{
type: 'separator'
},
{
label: i18n.__('Cut'),
accelerator: 'CommandOrControl+X',
role: 'cut'
},
{
label: i18n.__('Copy'),
accelerator: 'CommandOrControl+C',
role: 'copy'
},
{
label: i18n.__('Paste'),
accelerator: 'CommandOrControl+V',
role: 'paste'
},
{
label: i18n.__('Select_All'),
accelerator: 'CommandOrControl+A',
role: 'selectall'
}
{
label: i18n.__('Undo'),
accelerator: 'CommandOrControl+Z',
role: 'undo',
},
{
label: i18n.__('Redo'),
accelerator: 'CommandOrControl+Shift+Z',
role: 'redo',
},
{
type: 'separator',
},
{
label: i18n.__('Cut'),
accelerator: 'CommandOrControl+X',
role: 'cut',
},
{
label: i18n.__('Copy'),
accelerator: 'CommandOrControl+C',
role: 'copy',
},
{
label: i18n.__('Paste'),
accelerator: 'CommandOrControl+V',
role: 'paste',
},
{
label: i18n.__('Select_All'),
accelerator: 'CommandOrControl+A',
role: 'selectall',
},
];
export default editTemplate;

View file

@ -4,28 +4,28 @@ import i18n from '../../i18n/index.js';
const APP_NAME = remote.app.getName();
const helpTemplate = [
{
label: i18n.__('Help_Name', APP_NAME),
click: () => remote.shell.openExternal('https://rocket.chat/docs')
},
{
type: 'separator'
},
{
label: i18n.__('Report_Issue'),
click: () => remote.shell.openExternal('https://github.com/RocketChat/Rocket.Chat/issues')
},
{
label: i18n.__('Reset_App_Data'),
click: () => servers.resetAppData()
},
{
type: 'separator'
},
{
label: i18n.__('Learn_More'),
click: () => remote.shell.openExternal('https://rocket.chat')
}
{
label: i18n.__('Help_Name', APP_NAME),
click: () => remote.shell.openExternal('https://rocket.chat/docs'),
},
{
type: 'separator',
},
{
label: i18n.__('Report_Issue'),
click: () => remote.shell.openExternal('https://github.com/RocketChat/Rocket.Chat/issues'),
},
{
label: i18n.__('Reset_App_Data'),
click: () => servers.resetAppData(),
},
{
type: 'separator',
},
{
label: i18n.__('Learn_More'),
click: () => remote.shell.openExternal('https://rocket.chat'),
},
];
export default helpTemplate;

View file

@ -3,29 +3,29 @@ import i18n from '../../i18n/index.js';
const isMac = process.platform === 'darwin';
const macWindowTemplate = [
{
label: i18n.__('Back'),
accelerator: 'Command+left',
click: () => { webview.goBack(); }
},
{
label: i18n.__('Forward'),
accelerator: 'Command+right',
click: () => { webview.goForward(); }
}
{
label: i18n.__('Back'),
accelerator: 'Command+left',
click: () => { webview.goBack(); },
},
{
label: i18n.__('Forward'),
accelerator: 'Command+right',
click: () => { webview.goForward(); },
},
];
const windowTemplate = [
{
label: i18n.__('Back'),
accelerator: 'Alt+Left',
click: () => { webview.goBack(); }
},
{
label: i18n.__('Forward'),
accelerator: 'Alt+Right',
click: () => { webview.goForward(); }
},
{
label: i18n.__('Back'),
accelerator: 'Alt+Left',
click: () => { webview.goBack(); },
},
{
label: i18n.__('Forward'),
accelerator: 'Alt+Right',
click: () => { webview.goForward(); },
},
];
export default isMac ? macWindowTemplate : windowTemplate;

View file

@ -5,119 +5,119 @@ import sidebar from '../sidebar';
import tray from '../tray';
const isMac = process.platform === 'darwin';
const certificate = remote.require('./background').certificate;
const { certificate } = remote.require('./background');
const viewTemplate = [
{
label: i18n.__('Original_Zoom'),
accelerator: 'CommandOrControl+0',
role: 'resetzoom'
},
{
label: i18n.__('Zoom_In'),
accelerator: 'CommandOrControl+Plus',
role: 'zoomin'
},
{
label: i18n.__('Zoom_Out'),
accelerator: 'CommandOrControl+-',
role: 'zoomout'
},
{
type: 'separator'
},
{
label: i18n.__('Current_Server_Reload'),
accelerator: 'CommandOrControl+R',
click: function () {
const activeWebview = webview.getActive();
if (activeWebview) {
activeWebview.reload();
}
}
},
{
label: i18n.__('Current_Server_Toggle_DevTools'),
accelerator: isMac ? 'Command+Alt+I' : 'Ctrl+Shift+I',
click: function () {
const activeWebview = webview.getActive();
if (activeWebview) {
activeWebview.openDevTools();
}
}
},
{
type: 'separator'
},
{
label: i18n.__('Application_Reload'),
accelerator: 'CommandOrControl+Shift+R',
click: function () {
const mainWindow = remote.getCurrentWindow();
if (mainWindow.destroyTray) {
mainWindow.destroyTray();
}
mainWindow.reload();
}
},
{
label: i18n.__('Application_Toggle_DevTools'),
click: function () {
remote.getCurrentWindow().toggleDevTools();
}
},
{
type: 'separator',
id: 'toggle'
},
{
label: i18n.__('Toggle_Server_List'),
click: function () {
sidebar.toggle();
}
},
{
type: 'separator'
},
{
label: i18n.__('Clear'),
submenu: [
{
label: i18n.__('Clear_Trusted_Certificates'),
click: function () {
certificate.clear();
}
}
]
}
{
label: i18n.__('Original_Zoom'),
accelerator: 'CommandOrControl+0',
role: 'resetzoom',
},
{
label: i18n.__('Zoom_In'),
accelerator: 'CommandOrControl+Plus',
role: 'zoomin',
},
{
label: i18n.__('Zoom_Out'),
accelerator: 'CommandOrControl+-',
role: 'zoomout',
},
{
type: 'separator',
},
{
label: i18n.__('Current_Server_Reload'),
accelerator: 'CommandOrControl+R',
click() {
const activeWebview = webview.getActive();
if (activeWebview) {
activeWebview.reload();
}
},
},
{
label: i18n.__('Current_Server_Toggle_DevTools'),
accelerator: isMac ? 'Command+Alt+I' : 'Ctrl+Shift+I',
click() {
const activeWebview = webview.getActive();
if (activeWebview) {
activeWebview.openDevTools();
}
},
},
{
type: 'separator',
},
{
label: i18n.__('Application_Reload'),
accelerator: 'CommandOrControl+Shift+R',
click() {
const mainWindow = remote.getCurrentWindow();
if (mainWindow.destroyTray) {
mainWindow.destroyTray();
}
mainWindow.reload();
},
},
{
label: i18n.__('Application_Toggle_DevTools'),
click() {
remote.getCurrentWindow().toggleDevTools();
},
},
{
type: 'separator',
id: 'toggle',
},
{
label: i18n.__('Toggle_Server_List'),
click() {
sidebar.toggle();
},
},
{
type: 'separator',
},
{
label: i18n.__('Clear'),
submenu: [
{
label: i18n.__('Clear_Trusted_Certificates'),
click() {
certificate.clear();
},
},
],
},
];
if (isMac) {
viewTemplate.push({
label: i18n.__('Toggle_Tray_Icon'),
click: function () {
tray.toggle();
},
position: 'after=toggle'
}, {
label: i18n.__('Toggle_Full_Screen'),
accelerator: 'Control+Command+F',
click: function () {
const mainWindow = remote.getCurrentWindow();
mainWindow.setFullScreen(!mainWindow.isFullScreen());
},
position: 'after=toggle'
});
viewTemplate.push({
label: i18n.__('Toggle_Tray_Icon'),
click() {
tray.toggle();
},
position: 'after=toggle',
}, {
label: i18n.__('Toggle_Full_Screen'),
accelerator: 'Control+Command+F',
click() {
const mainWindow = remote.getCurrentWindow();
mainWindow.setFullScreen(!mainWindow.isFullScreen());
},
position: 'after=toggle',
});
} else {
viewTemplate.push({
label: i18n.__('Toggle_Menu_Bar'),
click: function () {
const current = localStorage.getItem('autohideMenu') === 'true';
remote.getCurrentWindow().setAutoHideMenuBar(!current);
localStorage.setItem('autohideMenu', JSON.stringify(!current));
},
position: 'after=toggle'
});
viewTemplate.push({
label: i18n.__('Toggle_Menu_Bar'),
click() {
const current = localStorage.getItem('autohideMenu') === 'true';
remote.getCurrentWindow().setAutoHideMenuBar(!current);
localStorage.setItem('autohideMenu', JSON.stringify(!current));
},
position: 'after=toggle',
});
}
export default viewTemplate;

View file

@ -5,70 +5,70 @@ import servers from '../servers';
const isMac = process.platform === 'darwin';
const macWindowTemplate = [
{
label: i18n.__('Minimize'),
accelerator: 'Command+M',
role: 'minimize'
},
{
label: i18n.__('Close'),
accelerator: 'Command+W',
role: 'close'
},
{
type: 'separator'
},
{
type: 'separator',
id: 'server-list-separator',
visible: false
},
{
label: i18n.__('Add_new_server'),
accelerator: 'Command+N',
click: function () {
const mainWindow = remote.getCurrentWindow();
mainWindow.show();
servers.clearActive();
webview.showLanding();
}
},
{
type: 'separator'
},
{
label: i18n.__('Bring_All_to_Front'),
click: function () {
const mainWindow = remote.getCurrentWindow();
mainWindow.show();
}
}
{
label: i18n.__('Minimize'),
accelerator: 'Command+M',
role: 'minimize',
},
{
label: i18n.__('Close'),
accelerator: 'Command+W',
role: 'close',
},
{
type: 'separator',
},
{
type: 'separator',
id: 'server-list-separator',
visible: false,
},
{
label: i18n.__('Add_new_server'),
accelerator: 'Command+N',
click() {
const mainWindow = remote.getCurrentWindow();
mainWindow.show();
servers.clearActive();
webview.showLanding();
},
},
{
type: 'separator',
},
{
label: i18n.__('Bring_All_to_Front'),
click() {
const mainWindow = remote.getCurrentWindow();
mainWindow.show();
},
},
];
const windowTemplate = [
{
type: 'separator',
id: 'server-list-separator',
visible: false
},
{
label: i18n.__('Add_new_server'),
accelerator: 'Ctrl+N',
click: function () {
servers.clearActive();
webview.showLanding();
}
},
{
type: 'separator'
},
{
label: i18n.__('Close'),
accelerator: 'Ctrl+W',
click: function () {
remote.getCurrentWindow().close();
}
}
{
type: 'separator',
id: 'server-list-separator',
visible: false,
},
{
label: i18n.__('Add_new_server'),
accelerator: 'Ctrl+N',
click() {
servers.clearActive();
webview.showLanding();
},
},
{
type: 'separator',
},
{
label: i18n.__('Close'),
accelerator: 'Ctrl+W',
click() {
remote.getCurrentWindow().close();
},
},
];
export default isMac ? macWindowTemplate : windowTemplate;

View file

@ -4,310 +4,310 @@ import jetpack from 'fs-jetpack';
import { EventEmitter } from 'events';
import { remote, ipcRenderer } from 'electron';
import i18n from '../i18n/index.js';
const remoteServers = remote.require('./background').remoteServers;
const { remoteServers } = remote.require('./background');
class Servers extends EventEmitter {
constructor () {
super();
this.load();
const processProtocol = this.getProtocolUrlFromProcess(remote.process.argv);
if (processProtocol) {
this.showHostConfirmation(processProtocol);
}
ipcRenderer.on('add-host', (e, host) => {
if (this.hostExists(host)) {
this.setActive(host);
} else {
this.showHostConfirmation(host);
}
});
}
constructor() {
super();
this.load();
const processProtocol = this.getProtocolUrlFromProcess(remote.process.argv);
if (processProtocol) {
this.showHostConfirmation(processProtocol);
}
ipcRenderer.on('add-host', (e, host) => {
if (this.hostExists(host)) {
this.setActive(host);
} else {
this.showHostConfirmation(host);
}
});
}
get hosts () {
return this._hosts;
}
get hosts() {
return this._hosts;
}
set hosts (hosts) {
this._hosts = hosts;
this.save();
return true;
}
set hosts(hosts) {
this._hosts = hosts;
this.save();
return true;
}
get hostsKey () {
return 'rocket.chat.hosts';
}
get hostsKey() {
return 'rocket.chat.hosts';
}
get activeKey () {
return 'rocket.chat.currentHost';
}
get activeKey() {
return 'rocket.chat.currentHost';
}
load () {
let hosts = localStorage.getItem(this.hostsKey);
load() {
let hosts = localStorage.getItem(this.hostsKey);
try {
hosts = JSON.parse(hosts);
} catch (e) {
if (typeof hosts === 'string' && hosts.match(/^https?:\/\//)) {
hosts = {};
hosts[hosts] = {
title: hosts,
url: hosts
};
}
try {
hosts = JSON.parse(hosts);
} catch (e) {
if (typeof hosts === 'string' && hosts.match(/^https?:\/\//)) {
hosts = {};
hosts[hosts] = {
title: hosts,
url: hosts,
};
}
localStorage.setItem(this.hostsKey, JSON.stringify(hosts));
}
localStorage.setItem(this.hostsKey, JSON.stringify(hosts));
}
if (hosts === null) {
hosts = {};
}
if (hosts === null) {
hosts = {};
}
if (Array.isArray(hosts)) {
const oldHosts = hosts;
hosts = {};
oldHosts.forEach(function (item) {
item = item.replace(/\/$/, '');
hosts[item] = {
title: item,
url: item
};
});
localStorage.setItem(this.hostsKey, JSON.stringify(hosts));
}
if (Array.isArray(hosts)) {
const oldHosts = hosts;
hosts = {};
oldHosts.forEach(function(item) {
item = item.replace(/\/$/, '');
hosts[item] = {
title: item,
url: item,
};
});
localStorage.setItem(this.hostsKey, JSON.stringify(hosts));
}
// Load server info from server config file
if (Object.keys(hosts).length === 0) {
const path = jetpack.find(remote.app.getPath('userData'), { matching: 'servers.json'})[0] ||
jetpack.find(jetpack.path(remote.app.getAppPath(), '..'), { matching: 'servers.json'})[0];
// Load server info from server config file
if (Object.keys(hosts).length === 0) {
const path = jetpack.find(remote.app.getPath('userData'), { matching: 'servers.json' })[0] ||
jetpack.find(jetpack.path(remote.app.getAppPath(), '..'), { matching: 'servers.json' })[0];
if (path) {
const pathToServerJson = jetpack.path(path);
if (path) {
const pathToServerJson = jetpack.path(path);
try {
const result = jetpack.read(pathToServerJson, 'json');
if (result) {
hosts = {};
Object.keys(result).forEach((title) => {
const url = result[title];
hosts[url] = { title, url };
});
localStorage.setItem(this.hostsKey, JSON.stringify(hosts));
// Assume user doesn't want sidebar if they only have one server
if (Object.keys(hosts).length === 1) {
localStorage.setItem('sidebar-closed', 'true');
}
}
try {
const result = jetpack.read(pathToServerJson, 'json');
if (result) {
hosts = {};
Object.keys(result).forEach((title) => {
const url = result[title];
hosts[url] = { title, url };
});
localStorage.setItem(this.hostsKey, JSON.stringify(hosts));
// Assume user doesn't want sidebar if they only have one server
if (Object.keys(hosts).length === 1) {
localStorage.setItem('sidebar-closed', 'true');
}
}
} catch (e) {
console.error('Server file invalid');
}
}
}
} catch (e) {
console.error('Server file invalid');
}
}
}
this._hosts = hosts;
remoteServers.loadServers(this._hosts);
this.emit('loaded');
}
this._hosts = hosts;
remoteServers.loadServers(this._hosts);
this.emit('loaded');
}
save () {
localStorage.setItem(this.hostsKey, JSON.stringify(this._hosts));
this.emit('saved');
}
save() {
localStorage.setItem(this.hostsKey, JSON.stringify(this._hosts));
this.emit('saved');
}
get (hostUrl) {
return this.hosts[hostUrl];
}
get(hostUrl) {
return this.hosts[hostUrl];
}
forEach (cb) {
for (const host in this.hosts) {
if (this.hosts.hasOwnProperty(host)) {
cb(this.hosts[host]);
}
}
}
forEach(cb) {
for (const host in this.hosts) {
if (this.hosts.hasOwnProperty(host)) {
cb(this.hosts[host]);
}
}
}
validateHost (hostUrl, timeout) {
timeout = timeout || 5000;
return new Promise(function (resolve, reject) {
let resolved = false;
$.getJSON(`${hostUrl}/api/info`).then(function () {
if (resolved) {
return;
}
resolved = true;
resolve();
}, function (request) {
if (request.status === 401) {
const authHeader = request.getResponseHeader('www-authenticate');
if (authHeader && authHeader.toLowerCase().indexOf('basic ') === 0) {
resolved = true;
reject('basic-auth');
}
}
if (resolved) {
return;
}
resolved = true;
reject('invalid');
});
if (timeout) {
setTimeout(function () {
if (resolved) {
return;
}
resolved = true;
reject('timeout');
}, timeout);
}
});
}
validateHost(hostUrl, timeout) {
timeout = timeout || 5000;
return new Promise(function(resolve, reject) {
let resolved = false;
$.getJSON(`${ hostUrl }/api/info`).then(function() {
if (resolved) {
return;
}
resolved = true;
resolve();
}, function(request) {
if (request.status === 401) {
const authHeader = request.getResponseHeader('www-authenticate');
if (authHeader && authHeader.toLowerCase().indexOf('basic ') === 0) {
resolved = true;
reject('basic-auth');
}
}
if (resolved) {
return;
}
resolved = true;
reject('invalid');
});
if (timeout) {
setTimeout(function() {
if (resolved) {
return;
}
resolved = true;
reject('timeout');
}, timeout);
}
});
}
hostExists (hostUrl) {
const hosts = this.hosts;
hostExists(hostUrl) {
const { hosts } = this;
return !!hosts[hostUrl];
}
return !!hosts[hostUrl];
}
addHost (hostUrl) {
const hosts = this.hosts;
addHost(hostUrl) {
const { hosts } = this;
const match = hostUrl.match(/^(https?:\/\/)([^:]+):([^@]+)@(.+)$/);
let username;
let password;
let authUrl;
if (match) {
authUrl = hostUrl;
hostUrl = match[1] + match[4];
username = match[2];
password = match[3];
}
const match = hostUrl.match(/^(https?:\/\/)([^:]+):([^@]+)@(.+)$/);
let username;
let password;
let authUrl;
if (match) {
authUrl = hostUrl;
hostUrl = match[1] + match[4];
username = match[2];
password = match[3];
}
if (this.hostExists(hostUrl) === true) {
this.setActive(hostUrl);
return false;
}
if (this.hostExists(hostUrl) === true) {
this.setActive(hostUrl);
return false;
}
hosts[hostUrl] = {
title: hostUrl,
url: hostUrl,
authUrl: authUrl,
username: username,
password: password
};
this.hosts = hosts;
hosts[hostUrl] = {
title: hostUrl,
url: hostUrl,
authUrl,
username,
password,
};
this.hosts = hosts;
remoteServers.loadServers(this.hosts);
remoteServers.loadServers(this.hosts);
this.emit('host-added', hostUrl);
this.emit('host-added', hostUrl);
return hostUrl;
}
return hostUrl;
}
removeHost (hostUrl) {
const hosts = this.hosts;
if (hosts[hostUrl]) {
delete hosts[hostUrl];
this.hosts = hosts;
removeHost(hostUrl) {
const { hosts } = this;
if (hosts[hostUrl]) {
delete hosts[hostUrl];
this.hosts = hosts;
remoteServers.loadServers(this.hosts);
remoteServers.loadServers(this.hosts);
if (this.active === hostUrl) {
this.clearActive();
}
this.emit('host-removed', hostUrl);
}
}
if (this.active === hostUrl) {
this.clearActive();
}
this.emit('host-removed', hostUrl);
}
}
get active () {
return localStorage.getItem(this.activeKey);
}
get active() {
return localStorage.getItem(this.activeKey);
}
setActive (hostUrl) {
let url;
if (this.hostExists(hostUrl)) {
url = hostUrl;
} else if (Object.keys(this._hosts).length > 0) {
url = Object.keys(this._hosts)[0];
}
setActive(hostUrl) {
let url;
if (this.hostExists(hostUrl)) {
url = hostUrl;
} else if (Object.keys(this._hosts).length > 0) {
url = Object.keys(this._hosts)[0];
}
if (url) {
localStorage.setItem(this.activeKey, hostUrl);
this.emit('active-setted', url);
return true;
}
this.emit('loaded');
return false;
}
if (url) {
localStorage.setItem(this.activeKey, hostUrl);
this.emit('active-setted', url);
return true;
}
this.emit('loaded');
return false;
}
restoreActive () {
this.setActive(this.active);
}
restoreActive() {
this.setActive(this.active);
}
clearActive () {
localStorage.removeItem(this.activeKey);
this.emit('active-cleared');
return true;
}
clearActive() {
localStorage.removeItem(this.activeKey);
this.emit('active-cleared');
return true;
}
setHostTitle (hostUrl, title) {
if (title === 'Rocket.Chat' && /https?:\/\/open\.rocket\.chat/.test(hostUrl) === false) {
title += ' - ' + hostUrl;
}
const hosts = this.hosts;
hosts[hostUrl].title = title;
this.hosts = hosts;
this.emit('title-setted', hostUrl, title);
}
getProtocolUrlFromProcess (args) {
let site = null;
if (args.length > 1) {
const protocolURI = args.find(arg => arg.startsWith('rocketchat://'));
if (protocolURI) {
site = protocolURI.split(/\/|\?/)[2];
if (site) {
let scheme = 'https://';
if (protocolURI.includes('insecure=true')) {
scheme = 'http://';
}
site = scheme + site;
}
}
}
return site;
}
showHostConfirmation (host) {
return remote.dialog.showMessageBox({
type: 'question',
buttons: [i18n.__('Add'), i18n.__('Cancel')],
defaultId: 0,
title: i18n.__('Add_Server'),
message: i18n.__('Add_host_to_servers', host)
}, (response) => {
if (response === 0) {
this.validateHost(host)
.then(() => this.addHost(host))
.then(() => this.setActive(host))
.catch(() => remote.dialog.showErrorBox(i18n.__('Invalid_Host'), i18n.__('Host_not_validated', host)));
}
});
}
setHostTitle(hostUrl, title) {
if (title === 'Rocket.Chat' && /https?:\/\/open\.rocket\.chat/.test(hostUrl) === false) {
title += ` - ${ hostUrl }`;
}
const { hosts } = this;
hosts[hostUrl].title = title;
this.hosts = hosts;
this.emit('title-setted', hostUrl, title);
}
getProtocolUrlFromProcess(args) {
let site = null;
if (args.length > 1) {
const protocolURI = args.find((arg) => arg.startsWith('rocketchat://'));
if (protocolURI) {
site = protocolURI.split(/\/|\?/)[2];
if (site) {
let scheme = 'https://';
if (protocolURI.includes('insecure=true')) {
scheme = 'http://';
}
site = scheme + site;
}
}
}
return site;
}
showHostConfirmation(host) {
return remote.dialog.showMessageBox({
type: 'question',
buttons: [i18n.__('Add'), i18n.__('Cancel')],
defaultId: 0,
title: i18n.__('Add_Server'),
message: i18n.__('Add_host_to_servers', host),
}, (response) => {
if (response === 0) {
this.validateHost(host)
.then(() => this.addHost(host))
.then(() => this.setActive(host))
.catch(() => remote.dialog.showErrorBox(i18n.__('Invalid_Host'), i18n.__('Host_not_validated', host)));
}
});
}
resetAppData () {
return remote.dialog.showMessageBox({
type: 'question',
buttons: ['Yes', 'Cancel'],
defaultId: 1,
title: 'Reset App Data',
message: 'This will sign you out from all your teams and reset the app back to its original settings. This cannot be undone.'
}, (response) => {
if (response === 0) {
const dataDir = remote.app.getPath('userData');
jetpack.remove(dataDir);
remote.app.relaunch();
remote.app.quit();
}
});
}
resetAppData() {
return remote.dialog.showMessageBox({
type: 'question',
buttons: ['Yes', 'Cancel'],
defaultId: 1,
title: 'Reset App Data',
message: 'This will sign you out from all your teams and reset the app back to its original settings. This cannot be undone.',
}, (response) => {
if (response === 0) {
const dataDir = remote.app.getPath('userData');
jetpack.remove(dataDir);
remote.app.relaunch();
remote.app.quit();
}
});
}
}

View file

@ -6,309 +6,309 @@ import webview from './webview';
import * as menus from './menus';
class SideBar extends EventEmitter {
constructor () {
super();
constructor() {
super();
this.sortOrder = JSON.parse(localStorage.getItem(this.sortOrderKey)) || [];
localStorage.setItem(this.sortOrderKey, JSON.stringify(this.sortOrder));
this.sortOrder = JSON.parse(localStorage.getItem(this.sortOrderKey)) || [];
localStorage.setItem(this.sortOrderKey, JSON.stringify(this.sortOrder));
this.listElement = document.getElementById('serverList');
this.listElement = document.getElementById('serverList');
Object.values(servers.hosts)
.sort((a, b) => this.sortOrder.indexOf(a.url) - this.sortOrder.indexOf(b.url))
.forEach((host) => {
this.add(host);
});
Object.values(servers.hosts)
.sort((a, b) => this.sortOrder.indexOf(a.url) - this.sortOrder.indexOf(b.url))
.forEach((host) => {
this.add(host);
});
servers.on('host-added', (hostUrl) => {
this.add(servers.get(hostUrl));
});
servers.on('host-added', (hostUrl) => {
this.add(servers.get(hostUrl));
});
servers.on('host-removed', (hostUrl) => {
this.remove(hostUrl);
});
servers.on('host-removed', (hostUrl) => {
this.remove(hostUrl);
});
servers.on('active-setted', (hostUrl) => {
this.setActive(hostUrl);
});
servers.on('active-setted', (hostUrl) => {
this.setActive(hostUrl);
});
servers.on('active-cleared', (hostUrl) => {
this.deactiveAll(hostUrl);
});
servers.on('active-cleared', (hostUrl) => {
this.deactiveAll(hostUrl);
});
servers.on('title-setted', (hostUrl, title) => {
this.setLabel(hostUrl, title);
});
servers.on('title-setted', (hostUrl, title) => {
this.setLabel(hostUrl, title);
});
webview.on('dom-ready', (hostUrl) => {
this.setActive(localStorage.getItem(servers.activeKey));
webview.getActive().send('request-sidebar-color');
this.setImage(hostUrl);
if (this.isHidden()) {
this.hide();
} else {
this.show();
}
});
webview.on('dom-ready', (hostUrl) => {
this.setActive(localStorage.getItem(servers.activeKey));
webview.getActive().send('request-sidebar-color');
this.setImage(hostUrl);
if (this.isHidden()) {
this.hide();
} else {
this.show();
}
});
}
}
get sortOrderKey () {
return 'rocket.chat.sortOrder';
}
get sortOrderKey() {
return 'rocket.chat.sortOrder';
}
add (host) {
let name = host.title.replace(/^https?:\/\/(?:www\.)?([^\/]+)(.*)/, '$1');
name = name.split('.');
name = name[0][0] + (name[1] ? name[1][0] : '');
name = name.toUpperCase();
add(host) {
let name = host.title.replace(/^https?:\/\/(?:www\.)?([^\/]+)(.*)/, '$1');
name = name.split('.');
name = name[0][0] + (name[1] ? name[1][0] : '');
name = name.toUpperCase();
const initials = document.createElement('span');
initials.innerHTML = name;
const initials = document.createElement('span');
initials.innerHTML = name;
const tooltip = document.createElement('div');
tooltip.classList.add('tooltip');
tooltip.innerHTML = host.title;
const tooltip = document.createElement('div');
tooltip.classList.add('tooltip');
tooltip.innerHTML = host.title;
const badge = document.createElement('div');
badge.classList.add('badge');
const badge = document.createElement('div');
badge.classList.add('badge');
const img = document.createElement('img');
img.onload = function () {
img.style.display = 'initial';
initials.style.display = 'none';
};
const img = document.createElement('img');
img.onload = function() {
img.style.display = 'initial';
initials.style.display = 'none';
};
let hostOrder = 0;
if (this.sortOrder.includes(host.url)) {
hostOrder = this.sortOrder.indexOf(host.url) + 1;
} else {
hostOrder = this.sortOrder.length + 1;
this.sortOrder.push(host.url);
}
let hostOrder = 0;
if (this.sortOrder.includes(host.url)) {
hostOrder = this.sortOrder.indexOf(host.url) + 1;
} else {
hostOrder = this.sortOrder.length + 1;
this.sortOrder.push(host.url);
}
const hotkey = document.createElement('div');
hotkey.classList.add('name');
if (process.platform === 'darwin') {
hotkey.innerHTML = `${hostOrder}`;
} else {
hotkey.innerHTML = `^${hostOrder}`;
}
const hotkey = document.createElement('div');
hotkey.classList.add('name');
if (process.platform === 'darwin') {
hotkey.innerHTML = `${ hostOrder }`;
} else {
hotkey.innerHTML = `^${ hostOrder }`;
}
const item = document.createElement('li');
item.appendChild(initials);
item.appendChild(tooltip);
item.appendChild(badge);
item.appendChild(img);
item.appendChild(hotkey);
const item = document.createElement('li');
item.appendChild(initials);
item.appendChild(tooltip);
item.appendChild(badge);
item.appendChild(img);
item.appendChild(hotkey);
item.dataset.host = host.url;
item.dataset.sortOrder = hostOrder;
item.setAttribute('server', host.url);
item.classList.add('instance');
item.dataset.host = host.url;
item.dataset.sortOrder = hostOrder;
item.setAttribute('server', host.url);
item.classList.add('instance');
item.setAttribute('draggable', true);
item.setAttribute('draggable', true);
item.ondragstart = (event) => {
window.dragged = event.target.nodeName !== 'LI' ? event.target.closest('li') : event.target;
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.dropEffect = 'move';
event.target.style.opacity = .5;
};
item.ondragstart = (event) => {
window.dragged = event.target.nodeName !== 'LI' ? event.target.closest('li') : event.target;
event.dataTransfer.effectAllowed = 'move';
event.dataTransfer.dropEffect = 'move';
event.target.style.opacity = .5;
};
item.ondragover = (event) => {
event.preventDefault();
};
item.ondragover = (event) => {
event.preventDefault();
};
item.ondragenter = (event) => {
if (this.isBefore(window.dragged, event.target)) {
event.currentTarget.parentNode.insertBefore(window.dragged, event.currentTarget);
} else if (event.currentTarget !== event.currentTarget.parentNode.lastChild) {
event.currentTarget.parentNode.insertBefore(window.dragged, event.currentTarget.nextSibling);
} else {
event.currentTarget.parentNode.appendChild(window.dragged);
}
};
item.ondragenter = (event) => {
if (this.isBefore(window.dragged, event.target)) {
event.currentTarget.parentNode.insertBefore(window.dragged, event.currentTarget);
} else if (event.currentTarget !== event.currentTarget.parentNode.lastChild) {
event.currentTarget.parentNode.insertBefore(window.dragged, event.currentTarget.nextSibling);
} else {
event.currentTarget.parentNode.appendChild(window.dragged);
}
};
item.ondragend = (event) => {
event.target.style.opacity = '';
};
item.ondragend = (event) => {
event.target.style.opacity = '';
};
item.ondrop = (event) => {
event.preventDefault();
item.ondrop = (event) => {
event.preventDefault();
const newSortOrder = [];
Array.from(event.currentTarget.parentNode.children)
.map((sideBarElement) => {
const url = sideBarElement.dataset.host;
newSortOrder.push(url);
this.remove(url);
const newSortOrder = [];
Array.from(event.currentTarget.parentNode.children)
.map((sideBarElement) => {
const url = sideBarElement.dataset.host;
newSortOrder.push(url);
this.remove(url);
return sideBarElement;
})
.map((sideBarElement) => {
this.sortOrder = newSortOrder;
localStorage.setItem(this.sortOrderKey, JSON.stringify(this.sortOrder));
return sideBarElement;
})
.forEach((sideBarElement) => {
this.sortOrder = newSortOrder;
localStorage.setItem(this.sortOrderKey, JSON.stringify(this.sortOrder));
const url = sideBarElement.dataset.host;
const host = { url, title: sideBarElement.querySelector('div.tooltip').innerHTML };
this.add(host);
this.setImage(url);
});
const url = sideBarElement.dataset.host;
const host = { url, title: sideBarElement.querySelector('div.tooltip').innerHTML };
this.add(host);
this.setImage(url);
});
this.setActive(window.dragged.dataset.host);
};
this.setActive(window.dragged.dataset.host);
};
item.onclick = () => {
servers.setActive(host.url);
};
item.onclick = () => {
servers.setActive(host.url);
};
this.listElement.appendChild(item);
menus.addServer(host, hostOrder);
}
this.listElement.appendChild(item);
menus.addServer(host, hostOrder);
}
setImage (hostUrl) {
const img = this.getByUrl(hostUrl).querySelector('img');
img.src = `${hostUrl}/assets/favicon.svg?v=${Math.round(Math.random()*10000)}`;
}
setImage(hostUrl) {
const img = this.getByUrl(hostUrl).querySelector('img');
img.src = `${ hostUrl }/assets/favicon.svg?v=${ Math.round(Math.random() * 10000) }`;
}
remove (hostUrl) {
const el = this.getByUrl(hostUrl);
if (el) {
el.remove();
menus.removeServer(hostUrl);
}
}
remove(hostUrl) {
const el = this.getByUrl(hostUrl);
if (el) {
el.remove();
menus.removeServer(hostUrl);
}
}
getByUrl (hostUrl) {
return this.listElement.querySelector(`.instance[server="${hostUrl}"]`);
}
getByUrl(hostUrl) {
return this.listElement.querySelector(`.instance[server="${ hostUrl }"]`);
}
getActive () {
return this.listElement.querySelector('.instance.active');
}
getActive() {
return this.listElement.querySelector('.instance.active');
}
isActive (hostUrl) {
return !!this.listElement.querySelector(`.instance.active[server="${hostUrl}"]`);
}
isActive(hostUrl) {
return !!this.listElement.querySelector(`.instance.active[server="${ hostUrl }"]`);
}
changeSidebarColor ({color, background}) {
const sidebar = document.querySelector('.server-list');
if (sidebar) {
sidebar.style.background = background;
sidebar.style.color = color;
}
}
changeSidebarColor({ color, background }) {
const sidebar = document.querySelector('.server-list');
if (sidebar) {
sidebar.style.background = background;
sidebar.style.color = color;
}
}
setActive (hostUrl) {
if (this.isActive(hostUrl)) {
return;
}
setActive(hostUrl) {
if (this.isActive(hostUrl)) {
return;
}
this.deactiveAll();
const item = this.getByUrl(hostUrl);
if (item) {
item.classList.add('active');
}
webview.getActive().send && webview.getActive().send('request-sidebar-color');
}
this.deactiveAll();
const item = this.getByUrl(hostUrl);
if (item) {
item.classList.add('active');
}
webview.getActive().send && webview.getActive().send('request-sidebar-color');
}
deactiveAll () {
let item;
while (!(item = this.getActive()) === false) {
item.classList.remove('active');
}
}
deactiveAll() {
let item;
while (!(item = this.getActive()) === false) {
item.classList.remove('active');
}
}
setLabel (hostUrl, label) {
this.listElement.querySelector(`.instance[server="${hostUrl}"] .tooltip`).innerHTML = label;
}
setLabel(hostUrl, label) {
this.listElement.querySelector(`.instance[server="${ hostUrl }"] .tooltip`).innerHTML = label;
}
setBadge (hostUrl, badge) {
const item = this.getByUrl(hostUrl);
const badgeEl = item.querySelector('.badge');
setBadge(hostUrl, badge) {
const item = this.getByUrl(hostUrl);
const badgeEl = item.querySelector('.badge');
if (badge !== null && badge !== undefined && badge !== '') {
item.classList.add('unread');
if (isNaN(parseInt(badge))) {
badgeEl.innerHTML = '';
} else {
badgeEl.innerHTML = badge;
}
} else {
badge = undefined;
item.classList.remove('unread');
badgeEl.innerHTML = '';
}
this.emit('badge-setted', hostUrl, badge);
}
if (badge !== null && badge !== undefined && badge !== '') {
item.classList.add('unread');
if (isNaN(parseInt(badge))) {
badgeEl.innerHTML = '';
} else {
badgeEl.innerHTML = badge;
}
} else {
badge = undefined;
item.classList.remove('unread');
badgeEl.innerHTML = '';
}
this.emit('badge-setted', hostUrl, badge);
}
getGlobalBadge () {
let count = 0;
let title = '';
const instanceEls = this.listElement.querySelectorAll('li.instance');
for (let i = instanceEls.length - 1; i >= 0; i--) {
const instanceEl = instanceEls[i];
const text = instanceEl.querySelector('.badge').innerHTML;
if (!isNaN(parseInt(text))) {
count += parseInt(text);
}
if (title === '' && instanceEl.classList.contains('unread') === true) {
title = '•';
}
}
if (count > 0) {
title = count.toString();
}
return {
count: count,
title: title,
showAlert: (title !== '')
};
}
getGlobalBadge() {
let count = 0;
let title = '';
const instanceEls = this.listElement.querySelectorAll('li.instance');
for (let i = instanceEls.length - 1; i >= 0; i--) {
const instanceEl = instanceEls[i];
const text = instanceEl.querySelector('.badge').innerHTML;
if (!isNaN(parseInt(text))) {
count += parseInt(text);
}
if (title === '' && instanceEl.classList.contains('unread') === true) {
title = '•';
}
}
if (count > 0) {
title = count.toString();
}
return {
count,
title,
showAlert: (title !== ''),
};
}
hide () {
document.body.classList.add('hide-server-list');
localStorage.setItem('sidebar-closed', 'true');
this.emit('hide');
if (process.platform === 'darwin') {
document.querySelectorAll('webview').forEach(
(webviewObj) => { if (webviewObj.insertCSS) { webviewObj.insertCSS('aside.side-nav{margin-top:15px;overflow:hidden; transition: margin .5s ease-in-out; } .sidebar{padding-top:10px;transition: margin .5s ease-in-out;}'); } });
}
}
hide() {
document.body.classList.add('hide-server-list');
localStorage.setItem('sidebar-closed', 'true');
this.emit('hide');
if (process.platform === 'darwin') {
document.querySelectorAll('webview').forEach(
(webviewObj) => { if (webviewObj.insertCSS) { webviewObj.insertCSS('aside.side-nav{margin-top:15px;overflow:hidden; transition: margin .5s ease-in-out; } .sidebar{padding-top:10px;transition: margin .5s ease-in-out;}'); } });
}
}
show () {
document.body.classList.remove('hide-server-list');
localStorage.setItem('sidebar-closed', 'false');
this.emit('show');
if (process.platform === 'darwin') {
document.querySelectorAll('webview').forEach(
(webviewObj) => { if (webviewObj.insertCSS) { webviewObj.insertCSS('aside.side-nav{margin-top:0; overflow:hidden; transition: margin .5s ease-in-out;} .sidebar{padding-top:0;transition: margin .5s ease-in-out;}'); } });
}
}
show() {
document.body.classList.remove('hide-server-list');
localStorage.setItem('sidebar-closed', 'false');
this.emit('show');
if (process.platform === 'darwin') {
document.querySelectorAll('webview').forEach(
(webviewObj) => { if (webviewObj.insertCSS) { webviewObj.insertCSS('aside.side-nav{margin-top:0; overflow:hidden; transition: margin .5s ease-in-out;} .sidebar{padding-top:0;transition: margin .5s ease-in-out;}'); } });
}
}
toggle () {
if (this.isHidden()) {
this.show();
} else {
this.hide();
}
}
toggle() {
if (this.isHidden()) {
this.show();
} else {
this.hide();
}
}
isHidden () {
return localStorage.getItem('sidebar-closed') === 'true';
}
isHidden() {
return localStorage.getItem('sidebar-closed') === 'true';
}
isBefore (a, b) {
if (a.parentNode === b.parentNode) {
for (let cur = a; cur; cur = cur.previousSibling) {
if (cur === b) {
return true;
}
}
}
return false;
}
isBefore(a, b) {
if (a.parentNode === b.parentNode) {
for (let cur = a; cur; cur = cur.previousSibling) {
if (cur === b) {
return true;
}
}
}
return false;
}
}
export default new SideBar();
@ -316,57 +316,57 @@ export default new SideBar();
let selectedInstance = null;
const instanceMenu = remote.Menu.buildFromTemplate([{
label: i18n.__('Reload_server'),
click: function () {
webview.getByUrl(selectedInstance.dataset.host).reload();
}
label: i18n.__('Reload_server'),
click() {
webview.getByUrl(selectedInstance.dataset.host).reload();
},
}, {
label: i18n.__('Remove_server'),
click: function () {
servers.removeHost(selectedInstance.dataset.host);
}
label: i18n.__('Remove_server'),
click() {
servers.removeHost(selectedInstance.dataset.host);
},
}, {
label: i18n.__('Open_DevTools'),
click: function () {
webview.getByUrl(selectedInstance.dataset.host).openDevTools();
}
label: i18n.__('Open_DevTools'),
click() {
webview.getByUrl(selectedInstance.dataset.host).openDevTools();
},
}]);
window.addEventListener('contextmenu', function (e) {
if (e.target.classList.contains('instance') || e.target.parentNode.classList.contains('instance')) {
e.preventDefault();
if (e.target.classList.contains('instance')) {
selectedInstance = e.target;
} else {
selectedInstance = e.target.parentNode;
}
window.addEventListener('contextmenu', function(e) {
if (e.target.classList.contains('instance') || e.target.parentNode.classList.contains('instance')) {
e.preventDefault();
if (e.target.classList.contains('instance')) {
selectedInstance = e.target;
} else {
selectedInstance = e.target.parentNode;
}
instanceMenu.popup(remote.getCurrentWindow());
}
instanceMenu.popup(remote.getCurrentWindow());
}
}, false);
if (process.platform === 'darwin') {
window.addEventListener('keydown', function (e) {
if (e.key === 'Meta') {
document.getElementsByClassName('server-list')[0].classList.add('command-pressed');
}
});
window.addEventListener('keydown', function(e) {
if (e.key === 'Meta') {
document.getElementsByClassName('server-list')[0].classList.add('command-pressed');
}
});
window.addEventListener('keyup', function (e) {
if (e.key === 'Meta') {
document.getElementsByClassName('server-list')[0].classList.remove('command-pressed');
}
});
window.addEventListener('keyup', function(e) {
if (e.key === 'Meta') {
document.getElementsByClassName('server-list')[0].classList.remove('command-pressed');
}
});
} else {
window.addEventListener('keydown', function (e) {
if (e.key === 'ctrlKey') {
document.getElementsByClassName('server-list')[0].classList.add('command-pressed');
}
});
window.addEventListener('keydown', function(e) {
if (e.key === 'ctrlKey') {
document.getElementsByClassName('server-list')[0].classList.add('command-pressed');
}
});
window.addEventListener('keyup', function (e) {
if (e.key === 'ctrlKey') {
document.getElementsByClassName('server-list')[0].classList.remove('command-pressed');
}
});
window.addEventListener('keyup', function(e) {
if (e.key === 'ctrlKey') {
document.getElementsByClassName('server-list')[0].classList.remove('command-pressed');
}
});
}

View file

@ -8,183 +8,183 @@ import webview from './webview';
import tray from './tray';
import './menus';
sidebar.on('badge-setted', function () {
const badge = sidebar.getGlobalBadge();
tray.showTrayAlert(badge);
sidebar.on('badge-setted', function() {
const badge = sidebar.getGlobalBadge();
tray.showTrayAlert(badge);
});
export const start = function () {
const defaultInstance = 'https://open.rocket.chat';
export const start = function() {
const defaultInstance = 'https://open.rocket.chat';
// connection check
function online () {
document.body.classList.remove('offline');
}
// connection check
function online() {
document.body.classList.remove('offline');
}
function offline () {
document.body.classList.add('offline');
}
function offline() {
document.body.classList.add('offline');
}
if (!navigator.onLine) {
offline();
}
window.addEventListener('online', online);
window.addEventListener('offline', offline);
// end connection check
if (!navigator.onLine) {
offline();
}
window.addEventListener('online', online);
window.addEventListener('offline', offline);
// end connection check
const form = document.querySelector('form');
const hostField = form.querySelector('[name="host"]');
const button = form.querySelector('[type="submit"]');
const invalidUrl = form.querySelector('#invalidUrl');
const form = document.querySelector('form');
const hostField = form.querySelector('[name="host"]');
const button = form.querySelector('[type="submit"]');
const invalidUrl = form.querySelector('#invalidUrl');
window.addEventListener('load', function () {
hostField.focus();
});
window.addEventListener('load', function() {
hostField.focus();
});
function validateHost () {
return new Promise(function (resolve, reject) {
const execValidation = function () {
invalidUrl.style.display = 'none';
hostField.classList.remove('wrong');
function validateHost() {
return new Promise(function(resolve, reject) {
const execValidation = function() {
invalidUrl.style.display = 'none';
hostField.classList.remove('wrong');
const host = hostField.value.trim();
hostField.value = host;
const host = hostField.value.trim();
hostField.value = host;
if (host.length === 0) {
button.value = i18n.__('Connect');
button.disabled = false;
resolve();
return;
}
if (host.length === 0) {
button.value = i18n.__('Connect');
button.disabled = false;
resolve();
return;
}
button.value = i18n.__('Validating');
button.disabled = true;
button.value = i18n.__('Validating');
button.disabled = true;
servers.validateHost(host, 2000).then(function () {
button.value = i18n.__('Connect');
button.disabled = false;
resolve();
}, function (status) {
// If the url begins with HTTP, mark as invalid
if (/^https?:\/\/.+/.test(host) || status === 'basic-auth') {
button.value = i18n.__('Invalid_url');
invalidUrl.style.display = 'block';
switch (status) {
case 'basic-auth':
invalidUrl.innerHTML = i18n.__('Auth_needed_try', '<b>username:password@host</b>');
break;
case 'invalid':
invalidUrl.innerHTML = i18n.__('No_valid_server_found');
break;
case 'timeout':
invalidUrl.innerHTML = i18n.__('Timeout_trying_to_connect');
break;
}
hostField.classList.add('wrong');
reject();
return;
}
servers.validateHost(host, 2000).then(function() {
button.value = i18n.__('Connect');
button.disabled = false;
resolve();
}, function(status) {
// If the url begins with HTTP, mark as invalid
if (/^https?:\/\/.+/.test(host) || status === 'basic-auth') {
button.value = i18n.__('Invalid_url');
invalidUrl.style.display = 'block';
switch (status) {
case 'basic-auth':
invalidUrl.innerHTML = i18n.__('Auth_needed_try', '<b>username:password@host</b>');
break;
case 'invalid':
invalidUrl.innerHTML = i18n.__('No_valid_server_found');
break;
case 'timeout':
invalidUrl.innerHTML = i18n.__('Timeout_trying_to_connect');
break;
}
hostField.classList.add('wrong');
reject();
return;
}
// // If the url begins with HTTPS, fallback to HTTP
// if (/^https:\/\/.+/.test(host)) {
// hostField.value = host.replace('https://', 'http://');
// return execValidation();
// }
// // If the url begins with HTTPS, fallback to HTTP
// if (/^https:\/\/.+/.test(host)) {
// hostField.value = host.replace('https://', 'http://');
// return execValidation();
// }
// If the url isn't localhost, don't have dots and don't have protocol
// try as a .rocket.chat subdomain
if (!/(^https?:\/\/)|(\.)|(^([^:]+:[^@]+@)?localhost(:\d+)?$)/.test(host)) {
hostField.value = `https://${host}.rocket.chat`;
return execValidation();
}
// If the url isn't localhost, don't have dots and don't have protocol
// try as a .rocket.chat subdomain
if (!/(^https?:\/\/)|(\.)|(^([^:]+:[^@]+@)?localhost(:\d+)?$)/.test(host)) {
hostField.value = `https://${ host }.rocket.chat`;
return execValidation();
}
// If the url don't start with protocol try HTTPS
if (!/^https?:\/\//.test(host)) {
hostField.value = `https://${host}`;
return execValidation();
}
});
};
execValidation();
});
}
// If the url don't start with protocol try HTTPS
if (!/^https?:\/\//.test(host)) {
hostField.value = `https://${ host }`;
return execValidation();
}
});
};
execValidation();
});
}
hostField.addEventListener('blur', function () {
validateHost().then(function () {}, function () {});
});
hostField.addEventListener('blur', function() {
validateHost().then(function() {}, function() {});
});
ipcRenderer.on('certificate-reload', function (event, url) {
hostField.value = url.replace(/\/api\/info$/, '');
validateHost().then(function () {}, function () {});
});
ipcRenderer.on('certificate-reload', function(event, url) {
hostField.value = url.replace(/\/api\/info$/, '');
validateHost().then(function() {}, function() {});
});
ipcRenderer.on('render-taskbar-icon', (event, messageCount) => {
// Create a canvas from unread messages
function createOverlayIcon (messageCount) {
const canvas = document.createElement('canvas');
canvas.height = 128;
canvas.width = 128;
ipcRenderer.on('render-taskbar-icon', (event, messageCount) => {
// Create a canvas from unread messages
function createOverlayIcon(messageCount) {
const canvas = document.createElement('canvas');
canvas.height = 128;
canvas.width = 128;
const ctx = canvas.getContext('2d');
ctx.beginPath();
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.fillStyle = 'red';
ctx.arc(64, 64, 64, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
canvas.style.letterSpacing = '-4px';
ctx.font = 'bold 92px sans-serif';
ctx.fillText(String(Math.min(99, messageCount)), 64, 98);
ctx.fillStyle = 'red';
ctx.arc(64, 64, 64, 0, 2 * Math.PI);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.textAlign = 'center';
canvas.style.letterSpacing = '-4px';
ctx.font = 'bold 92px sans-serif';
ctx.fillText(String(Math.min(99, messageCount)), 64, 98);
return canvas;
}
ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount));
});
return canvas;
}
ipcRenderer.send('update-taskbar-icon', createOverlayIcon(messageCount).toDataURL(), String(messageCount));
});
const submit = function () {
validateHost().then(function () {
const input = form.querySelector('[name="host"]');
let url = input.value;
const submit = function() {
validateHost().then(function() {
const input = form.querySelector('[name="host"]');
let url = input.value;
if (url.length === 0) {
url = defaultInstance;
}
if (url.length === 0) {
url = defaultInstance;
}
url = servers.addHost(url);
if (url !== false) {
sidebar.show();
servers.setActive(url);
}
url = servers.addHost(url);
if (url !== false) {
sidebar.show();
servers.setActive(url);
}
input.value = '';
}, function () {});
};
input.value = '';
}, function() {});
};
hostField.addEventListener('keydown', function (ev) {
if (ev.which === 13) {
ev.preventDefault();
ev.stopPropagation();
submit();
return false;
}
});
hostField.addEventListener('keydown', function(ev) {
if (ev.which === 13) {
ev.preventDefault();
ev.stopPropagation();
submit();
return false;
}
});
form.addEventListener('submit', function (ev) {
ev.preventDefault();
ev.stopPropagation();
submit();
return false;
});
form.addEventListener('submit', function(ev) {
ev.preventDefault();
ev.stopPropagation();
submit();
return false;
});
$('.add-server').on('click', function () {
servers.clearActive();
webview.showLanding();
});
$('.add-server').on('click', function() {
servers.clearActive();
webview.showLanding();
});
servers.restoreActive();
servers.restoreActive();
};
window.addEventListener('focus', function () {
webview.focusActive();
window.addEventListener('focus', function() {
webview.focusActive();
});

View file

@ -9,180 +9,187 @@ const { Tray, Menu } = remote;
const mainWindow = remote.getCurrentWindow();
const icons = {
win32: {
dir: 'windows'
},
linux: {
dir: 'linux'
},
darwin: {
dir: 'osx'
}
win32: {
dir: 'windows',
},
linux: {
dir: 'linux',
},
darwin: {
dir: 'osx',
},
};
const statusBullet = {
online: '\u001B[32m•',
away: '\u001B[33m•',
busy: '\u001B[31m•',
offline: '\u001B[30m•'
online: '\u001B[32m•',
away: '\u001B[33m•',
busy: '\u001B[31m•',
offline: '\u001B[37m•',
};
const messageCountColor = {
white: '\u001B[37m',
black: '\u001B[0m'
white: '\u001B[37m',
black: '\u001B[0m',
};
function getTrayImagePath (badge) {
let iconFilename;
if (badge.title === '•') {
iconFilename = "icon-tray-dot";
} else if (badge.count > 0) {
if (badge.count > 9) {
iconFilename = "icon-tray-9plus";
} else {
iconFilename = `icon-tray-${badge.count}`;
}
} else if (badge.showAlert) {
iconFilename = "icon-tray-alert";
} else {
iconFilename = "icon-tray-Template";
}
function getTrayImagePath(badge) {
let iconFilename;
if (badge.title === '•') {
iconFilename = 'icon-tray-dot';
} else if (badge.count > 0) {
if (badge.count > 9) {
iconFilename = 'icon-tray-9plus';
} else {
iconFilename = `icon-tray-${ badge.count }`;
}
} else if (badge.showAlert) {
iconFilename = 'icon-tray-alert';
} else {
iconFilename = 'icon-tray-Template';
}
if (process.platform === 'win32') {
iconFilename += ".ico";
} else {
iconFilename += ".png";
}
if (process.platform === 'win32') {
iconFilename += '.ico';
} else {
iconFilename += '.png';
}
return path.join(__dirname, 'images', icons[process.platform].dir, iconFilename);
return path.join(__dirname, 'images', icons[process.platform].dir, iconFilename);
}
function createAppTray () {
const _tray = new Tray(getTrayImagePath({title:'', count:0, showAlert:false}));
mainWindow.tray = _tray;
function createAppTray() {
const _tray = new Tray(getTrayImagePath({ title:'', count:0, showAlert:false }));
mainWindow.tray = _tray;
const contextMenuShow = Menu.buildFromTemplate([{
label: i18n.__('Show'),
click () {
mainWindow.show();
}
}, {
label: i18n.__('Quit'),
click () {
remote.app.quit();
}
}]);
const contextMenuShow = Menu.buildFromTemplate([{
label: i18n.__('Show'),
click() {
mainWindow.show();
},
}, {
label: i18n.__('Quit'),
click() {
remote.app.quit();
},
}]);
const contextMenuHide = Menu.buildFromTemplate([{
label: i18n.__('Hide'),
click () {
mainWindow.hide();
}
}, {
label: i18n.__('Quit'),
click () {
remote.app.quit();
}
}]);
const contextMenuHide = Menu.buildFromTemplate([{
label: i18n.__('Hide'),
click() {
mainWindow.hide();
},
}, {
label: i18n.__('Quit'),
click() {
remote.app.quit();
},
}]);
if (!mainWindow.isMinimized() && !mainWindow.isVisible()) {
_tray.setContextMenu(contextMenuShow);
} else {
_tray.setContextMenu(contextMenuHide);
}
if (!mainWindow.isMinimized() && !mainWindow.isVisible()) {
_tray.setContextMenu(contextMenuShow);
} else {
_tray.setContextMenu(contextMenuHide);
}
const onShow = function () {
_tray.setContextMenu(contextMenuHide);
};
const onShow = function() {
_tray.setContextMenu(contextMenuHide);
};
const onHide = function () {
_tray.setContextMenu(contextMenuShow);
};
const onHide = function() {
_tray.setContextMenu(contextMenuShow);
};
mainWindow.on('show', onShow);
mainWindow.on('restore', onShow);
mainWindow.on('show', onShow);
mainWindow.on('restore', onShow);
mainWindow.on('hide', onHide);
mainWindow.on('minimize', onHide);
mainWindow.on('hide', onHide);
mainWindow.on('minimize', onHide);
_tray.setToolTip(remote.app.getName());
_tray.setToolTip(remote.app.getName());
_tray.on('right-click', function (e, b) {
_tray.popUpContextMenu(undefined, b);
});
_tray.on('right-click', function(e, b) {
_tray.popUpContextMenu(undefined, b);
});
_tray.on('click', () => {
if (mainWindow.isVisible()) {
return mainWindow.hide();
}
_tray.on('click', () => {
if (mainWindow.isVisible()) {
return mainWindow.hide();
}
mainWindow.show();
});
mainWindow.show();
});
mainWindow.destroyTray = function () {
mainWindow.removeListener('show', onShow);
mainWindow.removeListener('hide', onHide);
_tray.destroy();
};
mainWindow.destroyTray = function() {
mainWindow.removeListener('show', onShow);
mainWindow.removeListener('hide', onHide);
_tray.destroy();
};
}
function showTrayAlert (badge, status = 'online') {
if (mainWindow.tray === null || mainWindow.tray === undefined) {
return;
}
mainWindow.tray.setImage(getTrayImagePath(badge));
const hasMentions = badge.showAlert && badge.count > 0;
function showTrayAlert(badge, status = 'online') {
if (mainWindow.tray === null || mainWindow.tray === undefined) {
return;
}
if (!mainWindow.isFocused()) {
mainWindow.flashFrame(hasMentions);
}
const trayDisplayed = localStorage.getItem('hideTray') !== 'true';
const hasMentions = badge.showAlert && badge.count > 0;
if (process.platform === 'win32') {
if (hasMentions) {
mainWindow.webContents.send('render-taskbar-icon', badge.count);
} else {
mainWindow.setOverlayIcon(null, '');
}
}
if (!mainWindow.isFocused()) {
mainWindow.flashFrame(hasMentions);
}
if (process.platform === 'darwin') {
let countColor = messageCountColor['black'];
if (remote.systemPreferences.isDarkMode()) {
countColor = messageCountColor['white'];
}
if (process.platform === 'win32') {
if (hasMentions) {
mainWindow.webContents.send('render-taskbar-icon', badge.count);
} else {
mainWindow.setOverlayIcon(null, '');
}
}
let trayTitle = `${statusBullet[status]}`;
if (hasMentions) {
trayTitle = `${statusBullet[status]} ${countColor}${badge.title}`;
}
mainWindow.tray.setTitle(trayTitle);
remote.app.dock.setBadge(badge.title);
}
if (process.platform === 'darwin') {
let countColor = messageCountColor.black;
if (remote.systemPreferences.isDarkMode()) {
countColor = messageCountColor.white;
}
if (process.platform === 'linux') {
remote.app.setBadgeCount(badge.count);
}
let trayTitle = `${ statusBullet[status] }`;
if (hasMentions) {
trayTitle = `${ statusBullet[status] } ${ countColor }${ badge.title }`;
}
remote.app.dock.setBadge(badge.title);
if (trayDisplayed) {
mainWindow.tray.setTitle(trayTitle);
}
}
if (process.platform === 'linux') {
remote.app.setBadgeCount(badge.count);
}
if (trayDisplayed) {
mainWindow.tray.setImage(getTrayImagePath(badge));
}
}
function removeAppTray () {
mainWindow.destroyTray();
function removeAppTray() {
mainWindow.destroyTray();
}
function toggle () {
if (localStorage.getItem('hideTray') === 'true') {
createAppTray();
localStorage.setItem('hideTray', 'false');
} else {
removeAppTray();
localStorage.setItem('hideTray', 'true');
}
function toggle() {
if (localStorage.getItem('hideTray') === 'true') {
createAppTray();
localStorage.setItem('hideTray', 'false');
} else {
removeAppTray();
localStorage.setItem('hideTray', 'true');
}
}
if (localStorage.getItem('hideTray') !== 'true') {
createAppTray();
createAppTray();
}
export default {
showTrayAlert,
toggle
showTrayAlert,
toggle,
};

View file

@ -2,212 +2,212 @@ import { EventEmitter } from 'events';
import servers from './servers';
import sidebar from './sidebar';
import tray from './tray';
import { desktopCapturer, ipcRenderer, shell } from 'electron';
import { desktopCapturer, ipcRenderer } from 'electron';
class WebView extends EventEmitter {
constructor () {
super();
constructor() {
super();
this.webviewParentElement = document.body;
this.webviewParentElement = document.body;
servers.forEach((host) => {
this.add(host);
});
servers.forEach((host) => {
this.add(host);
});
servers.on('host-added', (hostUrl) => {
this.add(servers.get(hostUrl));
});
servers.on('host-added', (hostUrl) => {
this.add(servers.get(hostUrl));
});
servers.on('host-removed', (hostUrl) => {
this.remove(hostUrl);
});
servers.on('host-removed', (hostUrl) => {
this.remove(hostUrl);
});
servers.on('active-setted', (hostUrl) => {
this.setActive(hostUrl);
});
servers.on('active-setted', (hostUrl) => {
this.setActive(hostUrl);
});
servers.on('active-cleared', (hostUrl) => {
this.deactiveAll(hostUrl);
});
servers.on('active-cleared', (hostUrl) => {
this.deactiveAll(hostUrl);
});
servers.once('loaded', () => {
this.loaded();
});
servers.once('loaded', () => {
this.loaded();
});
ipcRenderer.on('screenshare-result', (e, result) => {
const webviewObj = this.getActive();
webviewObj.executeJavaScript(`
ipcRenderer.on('screenshare-result', (e, result) => {
const webviewObj = this.getActive();
webviewObj.executeJavaScript(`
window.parent.postMessage({
sourceId: '${result}'
sourceId: '${ result }'
}, '*')
`);
});
}
});
}
loaded () {
document.querySelector('#loading').style.display = 'none';
document.querySelector('#login-card').style.display = 'block';
document.querySelector('footer').style.display = 'block';
}
loaded() {
document.querySelector('#loading').style.display = 'none';
document.querySelector('#login-card').style.display = 'block';
document.querySelector('footer').style.display = 'block';
}
loading () {
document.querySelector('#loading').style.display = 'block';
document.querySelector('#login-card').style.display = 'none';
document.querySelector('footer').style.display = 'none';
}
loading() {
document.querySelector('#loading').style.display = 'block';
document.querySelector('#login-card').style.display = 'none';
document.querySelector('footer').style.display = 'none';
}
add (host) {
let webviewObj = this.getByUrl(host.url);
if (webviewObj) {
return;
}
add(host) {
let webviewObj = this.getByUrl(host.url);
if (webviewObj) {
return;
}
webviewObj = document.createElement('webview');
webviewObj.setAttribute('server', host.url);
webviewObj.setAttribute('preload', './preload.js');
webviewObj.setAttribute('allowpopups', 'on');
webviewObj.setAttribute('disablewebsecurity', 'on');
webviewObj = document.createElement('webview');
webviewObj.setAttribute('server', host.url);
webviewObj.setAttribute('preload', './preload.js');
webviewObj.setAttribute('allowpopups', 'on');
webviewObj.setAttribute('disablewebsecurity', 'on');
webviewObj.addEventListener('did-navigate-in-page', (lastPath) => {
if ((lastPath.url).includes(host.url)) {
this.saveLastPath(host.url, lastPath.url);
}
});
webviewObj.addEventListener('did-navigate-in-page', (lastPath) => {
if ((lastPath.url).includes(host.url)) {
this.saveLastPath(host.url, lastPath.url);
}
});
webviewObj.addEventListener('console-message', (e) => {
console.log('webview:', e.message);
});
webviewObj.addEventListener('console-message', (e) => {
console.log('webview:', e.message);
});
webviewObj.addEventListener('ipc-message', (event) => {
this.emit('ipc-message-'+event.channel, host.url, event.args);
webviewObj.addEventListener('ipc-message', (event) => {
this.emit(`ipc-message-${ event.channel }`, host.url, event.args);
switch (event.channel) {
case 'title-changed':
servers.setHostTitle(host.url, event.args[0]);
break;
case 'unread-changed':
sidebar.setBadge(host.url, event.args[0]);
break;
case 'focus':
servers.setActive(host.url);
break;
case 'user-status-manually-set':
const badge = sidebar.getGlobalBadge();
tray.showTrayAlert(badge, event.args[0]);
break;
case 'get-sourceId':
desktopCapturer.getSources({types: ['window', 'screen']}, (error, sources) => {
if (error) {
throw error;
}
switch (event.channel) {
case 'title-changed':
servers.setHostTitle(host.url, event.args[0]);
break;
case 'unread-changed':
sidebar.setBadge(host.url, event.args[0]);
break;
case 'focus':
servers.setActive(host.url);
break;
case 'user-status-manually-set':
const badge = sidebar.getGlobalBadge();
tray.showTrayAlert(badge, event.args[0]);
break;
case 'get-sourceId':
desktopCapturer.getSources({ types: ['window', 'screen'] }, (error, sources) => {
if (error) {
throw error;
}
sources = sources.map(source => {
source.thumbnail = source.thumbnail.toDataURL();
return source;
});
ipcRenderer.send('screenshare', sources);
});
break;
case 'reload-server':
const active = this.getActive();
const server = active.getAttribute('server');
this.loading();
active.loadURL(server);
break;
case 'sidebar-background':
sidebar.changeSidebarColor(event.args[0]);
break;
}
});
sources = sources.map((source) => {
source.thumbnail = source.thumbnail.toDataURL();
return source;
});
ipcRenderer.send('screenshare', sources);
});
break;
case 'reload-server':
const active = this.getActive();
const server = active.getAttribute('server');
this.loading();
active.loadURL(server);
break;
case 'sidebar-background':
sidebar.changeSidebarColor(event.args[0]);
break;
}
});
webviewObj.addEventListener('dom-ready', () => {
this.emit('dom-ready', host.url);
});
webviewObj.addEventListener('dom-ready', () => {
this.emit('dom-ready', host.url);
});
webviewObj.addEventListener('did-fail-load', (e) => {
if (e.isMainFrame) {
webviewObj.loadURL('file://' + __dirname + '/loading-error.html');
}
});
webviewObj.addEventListener('did-fail-load', (e) => {
if (e.isMainFrame) {
webviewObj.loadURL(`file://${ __dirname }/loading-error.html`);
}
});
webviewObj.addEventListener('did-get-response-details', (e) => {
if (e.resourceType === 'mainFrame' && e.httpResponseCode >= 500) {
webviewObj.loadURL('file://' + __dirname + '/loading-error.html');
}
});
webviewObj.addEventListener('did-get-response-details', (e) => {
if (e.resourceType === 'mainFrame' && e.httpResponseCode >= 500) {
webviewObj.loadURL(`file://${ __dirname }/loading-error.html`);
}
});
this.webviewParentElement.appendChild(webviewObj);
this.webviewParentElement.appendChild(webviewObj);
webviewObj.src = host.lastPath || host.url;
}
webviewObj.src = host.lastPath || host.url;
}
remove (hostUrl) {
const el = this.getByUrl(hostUrl);
if (el) {
el.remove();
}
}
remove(hostUrl) {
const el = this.getByUrl(hostUrl);
if (el) {
el.remove();
}
}
saveLastPath (hostUrl, lastPathUrl) {
const hosts = servers.hosts;
hosts[hostUrl].lastPath = lastPathUrl;
servers.hosts = hosts;
}
saveLastPath(hostUrl, lastPathUrl) {
const { hosts } = servers;
hosts[hostUrl].lastPath = lastPathUrl;
servers.hosts = hosts;
}
getByUrl (hostUrl) {
return this.webviewParentElement.querySelector(`webview[server="${hostUrl}"]`);
}
getByUrl(hostUrl) {
return this.webviewParentElement.querySelector(`webview[server="${ hostUrl }"]`);
}
getActive () {
return document.querySelector('webview.active');
}
getActive() {
return document.querySelector('webview.active');
}
isActive (hostUrl) {
return !!this.webviewParentElement.querySelector(`webview.active[server="${hostUrl}"]`);
}
isActive(hostUrl) {
return !!this.webviewParentElement.querySelector(`webview.active[server="${ hostUrl }"]`);
}
deactiveAll () {
let item;
while (!(item = this.getActive()) === false) {
item.classList.remove('active');
}
document.querySelector('.landing-page').classList.add('hide');
}
deactiveAll() {
let item;
while (!(item = this.getActive()) === false) {
item.classList.remove('active');
}
document.querySelector('.landing-page').classList.add('hide');
}
showLanding () {
this.loaded();
document.querySelector('.landing-page').classList.remove('hide');
}
showLanding() {
this.loaded();
document.querySelector('.landing-page').classList.remove('hide');
}
setActive (hostUrl) {
if (this.isActive(hostUrl)) {
return;
}
setActive(hostUrl) {
if (this.isActive(hostUrl)) {
return;
}
this.deactiveAll();
const item = this.getByUrl(hostUrl);
if (item) {
item.classList.add('active');
}
this.focusActive();
}
this.deactiveAll();
const item = this.getByUrl(hostUrl);
if (item) {
item.classList.add('active');
}
this.focusActive();
}
focusActive () {
const active = this.getActive();
if (active) {
active.focus();
return true;
}
return false;
}
focusActive() {
const active = this.getActive();
if (active) {
active.focus();
return true;
}
return false;
}
goBack () {
this.getActive().goBack();
}
goBack() {
this.getActive().goBack();
}
goForward () {
this.getActive().goForward();
}
goForward() {
this.getActive().goForward();
}
}
export default new WebView();

View file

@ -4,6 +4,7 @@
@import "../branding/branding.less";
@import "fontello.less";
@import "utils/_loading.import.less";
@import "pages/about.less";
*,
*:before,
@ -305,7 +306,7 @@ input[type='password'] {
.loading-error {
height: 100%;
color: white;
color: white;
text-align: center;
background-color: @primary-background-color;
background-position: center bottom;

View file

@ -0,0 +1,97 @@
.about-page {
display: flex;
flex-direction: column;
padding: 0.25rem 0.75rem;
min-height: 100vh;
background-color: @secondary-background-color;
cursor: default;
user-select: none;
.hidden {
display: none !important;
}
.app-info {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
.app-logo img {
width: 100%;
}
.app-version {
margin: 0 auto;
font-size: 0.75rem;
.version {
cursor: text;
user-select: text;
font-weight: bold;
}
}
}
.updates {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
.check-for-updates {
height: 2.5rem;
}
.checking-for-updates {
display: flex;
justify-content: center;
align-items: center;
height: 2.5rem;
margin: 4px;
color: @secondary-font-color;
.dot {
width: 0.5rem;
height: 0.5rem;
border-radius: 100%;
margin: 0.1rem;
background-color: currentColor;
animation: loading-bouncedelay 1.4s infinite ease-in-out both;
&:nth-of-type(1) {
animation-delay: -0.32s;
}
&:nth-of-type(2) {
animation-delay: -0.16s;
}
}
.message {
display: none;
font-size: 1rem;
}
&.message-shown {
.dot {
display: none;
}
.message {
display: inline-flex;
}
}
}
.check-for-updates-on-start__label {
margin: 0.1rem auto;
font-size: 0.8rem;
}
}
.copyright {
margin: 0 auto;
font-size: 0.65rem;
}
}

View file

@ -10,55 +10,45 @@ const bundle = require('./bundle');
const utils = require('./utils');
const { beepSound, srcDir, configDir, appDir } = require('./utils');
gulp.task('public', () => {
return gulp.src(srcDir.path('public/**/*'))
.pipe(plumber())
.pipe(gulp.dest(appDir.path('public')));
});
gulp.task('public', () => gulp.src(srcDir.path('public/**/*'))
.pipe(plumber())
.pipe(gulp.dest(appDir.path('public'))));
gulp.task('i18n', () => {
return gulp.src(srcDir.path('i18n/lang/**/*'))
.pipe(plumber())
.pipe(gulp.dest(appDir.path('i18n/lang')));
});
gulp.task('i18n', () => gulp.src(srcDir.path('i18n/lang/**/*'))
.pipe(plumber())
.pipe(gulp.dest(appDir.path('i18n/lang'))));
gulp.task('bundle', () => {
return Promise.all([
bundle(srcDir.path('background.js'), appDir.path('background.js')),
bundle(srcDir.path('app.js'), appDir.path('app.js')),
bundle(srcDir.path('i18n/index.js'), appDir.path('i18n/index.js'))
]);
});
gulp.task('bundle', () => Promise.all([
bundle(srcDir.path('background.js'), appDir.path('background.js')),
bundle(srcDir.path('app.js'), appDir.path('app.js')),
bundle(srcDir.path('i18n/index.js'), appDir.path('i18n/index.js')),
]));
gulp.task('less', () => {
return gulp.src(srcDir.path('stylesheets/main.less'))
.pipe(plumber())
.pipe(less())
.pipe(gulp.dest(appDir.path('stylesheets')));
});
gulp.task('less', () => gulp.src(srcDir.path('stylesheets/main.less'))
.pipe(plumber())
.pipe(less())
.pipe(gulp.dest(appDir.path('stylesheets'))));
gulp.task('environment', () => {
return gulp.src(configDir.path(`env_${ utils.getEnvName() }.json`))
.pipe(plumber())
.pipe(rename('env.json'))
.pipe(gulp.dest(appDir.path('.')));
});
gulp.task('environment', () => gulp.src(configDir.path(`env_${ utils.getEnvName() }.json`))
.pipe(plumber())
.pipe(rename('env.json'))
.pipe(gulp.dest(appDir.path('.'))));
gulp.task('build-app', [ 'public', 'i18n', 'bundle', 'less', 'environment' ]);
gulp.task('build-app', ['public', 'i18n', 'bundle', 'less', 'environment']);
gulp.task('watch', () => {
const runOnChanges = taskName => batch((event, done) => {
gulp.start(taskName, err => {
if (err) {
beepSound();
}
done(err);
});
});
const runOnChanges = (taskName) => batch((event, done) => {
gulp.start(taskName, (err) => {
if (err) {
beepSound();
}
done(err);
});
});
watch(srcDir.path('public/**/*'), runOnChanges('public'));
watch(srcDir.path('i18n/lang/**/*'), runOnChanges('i18n'));
watch(srcDir.path('**/*.js'), runOnChanges('bundle'));
watch(srcDir.path('**/*.less'), runOnChanges('less'));
watch(configDir.path('**/*'), runOnChanges('environment'));
watch(srcDir.path('public/**/*'), runOnChanges('public'));
watch(srcDir.path('i18n/lang/**/*'), runOnChanges('i18n'));
watch(srcDir.path('**/*.js'), runOnChanges('bundle'));
watch(srcDir.path('**/*.less'), runOnChanges('less'));
watch(configDir.path('**/*'), runOnChanges('environment'));
});

View file

@ -5,30 +5,32 @@ const jetpack = require('fs-jetpack');
const bundle = require('./bundle');
const istanbul = require('rollup-plugin-istanbul');
const createEntryFile = (srcDir, matching, outputDir, entryFileName, rollupOptions) => {
return srcDir.findAsync('.', { matching })
.then(specPaths => specPaths.map(path => `import './${ path.replace(/\\/g, '/') }';`))
.then(imports => (
"// This file is generated automatically.\n" +
"// All modifications will be lost.\n" +
imports.join('\n')
))
.then(entryFileContent => srcDir.writeAsync(entryFileName, entryFileContent))
.then(() => bundle(srcDir.path(entryFileName), outputDir.path(entryFileName), rollupOptions))
.then(() => srcDir.remove(entryFileName));
const createEntryFile = async(srcDir, matching, outputDir, entryFileName, rollupOptions) => {
const entryFileContent = srcDir.find('.', { matching })
.map((path) => `import './${ path.replace(/\\/g, '/') }';`)
.join('\n');
srcDir.write(entryFileName, entryFileContent);
await bundle(srcDir.path(entryFileName), outputDir.path(entryFileName), rollupOptions);
srcDir.remove(entryFileName);
};
gulp.task('build-unit-tests', [ 'environment' ], () => {
return createEntryFile(jetpack.cwd('src'), '*.spec.js', jetpack.cwd('app'), 'specs.js.autogenerated', {
rollupPlugins: [
istanbul({
exclude: ['**/*.spec.js', '**/specs.js.autogenerated'],
sourcemap: true
})
]
});
});
gulp.task('build-unit-tests', ['environment'], () => createEntryFile(
jetpack.cwd('src'), '*.spec.js',
jetpack.cwd('app'), 'specs.js.autogenerated',
{
rollupPlugins: [
istanbul({
exclude: ['**/*.spec.js', '**/specs.js.autogenerated'],
sourcemap: true,
}),
],
}
));
gulp.task('build-e2e-tests', [ 'build-app' ], () => {
return createEntryFile(jetpack.cwd('e2e'), '*.e2e.js', jetpack.cwd('app'), 'e2e.js.autogenerated');
});
gulp.task('build-e2e-tests', ['build-app'], () => createEntryFile(
jetpack.cwd('src'), '*.e2e.js',
jetpack.cwd('app'), 'e2e.js.autogenerated'
));

View file

@ -1,56 +1,46 @@
'use strict';
const path = require('path');
const jetpack = require('fs-jetpack');
const rollup = require('rollup').rollup;
const { rollup } = require('rollup');
const rollupJson = require('rollup-plugin-json');
const appManifest = require('../package.json');
const nodeBuiltInModules = ['assert', 'buffer', 'child_process', 'cluster',
'console', 'constants', 'crypto', 'dgram', 'dns', 'domain', 'events',
'fs', 'http', 'https', 'module', 'net', 'os', 'path', 'process', 'punycode',
'querystring', 'readline', 'repl', 'stream', 'string_decoder', 'timers',
'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib'];
const nodeBuiltInModules = ['assert', 'buffer', 'child_process', 'cluster', 'console', 'constants', 'crypto', 'dgram',
'dns', 'domain', 'events', 'fs', 'http', 'https', 'module', 'net', 'os', 'path', 'process', 'punycode', 'querystring',
'readline', 'repl', 'stream', 'string_decoder', 'timers', 'tls', 'tty', 'url', 'util', 'v8', 'vm', 'zlib'];
const electronBuiltInModules = ['electron'];
const generateExternalModulesList = function () {
const appManifest = jetpack.read('./package.json', 'json');
return [].concat(
nodeBuiltInModules,
electronBuiltInModules,
Object.keys(appManifest.dependencies),
Object.keys(appManifest.devDependencies)
);
};
const externalModulesList = [
...nodeBuiltInModules,
...electronBuiltInModules,
...Object.keys(appManifest.dependencies),
...Object.keys(appManifest.devDependencies),
];
const cached = {};
module.exports = function (src, dest, opts) {
opts = opts || {};
opts.rollupPlugins = opts.rollupPlugins || [];
return rollup({
input: src,
external: generateExternalModulesList(),
cache: cached[src],
plugins: opts.rollupPlugins,
})
.then(function (bundle) {
cached[src] = bundle;
module.exports = async(src, dest, opts = {}) => {
const inputOptions = {
input: src,
external: externalModulesList,
cache: cached[src],
plugins: [
...(opts.rollupPlugins || []),
rollupJson(),
],
};
const jsFile = path.basename(dest);
return bundle.generate({
format: 'cjs',
sourcemap: true,
sourcemapFile: jsFile,
});
})
.then(function (result) {
// Wrap code in self invoking function so the variables don't
// pollute the global namespace.
const isolatedCode = '(function () {' + result.code + '\n}());';
const jsFile = path.basename(dest);
return Promise.all([
jetpack.writeAsync(dest, isolatedCode + '\n//# sourcemappingURL=' + jsFile + '.map'),
jetpack.writeAsync(dest + '.map', result.map.toString()),
]);
});
const outputOptions = {
format: 'cjs',
file: dest,
intro: '(function () {',
outro: '})()',
sourcemap: true,
sourcemapFile: path.basename(dest),
};
const bundle = await rollup(inputOptions);
cached[src] = bundle;
await bundle.write(outputOptions);
};

View file

@ -1,36 +1,18 @@
'use strict';
const gulp = require('gulp');
const sequence = require('gulp-sequence');
const childProcess = require('child_process');
const os = require('os');
const path = require('path');
const runSequence = require('run-sequence');
const { build } = require('electron-builder');
const config = require('../electron-builder.json');
const { getEnvName } = require('./utils');
const argv = process.argv.slice(3).filter(arg => !arg.startsWith('--env'));
const publishArgs = getEnvName() !== 'production' ? [ '--publish', 'never' ] : [];
const buildRelease = (...args) => cb => {
const buildPath = path.join('node_modules', '.bin', os.platform() === 'win32' ? 'build.cmd' : 'build');
childProcess.spawn(buildPath, [ ...argv, ...publishArgs, ...args ], { stdio: 'inherit' })
.on('close', () => cb());
};
gulp.task('release:osx', [ 'build-app' ], buildRelease('--x64', '--mac'));
gulp.task('release:win', [ 'build-app' ], buildRelease('--ia32', '--x64', '--win', 'nsis', 'appx'));
gulp.task('release:linux-x64', buildRelease('--x64', '--linux'));
gulp.task('release:linux-ia32', buildRelease('--ia32', '--linux', 'tar.gz', 'deb', 'rpm'));
gulp.task('release:linux', [ 'build-app' ], sequence('release:linux-x64', 'release:linux-ia32'));
gulp.task('release', cb => {
switch (os.platform()) {
case 'darwin':
return gulp.start('release:osx', cb);
case 'win32':
return gulp.start('release:win', cb);
case 'linux':
return gulp.start('release:linux', cb);
}
const publish = getEnvName() !== 'production' ? 'never' : 'onTagOrDraft';
gulp.task('release:darwin', () => build({ publish, x64: true, mac: [] }));
gulp.task('release:win32', () => build({ publish, x64: true, ia32: true, win: ['nsis', 'appx'] }));
gulp.task('release:linux', async() => {
const allLinuxTargetsButSnap = config.linux.target.filter((target) => target !== 'snap');
await build({ publish, x64: true, linux: [], c: { productName: 'rocketchat' } });
await build({ publish, ia32: true, linux: allLinuxTargetsButSnap, c: { productName: 'rocketchat' } });
});
gulp.task('release', (cb) => runSequence('build-app', `release:${ process.platform }`, cb));

View file

@ -4,7 +4,7 @@ const gulp = require('gulp');
const childProcess = require('child_process');
const electron = require('electron');
gulp.task('start', [ 'build-app', 'watch' ], () => {
childProcess.spawn(electron, [ '.' ], { stdio: 'inherit' })
.on('close', () => process.exit());
gulp.task('start', ['build-app', 'watch'], () => {
childProcess.spawn(electron, ['.'], { stdio: 'inherit' })
.on('close', () => process.exit());
});

1844
yarn.lock

File diff suppressed because it is too large Load diff