mirror of
https://github.com/element-hq/element-desktop.git
synced 2026-01-03 21:18:12 -05:00
Compare commits
89 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad337b1f7c | ||
|
|
8d380fe533 | ||
|
|
6e7913c7d1 | ||
|
|
9cb171f953 | ||
|
|
44310712f3 | ||
|
|
9b65962d26 | ||
|
|
8302e284a3 | ||
|
|
390e2306d0 | ||
|
|
60e61415ea | ||
|
|
f4be51959b | ||
|
|
397bc66522 | ||
|
|
4db0a0ac0c | ||
|
|
d353c68a75 | ||
|
|
0143b4b114 | ||
|
|
93dd8aa2ba | ||
|
|
140b0b8c29 | ||
|
|
744050d8f4 | ||
|
|
39f3e3b9c2 | ||
|
|
43c8b13d75 | ||
|
|
2d568f9688 | ||
|
|
3f49046980 | ||
|
|
53e7100033 | ||
|
|
b8cb53e11b | ||
|
|
c751470abf | ||
|
|
9860ac6b75 | ||
|
|
6e76d658b1 | ||
|
|
3aff9cb9eb | ||
|
|
6579ba80b4 | ||
|
|
37410e6bc4 | ||
|
|
b036113786 | ||
|
|
c579031afc | ||
|
|
ebcd68428e | ||
|
|
f63436a2cb | ||
|
|
c5dd6195f2 | ||
|
|
c3a36bb17d | ||
|
|
f3c1db3313 | ||
|
|
c57a173649 | ||
|
|
48dc1ab396 | ||
|
|
997f2c21bf | ||
|
|
a171fa417b | ||
|
|
115f25165a | ||
|
|
c1ca909c7c | ||
|
|
f61370505b | ||
|
|
02d0999b18 | ||
|
|
5b15bc9b5d | ||
|
|
6b4e6f6be6 | ||
|
|
10a11242ff | ||
|
|
e59a2588ec | ||
|
|
a7938ae514 | ||
|
|
cf3112a9a9 | ||
|
|
b23048c5ce | ||
|
|
c689529641 | ||
|
|
7b6ead738e | ||
|
|
b6b5547ad0 | ||
|
|
e28390fddb | ||
|
|
b18bcd9bed | ||
|
|
b470657cdb | ||
|
|
fc51063f7a | ||
|
|
099ecc468a | ||
|
|
99b5947d7d | ||
|
|
7418161475 | ||
|
|
40be024c9f | ||
|
|
b2f09570f7 | ||
|
|
c3f150f9f0 | ||
|
|
8a90a12683 | ||
|
|
e7bc785ae4 | ||
|
|
d1cb3092e8 | ||
|
|
a136ccbf4c | ||
|
|
231db351ec | ||
|
|
06b0f0fe01 | ||
|
|
4b71142a87 | ||
|
|
9726be5754 | ||
|
|
2293b6794c | ||
|
|
492bbcbe06 | ||
|
|
9cc1d91011 | ||
|
|
c08b62015e | ||
|
|
03abf9f9d0 | ||
|
|
69e4ec7287 | ||
|
|
57ba22f874 | ||
|
|
225456b8e0 | ||
|
|
03e59aa60c | ||
|
|
330e230f2c | ||
|
|
dee2ecdf6c | ||
|
|
f109065606 | ||
|
|
344800d835 | ||
|
|
8c99f0ad12 | ||
|
|
bc0e3bb317 | ||
|
|
b7e4e9e075 | ||
|
|
5055ad0aec |
22
.eslintrc.js
22
.eslintrc.js
@@ -1,7 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: [
|
||||
"matrix-org",
|
||||
],
|
||||
plugins: ["matrix-org"],
|
||||
extends: [
|
||||
"plugin:matrix-org/javascript",
|
||||
],
|
||||
@@ -19,5 +17,19 @@ module.exports = {
|
||||
"indent": "off",
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
}
|
||||
}
|
||||
},
|
||||
overrides: [{
|
||||
files: ["src/**/*.{ts,tsx}"],
|
||||
extends: [
|
||||
"plugin:matrix-org/typescript",
|
||||
],
|
||||
rules: {
|
||||
// Things we do that break the ideal style
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"quotes": "off",
|
||||
|
||||
// We disable this while we're transitioning
|
||||
"@typescript-eslint/no-explicit-any": "off",
|
||||
},
|
||||
}],
|
||||
};
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,5 @@
|
||||
/dist
|
||||
/lib
|
||||
/webapp
|
||||
/webapp.asar
|
||||
/packages
|
||||
|
||||
66
CHANGELOG.md
66
CHANGELOG.md
@@ -1,3 +1,69 @@
|
||||
Changes in [1.7.32](https://github.com/vector-im/element-desktop/releases/tag/v1.7.32) (2021-07-05)
|
||||
===================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/element-desktop/compare/v1.7.31...v1.7.32)
|
||||
|
||||
* Fix the build: make the rootDir correct
|
||||
[\#224](https://github.com/vector-im/element-desktop/pull/224)
|
||||
* Fix i18n in Element Desktop
|
||||
[\#223](https://github.com/vector-im/element-desktop/pull/223)
|
||||
* Convert preload.js to Typescript so that it gets copied to `lib`
|
||||
[\#222](https://github.com/vector-im/element-desktop/pull/222)
|
||||
* Bundle the `lib` dir now, not `src`
|
||||
[\#221](https://github.com/vector-im/element-desktop/pull/221)
|
||||
* Initial Typescripting for Element Desktop
|
||||
[\#219](https://github.com/vector-im/element-desktop/pull/219)
|
||||
* Translations update from Weblate
|
||||
[\#220](https://github.com/vector-im/element-desktop/pull/220)
|
||||
* Fix Windows target arch in native build
|
||||
[\#218](https://github.com/vector-im/element-desktop/pull/218)
|
||||
* Add libera.chat to default room directory
|
||||
[\#217](https://github.com/vector-im/element-desktop/pull/217)
|
||||
* Add update and native build support for Apple silicon
|
||||
[\#216](https://github.com/vector-im/element-desktop/pull/216)
|
||||
* Add numpad accelerators for zooming
|
||||
[\#203](https://github.com/vector-im/element-desktop/pull/203)
|
||||
* Add warning dialog when custom config.json is invalid
|
||||
[\#201](https://github.com/vector-im/element-desktop/pull/201)
|
||||
* Don't show Quit warning on keyUp residual event
|
||||
[\#215](https://github.com/vector-im/element-desktop/pull/215)
|
||||
* Fix accelerator for save-image-as clashing with copy-link-address
|
||||
[\#213](https://github.com/vector-im/element-desktop/pull/213)
|
||||
|
||||
Changes in [1.7.31](https://github.com/vector-im/element-desktop/releases/tag/v1.7.31) (2021-06-21)
|
||||
===================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/element-desktop/compare/v1.7.31-rc.1...v1.7.31)
|
||||
|
||||
* No changes since rc.1
|
||||
|
||||
Changes in [1.7.31-rc.1](https://github.com/vector-im/element-desktop/releases/tag/v1.7.31-rc.1) (2021-06-15)
|
||||
=============================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/element-desktop/compare/v1.7.30...v1.7.31-rc.1)
|
||||
|
||||
* Upgrade to Electron 12.0.11
|
||||
[\#211](https://github.com/vector-im/element-desktop/pull/211)
|
||||
* Translations update from Weblate
|
||||
[\#214](https://github.com/vector-im/element-desktop/pull/214)
|
||||
* Upgrade to Node 14
|
||||
[\#212](https://github.com/vector-im/element-desktop/pull/212)
|
||||
* Bump npm-registry-fetch from 4.0.2 to 4.0.7
|
||||
[\#210](https://github.com/vector-im/element-desktop/pull/210)
|
||||
* Update electron-builder for Node 16 compatibility
|
||||
[\#204](https://github.com/vector-im/element-desktop/pull/204)
|
||||
* Bump hosted-git-info from 2.8.5 to 2.8.9
|
||||
[\#209](https://github.com/vector-im/element-desktop/pull/209)
|
||||
* Bump glob-parent from 5.1.1 to 5.1.2
|
||||
[\#206](https://github.com/vector-im/element-desktop/pull/206)
|
||||
* Bump dot-prop from 4.2.0 to 4.2.1
|
||||
[\#208](https://github.com/vector-im/element-desktop/pull/208)
|
||||
* Bump y18n from 3.2.1 to 3.2.2
|
||||
[\#207](https://github.com/vector-im/element-desktop/pull/207)
|
||||
* Bump normalize-url from 4.5.0 to 4.5.1
|
||||
[\#205](https://github.com/vector-im/element-desktop/pull/205)
|
||||
* Put Preferences menu item in correct location on macOS
|
||||
[\#200](https://github.com/vector-im/element-desktop/pull/200)
|
||||
* Switch zoomIn accelerator to default
|
||||
[\#202](https://github.com/vector-im/element-desktop/pull/202)
|
||||
|
||||
Changes in [1.7.30](https://github.com/vector-im/element-desktop/releases/tag/v1.7.30) (2021-06-07)
|
||||
===================================================================================================
|
||||
[Full Changelog](https://github.com/vector-im/element-desktop/compare/v1.7.30-rc.1...v1.7.30)
|
||||
|
||||
@@ -30,7 +30,7 @@ ENV LC_ALL C.UTF-8
|
||||
|
||||
ENV DEBUG_COLORS true
|
||||
ENV FORCE_COLOR true
|
||||
ENV NODE_VERSION 12.16.1
|
||||
ENV NODE_VERSION 14.17.0
|
||||
|
||||
# this package is used for snapcraft and we should not clear apt list - to avoid apt-get update during snap build
|
||||
RUN curl -L https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.gz | tar xz -C /usr/local --strip-components=1 && \
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"roomDirectory": {
|
||||
"servers": [
|
||||
"matrix.org",
|
||||
"gitter.im"
|
||||
"gitter.im",
|
||||
"libera.chat"
|
||||
]
|
||||
},
|
||||
"enable_presence_by_hs_url": {
|
||||
|
||||
@@ -16,7 +16,8 @@
|
||||
"roomDirectory": {
|
||||
"servers": [
|
||||
"matrix.org",
|
||||
"gitter.im"
|
||||
"gitter.im",
|
||||
"libera.chat"
|
||||
]
|
||||
},
|
||||
"showLabsSettings": false,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -34,7 +34,7 @@ async function buildOpenSslWin(hakEnv, moduleInfo) {
|
||||
const version = moduleInfo.cfg.dependencies.openssl;
|
||||
const openSslDir = path.join(moduleInfo.moduleDotHakDir, `openssl-${version}`);
|
||||
|
||||
const openSslArch = hakEnv.arch === 'x64' ? 'VC-WIN64A' : 'VC-WIN32';
|
||||
const openSslArch = hakEnv.getTargetArch() === 'x64' ? 'VC-WIN64A' : 'VC-WIN32';
|
||||
|
||||
console.log("Building openssl in " + openSslDir);
|
||||
await new Promise((resolve, reject) => {
|
||||
@@ -182,9 +182,36 @@ async function buildSqlCipherUnix(hakEnv, moduleInfo) {
|
||||
if (hakEnv.isMac()) {
|
||||
args.push('--with-crypto-lib=commoncrypto');
|
||||
}
|
||||
args.push('CFLAGS=-DSQLITE_HAS_CODEC');
|
||||
|
||||
if (!hakEnv.isHost()) {
|
||||
// In the nonsense world of `configure`, it is assumed you are building
|
||||
// a compiler like `gcc`, so the `host` option actually means the target
|
||||
// the build output runs on.
|
||||
args.push(`--host=${hakEnv.getTargetId()}`);
|
||||
}
|
||||
|
||||
const cflags = [
|
||||
'-DSQLITE_HAS_CODEC',
|
||||
];
|
||||
|
||||
if (!hakEnv.isHost()) {
|
||||
// `clang` uses more logical option naming.
|
||||
cflags.push(`--target=${hakEnv.getTargetId()}`);
|
||||
}
|
||||
|
||||
if (cflags.length) {
|
||||
args.push(`CFLAGS=${cflags.join(' ')}`);
|
||||
}
|
||||
|
||||
const ldflags = [];
|
||||
|
||||
if (hakEnv.isMac()) {
|
||||
args.push('LDFLAGS=-framework Security -framework Foundation');
|
||||
ldflags.push('-framework Security');
|
||||
ldflags.push('-framework Foundation');
|
||||
}
|
||||
|
||||
if (ldflags.length) {
|
||||
args.push(`LDFLAGS=${ldflags.join(' ')}`);
|
||||
}
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
@@ -248,7 +275,11 @@ async function buildMatrixSeshat(hakEnv, moduleInfo) {
|
||||
// the build scripts since they run on the host, but vcvarsall.bat sets the c
|
||||
// compiler in the path to be the one for the target, so we just use the matching
|
||||
// toolchain for the target architecture which makes everything happy.
|
||||
env.RUSTUP_TOOLCHAIN = hakEnv.arch == 'x64' ? 'stable-x86_64-pc-windows-msvc' : 'stable-i686-pc-windows-msvc';
|
||||
env.RUSTUP_TOOLCHAIN = `stable-${hakEnv.getTargetId()}`;
|
||||
}
|
||||
|
||||
if (!hakEnv.isHost()) {
|
||||
env.CARGO_BUILD_TARGET = hakEnv.getTargetId();
|
||||
}
|
||||
|
||||
console.log("Running neon with env", env);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -34,7 +34,10 @@ module.exports = async function(hakEnv, moduleInfo) {
|
||||
});
|
||||
}
|
||||
|
||||
const tools = [['python', '--version']]; // node-gyp uses python for reasons beyond comprehension
|
||||
const tools = [
|
||||
['rustc', '--version'],
|
||||
['python', '--version'], // node-gyp uses python for reasons beyond comprehension
|
||||
];
|
||||
if (hakEnv.isWin()) {
|
||||
tools.push(['perl', '--version']); // for openssl configure
|
||||
tools.push(['patch', '--version']); // to patch sqlcipher Makefile.msc
|
||||
@@ -57,4 +60,18 @@ module.exports = async function(hakEnv, moduleInfo) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure Rust target exists
|
||||
await new Promise((resolve, reject) => {
|
||||
childProcess.execFile('rustup', ['target', 'list', '--installed'], (err, out) => {
|
||||
if (err) {
|
||||
reject("Can't find rustup");
|
||||
}
|
||||
const target = hakEnv.getTargetId();
|
||||
if (!out.includes(target)) {
|
||||
reject(`Rust target ${target} not installed`);
|
||||
}
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
38
package.json
38
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "element-desktop",
|
||||
"productName": "Element",
|
||||
"main": "src/electron-main.js",
|
||||
"version": "1.7.30",
|
||||
"main": "lib/electron-main.js",
|
||||
"version": "1.7.32",
|
||||
"description": "A feature-rich client for Matrix.org",
|
||||
"author": "Element",
|
||||
"repository": {
|
||||
@@ -18,18 +18,22 @@
|
||||
"mkdirs": "mkdirp packages deploys",
|
||||
"fetch": "yarn run mkdirs && node scripts/fetch-package.js",
|
||||
"asar-webapp": "asar p webapp webapp.asar",
|
||||
"start": "electron .",
|
||||
"lint": "eslint src/ scripts/ hak/",
|
||||
"start": "yarn run build:ts && yarn run build:res && electron .",
|
||||
"lint": "yarn lint:types && yarn lint:js",
|
||||
"lint:js": "eslint src/ scripts/ hak/",
|
||||
"lint:types": "tsc --noEmit",
|
||||
"build:native": "yarn run hak",
|
||||
"build32": "electron-builder --ia32",
|
||||
"build64": "electron-builder --x64",
|
||||
"build": "electron-builder",
|
||||
"build32": "yarn run build:ts && yarn run build:res && electron-builder --ia32",
|
||||
"build64": "yarn run build:ts && yarn run build:res && electron-builder --x64",
|
||||
"build": "yarn run build:ts && yarn run build:res && electron-builder",
|
||||
"build:ts": "tsc",
|
||||
"build:res": "node scripts/copy-res.js",
|
||||
"docker:setup": "docker build -t element-desktop-dockerbuild dockerbuild",
|
||||
"docker:build:native": "scripts/in-docker.sh yarn run hak",
|
||||
"docker:build": "scripts/in-docker.sh yarn run build",
|
||||
"docker:install": "scripts/in-docker.sh yarn install",
|
||||
"debrepo": "scripts/mkrepo.sh",
|
||||
"clean": "rimraf webapp.asar dist packages deploys",
|
||||
"clean": "rimraf webapp.asar dist packages deploys lib",
|
||||
"hak": "node scripts/hak/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
@@ -42,9 +46,16 @@
|
||||
"request": "^2.88.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/auto-launch": "^5.0.1",
|
||||
"@types/counterpart": "^0.18.1",
|
||||
"@types/minimist": "^1.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^4.17.0",
|
||||
"@typescript-eslint/parser": "^4.17.0",
|
||||
"asar": "^2.0.1",
|
||||
"electron-builder": "22.10.5",
|
||||
"electron-builder-squirrel-windows": "22.10.5",
|
||||
"chokidar": "^3.5.2",
|
||||
"electron": "12.0.11",
|
||||
"electron-builder": "22.11.4",
|
||||
"electron-builder-squirrel-windows": "22.11.4",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-notarize": "^1.0.0",
|
||||
"eslint": "7.18.0",
|
||||
@@ -60,7 +71,8 @@
|
||||
"npm": "^6.14.11",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.4",
|
||||
"tar": "^6.1.0"
|
||||
"tar": "^6.1.0",
|
||||
"typescript": "^4.1.3"
|
||||
},
|
||||
"hakDependencies": {
|
||||
"matrix-seshat": "^2.2.3",
|
||||
@@ -68,14 +80,14 @@
|
||||
},
|
||||
"build": {
|
||||
"appId": "im.riot.app",
|
||||
"electronVersion": "12.0.9",
|
||||
"electronVersion": "12.0.12",
|
||||
"files": [
|
||||
"package.json",
|
||||
{
|
||||
"from": ".hak/hakModules",
|
||||
"to": "node_modules"
|
||||
},
|
||||
"src/**"
|
||||
"lib/**"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
|
||||
121
scripts/copy-res.js
Executable file
121
scripts/copy-res.js
Executable file
@@ -0,0 +1,121 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// copies resources into the lib directory.
|
||||
|
||||
const parseArgs = require('minimist');
|
||||
const chokidar = require('chokidar');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
const argv = parseArgs(process.argv.slice(2), {});
|
||||
|
||||
const watch = argv.w;
|
||||
const verbose = argv.v;
|
||||
|
||||
function errCheck(err) {
|
||||
if (err) {
|
||||
console.error(err.message);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const I18N_BASE_PATH = "src/i18n/strings/";
|
||||
const INCLUDE_LANGS = fs.readdirSync(I18N_BASE_PATH).filter(fn => fn.endsWith(".json"));
|
||||
|
||||
// Ensure lib, lib/i18n and lib/i18n/strings all exist
|
||||
fs.mkdirSync('lib/i18n/strings', { recursive: true });
|
||||
|
||||
function genLangFile(file, dest) {
|
||||
let translations = {};
|
||||
[file].forEach(function(f) {
|
||||
if (fs.existsSync(f)) {
|
||||
try {
|
||||
Object.assign(
|
||||
translations,
|
||||
JSON.parse(fs.readFileSync(f).toString()),
|
||||
);
|
||||
} catch (e) {
|
||||
console.error("Failed: " + f, e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
translations = weblateToCounterpart(translations);
|
||||
|
||||
const json = JSON.stringify(translations, null, 4);
|
||||
const filename = path.basename(file);
|
||||
|
||||
fs.writeFileSync(dest + filename, json);
|
||||
if (verbose) {
|
||||
console.log("Generated language file: " + filename);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert translation key from weblate format
|
||||
* (which only supports a single level) to counterpart
|
||||
* which requires object values for 'count' translations.
|
||||
*
|
||||
* eg.
|
||||
* "there are %(count)s badgers|one": "a badger",
|
||||
* "there are %(count)s badgers|other": "%(count)s badgers"
|
||||
* becomes
|
||||
* "there are %(count)s badgers": {
|
||||
* "one": "a badger",
|
||||
* "other": "%(count)s badgers"
|
||||
* }
|
||||
*/
|
||||
function weblateToCounterpart(inTrs) {
|
||||
const outTrs = {};
|
||||
|
||||
for (const key of Object.keys(inTrs)) {
|
||||
const keyParts = key.split('|', 2);
|
||||
if (keyParts.length === 2) {
|
||||
let obj = outTrs[keyParts[0]];
|
||||
if (obj === undefined) {
|
||||
obj = {};
|
||||
outTrs[keyParts[0]] = obj;
|
||||
}
|
||||
obj[keyParts[1]] = inTrs[key];
|
||||
} else {
|
||||
outTrs[key] = inTrs[key];
|
||||
}
|
||||
}
|
||||
|
||||
return outTrs;
|
||||
}
|
||||
|
||||
/*
|
||||
watch the input files for a given language,
|
||||
regenerate the file, and regenerating languages.json with the new filename
|
||||
*/
|
||||
function watchLanguage(file, dest) {
|
||||
// XXX: Use a debounce because for some reason if we read the language
|
||||
// file immediately after the FS event is received, the file contents
|
||||
// appears empty. Possibly https://github.com/nodejs/node/issues/6112
|
||||
let makeLangDebouncer;
|
||||
const makeLang = () => {
|
||||
if (makeLangDebouncer) {
|
||||
clearTimeout(makeLangDebouncer);
|
||||
}
|
||||
makeLangDebouncer = setTimeout(() => {
|
||||
genLangFile(file, dest);
|
||||
}, 500);
|
||||
};
|
||||
|
||||
chokidar.watch(file)
|
||||
.on('add', makeLang)
|
||||
.on('change', makeLang)
|
||||
.on('error', errCheck);
|
||||
}
|
||||
|
||||
// language resources
|
||||
const I18N_DEST = "lib/i18n/strings/";
|
||||
INCLUDE_LANGS.forEach((file) => {
|
||||
genLangFile(I18N_BASE_PATH + file, I18N_DEST);
|
||||
}, {});
|
||||
|
||||
if (watch) {
|
||||
INCLUDE_LANGS.forEach(file => watchLanguage(I18N_BASE_PATH + file, I18N_DEST));
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -41,8 +41,8 @@ async function copy(hakEnv, moduleInfo) {
|
||||
|
||||
if (moduleInfo.cfg.copy) {
|
||||
console.log(
|
||||
"Copying " + moduleInfo.cfg.prune + " from " +
|
||||
moduleInfo.moduleOutDir + " to " + moduleInfo.moduleOutDir,
|
||||
"Copying files from " +
|
||||
moduleInfo.moduleBuildDir + " to " + moduleInfo.moduleOutDir,
|
||||
);
|
||||
const files = await new Promise(async (resolve, reject) => {
|
||||
glob(moduleInfo.cfg.copy, {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -18,6 +18,7 @@ const path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const nodePreGypVersioning = require('node-pre-gyp/lib/util/versioning');
|
||||
const { TARGETS, getHost, isHostId } = require('./target');
|
||||
|
||||
function getElectronVersion(packageJson) {
|
||||
// should we pick the version of an installed electron
|
||||
@@ -33,7 +34,7 @@ function getRuntime(packageJson) {
|
||||
return electronVersion ? 'electron' : 'node-webkit';
|
||||
}
|
||||
|
||||
function getTarget(packageJson) {
|
||||
function getRuntimeVersion(packageJson) {
|
||||
const electronVersion = getElectronVersion(packageJson);
|
||||
if (electronVersion) {
|
||||
return electronVersion;
|
||||
@@ -42,30 +43,24 @@ function getTarget(packageJson) {
|
||||
}
|
||||
}
|
||||
|
||||
function detectArch() {
|
||||
if (process.platform === 'win32') {
|
||||
// vcvarsall.bat (the script that sets up the environment for
|
||||
// visual studio build tools) sets an env var to tell us what
|
||||
// architecture the active build tools target, so we auto-detect
|
||||
// this.
|
||||
const targetArch = process.env.VSCMD_ARG_TGT_ARCH;
|
||||
if (targetArch === 'x86') {
|
||||
return 'ia32';
|
||||
} else if (targetArch === 'x64') {
|
||||
return 'x64';
|
||||
}
|
||||
}
|
||||
return process.arch;
|
||||
}
|
||||
|
||||
module.exports = class HakEnv {
|
||||
constructor(prefix, packageJson) {
|
||||
constructor(prefix, packageJson, targetId) {
|
||||
let target;
|
||||
if (targetId) {
|
||||
target = TARGETS[targetId];
|
||||
} else {
|
||||
target = getHost();
|
||||
}
|
||||
|
||||
if (!target) {
|
||||
throw new Error(`Unknown target ${targetId}!`);
|
||||
}
|
||||
|
||||
Object.assign(this, {
|
||||
// what we're targeting
|
||||
runtime: getRuntime(packageJson),
|
||||
target: getTarget(packageJson),
|
||||
platform: process.platform,
|
||||
arch: detectArch(),
|
||||
runtimeVersion: getRuntimeVersion(packageJson),
|
||||
target,
|
||||
|
||||
// paths
|
||||
projectRoot: prefix,
|
||||
@@ -76,34 +71,46 @@ module.exports = class HakEnv {
|
||||
getRuntimeAbi() {
|
||||
return nodePreGypVersioning.get_runtime_abi(
|
||||
this.runtime,
|
||||
this.target,
|
||||
this.runtimeVersion,
|
||||
);
|
||||
}
|
||||
|
||||
// {node_abi}-{platform}-{arch}
|
||||
getNodeTriple() {
|
||||
return this.getRuntimeAbi() + '-' + this.platform + '-' + this.arch;
|
||||
return this.getRuntimeAbi() + '-' + this.target.platform + '-' + this.target.arch;
|
||||
}
|
||||
|
||||
getTargetId() {
|
||||
return this.target.id;
|
||||
}
|
||||
|
||||
isWin() {
|
||||
return this.platform === 'win32';
|
||||
return this.target.platform === 'win32';
|
||||
}
|
||||
|
||||
isMac() {
|
||||
return this.platform === 'darwin';
|
||||
return this.target.platform === 'darwin';
|
||||
}
|
||||
|
||||
isLinux() {
|
||||
return this.platform === 'linux';
|
||||
return this.target.platform === 'linux';
|
||||
}
|
||||
|
||||
getTargetArch() {
|
||||
return this.target.arch;
|
||||
}
|
||||
|
||||
isHost() {
|
||||
return isHostId(this.target.id);
|
||||
}
|
||||
|
||||
makeGypEnv() {
|
||||
return Object.assign({}, process.env, {
|
||||
npm_config_target: this.target,
|
||||
npm_config_arch: this.arch,
|
||||
npm_config_target_arch: this.arch,
|
||||
npm_config_arch: this.target.arch,
|
||||
npm_config_target_arch: this.target.arch,
|
||||
npm_config_disturl: 'https://atom.io/download/electron',
|
||||
npm_config_runtime: this.runtime,
|
||||
npm_config_target: this.runtimeVersion,
|
||||
npm_config_build_from_source: true,
|
||||
npm_config_devdir: path.join(os.homedir(), ".electron-gyp"),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
@@ -53,7 +53,19 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const hakEnv = new HakEnv(prefix, packageJson);
|
||||
// Apply `--target <target>` option if specified
|
||||
const targetIndex = process.argv.indexOf('--target');
|
||||
let targetId;
|
||||
if (targetIndex >= 0) {
|
||||
if ((targetIndex + 1) >= process.argv.length) {
|
||||
console.error("--target option specified without a target");
|
||||
process.exit(1);
|
||||
}
|
||||
// Extract target ID and remove from args
|
||||
targetId = process.argv.splice(targetIndex, 2)[1];
|
||||
}
|
||||
|
||||
const hakEnv = new HakEnv(prefix, packageJson, targetId);
|
||||
|
||||
const deps = {};
|
||||
|
||||
@@ -133,4 +145,7 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(() => process.exit(1));
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
82
scripts/hak/target.js
Normal file
82
scripts/hak/target.js
Normal file
@@ -0,0 +1,82 @@
|
||||
"use strict";
|
||||
/*
|
||||
Copyright 2021 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* THIS FILE IS GENERATED, NOT MEANT FOR EDITING DIRECTLY
|
||||
* The original source is `target.ts` in the `element-builder` repo. You can
|
||||
* edit it over there, run `yarn build`, and paste the changes here. It is
|
||||
* currently assumed this file will rarely change, so a spearate package is not
|
||||
* yet warranted.
|
||||
*/
|
||||
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isHost = exports.isHostId = exports.getHost = exports.ENABLED_TARGETS = exports.TARGETS = void 0;
|
||||
const aarch64AppleDarwin = {
|
||||
id: 'aarch64-apple-darwin',
|
||||
platform: 'darwin',
|
||||
arch: 'arm64',
|
||||
};
|
||||
const i686PcWindowsMsvc = {
|
||||
id: 'i686-pc-windows-msvc',
|
||||
platform: 'win32',
|
||||
arch: 'ia32',
|
||||
vcVarsArch: 'x86',
|
||||
};
|
||||
const x8664PcWindowsMsvc = {
|
||||
id: 'x86_64-pc-windows-msvc',
|
||||
platform: 'win32',
|
||||
arch: 'x64',
|
||||
vcVarsArch: 'amd64',
|
||||
};
|
||||
const x8664AppleDarwin = {
|
||||
id: 'x86_64-apple-darwin',
|
||||
platform: 'darwin',
|
||||
arch: 'x64',
|
||||
};
|
||||
const x8664UnknownLinuxGnu = {
|
||||
id: 'x86_64-unknown-linux-gnu',
|
||||
platform: 'linux',
|
||||
arch: 'x64',
|
||||
};
|
||||
exports.TARGETS = {
|
||||
'aarch64-apple-darwin': aarch64AppleDarwin,
|
||||
'i686-pc-windows-msvc': i686PcWindowsMsvc,
|
||||
'x86_64-pc-windows-msvc': x8664PcWindowsMsvc,
|
||||
'x86_64-apple-darwin': x8664AppleDarwin,
|
||||
'x86_64-unknown-linux-gnu': x8664UnknownLinuxGnu,
|
||||
};
|
||||
// The set of targets we build by default, sorted by increasing complexity so
|
||||
// that we fail fast when the native host target fails.
|
||||
exports.ENABLED_TARGETS = [
|
||||
exports.TARGETS['x86_64-apple-darwin'],
|
||||
exports.TARGETS['aarch64-apple-darwin'],
|
||||
exports.TARGETS['x86_64-unknown-linux-gnu'],
|
||||
exports.TARGETS['i686-pc-windows-msvc'],
|
||||
];
|
||||
function getHost() {
|
||||
return Object.values(exports.TARGETS).find(target => (target.platform === process.platform &&
|
||||
target.arch === process.arch));
|
||||
}
|
||||
exports.getHost = getHost;
|
||||
function isHostId(id) {
|
||||
return getHost()?.id === id;
|
||||
}
|
||||
exports.isHostId = isHostId;
|
||||
function isHost(target) {
|
||||
return getHost()?.id === target.id;
|
||||
}
|
||||
exports.isHost = isHost;
|
||||
26
src/@types/global.d.ts
vendored
Normal file
26
src/@types/global.d.ts
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
Copyright 2021 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { BrowserWindow } from "electron";
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
mainWindow: BrowserWindow;
|
||||
appQuitting: boolean;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,34 +20,33 @@ limitations under the License.
|
||||
// Squirrel on windows starts the app with various flags
|
||||
// as hooks to tell us when we've been installed/uninstalled
|
||||
// etc.
|
||||
const checkSquirrelHooks = require('./squirrelhooks');
|
||||
if (checkSquirrelHooks()) return;
|
||||
import { checkSquirrelHooks } from "./squirrelhooks";
|
||||
if (checkSquirrelHooks()) process.exit(1);
|
||||
|
||||
const argv = require('minimist')(process.argv, {
|
||||
import { app, ipcMain, powerSaveBlocker, BrowserWindow, Menu, autoUpdater, protocol, dialog } from "electron";
|
||||
import AutoLaunch from "auto-launch";
|
||||
import path from "path";
|
||||
import windowStateKeeper from 'electron-window-state';
|
||||
import Store from 'electron-store';
|
||||
import fs, { promises as afs } from "fs";
|
||||
import crypto from "crypto";
|
||||
import { URL } from "url";
|
||||
import minimist from "minimist";
|
||||
|
||||
import * as tray from "./tray";
|
||||
import { buildMenuTemplate } from './vectormenu';
|
||||
import webContentsHandler from './webcontents-handler';
|
||||
import * as updater from './updater';
|
||||
import { getProfileFromDeeplink, protocolInit, recordSSOSession } from './protocol';
|
||||
import { _t, AppLocalization } from './language-helper';
|
||||
|
||||
const argv = minimist(process.argv, {
|
||||
alias: { help: "h" },
|
||||
});
|
||||
|
||||
const {
|
||||
app, ipcMain, powerSaveBlocker, BrowserWindow, Menu, autoUpdater, protocol, dialog,
|
||||
} = require('electron');
|
||||
const AutoLaunch = require('auto-launch');
|
||||
const path = require('path');
|
||||
|
||||
const tray = require('./tray');
|
||||
const buildMenuTemplate = require('./vectormenu');
|
||||
const webContentsHandler = require('./webcontents-handler');
|
||||
const updater = require('./updater');
|
||||
const { getProfileFromDeeplink, protocolInit, recordSSOSession } = require('./protocol');
|
||||
|
||||
const windowStateKeeper = require('electron-window-state');
|
||||
const Store = require('electron-store');
|
||||
|
||||
const fs = require('fs');
|
||||
const afs = fs.promises;
|
||||
|
||||
const crypto = require('crypto');
|
||||
let keytar;
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
keytar = require('keytar');
|
||||
} catch (e) {
|
||||
if (e.code === "MODULE_NOT_FOUND") {
|
||||
@@ -57,14 +56,13 @@ try {
|
||||
}
|
||||
}
|
||||
|
||||
const { _t, AppLocalization } = require('./language-helper');
|
||||
|
||||
let seshatSupported = false;
|
||||
let Seshat;
|
||||
let SeshatRecovery;
|
||||
let ReindexError;
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const seshatModule = require('matrix-seshat');
|
||||
Seshat = seshatModule.Seshat;
|
||||
SeshatRecovery = seshatModule.SeshatRecovery;
|
||||
@@ -181,6 +179,7 @@ async function setupGlobals() {
|
||||
]);
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
vectorConfig = require(asarPath + 'config.json');
|
||||
} catch (e) {
|
||||
// it would be nice to check the error code here and bail if the config
|
||||
@@ -192,6 +191,7 @@ async function setupGlobals() {
|
||||
|
||||
try {
|
||||
// Load local config and use it to override values from the one baked with the build
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const localConfig = require(path.join(app.getPath('userData'), 'config.json'));
|
||||
|
||||
// If the local config has a homeserver defined, don't use the homeserver from the build
|
||||
@@ -207,6 +207,16 @@ async function setupGlobals() {
|
||||
|
||||
vectorConfig = Object.assign(vectorConfig, localConfig);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
dialog.showMessageBox({
|
||||
type: "error",
|
||||
title: `Your ${vectorConfig.brand || 'Element'} is misconfigured`,
|
||||
message: `Your custom ${vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
|
||||
`Please correct the problem and reopen ${vectorConfig.brand || 'Element'}.`,
|
||||
detail: e.message || "",
|
||||
});
|
||||
}
|
||||
|
||||
// Could not load local config, this is expected in most cases.
|
||||
}
|
||||
|
||||
@@ -249,7 +259,13 @@ async function moveAutoLauncher() {
|
||||
}
|
||||
|
||||
const eventStorePath = path.join(app.getPath('userData'), 'EventStore');
|
||||
const store = new Store({ name: "electron-config" });
|
||||
const store = new Store<{
|
||||
warnBeforeExit?: boolean;
|
||||
minimizeToTray?: boolean;
|
||||
spellCheckerEnabled?: boolean;
|
||||
autoHideMenuBar?: boolean;
|
||||
locale?: string | string[];
|
||||
}>({ name: "electron-config" });
|
||||
|
||||
let eventIndex = null;
|
||||
|
||||
@@ -264,7 +280,8 @@ const exitShortcuts = [
|
||||
|
||||
const warnBeforeExit = (event, input) => {
|
||||
const shouldWarnBeforeExit = store.get('warnBeforeExit', true);
|
||||
const exitShortcutPressed = exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform));
|
||||
const exitShortcutPressed =
|
||||
input.type === 'keyDown' && exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform));
|
||||
|
||||
if (shouldWarnBeforeExit && exitShortcutPressed) {
|
||||
const shouldCancelCloseRequest = dialog.showMessageBoxSync(mainWindow, {
|
||||
@@ -821,6 +838,7 @@ app.on('ready', async () => {
|
||||
|
||||
if (argv['devtools']) {
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const { default: installExt, REACT_DEVELOPER_TOOLS, REACT_PERF } = require('electron-devtools-installer');
|
||||
installExt(REACT_DEVELOPER_TOOLS)
|
||||
.then((name) => console.log(`Added Extension: ${name}`))
|
||||
@@ -1005,7 +1023,7 @@ function beforeQuit() {
|
||||
}
|
||||
|
||||
app.on('before-quit', beforeQuit);
|
||||
app.on('before-quit-for-update', beforeQuit);
|
||||
autoUpdater.on('before-quit-for-update', beforeQuit);
|
||||
|
||||
app.on('second-instance', (ev, commandLine, workingDirectory) => {
|
||||
// If other instance launched with --hidden then skip showing window
|
||||
13
src/i18n/strings/hr.json
Normal file
13
src/i18n/strings/hr.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"Paste": "Zalijepiti",
|
||||
"Copy": "Kopirati",
|
||||
"Cut": "Izrezati",
|
||||
"Redo": "Preurediti",
|
||||
"Undo": "Poništi",
|
||||
"Edit": "Uredi",
|
||||
"Quit": "Prestati",
|
||||
"Show/Hide": "Pokaži/sakrij",
|
||||
"Are you sure you want to quit?": "Jesi li siguran da želiš odustati?",
|
||||
"Close Element": "Zatvori Element",
|
||||
"Cancel": "Otkazati"
|
||||
}
|
||||
@@ -26,8 +26,8 @@
|
||||
"Zoom Out": "Dar Zoom Out",
|
||||
"Zoom In": "Dar Zoom In",
|
||||
"Actual Size": "Tamanho de Verdade",
|
||||
"View": "Ver",
|
||||
"Select All": "Selecionar Tudo",
|
||||
"View": "Visualizar",
|
||||
"Select All": "Selecionar Todas",
|
||||
"Delete": "Deletar",
|
||||
"Paste and Match Style": "Colar e Adequar Estilo",
|
||||
"Paste": "Colar",
|
||||
@@ -38,7 +38,9 @@
|
||||
"Edit": "Editar",
|
||||
"Quit": "Sair",
|
||||
"Show/Hide": "Mostrar/Esconder",
|
||||
"Are you sure you want to quit?": "Você tem certeza que quer sair?",
|
||||
"Are you sure you want to quit?": "Você tem certeza que você quer sair?",
|
||||
"Close Element": "Fechar Element",
|
||||
"Cancel": "Cancelar"
|
||||
"Cancel": "Cancelar",
|
||||
"Bring All to Front": "Trazer Todas Para Frente",
|
||||
"Hide Others": "Esconder Outras(os)"
|
||||
}
|
||||
|
||||
46
src/i18n/strings/ta.json
Normal file
46
src/i18n/strings/ta.json
Normal file
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"Zoom": "பெரிதாக்குதல்",
|
||||
"Minimize": "சிறிதாக்கு",
|
||||
"Toggle Developer Tools": "படைப்பாளர் கருவிகளை நிலைமாற்று",
|
||||
"Toggle Full Screen": "முழு திரையை நிலைமாற்று",
|
||||
"Paste and Match Style": "ஒட்டு மற்றும் நடையை பொறுத்து",
|
||||
"Add to dictionary": "அகராதியில் சேர்",
|
||||
"The image failed to save": "படம் சேமிக்கத் தவறிவிட்டது",
|
||||
"Failed to save image": "படத்தைச் சேமிப்பதில் தோல்வி",
|
||||
"Save image as...": "படத்தை இவ்வாறு சேமி...",
|
||||
"Copy link address": "இணைப்பு முகவரியை நகலெடு",
|
||||
"Copy email address": "மின்னஞ்சல் முகவரியை நகலெடு",
|
||||
"Copy image": "படத்தை நகலெடு",
|
||||
"File": "கோப்பு",
|
||||
"Bring All to Front": "அனைத்தையும் முன்னால் கொண்டுவா",
|
||||
"Stop Speaking": "பேசுவதை நிறுத்து",
|
||||
"Start Speaking": "பேசத் துவங்கு",
|
||||
"Speech": "பேச்சு",
|
||||
"Unhide": "காட்டு",
|
||||
"Hide Others": "மற்றதை மறை",
|
||||
"Hide": "மறை",
|
||||
"Services": "சேவைகள்",
|
||||
"About": "இதனைப் பற்றி",
|
||||
"Element Help": "எலிமெண்ட் உதவி",
|
||||
"Help": "உதவி",
|
||||
"Close": "மூடு",
|
||||
"Window": "சாளரம்",
|
||||
"Preferences": "விருப்பத்தேர்வுகள்",
|
||||
"Zoom Out": "சிறிதாக்கு",
|
||||
"Zoom In": "பெரிதாக்கு",
|
||||
"Actual Size": "உண்மையான அளவு",
|
||||
"View": "காட்சி",
|
||||
"Select All": "அனைத்தையும் தெரிவுசெய்",
|
||||
"Delete": "அழி",
|
||||
"Paste": "ஒட்டு",
|
||||
"Copy": "நகலெடு",
|
||||
"Cut": "வெட்டு",
|
||||
"Redo": "மீண்டும் செய்",
|
||||
"Undo": "செயல்தவிர்",
|
||||
"Edit": "திருத்து",
|
||||
"Quit": "வெளியேறு",
|
||||
"Show/Hide": "காட்டு/மறை",
|
||||
"Are you sure you want to quit?": "நீங்கள் நிச்சயம் வெளியேற விரும்புகிறீர்களா?",
|
||||
"Close Element": "எலிமெண்ட் ஐ மூடு",
|
||||
"Cancel": "ரத்துசெய்"
|
||||
}
|
||||
@@ -14,15 +14,23 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const counterpart = require('counterpart');
|
||||
import counterpart from "counterpart";
|
||||
import type Store from 'electron-store';
|
||||
|
||||
const DEFAULT_LOCALE = "en";
|
||||
|
||||
function _td(text) {
|
||||
export function _td(text: string): string {
|
||||
return text;
|
||||
}
|
||||
|
||||
function _t(text, variables = {}) {
|
||||
type SubstitutionValue = number | string;
|
||||
|
||||
interface IVariables {
|
||||
[key: string]: SubstitutionValue;
|
||||
count?: number;
|
||||
}
|
||||
|
||||
export function _t(text: string, variables: IVariables = {}): string {
|
||||
const args = Object.assign({ interpolate: false }, variables);
|
||||
|
||||
const { count } = args;
|
||||
@@ -55,11 +63,17 @@ function _t(text, variables = {}) {
|
||||
return translated;
|
||||
}
|
||||
|
||||
class AppLocalization {
|
||||
constructor({ store, components = [] }) {
|
||||
// TODO: Should be static field, but that doesn't parse without Babel
|
||||
this.STORE_KEY = "locale";
|
||||
type Component = () => void;
|
||||
|
||||
type TypedStore = Store<{ locale?: string | string[] }>;
|
||||
|
||||
export class AppLocalization {
|
||||
private static readonly STORE_KEY = "locale";
|
||||
|
||||
private readonly store: TypedStore;
|
||||
private readonly localizedComponents: Set<Component>;
|
||||
|
||||
constructor({ store, components = [] }: { store: TypedStore, components: Component[] }) {
|
||||
counterpart.registerTranslations("en", this.fetchTranslationJson("en_EN"));
|
||||
counterpart.setFallbackLocale('en');
|
||||
counterpart.setSeparator('|');
|
||||
@@ -69,15 +83,15 @@ class AppLocalization {
|
||||
}
|
||||
|
||||
this.store = store;
|
||||
if (this.store.has(this.STORE_KEY)) {
|
||||
const locales = this.store.get(this.STORE_KEY);
|
||||
if (this.store.has(AppLocalization.STORE_KEY)) {
|
||||
const locales = this.store.get(AppLocalization.STORE_KEY);
|
||||
this.setAppLocale(locales);
|
||||
}
|
||||
|
||||
this.resetLocalizedUI();
|
||||
}
|
||||
|
||||
fetchTranslationJson(locale) {
|
||||
public fetchTranslationJson(locale: string): Record<string, string> {
|
||||
try {
|
||||
console.log("Fetching translation json for locale: " + locale);
|
||||
return require(`./i18n/strings/${locale}.json`);
|
||||
@@ -87,11 +101,7 @@ class AppLocalization {
|
||||
}
|
||||
}
|
||||
|
||||
get languageTranslationJson() {
|
||||
return this.translationJsonMap.get(this.language);
|
||||
}
|
||||
|
||||
setAppLocale(locales) {
|
||||
public setAppLocale(locales: string | string[]): void {
|
||||
console.log(`Changing application language to ${locales}`);
|
||||
|
||||
if (!Array.isArray(locales)) {
|
||||
@@ -105,13 +115,15 @@ class AppLocalization {
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore - this looks like a bug but is out of scope for this conversion
|
||||
counterpart.setLocale(locales);
|
||||
this.store.set(this.STORE_KEY, locales);
|
||||
this.store.set(AppLocalization.STORE_KEY, locales);
|
||||
|
||||
this.resetLocalizedUI();
|
||||
}
|
||||
|
||||
resetLocalizedUI() {
|
||||
public resetLocalizedUI(): void {
|
||||
console.log("Resetting the UI components after locale change");
|
||||
this.localizedComponents.forEach(componentSetup => {
|
||||
if (typeof componentSetup === "function") {
|
||||
@@ -120,9 +132,3 @@ class AppLocalization {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
AppLocalization,
|
||||
_t,
|
||||
_td,
|
||||
};
|
||||
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { ipcRenderer, desktopCapturer, contextBridge } = require('electron');
|
||||
import { ipcRenderer, desktopCapturer, contextBridge, IpcRendererEvent, SourcesOptions } from 'electron';
|
||||
|
||||
// Expose only expected IPC wrapper APIs to the renderer process to avoid
|
||||
// handing out generalised messaging access.
|
||||
@@ -36,26 +36,32 @@ const CHANNELS = [
|
||||
"userDownloadOpen",
|
||||
];
|
||||
|
||||
interface ISource {
|
||||
id: string;
|
||||
name: string;
|
||||
thumbnailURL: string;
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
"electron",
|
||||
{
|
||||
on(channel, listener) {
|
||||
on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): void {
|
||||
if (!CHANNELS.includes(channel)) {
|
||||
console.error(`Unknown IPC channel ${channel} ignored`);
|
||||
return;
|
||||
}
|
||||
ipcRenderer.on(channel, listener);
|
||||
},
|
||||
send(channel, ...args) {
|
||||
send(channel: string, ...args: any[]): void {
|
||||
if (!CHANNELS.includes(channel)) {
|
||||
console.error(`Unknown IPC channel ${channel} ignored`);
|
||||
return;
|
||||
}
|
||||
ipcRenderer.send(channel, ...args);
|
||||
},
|
||||
async getDesktopCapturerSources(options) {
|
||||
async getDesktopCapturerSources(options: SourcesOptions): Promise<ISource[]> {
|
||||
const sources = await desktopCapturer.getSources(options);
|
||||
const desktopCapturerSources = [];
|
||||
const desktopCapturerSources: ISource[] = [];
|
||||
|
||||
for (const source of sources) {
|
||||
desktopCapturerSources.push({
|
||||
102
src/protocol.js
102
src/protocol.js
@@ -1,102 +0,0 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { app } = require("electron");
|
||||
const path = require("path");
|
||||
const fs = require("fs");
|
||||
|
||||
const PROTOCOL = "element://";
|
||||
const SEARCH_PARAM = "element-desktop-ssoid";
|
||||
const STORE_FILE_NAME = "sso-sessions.json";
|
||||
|
||||
// we getPath userData before electron-main changes it, so this is the default value
|
||||
const storePath = path.join(app.getPath("userData"), STORE_FILE_NAME);
|
||||
|
||||
const processUrl = (url) => {
|
||||
if (!global.mainWindow) return;
|
||||
console.log("Handling link: ", url);
|
||||
global.mainWindow.loadURL(url.replace(PROTOCOL, "vector://"));
|
||||
};
|
||||
|
||||
const readStore = () => {
|
||||
try {
|
||||
const s = fs.readFileSync(storePath, { encoding: "utf8" });
|
||||
const o = JSON.parse(s);
|
||||
return typeof o === "object" ? o : {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const writeStore = (data) => {
|
||||
fs.writeFileSync(storePath, JSON.stringify(data));
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
recordSSOSession: (sessionID) => {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const store = readStore();
|
||||
for (const key in store) {
|
||||
// ensure each instance only has one (the latest) session ID to prevent the file growing unbounded
|
||||
if (store[key] === userDataPath) {
|
||||
delete store[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
store[sessionID] = userDataPath;
|
||||
writeStore(store);
|
||||
},
|
||||
getProfileFromDeeplink: (args) => {
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
const deeplinkUrl = args.find(arg => arg.startsWith('element://'));
|
||||
if (deeplinkUrl && deeplinkUrl.includes(SEARCH_PARAM)) {
|
||||
const parsedUrl = new URL(deeplinkUrl);
|
||||
if (parsedUrl.protocol === 'element:') {
|
||||
const ssoID = parsedUrl.searchParams.get(SEARCH_PARAM);
|
||||
const store = readStore();
|
||||
console.log("Forwarding to profile: ", store[ssoID]);
|
||||
return store[ssoID];
|
||||
}
|
||||
}
|
||||
},
|
||||
protocolInit: () => {
|
||||
// get all args except `hidden` as it'd mean the app would not get focused
|
||||
// XXX: passing args to protocol handlers only works on Windows, so unpackaged deep-linking
|
||||
// --profile/--profile-dir are passed via the SEARCH_PARAM var in the callback url
|
||||
const args = process.argv.slice(1).filter(arg => arg !== "--hidden" && arg !== "-hidden");
|
||||
if (app.isPackaged) {
|
||||
app.setAsDefaultProtocolClient('element', process.execPath, args);
|
||||
} else if (process.platform === 'win32') { // on Mac/Linux this would just cause the electron binary to open
|
||||
// special handler for running without being packaged, e.g `electron .` by passing our app path to electron
|
||||
app.setAsDefaultProtocolClient('element', process.execPath, [app.getAppPath(), ...args]);
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
// Protocol handler for macos
|
||||
app.on('open-url', function(ev, url) {
|
||||
ev.preventDefault();
|
||||
processUrl(url);
|
||||
});
|
||||
} else {
|
||||
// Protocol handler for win32/Linux
|
||||
app.on('second-instance', (ev, commandLine) => {
|
||||
const url = commandLine[commandLine.length - 1];
|
||||
if (!url.startsWith(PROTOCOL)) return;
|
||||
processUrl(url);
|
||||
});
|
||||
}
|
||||
},
|
||||
};
|
||||
103
src/protocol.ts
Normal file
103
src/protocol.ts
Normal file
@@ -0,0 +1,103 @@
|
||||
/*
|
||||
Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { app } from "electron";
|
||||
import { URL } from "url";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
|
||||
const PROTOCOL = "element://";
|
||||
const SEARCH_PARAM = "element-desktop-ssoid";
|
||||
const STORE_FILE_NAME = "sso-sessions.json";
|
||||
|
||||
// we getPath userData before electron-main changes it, so this is the default value
|
||||
const storePath = path.join(app.getPath("userData"), STORE_FILE_NAME);
|
||||
|
||||
function processUrl(url: string): void {
|
||||
if (!global.mainWindow) return;
|
||||
console.log("Handling link: ", url);
|
||||
global.mainWindow.loadURL(url.replace(PROTOCOL, "vector://"));
|
||||
}
|
||||
|
||||
function readStore(): object {
|
||||
try {
|
||||
const s = fs.readFileSync(storePath, { encoding: "utf8" });
|
||||
const o = JSON.parse(s);
|
||||
return typeof o === "object" ? o : {};
|
||||
} catch (e) {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function writeStore(data: object): void {
|
||||
fs.writeFileSync(storePath, JSON.stringify(data));
|
||||
}
|
||||
|
||||
export function recordSSOSession(sessionID: string): void {
|
||||
const userDataPath = app.getPath('userData');
|
||||
const store = readStore();
|
||||
for (const key in store) {
|
||||
// ensure each instance only has one (the latest) session ID to prevent the file growing unbounded
|
||||
if (store[key] === userDataPath) {
|
||||
delete store[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
store[sessionID] = userDataPath;
|
||||
writeStore(store);
|
||||
}
|
||||
|
||||
export function getProfileFromDeeplink(args): string | undefined {
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
const deeplinkUrl = args.find(arg => arg.startsWith('element://'));
|
||||
if (deeplinkUrl && deeplinkUrl.includes(SEARCH_PARAM)) {
|
||||
const parsedUrl = new URL(deeplinkUrl);
|
||||
if (parsedUrl.protocol === 'element:') {
|
||||
const ssoID = parsedUrl.searchParams.get(SEARCH_PARAM);
|
||||
const store = readStore();
|
||||
console.log("Forwarding to profile: ", store[ssoID]);
|
||||
return store[ssoID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function protocolInit(): void {
|
||||
// get all args except `hidden` as it'd mean the app would not get focused
|
||||
// XXX: passing args to protocol handlers only works on Windows, so unpackaged deep-linking
|
||||
// --profile/--profile-dir are passed via the SEARCH_PARAM var in the callback url
|
||||
const args = process.argv.slice(1).filter(arg => arg !== "--hidden" && arg !== "-hidden");
|
||||
if (app.isPackaged) {
|
||||
app.setAsDefaultProtocolClient('element', process.execPath, args);
|
||||
} else if (process.platform === 'win32') { // on Mac/Linux this would just cause the electron binary to open
|
||||
// special handler for running without being packaged, e.g `electron .` by passing our app path to electron
|
||||
app.setAsDefaultProtocolClient('element', process.execPath, [app.getAppPath(), ...args]);
|
||||
}
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
// Protocol handler for macos
|
||||
app.on('open-url', function(ev, url) {
|
||||
ev.preventDefault();
|
||||
processUrl(url);
|
||||
});
|
||||
} else {
|
||||
// Protocol handler for win32/Linux
|
||||
app.on('second-instance', (ev, commandLine) => {
|
||||
const url = commandLine[commandLine.length - 1];
|
||||
if (!url.startsWith(PROTOCOL)) return;
|
||||
processUrl(url);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -14,12 +14,12 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const path = require('path');
|
||||
const spawn = require('child_process').spawn;
|
||||
const { app } = require('electron');
|
||||
const fsProm = require('fs').promises;
|
||||
import path from "path";
|
||||
import { spawn } from "child_process";
|
||||
import { app } from "electron";
|
||||
import { promises as fsProm } from "fs";
|
||||
|
||||
function runUpdateExe(args) {
|
||||
function runUpdateExe(args: string[]): Promise<void> {
|
||||
// Invokes Squirrel's Update.exe which will do things for us like create shortcuts
|
||||
// Note that there's an Update.exe in the app-x.x.x directory and one in the parent
|
||||
// directory: we need to run the one in the parent directory, because it discovers
|
||||
@@ -33,7 +33,7 @@ function runUpdateExe(args) {
|
||||
});
|
||||
}
|
||||
|
||||
function checkSquirrelHooks() {
|
||||
export function checkSquirrelHooks(): boolean {
|
||||
if (process.platform !== 'win32') return false;
|
||||
|
||||
const cmd = process.argv[1];
|
||||
@@ -82,5 +82,3 @@ function checkSquirrelHooks() {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
module.exports = checkSquirrelHooks;
|
||||
@@ -15,26 +15,26 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { app, Tray, Menu, nativeImage } = require('electron');
|
||||
const pngToIco = require('png-to-ico');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const { _t } = require('./language-helper');
|
||||
import { app, Tray, Menu, nativeImage } from "electron";
|
||||
import pngToIco from "png-to-ico";
|
||||
import path from "path";
|
||||
import fs from "fs";
|
||||
import { _t } from "./language-helper";
|
||||
|
||||
let trayIcon = null;
|
||||
let trayIcon: Tray = null;
|
||||
|
||||
exports.hasTray = function hasTray() {
|
||||
export function hasTray(): boolean {
|
||||
return (trayIcon !== null);
|
||||
};
|
||||
}
|
||||
|
||||
exports.destroy = function() {
|
||||
export function destroy(): void {
|
||||
if (trayIcon) {
|
||||
trayIcon.destroy();
|
||||
trayIcon = null;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const toggleWin = function() {
|
||||
function toggleWin(): void {
|
||||
if (global.mainWindow.isVisible() && !global.mainWindow.isMinimized()) {
|
||||
global.mainWindow.hide();
|
||||
} else {
|
||||
@@ -42,9 +42,14 @@ const toggleWin = function() {
|
||||
if (!global.mainWindow.isVisible()) global.mainWindow.show();
|
||||
global.mainWindow.focus();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
exports.create = function(config) {
|
||||
interface IConfig {
|
||||
icon_path: string; // eslint-disable-line camelcase
|
||||
brand: string;
|
||||
}
|
||||
|
||||
export function create(config: IConfig): void {
|
||||
// no trays on darwin
|
||||
if (process.platform === 'darwin' || trayIcon) return;
|
||||
const defaultIcon = nativeImage.createFromPath(config.icon_path);
|
||||
@@ -89,9 +94,9 @@ exports.create = function(config) {
|
||||
global.mainWindow.webContents.on('page-title-updated', function(ev, title) {
|
||||
trayIcon.setToolTip(title);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function initApplicationMenu() {
|
||||
export function initApplicationMenu(): void {
|
||||
if (!trayIcon) {
|
||||
return;
|
||||
}
|
||||
@@ -112,5 +117,3 @@ function initApplicationMenu() {
|
||||
|
||||
trayIcon.setContextMenu(contextMenu);
|
||||
}
|
||||
|
||||
exports.initApplicationMenu = initApplicationMenu;
|
||||
@@ -1,16 +1,32 @@
|
||||
const { app, autoUpdater, ipcMain } = require('electron');
|
||||
/*
|
||||
Copyright 2016-2021 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { app, autoUpdater, ipcMain } from "electron";
|
||||
|
||||
const UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000;
|
||||
const INITIAL_UPDATE_DELAY_MS = 30 * 1000;
|
||||
|
||||
function installUpdate() {
|
||||
function installUpdate(): void {
|
||||
// for some reason, quitAndInstall does not fire the
|
||||
// before-quit event, so we need to set the flag here.
|
||||
global.appQuitting = true;
|
||||
autoUpdater.quitAndInstall();
|
||||
}
|
||||
|
||||
function pollForUpdates() {
|
||||
function pollForUpdates(): void {
|
||||
try {
|
||||
autoUpdater.checkForUpdates();
|
||||
} catch (e) {
|
||||
@@ -18,8 +34,7 @@ function pollForUpdates() {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {};
|
||||
module.exports.start = function startAutoUpdate(updateBaseUrl) {
|
||||
export function start(updateBaseUrl: string): void {
|
||||
if (updateBaseUrl.slice(-1) !== '/') {
|
||||
updateBaseUrl = updateBaseUrl + '/';
|
||||
}
|
||||
@@ -37,7 +52,7 @@ module.exports.start = function startAutoUpdate(updateBaseUrl) {
|
||||
// rely on NSURLConnection setting the User-Agent to what we expect,
|
||||
// and also acts as a convenient cache-buster to ensure that when the
|
||||
// app updates it always gets a fresh value to avoid update-looping.
|
||||
url = `${updateBaseUrl}macos/?localVersion=${encodeURIComponent(app.getVersion())}`;
|
||||
url = `${updateBaseUrl}macos/${process.arch}/?localVersion=${encodeURIComponent(app.getVersion())}`;
|
||||
} else if (process.platform === 'win32') {
|
||||
url = `${updateBaseUrl}win32/${process.arch}/`;
|
||||
} else {
|
||||
@@ -48,6 +63,7 @@ module.exports.start = function startAutoUpdate(updateBaseUrl) {
|
||||
}
|
||||
|
||||
if (url) {
|
||||
console.log(`Update URL: ${url}`);
|
||||
autoUpdater.setFeedURL(url);
|
||||
// We check for updates ourselves rather than using 'updater' because we need to
|
||||
// do it in the main process (and we don't really need to check every 10 minutes:
|
||||
@@ -63,18 +79,25 @@ module.exports.start = function startAutoUpdate(updateBaseUrl) {
|
||||
// will fail if running in debug mode
|
||||
console.log('Couldn\'t enable update checking', err);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
ipcMain.on('install_update', installUpdate);
|
||||
ipcMain.on('check_updates', pollForUpdates);
|
||||
|
||||
function ipcChannelSendUpdateStatus(status) {
|
||||
function ipcChannelSendUpdateStatus(status: boolean | string): void {
|
||||
if (!global.mainWindow) return;
|
||||
global.mainWindow.webContents.send('check_updates', status);
|
||||
}
|
||||
|
||||
interface ICachedUpdate {
|
||||
releaseNotes: string;
|
||||
releaseName: string;
|
||||
releaseDate: Date;
|
||||
updateURL: string;
|
||||
}
|
||||
|
||||
// cache the latest update which has been downloaded as electron offers no api to read it
|
||||
let latestUpdateDownloaded;
|
||||
let latestUpdateDownloaded: ICachedUpdate;
|
||||
autoUpdater.on('update-available', function() {
|
||||
ipcChannelSendUpdateStatus(true);
|
||||
}).on('update-not-available', function() {
|
||||
@@ -14,12 +14,14 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
const { app, shell, Menu } = require('electron');
|
||||
const { _t } = require('./language-helper');
|
||||
import { app, shell, Menu, MenuItem, MenuItemConstructorOptions } from 'electron';
|
||||
import { _t } from './language-helper';
|
||||
|
||||
function buildMenuTemplate() {
|
||||
const isMac = process.platform === 'darwin';
|
||||
|
||||
export function buildMenuTemplate(): Menu {
|
||||
// Menu template from http://electron.atom.io/docs/api/menu/, edited
|
||||
const template = [
|
||||
const template: Array<(MenuItemConstructorOptions) | (MenuItem)> = [
|
||||
{
|
||||
label: _t('Edit'),
|
||||
accelerator: 'e',
|
||||
@@ -46,7 +48,7 @@ function buildMenuTemplate() {
|
||||
label: _t('Paste'),
|
||||
},
|
||||
{
|
||||
role: 'pasteandmatchstyle',
|
||||
role: 'pasteAndMatchStyle',
|
||||
label: _t('Paste and Match Style'),
|
||||
},
|
||||
{
|
||||
@@ -54,7 +56,7 @@ function buildMenuTemplate() {
|
||||
label: _t('Delete'),
|
||||
},
|
||||
{
|
||||
role: 'selectall',
|
||||
role: 'selectAll',
|
||||
label: _t('Select All'),
|
||||
},
|
||||
],
|
||||
@@ -65,30 +67,44 @@ function buildMenuTemplate() {
|
||||
submenu: [
|
||||
{ type: 'separator' },
|
||||
{
|
||||
role: 'resetzoom',
|
||||
role: 'resetZoom',
|
||||
accelerator: 'CmdOrCtrl+Num0',
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
role: 'zoomIn',
|
||||
accelerator: 'CmdOrCtrl+NumAdd',
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
role: 'zoomOut',
|
||||
accelerator: 'CmdOrCtrl+NumSub',
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
role: 'resetZoom',
|
||||
label: _t('Actual Size'),
|
||||
},
|
||||
{
|
||||
role: 'zoomin',
|
||||
accelerator: 'CommandOrControl+=',
|
||||
role: 'zoomIn',
|
||||
label: _t('Zoom In'),
|
||||
},
|
||||
{
|
||||
role: 'zoomout',
|
||||
role: 'zoomOut',
|
||||
label: _t('Zoom Out'),
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
// in macOS the Preferences menu item goes in the first menu
|
||||
...(!isMac ? [{
|
||||
label: _t('Preferences'),
|
||||
accelerator: 'Command+,', // Mac-only accelerator
|
||||
click() { global.mainWindow.webContents.send('preferences'); },
|
||||
},
|
||||
}] : []),
|
||||
{
|
||||
role: 'togglefullscreen',
|
||||
label: _t('Toggle Full Screen'),
|
||||
},
|
||||
{
|
||||
role: 'toggledevtools',
|
||||
role: 'toggleDevTools',
|
||||
label: _t('Toggle Developer Tools'),
|
||||
},
|
||||
],
|
||||
@@ -122,7 +138,7 @@ function buildMenuTemplate() {
|
||||
];
|
||||
|
||||
// macOS has specific menu conventions...
|
||||
if (process.platform === 'darwin') {
|
||||
if (isMac) {
|
||||
template.unshift({
|
||||
// first macOS menu is the name of the app
|
||||
role: 'appMenu',
|
||||
@@ -130,7 +146,13 @@ function buildMenuTemplate() {
|
||||
submenu: [
|
||||
{
|
||||
role: 'about',
|
||||
label: _t('About'),
|
||||
label: _t('About') + ' ' + app.name,
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: _t('Preferences') + '…',
|
||||
accelerator: 'Command+,', // Mac-only accelerator
|
||||
click() { global.mainWindow.webContents.send('preferences'); },
|
||||
},
|
||||
{ type: 'separator' },
|
||||
{
|
||||
@@ -144,7 +166,7 @@ function buildMenuTemplate() {
|
||||
label: _t('Hide'),
|
||||
},
|
||||
{
|
||||
role: 'hideothers',
|
||||
role: 'hideOthers',
|
||||
label: _t('Hide Others'),
|
||||
},
|
||||
{
|
||||
@@ -160,17 +182,17 @@ function buildMenuTemplate() {
|
||||
});
|
||||
// Edit menu.
|
||||
// This has a 'speech' section on macOS
|
||||
template[1].submenu.push(
|
||||
(template[1].submenu as MenuItemConstructorOptions[]).push(
|
||||
{ type: 'separator' },
|
||||
{
|
||||
label: _t('Speech'),
|
||||
submenu: [
|
||||
{
|
||||
role: 'startspeaking',
|
||||
role: 'startSpeaking',
|
||||
label: _t('Start Speaking'),
|
||||
},
|
||||
{
|
||||
role: 'stopspeaking',
|
||||
role: 'stopSpeaking',
|
||||
label: _t('Stop Speaking'),
|
||||
},
|
||||
],
|
||||
@@ -221,6 +243,3 @@ function buildMenuTemplate() {
|
||||
|
||||
return Menu.buildFromTemplate(template);
|
||||
}
|
||||
|
||||
module.exports = buildMenuTemplate;
|
||||
|
||||
@@ -1,19 +1,49 @@
|
||||
const { clipboard, nativeImage, Menu, MenuItem, shell, dialog, ipcMain } = require('electron');
|
||||
const url = require('url');
|
||||
const fs = require('fs');
|
||||
const request = require('request');
|
||||
const path = require('path');
|
||||
const { _t } = require('./language-helper');
|
||||
/*
|
||||
Copyright 2021 New Vector Ltd
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
clipboard,
|
||||
nativeImage,
|
||||
Menu,
|
||||
MenuItem,
|
||||
shell,
|
||||
dialog,
|
||||
ipcMain,
|
||||
NativeImage,
|
||||
WebContents,
|
||||
ContextMenuParams,
|
||||
DownloadItem,
|
||||
MenuItemConstructorOptions,
|
||||
IpcMainEvent,
|
||||
} from 'electron';
|
||||
import url from 'url';
|
||||
import fs from 'fs';
|
||||
import request from 'request';
|
||||
import path from 'path';
|
||||
import { _t } from './language-helper';
|
||||
|
||||
const MAILTO_PREFIX = "mailto:";
|
||||
|
||||
const PERMITTED_URL_SCHEMES = [
|
||||
const PERMITTED_URL_SCHEMES: string[] = [
|
||||
'http:',
|
||||
'https:',
|
||||
MAILTO_PREFIX,
|
||||
];
|
||||
|
||||
function safeOpenURL(target) {
|
||||
function safeOpenURL(target: string): void {
|
||||
// openExternal passes the target to open/start/xdg-open,
|
||||
// so put fairly stringent limits on what can be opened
|
||||
// (for instance, open /bin/sh does indeed open a terminal
|
||||
@@ -28,7 +58,7 @@ function safeOpenURL(target) {
|
||||
}
|
||||
}
|
||||
|
||||
function onWindowOrNavigate(ev, target) {
|
||||
function onWindowOrNavigate(ev: Event, target: string): void {
|
||||
// always prevent the default: if something goes wrong,
|
||||
// we don't want to end up opening it in the electron
|
||||
// app, as we could end up opening any sort of random
|
||||
@@ -37,7 +67,7 @@ function onWindowOrNavigate(ev, target) {
|
||||
safeOpenURL(target);
|
||||
}
|
||||
|
||||
function writeNativeImage(filePath, img) {
|
||||
function writeNativeImage(filePath: string, img: NativeImage): Promise<void> {
|
||||
switch (filePath.split('.').pop().toLowerCase()) {
|
||||
case "jpg":
|
||||
case "jpeg":
|
||||
@@ -50,7 +80,7 @@ function writeNativeImage(filePath, img) {
|
||||
}
|
||||
}
|
||||
|
||||
function onLinkContextMenu(ev, params) {
|
||||
function onLinkContextMenu(ev: Event, params: ContextMenuParams, webContents: WebContents): void {
|
||||
let url = params.linkURL || params.srcURL;
|
||||
|
||||
if (url.startsWith('vector://vector/webapp')) {
|
||||
@@ -76,7 +106,7 @@ function onLinkContextMenu(ev, params) {
|
||||
label: _t('Copy image'),
|
||||
accelerator: 'c',
|
||||
click() {
|
||||
ev.sender.copyImageAt(params.x, params.y);
|
||||
webContents.copyImageAt(params.x, params.y);
|
||||
},
|
||||
}));
|
||||
}
|
||||
@@ -108,7 +138,7 @@ function onLinkContextMenu(ev, params) {
|
||||
if (params.hasImageContents && !url.startsWith('blob:')) {
|
||||
popupMenu.append(new MenuItem({
|
||||
label: _t('Save image as...'),
|
||||
accelerator: 'a',
|
||||
accelerator: 's',
|
||||
async click() {
|
||||
const targetFileName = params.titleText || "image.png";
|
||||
const { filePath } = await dialog.showSaveDialog({
|
||||
@@ -140,8 +170,8 @@ function onLinkContextMenu(ev, params) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function _CutCopyPasteSelectContextMenus(params) {
|
||||
const options = [];
|
||||
function cutCopyPasteSelectContextMenus(params: ContextMenuParams): MenuItemConstructorOptions[] {
|
||||
const options: MenuItemConstructorOptions[] = [];
|
||||
|
||||
if (params.misspelledWord) {
|
||||
params.dictionarySuggestions.forEach(word => {
|
||||
@@ -180,10 +210,10 @@ function _CutCopyPasteSelectContextMenus(params) {
|
||||
accelerator: 'p',
|
||||
enabled: params.editFlags.canPaste,
|
||||
}, {
|
||||
role: 'pasteandmatchstyle',
|
||||
role: 'pasteAndMatchStyle',
|
||||
enabled: params.editFlags.canPaste,
|
||||
}, {
|
||||
role: 'selectall',
|
||||
role: 'selectAll',
|
||||
label: _t("Select All"),
|
||||
accelerator: 'a',
|
||||
enabled: params.editFlags.canSelectAll,
|
||||
@@ -192,7 +222,7 @@ function _CutCopyPasteSelectContextMenus(params) {
|
||||
}
|
||||
|
||||
function onSelectedContextMenu(ev, params) {
|
||||
const items = _CutCopyPasteSelectContextMenus(params);
|
||||
const items = cutCopyPasteSelectContextMenus(params);
|
||||
const popupMenu = Menu.buildFromTemplate(items);
|
||||
|
||||
// popup() requires an options object even for no options
|
||||
@@ -200,12 +230,13 @@ function onSelectedContextMenu(ev, params) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
function onEditableContextMenu(ev, params) {
|
||||
const items = [
|
||||
function onEditableContextMenu(ev: Event, params: ContextMenuParams) {
|
||||
const items: MenuItemConstructorOptions[] = [
|
||||
{ role: 'undo' },
|
||||
{ role: 'redo', enabled: params.editFlags.canRedo },
|
||||
{ type: 'separator' },
|
||||
].concat(_CutCopyPasteSelectContextMenus(params));
|
||||
...cutCopyPasteSelectContextMenus(params),
|
||||
];
|
||||
|
||||
const popupMenu = Menu.buildFromTemplate(items);
|
||||
|
||||
@@ -214,20 +245,20 @@ function onEditableContextMenu(ev, params) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
ipcMain.on('userDownloadOpen', function(ev, { path }) {
|
||||
ipcMain.on('userDownloadOpen', function(ev: IpcMainEvent, { path }) {
|
||||
shell.openPath(path);
|
||||
});
|
||||
|
||||
module.exports = (webContents) => {
|
||||
export default (webContents: WebContents): void => {
|
||||
webContents.on('new-window', onWindowOrNavigate);
|
||||
webContents.on('will-navigate', (ev, target) => {
|
||||
webContents.on('will-navigate', (ev: Event, target: string): void => {
|
||||
if (target.startsWith("vector://")) return;
|
||||
return onWindowOrNavigate(ev, target);
|
||||
});
|
||||
|
||||
webContents.on('context-menu', function(ev, params) {
|
||||
webContents.on('context-menu', function(ev: Event, params: ContextMenuParams): void {
|
||||
if (params.linkURL || params.srcURL) {
|
||||
onLinkContextMenu(ev, params);
|
||||
onLinkContextMenu(ev, params, webContents);
|
||||
} else if (params.selectionText) {
|
||||
onSelectedContextMenu(ev, params);
|
||||
} else if (params.isEditable) {
|
||||
@@ -235,7 +266,7 @@ module.exports = (webContents) => {
|
||||
}
|
||||
});
|
||||
|
||||
webContents.session.on('will-download', (event, item) => {
|
||||
webContents.session.on('will-download', (event: Event, item: DownloadItem): void => {
|
||||
item.once('done', (event, state) => {
|
||||
if (state === 'completed') {
|
||||
const savePath = item.getSavePath();
|
||||
24
tsconfig.json
Normal file
24
tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"esModuleInterop": true,
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"target": "es2016",
|
||||
"noImplicitAny": false,
|
||||
"sourceMap": false,
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src",
|
||||
"declaration": true,
|
||||
"types": [
|
||||
"node"
|
||||
],
|
||||
"lib": [
|
||||
"es2019",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./src/**/*.ts"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user