mirror of
https://github.com/element-hq/element-desktop.git
synced 2026-01-02 04:29:38 -05:00
Compare commits
1 Commits
t3chguy/no
...
t3chguy/fi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0acb44d696 |
31
.eslintrc.js
31
.eslintrc.js
@@ -1,10 +1,6 @@
|
||||
module.exports = {
|
||||
plugins: ["matrix-org"],
|
||||
extends: [
|
||||
"plugin:matrix-org/javascript",
|
||||
],
|
||||
parserOptions: {
|
||||
ecmaVersion: 2021,
|
||||
ecmaVersion: 8,
|
||||
},
|
||||
env: {
|
||||
es6: true,
|
||||
@@ -12,26 +8,15 @@ module.exports = {
|
||||
// we also have some browser code (ie. the preload script)
|
||||
browser: true,
|
||||
},
|
||||
// NOTE: These rules are frozen and new rules should not be added here.
|
||||
// New changes belong in https://github.com/matrix-org/eslint-plugin-matrix-org/
|
||||
extends: ["matrix-org"],
|
||||
rules: {
|
||||
// js-sdk uses a babel rule which we can't use because we
|
||||
// don't use babel, so remove it & put the original back
|
||||
"babel/no-invalid-this": "off",
|
||||
"no-invalid-this": "error",
|
||||
"quotes": "off",
|
||||
"indent": "off",
|
||||
"prefer-promise-reject-errors": "off",
|
||||
"no-async-promise-executor": "off",
|
||||
},
|
||||
overrides: [{
|
||||
files: ["{src,scripts,hak}/**/*.{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
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -1 +0,0 @@
|
||||
* @vector-im/element-web
|
||||
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
13
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,13 +0,0 @@
|
||||
<!-- Thanks for submitting a PR! Please ensure the following requirements are met in order for us to review your PR -->
|
||||
|
||||
## Checklist
|
||||
|
||||
* [ ] Ensure your code works with manual testing
|
||||
* [ ] Linter and other CI checks pass
|
||||
* [ ] Sign-off given on the changes (see [CONTRIBUTING.md](https://github.com/vector-im/element-desktop/blob/develop/CONTRIBUTING.md))
|
||||
|
||||
<!--
|
||||
If you would like to specify text for the changelog entry other than your PR title, add the following:
|
||||
|
||||
Notes: Add super cool feature
|
||||
-->
|
||||
6
.github/renovate.json
vendored
6
.github/renovate.json
vendored
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": [
|
||||
"github>matrix-org/renovate-config-element-web"
|
||||
]
|
||||
}
|
||||
30
.github/workflows/backport.yml
vendored
30
.github/workflows/backport.yml
vendored
@@ -1,30 +0,0 @@
|
||||
name: Backport
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- closed
|
||||
- labeled
|
||||
branches:
|
||||
- develop
|
||||
|
||||
jobs:
|
||||
backport:
|
||||
name: Backport
|
||||
runs-on: ubuntu-latest
|
||||
# Only react to merged PRs for security reasons.
|
||||
# See https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target.
|
||||
if: >
|
||||
github.event.pull_request.merged
|
||||
&& (
|
||||
github.event.action == 'closed'
|
||||
|| (
|
||||
github.event.action == 'labeled'
|
||||
&& contains(github.event.label.name, 'backport')
|
||||
)
|
||||
)
|
||||
steps:
|
||||
- uses: tibdex/backport@v2
|
||||
with:
|
||||
labels_template: "<%= JSON.stringify([...labels, 'X-Release-Blocker']) %>"
|
||||
# We can't use GITHUB_TOKEN here or CI won't run on the new PR
|
||||
github_token: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
288
.github/workflows/build.yaml
vendored
288
.github/workflows/build.yaml
vendored
@@ -1,288 +0,0 @@
|
||||
name: Build and Test
|
||||
on:
|
||||
pull_request: { }
|
||||
push:
|
||||
branches: [ develop, master ]
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
jobs:
|
||||
fetch:
|
||||
name: Prepare
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: 16
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Fetch Element Web (develop)
|
||||
if: github.event.pull_request.base.ref == 'develop'
|
||||
run: yarn run fetch --noverify develop -d element.io/nightly
|
||||
|
||||
- name: Fetch Element Web
|
||||
if: github.event.pull_request.base.ref != 'develop'
|
||||
run: yarn run fetch --noverify --cfgdir element.io/release
|
||||
|
||||
- uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
retention-days: 1
|
||||
path: |
|
||||
webapp.asar
|
||||
package.json
|
||||
|
||||
windows:
|
||||
needs: fetch
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: x86_64-pc-windows-msvc
|
||||
arch: x64
|
||||
- target: i686-pc-windows-msvc
|
||||
arch: x86
|
||||
build-args: --ia32
|
||||
name: Windows (${{ matrix.arch }})
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ runner.os }}-${{ hashFiles('./yarn.lock') }}
|
||||
path: |
|
||||
./.hak
|
||||
|
||||
- name: Set up build tools
|
||||
uses: ilammy/msvc-dev-cmd@v1
|
||||
with:
|
||||
arch: ${{ matrix.arch }}
|
||||
|
||||
# ActiveTCL package on choco is from 2015,
|
||||
# this one is newer but includes more than we need
|
||||
- name: Choco install tclsh
|
||||
shell: pwsh
|
||||
run: |
|
||||
choco install -y magicsplat-tcl-tk --no-progress
|
||||
echo "${HOME}/AppData/Local/Apps/Tcl86/bin" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Choco install NetWide Assembler
|
||||
shell: pwsh
|
||||
run: |
|
||||
choco install -y nasm --no-progress
|
||||
echo "C:/Program Files/NASM" | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: ${{ matrix.target }}
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: 16
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Build Natives
|
||||
run: |
|
||||
refreshenv
|
||||
yarn build:native --target ${{ matrix.target }}
|
||||
|
||||
- name: Build App
|
||||
run: "yarn build --publish never -w ${{ matrix.build-args }}"
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: win-${{ matrix.arch }}
|
||||
path: dist
|
||||
retention-days: 1
|
||||
|
||||
linux:
|
||||
needs: fetch
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- sqlcipher: system
|
||||
- sqlcipher: static
|
||||
static: 1
|
||||
name: 'Linux (sqlcipher: ${{ matrix.sqlcipher }})'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ hashFiles('./yarn.lock') }}
|
||||
path: |
|
||||
./.hak
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Install libsqlcipher-dev
|
||||
if: matrix.sqlcipher == 'system'
|
||||
run: sudo apt-get install -y libsqlcipher-dev
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: 16
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Build Natives
|
||||
run: "yarn build:native"
|
||||
env:
|
||||
SQLCIPHER_STATIC: ${{ matrix.static }}
|
||||
|
||||
- name: Build App
|
||||
run: "yarn build --publish never"
|
||||
|
||||
- name: Install .deb
|
||||
run: "sudo apt install ./dist/*.deb"
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: linux-sqlcipher-${{ matrix.sqlcipher }}
|
||||
path: dist
|
||||
retention-days: 1
|
||||
|
||||
macos:
|
||||
needs: fetch
|
||||
name: macOS (universal)
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: webapp
|
||||
|
||||
- name: Cache .hak
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
key: ${{ hashFiles('./yarn.lock') }}
|
||||
path: |
|
||||
./.hak
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
target: aarch64-apple-darwin
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: 16
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Build Natives
|
||||
run: "yarn build:native:universal"
|
||||
|
||||
- name: Build App
|
||||
run: "yarn build:universal --publish never"
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: macos
|
||||
path: dist
|
||||
retention-days: 1
|
||||
|
||||
test:
|
||||
needs:
|
||||
- macos
|
||||
- linux
|
||||
- windows
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
# Disable macOS tests for now, they fail to run in CI, needs investigation.
|
||||
# - name: macOS Universal
|
||||
# os: macos
|
||||
# artifact: macos
|
||||
# executable: "./dist/mac-universal/Element.app/Contents/MacOS/Element"
|
||||
# prepare_cmd: "chmod +x ./dist/mac-universal/Element.app/Contents/MacOS/Element"
|
||||
- name: 'Linux (sqlcipher: system)'
|
||||
os: ubuntu
|
||||
artifact: linux-sqlcipher-system
|
||||
executable: "element-desktop"
|
||||
prepare_cmd: "sudo apt install ./dist/*.deb"
|
||||
- name: 'Linux (sqlcipher: static)'
|
||||
os: ubuntu
|
||||
artifact: linux-sqlcipher-static
|
||||
executable: "element-desktop"
|
||||
prepare_cmd: "sudo apt install ./dist/*.deb"
|
||||
- name: Windows (x86)
|
||||
os: windows
|
||||
artifact: win-x86
|
||||
executable: "./dist/win-ia32-unpacked/Element.exe"
|
||||
- name: Windows (x64)
|
||||
os: windows
|
||||
artifact: win-x64
|
||||
executable: "./dist/win-unpacked/Element.exe"
|
||||
name: Test ${{ matrix.name }}
|
||||
runs-on: ${{ matrix.os }}-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: "yarn"
|
||||
node-version: 16
|
||||
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: dist
|
||||
|
||||
- name: Prepare for tests
|
||||
run: ${{ matrix.prepare_cmd }}
|
||||
if: matrix.prepare_cmd
|
||||
|
||||
- name: Run tests
|
||||
uses: GabrielBB/xvfb-action@v1
|
||||
timeout-minutes: 5
|
||||
with:
|
||||
run: "yarn test"
|
||||
env:
|
||||
ELEMENT_DESKTOP_EXECUTABLE: ${{ matrix.executable }}
|
||||
|
||||
- name: Upload Artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ matrix.artifact }}
|
||||
path: test_artifacts
|
||||
retention-days: 1
|
||||
12
.github/workflows/pull_request.yaml
vendored
12
.github/workflows/pull_request.yaml
vendored
@@ -1,12 +0,0 @@
|
||||
name: Pull Request
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [ opened, edited, labeled, unlabeled, synchronize ]
|
||||
concurrency: ${{ github.workflow }}-${{ github.event.pull_request.head.ref }}
|
||||
jobs:
|
||||
action:
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/pull_request.yaml@develop
|
||||
with:
|
||||
labels: "T-Defect,T-Enhancement,T-Task"
|
||||
secrets:
|
||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
45
.github/workflows/static_analysis.yaml
vendored
45
.github/workflows/static_analysis.yaml
vendored
@@ -1,45 +0,0 @@
|
||||
name: Static Analysis
|
||||
on:
|
||||
pull_request: { }
|
||||
push:
|
||||
branches: [ develop, master ]
|
||||
jobs:
|
||||
ts_lint:
|
||||
name: "Typescript Syntax Check"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'yarn'
|
||||
node-version: 16
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Typecheck
|
||||
run: "yarn run lint:types"
|
||||
|
||||
i18n_lint:
|
||||
name: "i18n Check"
|
||||
uses: matrix-org/matrix-react-sdk/.github/workflows/i18n_check.yml@develop
|
||||
|
||||
js_lint:
|
||||
name: "ESLint"
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: 'yarn'
|
||||
node-version: 16
|
||||
|
||||
# Does not need branch matching as only analyses this layer
|
||||
- name: Install Deps
|
||||
run: "yarn install --pure-lockfile"
|
||||
|
||||
- name: Run Linter
|
||||
run: "yarn run lint:js"
|
||||
8
.github/workflows/upgrade_dependencies.yml
vendored
8
.github/workflows/upgrade_dependencies.yml
vendored
@@ -1,8 +0,0 @@
|
||||
name: Upgrade Dependencies
|
||||
on:
|
||||
workflow_dispatch: { }
|
||||
jobs:
|
||||
upgrade:
|
||||
uses: matrix-org/matrix-js-sdk/.github/workflows/upgrade_dependencies.yml@develop
|
||||
secrets:
|
||||
ELEMENT_BOT_TOKEN: ${{ secrets.ELEMENT_BOT_TOKEN }}
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
/dist
|
||||
/lib
|
||||
/webapp
|
||||
/webapp.asar
|
||||
/packages
|
||||
@@ -11,7 +10,3 @@
|
||||
/.yarnrc
|
||||
/docker
|
||||
/.npmrc
|
||||
.vscode
|
||||
.vscode/
|
||||
/test_artifacts/
|
||||
/coverage/
|
||||
|
||||
2004
CHANGELOG.md
2004
CHANGELOG.md
File diff suppressed because it is too large
Load Diff
56
README.md
56
README.md
@@ -1,10 +1,3 @@
|
||||

|
||||

|
||||
[](https://translate.element.io/engage/element-desktop/)
|
||||
[](https://sonarcloud.io/summary/new_code?id=element-desktop)
|
||||
[](https://sonarcloud.io/summary/new_code?id=element-desktop)
|
||||
[](https://sonarcloud.io/summary/new_code?id=element-desktop)
|
||||
|
||||
Element Desktop
|
||||
===============
|
||||
|
||||
@@ -27,7 +20,7 @@ so the first step is to get a working copy of Element Web. There are a few ways
|
||||
# Fetch the prebuilt release Element package from the element-web GitHub releases page. The version
|
||||
# fetched will be the same as the local element-desktop package.
|
||||
# We're explicitly asking for no config, so the packaged Element will have no config.json.
|
||||
yarn run fetch --noverify --cfgdir ""
|
||||
yarn run fetch --noverify --cfgdir ''
|
||||
```
|
||||
|
||||
...or if you'd like to use GPG to verify the downloaded package:
|
||||
@@ -37,14 +30,14 @@ yarn run fetch --noverify --cfgdir ""
|
||||
# once.
|
||||
yarn run fetch --importkey
|
||||
# Fetch the package and verify the signature
|
||||
yarn run fetch --cfgdir ""
|
||||
yarn run fetch --cfgdir ''
|
||||
```
|
||||
|
||||
...or either of the above, but fetching a specific version of Element:
|
||||
```
|
||||
# Fetch the prebuilt release Element package from the element-web GitHub releases page. The version
|
||||
# fetched will be the same as the local element-desktop package.
|
||||
yarn run fetch --noverify --cfgdir "" v1.5.6
|
||||
yarn run fetch --noverify --cfgdir '' v1.5.6
|
||||
```
|
||||
|
||||
If you only want to run the app locally and don't need to build packages, you can
|
||||
@@ -56,17 +49,26 @@ ln -s ../element-web/webapp ./
|
||||
|
||||
[TODO: add support for fetching develop builds, arbitrary URLs and arbitrary paths]
|
||||
|
||||
|
||||
Building
|
||||
========
|
||||
Now you have a copy of Element, you're ready to build packages. If you'd just like to
|
||||
run Element locally, skip to the next section.
|
||||
|
||||
## Native Build
|
||||
If you'd like to build the native modules (for searching in encrypted rooms and
|
||||
secure storage), do this first. This will take 10 minutes or so, and will
|
||||
require a number of native tools to be installed, depending on your OS (eg.
|
||||
rust, tcl, make/nmake). If you don't need these features, you can skip this
|
||||
step.
|
||||
```
|
||||
yarn run build:native
|
||||
```
|
||||
|
||||
TODO: List native pre-requisites
|
||||
On Windows, this will automatically determine the architecture to build for based
|
||||
on the environment (ie. set up by vcvarsall.bat).
|
||||
|
||||
Optionally, [build the native modules](https://github.com/vector-im/element-desktop/blob/develop/docs/native-node-modules.md),
|
||||
which include support for searching in encrypted rooms and secure storage. Skipping this step is fine, you just won't have those features.
|
||||
Now you can build the package:
|
||||
|
||||
Then, run
|
||||
```
|
||||
yarn run build
|
||||
```
|
||||
@@ -76,9 +78,18 @@ This will do a couple of things:
|
||||
* Run electron-builder to build a package. The package built will match the operating system
|
||||
you're running the build process on.
|
||||
|
||||
## Docker
|
||||
If you're on Windows, you can choose to build specifically for 32 or 64 bit:
|
||||
```
|
||||
yarn run build32
|
||||
```
|
||||
or
|
||||
```
|
||||
yarn run build64
|
||||
```
|
||||
|
||||
Alternatively, you can also build using docker, which will always produce the linux package:
|
||||
This build step will not build any native modules.
|
||||
|
||||
You can also build using docker, which will always produce the linux package:
|
||||
```
|
||||
# Run this once to make the docker image
|
||||
yarn run docker:setup
|
||||
@@ -129,7 +140,7 @@ User-specified config.json
|
||||
==========================
|
||||
|
||||
+ `%APPDATA%\$NAME\config.json` on Windows
|
||||
+ `$XDG_CONFIG_HOME/$NAME/config.json` or `~/.config/$NAME/config.json` on Linux
|
||||
+ `$XDG_CONFIG_HOME\$NAME\config.json` or `~/.config/$NAME/config.json` on Linux
|
||||
+ `~/Library/Application Support/$NAME/config.json` on macOS
|
||||
|
||||
In the paths above, `$NAME` is typically `Element`, unless you use `--profile
|
||||
@@ -137,15 +148,6 @@ $PROFILE` in which case it becomes `Element-$PROFILE`, or it is using one of
|
||||
the above created by a pre-1.7 install, in which case it will be `Riot` or
|
||||
`Riot-$PROFILE`.
|
||||
|
||||
Translations
|
||||
==========================
|
||||
|
||||
To add a new translation, head to the [translating doc](https://github.com/vector-im/element-web/blob/develop/docs/translating.md).
|
||||
|
||||
For a developer guide, see the [translating dev doc](https://github.com/vector-im/element-web/blob/develop/docs/translating-dev.md).
|
||||
|
||||
[<img src="https://translate.element.io/widgets/element-desktop/-/multi-auto.svg" alt="translationsstatus" width="340">](https://translate.element.io/engage/element-desktop/?utm_source=widget)
|
||||
|
||||
Report bugs & give feedback
|
||||
==========================
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
module.exports = {
|
||||
presets: [
|
||||
['@babel/preset-env', { targets: { node: 'current' } }],
|
||||
'@babel/preset-typescript',
|
||||
],
|
||||
};
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM buildpack-deps:bionic-curl
|
||||
FROM buildpack-deps:xenial-curl
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
|
||||
@@ -9,12 +9,11 @@ RUN apt-get -qq update && apt-get -qq dist-upgrade && \
|
||||
# git ssh for using as docker image on CircleCI
|
||||
# python for node-gyp
|
||||
# rpm is required for FPM to build rpm package
|
||||
# tclsh is required for building SQLite as part of SQLCipher
|
||||
# libsecret-1-dev and libgnome-keyring-dev are required even for prebuild keytar
|
||||
apt-get -qq install --no-install-recommends qtbase5-dev bsdtar build-essential autoconf libssl-dev gcc-multilib g++-multilib lzip rpm python libcurl4 git git-lfs ssh unzip tcl \
|
||||
apt-get -qq install --no-install-recommends qtbase5-dev bsdtar build-essential autoconf libssl-dev gcc-multilib g++-multilib lzip rpm python libcurl3 git git-lfs ssh unzip \
|
||||
libsecret-1-dev libgnome-keyring-dev \
|
||||
libopenjp2-tools \
|
||||
# Used by seshat (when not SQLCIPHER_STATIC) \
|
||||
# Used by Seshat
|
||||
libsqlcipher-dev && \
|
||||
# git-lfs
|
||||
git lfs install && \
|
||||
@@ -31,7 +30,7 @@ ENV LC_ALL C.UTF-8
|
||||
|
||||
ENV DEBUG_COLORS true
|
||||
ENV FORCE_COLOR true
|
||||
ENV NODE_VERSION 14.17.0
|
||||
ENV NODE_VERSION 12.16.1
|
||||
|
||||
# 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 && \
|
||||
|
||||
@@ -10,26 +10,20 @@ modules from source to ensure we can trust the compiled output. In the future,
|
||||
we may offer a pre-compiled path for those who want to use these features in a
|
||||
custom build of Element without installing the various build tools required.
|
||||
|
||||
Do note that compiling a module for a particular operating system
|
||||
(Linux/macOS/Windows) will need to be done on that operating system.
|
||||
Cross-compiling from a host OS for a different target OS may be possible, but
|
||||
we don't support this flow with Element dependencies at this time.
|
||||
|
||||
The process is automated by [vector-im/element-builder](https://github.com/vector-im/element-builder)
|
||||
when releasing.
|
||||
when releasing.
|
||||
The following sections explain the manual steps you can use with a custom build of Element to enable
|
||||
these features if you'd like to try them out.
|
||||
It is possible to [build those native modules locally automatically](https://github.com/vector-im/element-desktop#building).
|
||||
|
||||
## Building
|
||||
|
||||
Install the pre-requisites for your system:
|
||||
|
||||
* [Windows pre-requisites](https://github.com/vector-im/element-desktop/blob/develop/docs/windows-requirements.md)
|
||||
* Linux: TODO
|
||||
* OS X: TODO
|
||||
|
||||
Then optionally, [add seshat and dependencies to support search in E2E rooms](#adding-seshat-for-search-in-e2e-encrypted-rooms).
|
||||
|
||||
Then, to build for an architecture selected automatically based on your system (recommended), run:
|
||||
```
|
||||
yarn run build:native
|
||||
```
|
||||
|
||||
If you need to build for a specific architecture, see [here](#compiling-for-specific-architectures).
|
||||
|
||||
## Adding Seshat for search in E2E encrypted rooms
|
||||
|
||||
Seshat is a native Node module that adds support for local event indexing and
|
||||
@@ -47,7 +41,7 @@ using yarn at the root of this project:
|
||||
|
||||
yarn add matrix-seshat
|
||||
|
||||
You will have to rebuild the native libraries against electron's version
|
||||
You will have to rebuild the native libraries against electron's version of
|
||||
of node rather than your system node, using the `electron-build-env` tool.
|
||||
This is also needed to when pulling in changes to Seshat using `yarn link`.
|
||||
|
||||
@@ -65,85 +59,3 @@ After this is done the Electron version of Element can be run from the main fold
|
||||
as usual using:
|
||||
|
||||
yarn start
|
||||
|
||||
### Statically linking libsqlcipher
|
||||
|
||||
On Windows & macOS we always statically link libsqlcipher for it is not generally available.
|
||||
On Linux by default we will use a system package, on debian & ubuntu this is `libsqlcipher0`,
|
||||
but this is problematic for some other packages.
|
||||
By including `SQLCIPHER_STATIC=1` in the build environment, the build scripts will statically link sqlcipher,
|
||||
note that this will want a `libcrypto1.1` shared library available in the system.
|
||||
|
||||
More info can be found at https://github.com/matrix-org/seshat/issues/102
|
||||
and https://github.com/vector-im/element-web/issues/20926.
|
||||
|
||||
## Compiling for specific architectures
|
||||
|
||||
### macOS
|
||||
|
||||
On macOS, you can build universal native modules too:
|
||||
```
|
||||
yarn run build:native:universal
|
||||
```
|
||||
|
||||
...or you can build for a specific architecture:
|
||||
```
|
||||
yarn run build:native --target x86_64-apple-darwin
|
||||
```
|
||||
or
|
||||
```
|
||||
yarn run build:native --target aarch64-apple-darwin
|
||||
```
|
||||
|
||||
You'll then need to create a built bundle with the same architecture.
|
||||
To bundle a universal build for macOS, run:
|
||||
|
||||
```
|
||||
yarn run build:universal
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
If you're on Windows, you can choose to build specifically for 32 or 64 bit:
|
||||
```
|
||||
yarn run build:32
|
||||
```
|
||||
or
|
||||
```
|
||||
yarn run build:64
|
||||
```
|
||||
|
||||
### Cross compiling
|
||||
|
||||
Compiling a module for a particular operating system (Linux/macOS/Windows) needs
|
||||
to be done on that operating system. Cross-compiling from a host OS for a different
|
||||
target OS may be possible, but we don't support this flow with Element dependencies
|
||||
at this time.
|
||||
|
||||
### Switching between architectures
|
||||
|
||||
The native module build system keeps the different architectures
|
||||
separate, so you can keep native modules for several architectures at the same
|
||||
time and switch which are active using a `yarn run hak copy` command, passing
|
||||
the appropriate architectures. This will error if you haven't yet built those
|
||||
architectures. eg:
|
||||
|
||||
```
|
||||
yarn run build:native --target x86_64-apple-darwin
|
||||
# We've now built & linked into place native modules for Intel
|
||||
yarn run build:native --target aarch64-apple-darwin
|
||||
# We've now built Apple Silicon modules too, and linked them into place as the active ones
|
||||
|
||||
yarn run hak copy --target x86_64-apple-darwin
|
||||
# We've now switched back to our Intel modules
|
||||
yarn run hak copy --target x86_64-apple-darwin --target aarch64-apple-darwin
|
||||
# Now our native modules are universal x86_64+aarch64 binaries
|
||||
```
|
||||
|
||||
The current set of native modules are stored in `.hak/hakModules`,
|
||||
so you can use this to check what architecture is currently in place, eg:
|
||||
|
||||
```
|
||||
$ lipo -info .hak/hakModules/keytar/build/Release/keytar.node
|
||||
Architectures in the fat file: .hak/hakModules/keytar/build/Release/keytar.node are: x86_64 arm64
|
||||
```
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
# Windows
|
||||
|
||||
## Requirements to build native modules
|
||||
|
||||
If you want to build native modules, make sure that the following tools are installed on your system.
|
||||
|
||||
- [Git for Windows](https://git-scm.com/download/win)
|
||||
- [Node 14](https://nodejs.org)
|
||||
- [Python 3](https://www.python.org/downloads/) (if you type 'python' into command prompt it will offer to install it from the windows store)
|
||||
- [Strawberry Perl](https://strawberryperl.com/)
|
||||
- [Rustup](https://rustup.rs/)
|
||||
- [NASM](https://www.nasm.us/)
|
||||
- [Build Tools for Visual Studio 2019](https://visualstudio.microsoft.com/downloads/#build-tools-for-visual-studio-2019) with the following configuration:
|
||||
- On the Workloads tab:
|
||||
- Desktop & Mobile -> C++ build tools
|
||||
- On the Individual components tab:
|
||||
- MSVC VS 2019 C++ build tools
|
||||
- Windows 10 SDK (latest version available)
|
||||
- C++ CMake tools for Windows
|
||||
|
||||
Once installed make sure all those utilities are accessible in your `PATH`.
|
||||
|
||||
If you want to be able to build x86 targets from an x64 host install the right toolchain:
|
||||
```cmd
|
||||
rustup toolchain install stable-i686-pc-windows-msvc
|
||||
rustup target add i686-pc-windows-msvc
|
||||
```
|
||||
|
||||
In order to load all the C++ utilities installed by Visual Studio you can run the following in a terminal window.
|
||||
|
||||
```
|
||||
call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvarsall.bat" amd64
|
||||
```
|
||||
|
||||
You can replace `amd64` with `x86` depending on your CPU architecture.
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"update_base_url": "https://packages.element.io/nightly/update/",
|
||||
"update_base_url": "https://packages.riot.im/nightly/update/",
|
||||
"default_server_name": "matrix.org",
|
||||
"brand": "Element Nightly",
|
||||
"integrations_ui_url": "https://scalar.vector.im/",
|
||||
@@ -13,13 +13,16 @@
|
||||
],
|
||||
"hosting_signup_link": "https://element.io/matrix-services?utm_source=element-web&utm_medium=web",
|
||||
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
|
||||
"uisi_autorageshake_app": "element-auto-uisi",
|
||||
"showLabsSettings": true,
|
||||
"piwik": {
|
||||
"url": "https://piwik.riot.im/",
|
||||
"siteId": 1,
|
||||
"policyUrl": "https://element.io/cookie-policy"
|
||||
},
|
||||
"roomDirectory": {
|
||||
"servers": [
|
||||
"matrix.org",
|
||||
"gitter.im",
|
||||
"libera.chat"
|
||||
"gitter.im"
|
||||
]
|
||||
},
|
||||
"enable_presence_by_hs_url": {
|
||||
@@ -35,22 +38,5 @@
|
||||
"url": "https://element.io/cookie-policy",
|
||||
"text": "Cookie Policy"
|
||||
}
|
||||
],
|
||||
"sentry": {
|
||||
"dsn": "https://029a0eb289f942508ae0fb17935bd8c5@sentry.matrix.org/6",
|
||||
"environment": "nightly"
|
||||
},
|
||||
"posthog": {
|
||||
"projectApiKey": "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO",
|
||||
"apiHost": "https://posthog.element.io"
|
||||
},
|
||||
"privacy_policy_url": "https://element.io/cookie-policy",
|
||||
"features": {
|
||||
"feature_spotlight": true,
|
||||
"feature_video_rooms": true
|
||||
},
|
||||
"element_call": {
|
||||
"url": "https://element-call.netlify.app"
|
||||
},
|
||||
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,10 +3,9 @@ License: Apache-2.0
|
||||
Vendor: support@element.io
|
||||
Architecture: amd64
|
||||
Maintainer: support@element.io
|
||||
Depends: libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1, libsecret-1-0
|
||||
Recommends: libappindicator3-1, libsqlcipher0
|
||||
Depends: libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1, libappindicator3-1, libsecret-1-0, libsqlcipher0
|
||||
Section: net
|
||||
Priority: extra
|
||||
Homepage: https://element.io/
|
||||
Description:
|
||||
Description:
|
||||
riot.im A feature-rich client for Matrix.org (nightly unstable build).
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"update_base_url": "https://packages.element.io/desktop/update/",
|
||||
"update_base_url": "https://packages.riot.im/desktop/update/",
|
||||
"default_server_name": "matrix.org",
|
||||
"brand": "Element",
|
||||
"integrations_ui_url": "https://scalar.vector.im/",
|
||||
@@ -13,15 +13,18 @@
|
||||
],
|
||||
"hosting_signup_link": "https://element.io/matrix-services?utm_source=element-web&utm_medium=web",
|
||||
"bug_report_endpoint_url": "https://element.io/bugreports/submit",
|
||||
"uisi_autorageshake_app": "element-auto-uisi",
|
||||
"roomDirectory": {
|
||||
"servers": [
|
||||
"matrix.org",
|
||||
"gitter.im",
|
||||
"libera.chat"
|
||||
"gitter.im"
|
||||
]
|
||||
},
|
||||
"showLabsSettings": false,
|
||||
"piwik": {
|
||||
"url": "https://piwik.riot.im/",
|
||||
"siteId": 1,
|
||||
"policyUrl": "https://element.io/cookie-policy"
|
||||
},
|
||||
"enable_presence_by_hs_url": {
|
||||
"https://matrix.org": false,
|
||||
"https://matrix-client.matrix.org": false
|
||||
@@ -35,11 +38,5 @@
|
||||
"url": "https://element.io/cookie-policy",
|
||||
"text": "Cookie Policy"
|
||||
}
|
||||
],
|
||||
"posthog": {
|
||||
"projectApiKey": "phc_Jzsm6DTm6V2705zeU5dcNvQDlonOR68XvX2sh1sEOHO",
|
||||
"apiHost": "https://posthog.element.io"
|
||||
},
|
||||
"privacy_policy_url": "https://element.io/cookie-policy",
|
||||
"map_style_url": "https://api.maptiler.com/maps/streets/style.json?key=fU3vlMsMn4Jb6dnEIFsx"
|
||||
]
|
||||
}
|
||||
|
||||
@@ -3,12 +3,11 @@ License: Apache-2.0
|
||||
Vendor: support@element.io
|
||||
Architecture: amd64
|
||||
Maintainer: support@element.io
|
||||
Depends: libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1, libsecret-1-0
|
||||
Recommends: libappindicator3-1, libsqlcipher0
|
||||
Depends: libgtk-3-0, libnotify4, libnss3, libxss1, libxtst6, xdg-utils, libatspi2.0-0, libuuid1, libappindicator3-1, libsecret-1-0, libsqlcipher0
|
||||
Replaces: riot-desktop (<< 1.7.0), riot-web (<< 1.7.0)
|
||||
Breaks: riot-desktop (<< 1.7.0), riot-web (<< 1.7.0)
|
||||
Section: net
|
||||
Priority: extra
|
||||
Homepage: https://element.io/
|
||||
Description:
|
||||
Description:
|
||||
A feature-rich client for Matrix.org
|
||||
|
||||
@@ -14,17 +14,18 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import childProcess from 'child_process';
|
||||
const path = require('path');
|
||||
const childProcess = require('child_process');
|
||||
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
module.exports = async function(hakEnv, moduleInfo) {
|
||||
await buildKeytar(hakEnv, moduleInfo);
|
||||
};
|
||||
|
||||
export default async function buildKeytar(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
async function buildKeytar(hakEnv, moduleInfo) {
|
||||
const env = hakEnv.makeGypEnv();
|
||||
|
||||
console.log("Running yarn with env", env);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
path.join(moduleInfo.nodeModuleBinDir, 'node-gyp' + (hakEnv.isWin() ? '.cmd' : '')),
|
||||
['rebuild'],
|
||||
@@ -14,16 +14,13 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import childProcess from 'child_process';
|
||||
const childProcess = require('child_process');
|
||||
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
|
||||
export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
module.exports = async function(hakEnv, moduleInfo) {
|
||||
const tools = [['python', '--version']]; // node-gyp uses python for reasons beyond comprehension
|
||||
|
||||
for (const tool of tools) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(tool[0], tool.slice(1), {
|
||||
stdio: ['ignore'],
|
||||
});
|
||||
@@ -36,4 +33,4 @@ export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promi
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"scripts": {
|
||||
"check": "check.ts",
|
||||
"build": "build.ts"
|
||||
"check": "check.js",
|
||||
"build": "build.js"
|
||||
},
|
||||
"copy": "build/Release/keytar.node",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
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.
|
||||
@@ -14,40 +14,38 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import childProcess from 'child_process';
|
||||
import mkdirp from 'mkdirp';
|
||||
import fsExtra from 'fs-extra';
|
||||
const path = require('path');
|
||||
const childProcess = require('child_process');
|
||||
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
const mkdirp = require('mkdirp');
|
||||
const fsExtra = require('fs-extra');
|
||||
|
||||
export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
module.exports = async function(hakEnv, moduleInfo) {
|
||||
if (hakEnv.isWin()) {
|
||||
await buildOpenSslWin(hakEnv, moduleInfo);
|
||||
await buildSqlCipherWin(hakEnv, moduleInfo);
|
||||
} else if (hakEnv.wantsStaticSqlCipherUnix()) {
|
||||
} else if (hakEnv.isMac()) {
|
||||
await buildSqlCipherUnix(hakEnv, moduleInfo);
|
||||
}
|
||||
await buildMatrixSeshat(hakEnv, moduleInfo);
|
||||
}
|
||||
};
|
||||
|
||||
async function buildOpenSslWin(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
async function buildOpenSslWin(hakEnv, moduleInfo) {
|
||||
const version = moduleInfo.cfg.dependencies.openssl;
|
||||
const openSslDir = path.join(moduleInfo.moduleTargetDotHakDir, `openssl-${version}`);
|
||||
const openSslDir = path.join(moduleInfo.moduleDotHakDir, `openssl-${version}`);
|
||||
|
||||
const openSslArch = hakEnv.getTargetArch() === 'x64' ? 'VC-WIN64A' : 'VC-WIN32';
|
||||
const openSslArch = hakEnv.arch === 'x64' ? 'VC-WIN64A' : 'VC-WIN32';
|
||||
|
||||
console.log("Building openssl in " + openSslDir);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'perl',
|
||||
[
|
||||
'Configure',
|
||||
'--prefix=' + moduleInfo.depPrefix,
|
||||
// sqlcipher only uses about a tiny part of openssl. We link statically
|
||||
// so will only pull in the symbols we use, but we may as well turn off
|
||||
// as much as possible to save on build time.
|
||||
// sqlcipher only uses about a tiny part of openssl. We link statically
|
||||
// so will only pull in the symbols we use, but we may as well turn off
|
||||
// as much as possible to save on build time.
|
||||
'no-afalgeng',
|
||||
'no-capieng',
|
||||
'no-cms',
|
||||
@@ -105,7 +103,7 @@ async function buildOpenSslWin(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'nmake',
|
||||
['build_libs'],
|
||||
@@ -119,7 +117,7 @@ async function buildOpenSslWin(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'nmake',
|
||||
['install_dev'],
|
||||
@@ -134,14 +132,14 @@ async function buildOpenSslWin(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
});
|
||||
}
|
||||
|
||||
async function buildSqlCipherWin(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
async function buildSqlCipherWin(hakEnv, moduleInfo) {
|
||||
const version = moduleInfo.cfg.dependencies.sqlcipher;
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleTargetDotHakDir, `sqlcipher-${version}`);
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleDotHakDir, `sqlcipher-${version}`);
|
||||
const buildDir = path.join(sqlCipherDir, 'bld');
|
||||
|
||||
await mkdirp(buildDir);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'nmake',
|
||||
['/f', path.join('..', 'Makefile.msc'), 'libsqlite3.lib', 'TOP=..'],
|
||||
@@ -171,61 +169,25 @@ async function buildSqlCipherWin(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
);
|
||||
}
|
||||
|
||||
async function buildSqlCipherUnix(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
async function buildSqlCipherUnix(hakEnv, moduleInfo) {
|
||||
const version = moduleInfo.cfg.dependencies.sqlcipher;
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleTargetDotHakDir, `sqlcipher-${version}`);
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleDotHakDir, `sqlcipher-${version}`);
|
||||
|
||||
const args = [
|
||||
'--prefix=' + moduleInfo.depPrefix + '',
|
||||
'--enable-tempstore=yes',
|
||||
'--enable-shared=no',
|
||||
'--enable-tcl=no',
|
||||
];
|
||||
|
||||
if (hakEnv.isMac()) {
|
||||
args.push('--with-crypto-lib=commoncrypto');
|
||||
}
|
||||
|
||||
if (hakEnv.wantsStaticSqlCipherUnix()) {
|
||||
args.push('--enable-tcl=no');
|
||||
|
||||
if (hakEnv.isLinux()) {
|
||||
args.push('--with-pic=yes');
|
||||
}
|
||||
}
|
||||
|
||||
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: string[] = [];
|
||||
|
||||
args.push('CFLAGS=-DSQLITE_HAS_CODEC');
|
||||
if (hakEnv.isMac()) {
|
||||
ldflags.push('-framework Security');
|
||||
ldflags.push('-framework Foundation');
|
||||
args.push('LDFLAGS=-framework Security -framework Foundation');
|
||||
}
|
||||
|
||||
if (ldflags.length) {
|
||||
args.push(`LDFLAGS=${ldflags.join(' ')}`);
|
||||
}
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
path.join(sqlCipherDir, 'configure'),
|
||||
args,
|
||||
@@ -239,7 +201,7 @@ async function buildSqlCipherUnix(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'make',
|
||||
[],
|
||||
@@ -253,7 +215,7 @@ async function buildSqlCipherUnix(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
'make',
|
||||
['install'],
|
||||
@@ -268,13 +230,10 @@ async function buildSqlCipherUnix(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
});
|
||||
}
|
||||
|
||||
async function buildMatrixSeshat(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
// seshat now uses n-api so we shouldn't need to specify a node version to
|
||||
// build against, but it does seems to still need something in here, so leaving
|
||||
// it for now: we should confirm how much of this it still actually needs.
|
||||
async function buildMatrixSeshat(hakEnv, moduleInfo) {
|
||||
const env = hakEnv.makeGypEnv();
|
||||
|
||||
if (!hakEnv.isLinux() || hakEnv.wantsStaticSqlCipherUnix()) {
|
||||
if (!hakEnv.isLinux()) {
|
||||
Object.assign(env, {
|
||||
SQLCIPHER_STATIC: 1,
|
||||
SQLCIPHER_LIB_DIR: path.join(moduleInfo.depPrefix, 'lib'),
|
||||
@@ -282,25 +241,6 @@ async function buildMatrixSeshat(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
});
|
||||
}
|
||||
|
||||
if (hakEnv.isLinux() && hakEnv.wantsStaticSqlCipherUnix()) {
|
||||
// Ensure Element uses the statically-linked seshat build, and prevent other applications
|
||||
// from attempting to use this one. Detailed explanation:
|
||||
//
|
||||
// RUSTFLAGS
|
||||
// An environment variable containing a list of arguments to pass to rustc.
|
||||
// -Clink-arg=VALUE
|
||||
// A rustc argument to pass a single argument to the linker.
|
||||
// -Wl,
|
||||
// gcc syntax to pass an argument (from gcc) to the linker (ld).
|
||||
// -Bsymbolic:
|
||||
// Prefer local/statically linked symbols over those in the environment.
|
||||
// Prevent overriding native libraries by LD_PRELOAD etc.
|
||||
// --exclude-libs ALL
|
||||
// Prevent symbols from being exported by any archive libraries.
|
||||
// Reduces output filesize and prevents being dynamically linked against.
|
||||
env.RUSTFLAGS = '-Clink-arg=-Wl,-Bsymbolic -Clink-arg=-Wl,--exclude-libs,ALL';
|
||||
}
|
||||
|
||||
if (hakEnv.isWin()) {
|
||||
env.RUSTFLAGS = '-Ctarget-feature=+crt-static -Clink-args=libcrypto.lib';
|
||||
// Note that in general, you can specify targets in Rust without having to have
|
||||
@@ -308,15 +248,11 @@ async function buildMatrixSeshat(hakEnv: HakEnv, moduleInfo: DependencyInfo) {
|
||||
// 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 = `stable-${hakEnv.getTargetId()}`;
|
||||
}
|
||||
|
||||
if (!hakEnv.isHost()) {
|
||||
env.CARGO_BUILD_TARGET = hakEnv.getTargetId();
|
||||
env.RUSTUP_TOOLCHAIN = hakEnv.arch == 'x64' ? 'stable-x86_64-pc-windows-msvc' : 'stable-i686-pc-windows-msvc';
|
||||
}
|
||||
|
||||
console.log("Running neon with env", env);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
path.join(moduleInfo.nodeModuleBinDir, 'neon' + (hakEnv.isWin() ? '.cmd' : '')),
|
||||
['build', '--release'],
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
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.
|
||||
@@ -14,16 +14,12 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import childProcess from 'child_process';
|
||||
import fsProm from 'fs/promises';
|
||||
const childProcess = require('child_process');
|
||||
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
|
||||
export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (hakEnv.wantsStaticSqlCipher()) {
|
||||
// of course tcl doesn't have a --version
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
module.exports = async function(hakEnv, moduleInfo) {
|
||||
// of course tcl doesn't have a --version
|
||||
if (!hakEnv.isLinux()) {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn('tclsh', [], {
|
||||
stdio: ['pipe', 'ignore', 'ignore'],
|
||||
});
|
||||
@@ -38,13 +34,9 @@ export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promi
|
||||
});
|
||||
}
|
||||
|
||||
const tools = [
|
||||
['rustc', '--version'],
|
||||
['python', '--version'], // node-gyp uses python for reasons beyond comprehension
|
||||
];
|
||||
const tools = [['python', '--version']]; // node-gyp uses python for reasons beyond comprehension
|
||||
if (hakEnv.isWin()) {
|
||||
tools.push(['perl', '--version']); // for openssl configure
|
||||
tools.push(['nasm', '-v']); // for openssl building
|
||||
tools.push(['patch', '--version']); // to patch sqlcipher Makefile.msc
|
||||
tools.push(['nmake', '/?']);
|
||||
} else {
|
||||
@@ -52,7 +44,7 @@ export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promi
|
||||
}
|
||||
|
||||
for (const tool of tools) {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(tool[0], tool.slice(1), {
|
||||
stdio: ['ignore'],
|
||||
});
|
||||
@@ -65,22 +57,4 @@ export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promi
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Ensure Rust target exists (nb. we avoid depending on rustup)
|
||||
await new Promise((resolve, reject) => {
|
||||
const rustc = childProcess.execFile('rustc', [
|
||||
'--target', hakEnv.getTargetId(), '-o', 'tmp', '-',
|
||||
], (err, out) => {
|
||||
if (err) {
|
||||
reject(
|
||||
"rustc can't build for target " + hakEnv.getTargetId() +
|
||||
": ensure target is installed via `rustup target add " + hakEnv.getTargetId() + "` " +
|
||||
"or your package manager if not using `rustup`",
|
||||
);
|
||||
}
|
||||
fsProm.unlink('tmp').then(resolve);
|
||||
});
|
||||
rustc.stdin.write('fn main() {}');
|
||||
rustc.stdin.end();
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -14,31 +14,29 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import childProcess from 'child_process';
|
||||
import fs from 'fs';
|
||||
import fsProm from 'fs/promises';
|
||||
import needle from 'needle';
|
||||
import tar from 'tar';
|
||||
const path = require('path');
|
||||
const childProcess = require('child_process');
|
||||
|
||||
import HakEnv from '../../scripts/hak/hakEnv';
|
||||
import { DependencyInfo } from '../../scripts/hak/dep';
|
||||
const fs = require('fs');
|
||||
const fsProm = require('fs').promises;
|
||||
const needle = require('needle');
|
||||
const tar = require('tar');
|
||||
|
||||
export default async function(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (hakEnv.wantsStaticSqlCipher()) {
|
||||
module.exports = async function(hakEnv, moduleInfo) {
|
||||
if (!hakEnv.isLinux()) {
|
||||
await getSqlCipher(hakEnv, moduleInfo);
|
||||
}
|
||||
|
||||
if (hakEnv.isWin()) {
|
||||
await getOpenSsl(hakEnv, moduleInfo);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
async function getSqlCipher(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
async function getSqlCipher(hakEnv, moduleInfo) {
|
||||
const version = moduleInfo.cfg.dependencies.sqlcipher;
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleTargetDotHakDir, `sqlcipher-${version}`);
|
||||
const sqlCipherDir = path.join(moduleInfo.moduleDotHakDir, `sqlcipher-${version}`);
|
||||
|
||||
let haveSqlcipher: boolean;
|
||||
let haveSqlcipher;
|
||||
try {
|
||||
await fsProm.stat(sqlCipherDir);
|
||||
haveSqlcipher = true;
|
||||
@@ -49,7 +47,7 @@ async function getSqlCipher(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise
|
||||
if (haveSqlcipher) return;
|
||||
|
||||
const sqlCipherTarball = path.join(moduleInfo.moduleDotHakDir, `sqlcipher-${version}.tar.gz`);
|
||||
let haveSqlcipherTar: boolean;
|
||||
let haveSqlcipherTar;
|
||||
try {
|
||||
await fsProm.stat(sqlCipherTarball);
|
||||
haveSqlcipherTar = true;
|
||||
@@ -64,10 +62,9 @@ async function getSqlCipher(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise
|
||||
await bob;
|
||||
}
|
||||
|
||||
// Extract the tarball to per-target directories, then we avoid cross-contaiminating archs
|
||||
await tar.x({
|
||||
file: sqlCipherTarball,
|
||||
cwd: moduleInfo.moduleTargetDotHakDir,
|
||||
cwd: moduleInfo.moduleDotHakDir,
|
||||
});
|
||||
|
||||
if (hakEnv.isWin()) {
|
||||
@@ -76,8 +73,8 @@ async function getSqlCipher(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise
|
||||
// set it to 2 (default to memory).
|
||||
const patchFile = path.join(moduleInfo.moduleHakDir, `sqlcipher-${version}-win.patch`);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const readStream = fs.createReadStream(patchFile);
|
||||
await new Promise((resolve, reject) => {
|
||||
const readStream = fs.createReadStream(patchFile);
|
||||
|
||||
const proc = childProcess.spawn(
|
||||
'patch',
|
||||
@@ -95,11 +92,11 @@ async function getSqlCipher(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise
|
||||
}
|
||||
}
|
||||
|
||||
async function getOpenSsl(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
async function getOpenSsl(hakEnv, moduleInfo) {
|
||||
const version = moduleInfo.cfg.dependencies.openssl;
|
||||
const openSslDir = path.join(moduleInfo.moduleTargetDotHakDir, `openssl-${version}`);
|
||||
const openSslDir = path.join(moduleInfo.moduleDotHakDir, `openssl-${version}`);
|
||||
|
||||
let haveOpenSsl: boolean;
|
||||
let haveOpenSsl;
|
||||
try {
|
||||
await fsProm.stat(openSslDir);
|
||||
haveOpenSsl = true;
|
||||
@@ -110,7 +107,7 @@ async function getOpenSsl(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<v
|
||||
if (haveOpenSsl) return;
|
||||
|
||||
const openSslTarball = path.join(moduleInfo.moduleDotHakDir, `openssl-${version}.tar.gz`);
|
||||
let haveOpenSslTar: boolean;
|
||||
let haveOpenSslTar;
|
||||
try {
|
||||
await fsProm.stat(openSslTarball);
|
||||
haveOpenSslTar = true;
|
||||
@@ -124,9 +121,9 @@ async function getOpenSsl(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<v
|
||||
});
|
||||
}
|
||||
|
||||
console.log("extracting " + openSslTarball + " in " + moduleInfo.moduleTargetDotHakDir);
|
||||
console.log("extracting " + openSslTarball + " in " + moduleInfo.moduleDotHakDir);
|
||||
await tar.x({
|
||||
file: openSslTarball,
|
||||
cwd: moduleInfo.moduleTargetDotHakDir,
|
||||
cwd: moduleInfo.moduleDotHakDir,
|
||||
});
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"scripts": {
|
||||
"check": "check.ts",
|
||||
"fetchDeps": "fetchDeps.ts",
|
||||
"build": "build.ts"
|
||||
"check": "check.js",
|
||||
"fetchDeps": "fetchDeps.js",
|
||||
"build": "build.js"
|
||||
},
|
||||
"prune": "native",
|
||||
"copy": "native/index.node",
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"target": "es2016",
|
||||
"sourceMap": false,
|
||||
"lib": [
|
||||
"es2019",
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
],
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
105
package.json
105
package.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "element-desktop",
|
||||
"productName": "Element",
|
||||
"main": "lib/electron-main.js",
|
||||
"version": "1.11.10",
|
||||
"main": "src/electron-main.js",
|
||||
"version": "1.7.25",
|
||||
"description": "A feature-rich client for Matrix.org",
|
||||
"author": "Element",
|
||||
"repository": {
|
||||
@@ -12,106 +12,64 @@
|
||||
"license": "Apache-2.0",
|
||||
"files": [],
|
||||
"scripts": {
|
||||
"i18n": "matrix-gen-i18n",
|
||||
"prunei18n": "matrix-prune-i18n",
|
||||
"diff-i18n": "cp src/i18n/strings/en_EN.json src/i18n/strings/en_EN_orig.json && matrix-gen-i18n && matrix-compare-i18n-files src/i18n/strings/en_EN_orig.json src/i18n/strings/en_EN.json",
|
||||
"mkdirs": "mkdirp packages deploys",
|
||||
"fetch": "yarn run mkdirs && node scripts/fetch-package.js",
|
||||
"asar-webapp": "asar p webapp webapp.asar",
|
||||
"start": "yarn run build:ts && yarn run build:res && electron .",
|
||||
"lint": "yarn lint:types && yarn lint:js",
|
||||
"lint:js": "eslint --max-warnings 0 src scripts hak",
|
||||
"lint:js-fix": "eslint --fix src scripts hak",
|
||||
"lint:types": "tsc --noEmit && tsc -p scripts/hak/tsconfig.json --noEmit && tsc -p hak/tsconfig.json --noEmit",
|
||||
"start": "electron .",
|
||||
"lint": "eslint src/ scripts/ hak/",
|
||||
"build:native": "yarn run hak",
|
||||
"build:native:universal": "yarn run hak --target x86_64-apple-darwin fetchandbuild && yarn run hak --target aarch64-apple-darwin fetchandbuild && yarn run hak --target x86_64-apple-darwin --target aarch64-apple-darwin copyandlink",
|
||||
"build:32": "yarn run build:ts && yarn run build:res && electron-builder --ia32",
|
||||
"build:64": "yarn run build:ts && yarn run build:res && electron-builder --x64",
|
||||
"build:universal": "yarn run build:ts && yarn run build:res && electron-builder --universal",
|
||||
"build": "yarn run build:ts && yarn run build:res && electron-builder",
|
||||
"build:ts": "tsc",
|
||||
"build:res": "node scripts/copy-res.js",
|
||||
"build32": "electron-builder --ia32",
|
||||
"build64": "electron-builder --x64",
|
||||
"build": "electron-builder",
|
||||
"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 lib",
|
||||
"hak": "ts-node scripts/hak/index.ts",
|
||||
"test": "jest"
|
||||
"clean": "rimraf webapp.asar dist packages deploys",
|
||||
"hak": "node scripts/hak/index.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"auto-launch": "^5.0.5",
|
||||
"counterpart": "^0.18.6",
|
||||
"electron-store": "^8.0.2",
|
||||
"electron-store": "^6.0.1",
|
||||
"electron-window-state": "^5.0.3",
|
||||
"minimist": "^1.2.6",
|
||||
"minimist": "^1.2.3",
|
||||
"png-to-ico": "^2.1.1",
|
||||
"request": "^2.88.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.18.10",
|
||||
"@babel/preset-env": "^7.18.10",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"@types/auto-launch": "^5.0.1",
|
||||
"@types/counterpart": "^0.18.1",
|
||||
"@types/detect-libc": "^1.0.0",
|
||||
"@types/jest": "^28",
|
||||
"@types/minimist": "^1.2.1",
|
||||
"@types/mkdirp": "^1.0.2",
|
||||
"@types/pacote": "^11.1.1",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.6.0",
|
||||
"@typescript-eslint/parser": "^5.6.0",
|
||||
"allchange": "^1.0.6",
|
||||
"app-builder-lib": "^22.14.10",
|
||||
"asar": "^2.0.1",
|
||||
"babel-jest": "^28.1.3",
|
||||
"chokidar": "^3.5.2",
|
||||
"detect-libc": "^1.0.3",
|
||||
"electron": "^20",
|
||||
"electron-builder": "22.11.4",
|
||||
"electron-builder-squirrel-windows": "22.11.4",
|
||||
"electron-builder": "22.10.5",
|
||||
"electron-builder-squirrel-windows": "22.10.5",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-notarize": "^1.0.0",
|
||||
"eslint": "7.18.0",
|
||||
"eslint-config-google": "^0.14.0",
|
||||
"eslint-plugin-import": "^2.25.4",
|
||||
"eslint-plugin-matrix-org": "^0.4.0",
|
||||
"expect-playwright": "^0.8.0",
|
||||
"eslint": "7.3.1",
|
||||
"eslint-config-matrix-org": "^0.1.2",
|
||||
"find-npm-prefix": "^1.0.2",
|
||||
"fs-extra": "^8.1.0",
|
||||
"glob": "^7.1.6",
|
||||
"jest": "^28",
|
||||
"matrix-web-i18n": "^1.3.0",
|
||||
"mkdirp": "^1.0.3",
|
||||
"needle": "^2.5.0",
|
||||
"node-pre-gyp": "^0.15.0",
|
||||
"pacote": "^11.3.5",
|
||||
"playwright": "^1.25.0",
|
||||
"npm": "^6.14.11",
|
||||
"rimraf": "^3.0.2",
|
||||
"tar": "^6.1.2",
|
||||
"ts-jest": "^28.0.8",
|
||||
"ts-node": "^10.4.0",
|
||||
"typescript": "4.5.5"
|
||||
"semver": "^7.3.4",
|
||||
"tar": "^6.1.0"
|
||||
},
|
||||
"hakDependencies": {
|
||||
"matrix-seshat": "^2.3.3",
|
||||
"keytar": "^7.9.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/node": "16.11.38"
|
||||
"matrix-seshat": "^2.2.3",
|
||||
"keytar": "^5.6.0"
|
||||
},
|
||||
"build": {
|
||||
"appId": "im.riot.app",
|
||||
"asarUnpack": "**/*.node",
|
||||
"electronVersion": "12.0.2",
|
||||
"files": [
|
||||
"package.json",
|
||||
{
|
||||
"from": ".hak/hakModules",
|
||||
"to": "node_modules"
|
||||
},
|
||||
"lib/**"
|
||||
"src/**"
|
||||
],
|
||||
"extraResources": [
|
||||
{
|
||||
@@ -133,15 +91,11 @@
|
||||
"darkModeSupport": true
|
||||
},
|
||||
"win": {
|
||||
"target": [
|
||||
"squirrel",
|
||||
"msi"
|
||||
],
|
||||
"target": {
|
||||
"target": "squirrel"
|
||||
},
|
||||
"sign": "scripts/electron_winSign"
|
||||
},
|
||||
"msi": {
|
||||
"perMachine": true
|
||||
},
|
||||
"directories": {
|
||||
"output": "dist"
|
||||
},
|
||||
@@ -155,14 +109,5 @@
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"jest": {
|
||||
"testEnvironment": "node",
|
||||
"testMatch": [
|
||||
"<rootDir>/test/**/*-test.[jt]s?(x)"
|
||||
],
|
||||
"setupFilesAfterEnv": [
|
||||
"expect-playwright"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#!/bin/bash
|
||||
#
|
||||
# Script to perform a release of element-desktop.
|
||||
#
|
||||
# Requires githib-changelog-generator; to install, do
|
||||
# pip install git+https://github.com/matrix-org/github-changelog-generator.git
|
||||
|
||||
set -e
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
cd `dirname $0`
|
||||
|
||||
./node_modules/matrix-js-sdk/release.sh "$@"
|
||||
./node_modules/matrix-js-sdk/release.sh -n -z "$@"
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
signing_id: releases@riot.im
|
||||
subprojects:
|
||||
element-web:
|
||||
includeByDefault: true
|
||||
# Because element-web is not in our dependencies, but the versions
|
||||
# follow those of this project (well, vice-versa really)
|
||||
mirrorVersion: true
|
||||
|
||||
5
scripts/ci/install-deps.sh
Executable file
5
scripts/ci/install-deps.sh
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -ex
|
||||
|
||||
yarn install $@
|
||||
@@ -1,127 +0,0 @@
|
||||
#!/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]] = {};
|
||||
} else if (typeof obj === "string") {
|
||||
// This is a transitional edge case if a string went from singular to pluralised and both still remain
|
||||
// in the translation json file. Use the singular translation as `other` and merge pluralisation atop.
|
||||
obj = outTrs[keyParts[0]] = {
|
||||
"other": inTrs[key],
|
||||
};
|
||||
console.warn("Found entry in i18n file in both singular and pluralised form", keyParts[0]);
|
||||
}
|
||||
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,6 +1,5 @@
|
||||
const { notarize } = require('electron-notarize');
|
||||
|
||||
let warned = false;
|
||||
exports.default = async function(context) {
|
||||
const { electronPlatformName, appOutDir } = context;
|
||||
const appId = context.packager.info.appInfo.id;
|
||||
@@ -12,13 +11,10 @@ exports.default = async function(context) {
|
||||
// from the keychain, so we need to get it from the environment.
|
||||
const userId = process.env.NOTARIZE_APPLE_ID;
|
||||
if (userId === undefined) {
|
||||
if (!warned) {
|
||||
console.log("*************************************");
|
||||
console.log("* NOTARIZE_APPLE_ID is not set. *");
|
||||
console.log("* This build will NOT be notarised. *");
|
||||
console.log("*************************************");
|
||||
warned = true;
|
||||
}
|
||||
console.log("*************************************");
|
||||
console.log("* NOTARIZE_APPLE_ID is not set. *");
|
||||
console.log("* This build will NOT be notarised. *");
|
||||
console.log("*************************************");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -46,19 +46,15 @@ function computeSignToolArgs(options, keyContainer) {
|
||||
return args;
|
||||
}
|
||||
|
||||
let warned = false;
|
||||
exports.default = async function(options) {
|
||||
const keyContainer = process.env.SIGNING_KEY_CONTAINER;
|
||||
if (keyContainer === undefined) {
|
||||
if (!warned) {
|
||||
console.warn(
|
||||
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +
|
||||
"! Skipping Windows signing. !\n" +
|
||||
"! SIGNING_KEY_CONTAINER not defined. !\n" +
|
||||
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
|
||||
);
|
||||
warned = true;
|
||||
}
|
||||
console.warn(
|
||||
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n" +
|
||||
"! Skipping Windows signing. !\n" +
|
||||
"! SIGNING_KEY_CONTAINER not defined. !\n" +
|
||||
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
115
scripts/fetch-package.js
Normal file → Executable file
115
scripts/fetch-package.js
Normal file → Executable file
@@ -10,13 +10,75 @@ const asar = require('asar');
|
||||
const needle = require('needle');
|
||||
|
||||
const riotDesktopPackageJson = require('../package.json');
|
||||
const { setPackageVersion } = require('./set-version.js');
|
||||
|
||||
const PUB_KEY_URL = "https://packages.riot.im/element-release-key.asc";
|
||||
const PACKAGE_URL_PREFIX = "https://github.com/vector-im/element-web/releases/download/";
|
||||
const DEVELOP_TGZ_URL = "https://vector-im.github.io/element-web/develop.tar.gz";
|
||||
const ASAR_PATH = 'webapp.asar';
|
||||
|
||||
const {setPackageVersion, setDebVersion} = require('./set-version.js');
|
||||
|
||||
async function getLatestDevelopUrl(bkToken) {
|
||||
const buildsResult = await needle('get',
|
||||
"https://api.buildkite.com/v2/organizations/matrix-dot-org/pipelines/element-web/builds",
|
||||
{
|
||||
branch: 'develop',
|
||||
state: 'passed',
|
||||
per_page: 1,
|
||||
},
|
||||
{
|
||||
headers: {
|
||||
authorization: "Bearer " + bkToken,
|
||||
},
|
||||
},
|
||||
);
|
||||
const latestBuild = buildsResult.body[0];
|
||||
console.log("Latest build is " + latestBuild.number);
|
||||
let artifactUrl;
|
||||
for (const job of latestBuild.jobs) {
|
||||
// Strip any colon-form emoji from the build name
|
||||
if (job.name && job.name.replace(/:\w*:\s*/, '') === 'Package') {
|
||||
artifactUrl = job.artifacts_url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (artifactUrl === undefined) {
|
||||
throw new Error("Couldn't find artifact URL - has the name of the package job changed?");
|
||||
}
|
||||
|
||||
const artifactsResult = await needle('get', artifactUrl, {},
|
||||
{
|
||||
headers: {
|
||||
authorization: "Bearer " + bkToken,
|
||||
},
|
||||
},
|
||||
);
|
||||
let dlUrl;
|
||||
let dlFilename;
|
||||
for (const artifact of artifactsResult.body) {
|
||||
if (artifact.filename && /^element-.*\.tar.gz$/.test(artifact.filename)) {
|
||||
dlUrl = artifact.download_url;
|
||||
dlFilename = artifact.filename;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (dlUrl === undefined) {
|
||||
throw new Error("Couldn't find artifact download URL - has the artifact filename changed?");
|
||||
}
|
||||
console.log("Fetching artifact URL...");
|
||||
const dlResult = await needle('get', dlUrl, {},
|
||||
{
|
||||
headers: {
|
||||
authorization: "Bearer " + bkToken,
|
||||
},
|
||||
// This URL will give us a Location header, but will also give us
|
||||
// a JSON object with the direct URL. We'll take the URL and pass it
|
||||
// back, then we can easily support specifying a URL directly.
|
||||
follow_max: 0,
|
||||
},
|
||||
);
|
||||
return [dlFilename, dlResult.body.url];
|
||||
}
|
||||
|
||||
async function downloadToFile(url, filename) {
|
||||
console.log("Downloading " + url + "...");
|
||||
|
||||
@@ -87,21 +149,24 @@ async function main() {
|
||||
|
||||
if (targetVersion === undefined) {
|
||||
targetVersion = 'v' + riotDesktopPackageJson.version;
|
||||
} else if (targetVersion !== 'develop') {
|
||||
setVersion = true; // version was specified
|
||||
}
|
||||
|
||||
if (targetVersion === 'develop') {
|
||||
filename = 'develop.tar.gz';
|
||||
url = DEVELOP_TGZ_URL;
|
||||
verify = false; // develop builds aren't signed
|
||||
} else if (targetVersion.includes("://")) {
|
||||
filename = targetVersion.substring(targetVersion.lastIndexOf("/") + 1);
|
||||
url = targetVersion;
|
||||
verify = false; // manually verified
|
||||
} else {
|
||||
filename = `element-${targetVersion}.tar.gz`;
|
||||
filename = 'element-' + targetVersion + '.tar.gz';
|
||||
url = PACKAGE_URL_PREFIX + targetVersion + '/' + filename;
|
||||
} else if (targetVersion === 'develop') {
|
||||
const buildKiteApiKey = process.env.BUILDKITE_API_KEY;
|
||||
if (buildKiteApiKey === undefined) {
|
||||
console.log("Set BUILDKITE_API_KEY to fetch latest develop version");
|
||||
console.log(
|
||||
"Sorry - Buildkite's API requires authentication to access builds, " +
|
||||
"even if those builds are accessible on the web with no auth.",
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
[filename, url] = await getLatestDevelopUrl(buildKiteApiKey);
|
||||
verify = false; // develop builds aren't signed
|
||||
} else {
|
||||
filename = 'element-' + targetVersion + '.tar.gz';
|
||||
url = PACKAGE_URL_PREFIX + targetVersion + '/' + filename;
|
||||
setVersion = true;
|
||||
}
|
||||
|
||||
const haveGpg = await new Promise((resolve) => {
|
||||
@@ -143,7 +208,7 @@ async function main() {
|
||||
}
|
||||
|
||||
let haveDeploy = false;
|
||||
let expectedDeployDir = path.join(deployDir, path.basename(filename).replace(/\.tar\.gz/, ''));
|
||||
const expectedDeployDir = path.join(deployDir, path.basename(filename).replace(/\.tar\.gz/, ''));
|
||||
try {
|
||||
await fs.opendir(expectedDeployDir);
|
||||
console.log(expectedDeployDir + "already exists");
|
||||
@@ -192,12 +257,6 @@ async function main() {
|
||||
await tar.x({
|
||||
file: outPath,
|
||||
cwd: deployDir,
|
||||
onentry: entry => {
|
||||
// Find the appropriate extraction path, only needed for `develop` where the dir name is unknown
|
||||
if (entry.type === "Directory" && !path.join(deployDir, entry.path).startsWith(expectedDeployDir)) {
|
||||
expectedDeployDir = path.join(deployDir, entry.path);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -221,17 +280,13 @@ async function main() {
|
||||
await asar.createPackage(expectedDeployDir, ASAR_PATH);
|
||||
|
||||
if (setVersion) {
|
||||
const semVer = fs.readFileSync(path.join(expectedDeployDir, "version"), "utf-8").trim();
|
||||
const semVer = targetVersion.slice(1);
|
||||
console.log("Updating version to " + semVer);
|
||||
await setPackageVersion(semVer);
|
||||
await setDebVersion(semVer);
|
||||
}
|
||||
|
||||
console.log("Done!");
|
||||
}
|
||||
|
||||
main().then((ret) => {
|
||||
process.exit(ret);
|
||||
}).catch(e => {
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
});
|
||||
main().then((ret) => process.exit(ret)).catch(e => process.exit(1));
|
||||
|
||||
@@ -17,8 +17,7 @@ clone() {
|
||||
if [ -n "$branch" ]
|
||||
then
|
||||
echo "Trying to use $org/$repo#$branch"
|
||||
# Disable auth prompts: https://serverfault.com/a/665959
|
||||
GIT_TERMINAL_PROMPT=0 git clone https://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0
|
||||
git clone git://github.com/$org/$repo.git $repo --branch "$branch" --depth 1 && exit 0
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,8 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { DependencyInfo } from "./dep";
|
||||
import HakEnv from "./hakEnv";
|
||||
|
||||
export default async function build(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
async function build(hakEnv, moduleInfo) {
|
||||
await moduleInfo.scripts.build(hakEnv, moduleInfo);
|
||||
}
|
||||
|
||||
module.exports = build;
|
||||
@@ -14,11 +14,10 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import { DependencyInfo } from "./dep";
|
||||
import HakEnv from "./hakEnv";
|
||||
|
||||
export default async function check(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
async function check(hakEnv, moduleInfo) {
|
||||
if (moduleInfo.scripts.check) {
|
||||
await moduleInfo.scripts.check(hakEnv, moduleInfo);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = check;
|
||||
@@ -14,15 +14,13 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import rimraf from 'rimraf';
|
||||
const path = require('path');
|
||||
|
||||
import { DependencyInfo } from './dep';
|
||||
import HakEnv from './hakEnv';
|
||||
const rimraf = require('rimraf');
|
||||
|
||||
export default async function clean(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
rimraf(moduleInfo.moduleDotHakDir, (err: Error) => {
|
||||
async function clean(hakEnv, moduleInfo) {
|
||||
await new Promise((resolve, reject) => {
|
||||
rimraf(moduleInfo.moduleDotHakDir, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -31,8 +29,8 @@ export default async function clean(hakEnv: HakEnv, moduleInfo: DependencyInfo):
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
rimraf(path.join(hakEnv.dotHakDir, 'links', moduleInfo.name), (err: Error) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
rimraf(path.join(hakEnv.dotHakDir, 'links', moduleInfo.name), (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -41,8 +39,8 @@ export default async function clean(hakEnv: HakEnv, moduleInfo: DependencyInfo):
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
rimraf(path.join(hakEnv.projectRoot, 'node_modules', moduleInfo.name), (err: Error) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
rimraf(path.join(hakEnv.projectRoot, 'node_modules', moduleInfo.name), (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
@@ -51,3 +49,5 @@ export default async function clean(hakEnv: HakEnv, moduleInfo: DependencyInfo):
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = clean;
|
||||
67
scripts/hak/copy.js
Normal file
67
scripts/hak/copy.js
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
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 path = require('path');
|
||||
const fsProm = require('fs').promises;
|
||||
|
||||
const rimraf = require('rimraf');
|
||||
const glob = require('glob');
|
||||
const mkdirp = require('mkdirp');
|
||||
|
||||
async function copy(hakEnv, moduleInfo) {
|
||||
if (moduleInfo.cfg.prune) {
|
||||
console.log("Removing " + moduleInfo.cfg.prune + " from " + moduleInfo.moduleOutDir);
|
||||
// rimraf doesn't have a 'cwd' option: it always uses process.cwd()
|
||||
// (and if you set glob.cwd it just breaks because it can't find the files)
|
||||
const oldCwd = process.cwd();
|
||||
try {
|
||||
process.chdir(moduleInfo.moduleOutDir);
|
||||
await new Promise((resolve, reject) => {
|
||||
rimraf(moduleInfo.cfg.prune, {}, err => {
|
||||
err ? reject(err) : resolve();
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
process.chdir(oldCwd);
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleInfo.cfg.copy) {
|
||||
console.log(
|
||||
"Copying " + moduleInfo.cfg.prune + " from " +
|
||||
moduleInfo.moduleOutDir + " to " + moduleInfo.moduleOutDir,
|
||||
);
|
||||
const files = await new Promise(async (resolve, reject) => {
|
||||
glob(moduleInfo.cfg.copy, {
|
||||
nosort: true,
|
||||
silent: true,
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
}, (err, files) => {
|
||||
err ? reject(err) : resolve(files);
|
||||
});
|
||||
});
|
||||
for (const f of files) {
|
||||
console.log("\t" + f);
|
||||
const src = path.join(moduleInfo.moduleBuildDir, f);
|
||||
const dst = path.join(moduleInfo.moduleOutDir, f);
|
||||
|
||||
await mkdirp(path.dirname(dst));
|
||||
await fsProm.copyFile(src, dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = copy;
|
||||
@@ -1,101 +0,0 @@
|
||||
/*
|
||||
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.
|
||||
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 path from 'path';
|
||||
import fsProm from 'fs/promises';
|
||||
import childProcess from 'child_process';
|
||||
import rimraf from 'rimraf';
|
||||
import glob from 'glob';
|
||||
import mkdirp from 'mkdirp';
|
||||
|
||||
import HakEnv from './hakEnv';
|
||||
import { DependencyInfo } from './dep';
|
||||
|
||||
export default async function copy(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
if (moduleInfo.cfg.prune) {
|
||||
console.log("Removing " + moduleInfo.cfg.prune + " from " + moduleInfo.moduleOutDir);
|
||||
// rimraf doesn't have a 'cwd' option: it always uses process.cwd()
|
||||
// (and if you set glob.cwd it just breaks because it can't find the files)
|
||||
const oldCwd = process.cwd();
|
||||
try {
|
||||
await mkdirp(moduleInfo.moduleOutDir);
|
||||
process.chdir(moduleInfo.moduleOutDir);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
rimraf(moduleInfo.cfg.prune, {}, err => {
|
||||
err ? reject(err) : resolve();
|
||||
});
|
||||
});
|
||||
} finally {
|
||||
process.chdir(oldCwd);
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleInfo.cfg.copy) {
|
||||
// If there are multiple moduleBuildDirs, singular moduleBuildDir
|
||||
// is the same as moduleBuildDirs[0], so we're just listing the contents
|
||||
// of the first one.
|
||||
const files = await new Promise<string[]>((resolve, reject) => {
|
||||
glob(moduleInfo.cfg.copy, {
|
||||
nosort: true,
|
||||
silent: true,
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
}, (err, files) => {
|
||||
err ? reject(err) : resolve(files);
|
||||
});
|
||||
});
|
||||
|
||||
if (moduleInfo.moduleBuildDirs.length > 1) {
|
||||
if (!hakEnv.isMac()) {
|
||||
console.error(
|
||||
"You asked me to copy multiple targets but I've only been taught " +
|
||||
"how to do that on macOS.",
|
||||
);
|
||||
throw new Error("Can't copy multiple targets on this platform");
|
||||
}
|
||||
|
||||
for (const f of files) {
|
||||
const components = moduleInfo.moduleBuildDirs.map(dir => path.join(dir, f));
|
||||
const dst = path.join(moduleInfo.moduleOutDir, f);
|
||||
|
||||
await mkdirp(path.dirname(dst));
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
childProcess.execFile('lipo',
|
||||
['-create', '-output', dst, ...components], (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(
|
||||
"Copying files from " +
|
||||
moduleInfo.moduleBuildDir + " to " + moduleInfo.moduleOutDir,
|
||||
);
|
||||
for (const f of files) {
|
||||
console.log("\t" + f);
|
||||
const src = path.join(moduleInfo.moduleBuildDir, f);
|
||||
const dst = path.join(moduleInfo.moduleOutDir, f);
|
||||
|
||||
await mkdirp(path.dirname(dst));
|
||||
await fsProm.copyFile(src, dst);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import HakEnv from "./hakEnv";
|
||||
|
||||
export interface DependencyInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
cfg: Record<string, any>;
|
||||
moduleHakDir: string;
|
||||
moduleDotHakDir: string;
|
||||
moduleTargetDotHakDir: string;
|
||||
moduleBuildDir: string;
|
||||
moduleBuildDirs: string[];
|
||||
moduleOutDir: string;
|
||||
nodeModuleBinDir: string;
|
||||
depPrefix: string;
|
||||
scripts: Record<string, (hakEnv: HakEnv, moduleInfo: DependencyInfo) => Promise<void> >;
|
||||
}
|
||||
125
scripts/hak/fetch.js
Normal file
125
scripts/hak/fetch.js
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
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 path = require('path');
|
||||
const url = require('url');
|
||||
const fsProm = require('fs').promises;
|
||||
const childProcess = require('child_process');
|
||||
|
||||
const npm = require('npm');
|
||||
const semver = require('semver');
|
||||
const needle = require('needle');
|
||||
const mkdirp = require('mkdirp');
|
||||
const tar = require('tar');
|
||||
|
||||
async function fetch(hakEnv, moduleInfo) {
|
||||
let haveModuleBuildDir;
|
||||
try {
|
||||
const stats = await fsProm.stat(moduleInfo.moduleBuildDir);
|
||||
haveModuleBuildDir = stats.isDirectory();
|
||||
} catch (e) {
|
||||
haveModuleBuildDir = false;
|
||||
}
|
||||
|
||||
if (haveModuleBuildDir) return;
|
||||
|
||||
await new Promise((resolve) => {
|
||||
npm.load({'loglevel': 'silent'}, resolve);
|
||||
});
|
||||
|
||||
console.log("Fetching " + moduleInfo.name + " at version " + moduleInfo.version);
|
||||
const versions = await new Promise((resolve, reject) => {
|
||||
npm.view([
|
||||
moduleInfo.name + '@' + moduleInfo.version,
|
||||
'dist.tarball',
|
||||
(err, versions) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(versions);
|
||||
}
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
const orderedVersions = Object.keys(versions);
|
||||
semver.sort(orderedVersions);
|
||||
|
||||
console.log("Resolved version " + orderedVersions[0] + " for " + moduleInfo.name);
|
||||
|
||||
const tarballUrl = versions[orderedVersions[0]]['dist.tarball'];
|
||||
|
||||
await mkdirp(moduleInfo.moduleDotHakDir);
|
||||
|
||||
const parsedUrl = url.parse(tarballUrl);
|
||||
const tarballFile = path.join(moduleInfo.moduleDotHakDir, path.basename(parsedUrl.path));
|
||||
|
||||
let haveTarball;
|
||||
try {
|
||||
await fsProm.stat(tarballFile);
|
||||
haveTarball = true;
|
||||
} catch (e) {
|
||||
haveTarball = false;
|
||||
}
|
||||
if (!haveTarball) {
|
||||
console.log("Downloading " + tarballUrl);
|
||||
await needle('get', tarballUrl, { output: tarballFile });
|
||||
} else {
|
||||
console.log(tarballFile + " already exists.");
|
||||
}
|
||||
|
||||
await mkdirp(moduleInfo.moduleBuildDir);
|
||||
|
||||
await tar.x({
|
||||
file: tarballFile,
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
strip: 1,
|
||||
});
|
||||
|
||||
console.log("Running yarn install in " + moduleInfo.moduleBuildDir);
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
hakEnv.isWin() ? 'yarn.cmd' : 'yarn',
|
||||
['install', '--ignore-scripts'],
|
||||
{
|
||||
stdio: 'inherit',
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
},
|
||||
);
|
||||
proc.on('exit', code => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// also extract another copy to the output directory at this point
|
||||
// nb. we do not yarn install in the output copy: we could install in
|
||||
// production mode to get only runtime dependencies and not devDependencies,
|
||||
// but usually native modules come with dependencies that are needed for
|
||||
// building/fetching the native modules (eg. node-pre-gyp) rather than
|
||||
// actually used at runtime: we do not want to bundle these into our app.
|
||||
// We therefore just install no dependencies at all, and accept that any
|
||||
// actual runtime dependencies will have to be added to the main app's
|
||||
// dependencies. We can't tell what dependencies are real runtime deps
|
||||
// and which are just used for native module building.
|
||||
await mkdirp(moduleInfo.moduleOutDir);
|
||||
await tar.x({
|
||||
file: tarballFile,
|
||||
cwd: moduleInfo.moduleOutDir,
|
||||
strip: 1,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = fetch;
|
||||
@@ -1,70 +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.
|
||||
*/
|
||||
|
||||
import fsProm from 'fs/promises';
|
||||
import childProcess from 'child_process';
|
||||
import pacote from 'pacote';
|
||||
|
||||
import HakEnv from './hakEnv';
|
||||
import { DependencyInfo } from './dep';
|
||||
|
||||
export default async function fetch(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
let haveModuleBuildDir;
|
||||
try {
|
||||
const stats = await fsProm.stat(moduleInfo.moduleBuildDir);
|
||||
haveModuleBuildDir = stats.isDirectory();
|
||||
} catch (e) {
|
||||
haveModuleBuildDir = false;
|
||||
}
|
||||
|
||||
if (haveModuleBuildDir) return;
|
||||
|
||||
console.log("Fetching " + moduleInfo.name + "@" + moduleInfo.version);
|
||||
|
||||
const packumentCache = new Map();
|
||||
await pacote.extract(`${moduleInfo.name}@${moduleInfo.version}`, moduleInfo.moduleBuildDir, {
|
||||
packumentCache,
|
||||
});
|
||||
|
||||
console.log("Running yarn install in " + moduleInfo.moduleBuildDir);
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
const proc = childProcess.spawn(
|
||||
hakEnv.isWin() ? 'yarn.cmd' : 'yarn',
|
||||
['install', '--ignore-scripts'],
|
||||
{
|
||||
stdio: 'inherit',
|
||||
cwd: moduleInfo.moduleBuildDir,
|
||||
},
|
||||
);
|
||||
proc.on('exit', code => {
|
||||
code ? reject(code) : resolve();
|
||||
});
|
||||
});
|
||||
|
||||
// also extract another copy to the output directory at this point
|
||||
// nb. we do not yarn install in the output copy: we could install in
|
||||
// production mode to get only runtime dependencies and not devDependencies,
|
||||
// but usually native modules come with dependencies that are needed for
|
||||
// building/fetching the native modules (eg. node-pre-gyp) rather than
|
||||
// actually used at runtime: we do not want to bundle these into our app.
|
||||
// We therefore just install no dependencies at all, and accept that any
|
||||
// actual runtime dependencies will have to be added to the main app's
|
||||
// dependencies. We can't tell what dependencies are real runtime deps
|
||||
// and which are just used for native module building.
|
||||
await pacote.extract(`${moduleInfo.name}@${moduleInfo.version}`, moduleInfo.moduleOutDir, {
|
||||
packumentCache,
|
||||
});
|
||||
}
|
||||
@@ -14,14 +14,13 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import mkdirp from 'mkdirp';
|
||||
const mkdirp = require('mkdirp');
|
||||
|
||||
import { DependencyInfo } from './dep';
|
||||
import HakEnv from './hakEnv';
|
||||
|
||||
export default async function fetchDeps(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
async function fetchDeps(hakEnv, moduleInfo) {
|
||||
await mkdirp(moduleInfo.moduleDotHakDir);
|
||||
if (moduleInfo.scripts.fetchDeps) {
|
||||
await moduleInfo.scripts.fetchDeps(hakEnv, moduleInfo);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = fetchDeps;
|
||||
115
scripts/hak/hakEnv.js
Normal file
115
scripts/hak/hakEnv.js
Normal file
@@ -0,0 +1,115 @@
|
||||
/*
|
||||
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 path = require('path');
|
||||
const os = require('os');
|
||||
|
||||
const nodePreGypVersioning = require('node-pre-gyp/lib/util/versioning');
|
||||
|
||||
function getElectronVersion(packageJson) {
|
||||
// should we pick the version of an installed electron
|
||||
// dependency, and if so, before or after electronVersion?
|
||||
if (packageJson.build && packageJson.build.electronVersion) {
|
||||
return packageJson.build.electronVersion;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getRuntime(packageJson) {
|
||||
const electronVersion = getElectronVersion(packageJson);
|
||||
return electronVersion ? 'electron' : 'node-webkit';
|
||||
}
|
||||
|
||||
function getTarget(packageJson) {
|
||||
const electronVersion = getElectronVersion(packageJson);
|
||||
if (electronVersion) {
|
||||
return electronVersion;
|
||||
} else {
|
||||
return process.version.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
Object.assign(this, {
|
||||
// what we're targeting
|
||||
runtime: getRuntime(packageJson),
|
||||
target: getTarget(packageJson),
|
||||
platform: process.platform,
|
||||
arch: detectArch(),
|
||||
|
||||
// paths
|
||||
projectRoot: prefix,
|
||||
dotHakDir: path.join(prefix, '.hak'),
|
||||
});
|
||||
}
|
||||
|
||||
getRuntimeAbi() {
|
||||
return nodePreGypVersioning.get_runtime_abi(
|
||||
this.runtime,
|
||||
this.target,
|
||||
);
|
||||
}
|
||||
|
||||
// {node_abi}-{platform}-{arch}
|
||||
getNodeTriple() {
|
||||
return this.getRuntimeAbi() + '-' + this.platform + '-' + this.arch;
|
||||
}
|
||||
|
||||
isWin() {
|
||||
return this.platform === 'win32';
|
||||
}
|
||||
|
||||
isMac() {
|
||||
return this.platform === 'darwin';
|
||||
}
|
||||
|
||||
isLinux() {
|
||||
return this.platform === 'linux';
|
||||
}
|
||||
|
||||
makeGypEnv() {
|
||||
return Object.assign({}, process.env, {
|
||||
npm_config_target: this.target,
|
||||
npm_config_arch: this.arch,
|
||||
npm_config_target_arch: this.arch,
|
||||
npm_config_disturl: 'https://atom.io/download/electron',
|
||||
npm_config_runtime: this.runtime,
|
||||
npm_config_build_from_source: true,
|
||||
npm_config_devdir: path.join(os.homedir(), ".electron-gyp"),
|
||||
});
|
||||
}
|
||||
|
||||
getNodeModuleBin(name) {
|
||||
return path.join(this.projectRoot, 'node_modules', '.bin', name);
|
||||
}
|
||||
};
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
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.
|
||||
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 path from 'path';
|
||||
import os from 'os';
|
||||
import nodePreGypVersioning from "node-pre-gyp/lib/util/versioning";
|
||||
import { getElectronVersion } from "app-builder-lib/out/electron/electronVersion";
|
||||
|
||||
import { Arch, Target, TARGETS, getHost, isHostId, TargetId } from './target';
|
||||
|
||||
async function getRuntime(projectRoot: string): Promise<string> {
|
||||
const electronVersion = await getElectronVersion(projectRoot);
|
||||
return electronVersion ? 'electron' : 'node-webkit';
|
||||
}
|
||||
|
||||
async function getRuntimeVersion(projectRoot: string): Promise<string> {
|
||||
const electronVersion = await getElectronVersion(projectRoot);
|
||||
if (electronVersion) {
|
||||
return electronVersion;
|
||||
} else {
|
||||
return process.version.substr(1);
|
||||
}
|
||||
}
|
||||
|
||||
export default class HakEnv {
|
||||
public readonly target: Target;
|
||||
public runtime: string;
|
||||
public runtimeVersion: string;
|
||||
public dotHakDir: string;
|
||||
|
||||
constructor(public readonly projectRoot: string, targetId: TargetId | null) {
|
||||
if (targetId) {
|
||||
this.target = TARGETS[targetId];
|
||||
} else {
|
||||
this.target = getHost();
|
||||
}
|
||||
|
||||
if (!this.target) {
|
||||
throw new Error(`Unknown target ${targetId}!`);
|
||||
}
|
||||
this.dotHakDir = path.join(this.projectRoot, '.hak');
|
||||
}
|
||||
|
||||
public async init() {
|
||||
this.runtime = await getRuntime(this.projectRoot);
|
||||
this.runtimeVersion = await getRuntimeVersion(this.projectRoot);
|
||||
}
|
||||
|
||||
public getRuntimeAbi(): string {
|
||||
return nodePreGypVersioning.get_runtime_abi(
|
||||
this.runtime,
|
||||
this.runtimeVersion,
|
||||
);
|
||||
}
|
||||
|
||||
// {node_abi}-{platform}-{arch}
|
||||
public getNodeTriple(): string {
|
||||
return this.getRuntimeAbi() + '-' + this.target.platform + '-' + this.target.arch;
|
||||
}
|
||||
|
||||
public getTargetId(): TargetId {
|
||||
return this.target.id;
|
||||
}
|
||||
|
||||
public isWin(): boolean {
|
||||
return this.target.platform === 'win32';
|
||||
}
|
||||
|
||||
public isMac(): boolean {
|
||||
return this.target.platform === 'darwin';
|
||||
}
|
||||
|
||||
public isLinux(): boolean {
|
||||
return this.target.platform === 'linux';
|
||||
}
|
||||
|
||||
public getTargetArch(): Arch {
|
||||
return this.target.arch;
|
||||
}
|
||||
|
||||
public isHost(): boolean {
|
||||
return isHostId(this.target.id);
|
||||
}
|
||||
|
||||
public makeGypEnv(): Record<string, string> {
|
||||
return Object.assign({}, process.env, {
|
||||
npm_config_arch: this.target.arch,
|
||||
npm_config_target_arch: this.target.arch,
|
||||
npm_config_disturl: 'https://electronjs.org/headers',
|
||||
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"),
|
||||
});
|
||||
}
|
||||
|
||||
public getNodeModuleBin(name: string): string {
|
||||
return path.join(this.projectRoot, 'node_modules', '.bin', name);
|
||||
}
|
||||
|
||||
public wantsStaticSqlCipherUnix(): boolean {
|
||||
return this.isMac() || process.env.SQLCIPHER_STATIC == '1';
|
||||
}
|
||||
|
||||
public wantsStaticSqlCipher(): boolean {
|
||||
return this.isWin() || this.wantsStaticSqlCipherUnix();
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Copyright 2020-2021 The Matrix.org Foundation C.I.C.
|
||||
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.
|
||||
@@ -14,12 +14,11 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import findNpmPrefix from 'find-npm-prefix';
|
||||
const path = require('path');
|
||||
|
||||
import HakEnv from './hakEnv';
|
||||
import { TargetId } from './target';
|
||||
import { DependencyInfo } from './dep';
|
||||
const findNpmPrefix = require('find-npm-prefix');
|
||||
|
||||
const HakEnv = require('./hakEnv');
|
||||
|
||||
const GENERALCOMMANDS = [
|
||||
'target',
|
||||
@@ -36,13 +35,6 @@ const MODULECOMMANDS = [
|
||||
'clean',
|
||||
];
|
||||
|
||||
// Shortcuts for multiple commands at once (useful for building universal binaries
|
||||
// because you can run the fetch/fetchDeps/build for each arch and then copy/link once)
|
||||
const METACOMMANDS = {
|
||||
'fetchandbuild': ['check', 'fetch', 'fetchDeps', 'build'],
|
||||
'copyandlink': ['copy', 'link'],
|
||||
};
|
||||
|
||||
// Scripts valid in a hak.json 'scripts' section
|
||||
const HAKSCRIPTS = [
|
||||
'check',
|
||||
@@ -61,36 +53,15 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const targetIds: TargetId[] = [];
|
||||
// Apply `--target <target>` option if specified
|
||||
// Can be specified multiple times for the copy command to bundle
|
||||
// multiple archs into a single universal output module)
|
||||
while (true) { // eslint-disable-line no-constant-condition
|
||||
const targetIndex = process.argv.indexOf('--target');
|
||||
if (targetIndex === -1) break;
|
||||
const hakEnv = new HakEnv(prefix, packageJson);
|
||||
|
||||
if ((targetIndex + 1) >= process.argv.length) {
|
||||
console.error("--target option specified without a target");
|
||||
process.exit(1);
|
||||
}
|
||||
// Extract target ID and remove from args
|
||||
targetIds.push(process.argv.splice(targetIndex, 2)[1] as TargetId);
|
||||
}
|
||||
|
||||
const hakEnvs = targetIds.map(tid => new HakEnv(prefix, tid));
|
||||
if (hakEnvs.length == 0) hakEnvs.push(new HakEnv(prefix, null));
|
||||
for (const h of hakEnvs) {
|
||||
await h.init();
|
||||
}
|
||||
const hakEnv = hakEnvs[0];
|
||||
|
||||
const deps: Record<string, DependencyInfo> = {};
|
||||
const deps = {};
|
||||
|
||||
const hakDepsCfg = packageJson.hakDependencies || {};
|
||||
|
||||
for (const dep of Object.keys(hakDepsCfg)) {
|
||||
const hakJsonPath = path.join(prefix, 'hak', dep, 'hak.json');
|
||||
let hakJson: Record<string, any>;
|
||||
let hakJson;
|
||||
try {
|
||||
hakJson = await require(hakJsonPath);
|
||||
} catch (e) {
|
||||
@@ -104,42 +75,27 @@ async function main() {
|
||||
cfg: hakJson,
|
||||
moduleHakDir: path.join(prefix, 'hak', dep),
|
||||
moduleDotHakDir: path.join(hakEnv.dotHakDir, dep),
|
||||
moduleTargetDotHakDir: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId()),
|
||||
moduleBuildDir: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId(), 'build'),
|
||||
moduleBuildDirs: hakEnvs.map(h => path.join(h.dotHakDir, dep, h.getTargetId(), 'build')),
|
||||
moduleBuildDir: path.join(hakEnv.dotHakDir, dep, 'build'),
|
||||
moduleOutDir: path.join(hakEnv.dotHakDir, 'hakModules', dep),
|
||||
nodeModuleBinDir: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId(), 'build', 'node_modules', '.bin'),
|
||||
depPrefix: path.join(hakEnv.dotHakDir, dep, hakEnv.getTargetId(), 'opt'),
|
||||
nodeModuleBinDir: path.join(hakEnv.dotHakDir, dep, 'build', 'node_modules', '.bin'),
|
||||
depPrefix: path.join(hakEnv.dotHakDir, dep, 'opt'),
|
||||
scripts: {},
|
||||
};
|
||||
|
||||
for (const s of HAKSCRIPTS) {
|
||||
if (hakJson.scripts && hakJson.scripts[s]) {
|
||||
const scriptModule = await import(path.join(prefix, 'hak', dep, hakJson.scripts[s]));
|
||||
if (scriptModule.__esModule) {
|
||||
deps[dep].scripts[s] = scriptModule.default;
|
||||
} else {
|
||||
deps[dep].scripts[s] = scriptModule;
|
||||
}
|
||||
deps[dep].scripts[s] = require(path.join(prefix, 'hak', dep, hakJson.scripts[s]));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let cmds: string[];
|
||||
let cmds;
|
||||
if (process.argv.length < 3) {
|
||||
cmds = ['check', 'fetch', 'fetchDeps', 'build', 'copy', 'link'];
|
||||
} else if (METACOMMANDS[process.argv[2]]) {
|
||||
cmds = METACOMMANDS[process.argv[2]];
|
||||
} else {
|
||||
cmds = [process.argv[2]];
|
||||
}
|
||||
|
||||
if (hakEnvs.length > 1 && cmds.some(c => !['copy', 'link'].includes(c))) {
|
||||
// We allow link here too for convenience because it's completely arch independent
|
||||
console.error("Multiple targets only supported with the copy command");
|
||||
return;
|
||||
}
|
||||
|
||||
let modules = process.argv.slice(3);
|
||||
if (modules.length === 0) modules = Object.keys(deps);
|
||||
|
||||
@@ -160,7 +116,7 @@ async function main() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const cmdFunc = (await import('./' + cmd)).default;
|
||||
const cmdFunc = require('./' + cmd);
|
||||
|
||||
for (const mod of modules) {
|
||||
const depInfo = deps[mod];
|
||||
@@ -177,7 +133,4 @@ async function main() {
|
||||
}
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
main().catch(() => process.exit(1));
|
||||
@@ -14,15 +14,12 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
import path from 'path';
|
||||
import os from 'os';
|
||||
import fsProm from 'fs/promises';
|
||||
import childProcess from 'child_process';
|
||||
const path = require('path');
|
||||
const os = require('os');
|
||||
const fsProm = require('fs').promises;
|
||||
const childProcess = require('child_process');
|
||||
|
||||
import HakEnv from './hakEnv';
|
||||
import { DependencyInfo } from './dep';
|
||||
|
||||
export default async function link(hakEnv: HakEnv, moduleInfo: DependencyInfo): Promise<void> {
|
||||
async function link(hakEnv, moduleInfo) {
|
||||
const yarnrc = path.join(hakEnv.projectRoot, '.yarnrc');
|
||||
// this is fairly terrible but it's reasonably clunky to either parse a yarnrc
|
||||
// properly or get yarn to do it, so this will probably suffice for now.
|
||||
@@ -49,7 +46,7 @@ export default async function link(hakEnv: HakEnv, moduleInfo: DependencyInfo):
|
||||
|
||||
const yarnCmd = 'yarn' + (hakEnv.isWin() ? '.cmd' : '');
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(yarnCmd, ['link'], {
|
||||
cwd: moduleInfo.moduleOutDir,
|
||||
stdio: 'inherit',
|
||||
@@ -59,7 +56,7 @@ export default async function link(hakEnv: HakEnv, moduleInfo: DependencyInfo):
|
||||
});
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
const proc = childProcess.spawn(yarnCmd, ['link', moduleInfo.name], {
|
||||
cwd: hakEnv.projectRoot,
|
||||
stdio: 'inherit',
|
||||
@@ -69,3 +66,5 @@ export default async function link(hakEnv: HakEnv, moduleInfo: DependencyInfo):
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = link;
|
||||
@@ -1,196 +0,0 @@
|
||||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
import { GLIBC, MUSL, family as processLibC } from "detect-libc";
|
||||
|
||||
// We borrow Rust's target naming scheme as a way of expressing all target
|
||||
// details in a single string.
|
||||
// See https://doc.rust-lang.org/rustc/platform-support.html.
|
||||
export type TargetId =
|
||||
'aarch64-apple-darwin' |
|
||||
'x86_64-apple-darwin' |
|
||||
'universal-apple-darwin' |
|
||||
'i686-pc-windows-msvc' |
|
||||
'x86_64-pc-windows-msvc' |
|
||||
'i686-unknown-linux-musl' |
|
||||
'i686-unknown-linux-gnu' |
|
||||
'x86_64-unknown-linux-musl' |
|
||||
'x86_64-unknown-linux-gnu' |
|
||||
'aarch64-unknown-linux-musl' |
|
||||
'aarch64-unknown-linux-gnu' |
|
||||
'powerpc64le-unknown-linux-musl' |
|
||||
'powerpc64le-unknown-linux-gnu';
|
||||
|
||||
// Values are expected to match those used in `process.platform`.
|
||||
export type Platform = 'darwin' | 'linux' | 'win32';
|
||||
|
||||
// Values are expected to match those used in `process.arch`.
|
||||
export type Arch = 'arm64' | 'ia32' | 'x64' | 'ppc64' | 'universal';
|
||||
|
||||
// Values are expected to match those used by Visual Studio's `vcvarsall.bat`.
|
||||
// See https://docs.microsoft.com/cpp/build/building-on-the-command-line?view=msvc-160#vcvarsall-syntax
|
||||
export type VcVarsArch = 'amd64' | 'arm64' | 'x86';
|
||||
|
||||
export type Target = {
|
||||
id: TargetId;
|
||||
platform: Platform;
|
||||
arch: Arch;
|
||||
};
|
||||
|
||||
export type WindowsTarget = Target & {
|
||||
platform: 'win32';
|
||||
vcVarsArch: VcVarsArch;
|
||||
};
|
||||
|
||||
export type LinuxTarget = Target & {
|
||||
platform: 'linux';
|
||||
libC: typeof processLibC;
|
||||
};
|
||||
|
||||
export type UniversalTarget = Target & {
|
||||
arch: 'universal';
|
||||
subtargets: Target[];
|
||||
};
|
||||
|
||||
const aarch64AppleDarwin: Target = {
|
||||
id: 'aarch64-apple-darwin',
|
||||
platform: 'darwin',
|
||||
arch: 'arm64',
|
||||
};
|
||||
|
||||
const x8664AppleDarwin: Target = {
|
||||
id: 'x86_64-apple-darwin',
|
||||
platform: 'darwin',
|
||||
arch: 'x64',
|
||||
};
|
||||
|
||||
const universalAppleDarwin: UniversalTarget = {
|
||||
id: 'universal-apple-darwin',
|
||||
platform: 'darwin',
|
||||
arch: 'universal',
|
||||
subtargets: [
|
||||
aarch64AppleDarwin,
|
||||
x8664AppleDarwin,
|
||||
],
|
||||
};
|
||||
|
||||
const i686PcWindowsMsvc: WindowsTarget = {
|
||||
id: 'i686-pc-windows-msvc',
|
||||
platform: 'win32',
|
||||
arch: 'ia32',
|
||||
vcVarsArch: 'x86',
|
||||
};
|
||||
|
||||
const x8664PcWindowsMsvc: WindowsTarget = {
|
||||
id: 'x86_64-pc-windows-msvc',
|
||||
platform: 'win32',
|
||||
arch: 'x64',
|
||||
vcVarsArch: 'amd64',
|
||||
};
|
||||
|
||||
const x8664UnknownLinuxGnu: LinuxTarget = {
|
||||
id: 'x86_64-unknown-linux-gnu',
|
||||
platform: 'linux',
|
||||
arch: 'x64',
|
||||
libC: GLIBC,
|
||||
};
|
||||
|
||||
const x8664UnknownLinuxMusl: LinuxTarget = {
|
||||
id: 'x86_64-unknown-linux-musl',
|
||||
platform: 'linux',
|
||||
arch: 'x64',
|
||||
libC: MUSL,
|
||||
};
|
||||
|
||||
const i686UnknownLinuxGnu: LinuxTarget = {
|
||||
id: 'i686-unknown-linux-gnu',
|
||||
platform: 'linux',
|
||||
arch: 'ia32',
|
||||
libC: GLIBC,
|
||||
};
|
||||
|
||||
const i686UnknownLinuxMusl: LinuxTarget = {
|
||||
id: 'i686-unknown-linux-musl',
|
||||
platform: 'linux',
|
||||
arch: 'ia32',
|
||||
libC: MUSL,
|
||||
};
|
||||
|
||||
const aarch64UnknownLinuxGnu: LinuxTarget = {
|
||||
id: 'aarch64-unknown-linux-gnu',
|
||||
platform: 'linux',
|
||||
arch: 'arm64',
|
||||
libC: GLIBC,
|
||||
};
|
||||
|
||||
const aarch64UnknownLinuxMusl: LinuxTarget = {
|
||||
id: 'aarch64-unknown-linux-musl',
|
||||
platform: 'linux',
|
||||
arch: 'arm64',
|
||||
libC: MUSL,
|
||||
};
|
||||
|
||||
const powerpc64leUnknownLinuxGnu: LinuxTarget = {
|
||||
id: 'powerpc64le-unknown-linux-gnu',
|
||||
platform: 'linux',
|
||||
arch: 'ppc64',
|
||||
libC: GLIBC,
|
||||
};
|
||||
|
||||
const powerpc64leUnknownLinuxMusl: LinuxTarget = {
|
||||
id: 'powerpc64le-unknown-linux-musl',
|
||||
platform: 'linux',
|
||||
arch: 'ppc64',
|
||||
libC: MUSL,
|
||||
};
|
||||
|
||||
export const TARGETS: Record<TargetId, Target> = {
|
||||
// macOS
|
||||
'aarch64-apple-darwin': aarch64AppleDarwin,
|
||||
'x86_64-apple-darwin': x8664AppleDarwin,
|
||||
'universal-apple-darwin': universalAppleDarwin,
|
||||
// Windows
|
||||
'i686-pc-windows-msvc': i686PcWindowsMsvc,
|
||||
'x86_64-pc-windows-msvc': x8664PcWindowsMsvc,
|
||||
// Linux
|
||||
'i686-unknown-linux-musl': i686UnknownLinuxMusl,
|
||||
'i686-unknown-linux-gnu': i686UnknownLinuxGnu,
|
||||
'x86_64-unknown-linux-musl': x8664UnknownLinuxMusl,
|
||||
'x86_64-unknown-linux-gnu': x8664UnknownLinuxGnu,
|
||||
'aarch64-unknown-linux-musl': aarch64UnknownLinuxMusl,
|
||||
'aarch64-unknown-linux-gnu': aarch64UnknownLinuxGnu,
|
||||
'powerpc64le-unknown-linux-musl': powerpc64leUnknownLinuxMusl,
|
||||
'powerpc64le-unknown-linux-gnu': powerpc64leUnknownLinuxGnu,
|
||||
};
|
||||
|
||||
export function getHost(): Target | undefined {
|
||||
return Object.values(TARGETS).find(target => (
|
||||
target.platform === process.platform &&
|
||||
target.arch === process.arch &&
|
||||
(
|
||||
process.platform !== 'linux' ||
|
||||
(target as LinuxTarget).libC === processLibC
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
export function isHostId(id: TargetId): boolean {
|
||||
return getHost()?.id === id;
|
||||
}
|
||||
|
||||
export function isHost(target: Target): boolean {
|
||||
return getHost()?.id === target.id;
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"target": "es2017",
|
||||
"module": "commonjs",
|
||||
"sourceMap": false,
|
||||
"lib": [
|
||||
"es2019",
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*.ts"
|
||||
],
|
||||
"ts-node": {
|
||||
"transpileOnly": true
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
IMAGE=${DOCKER_IMAGE_NAME:-"element-desktop-dockerbuild"}
|
||||
|
||||
docker inspect "$IMAGE" 2> /dev/null > /dev/null
|
||||
docker inspect element-desktop-dockerbuild 2> /dev/null > /dev/null
|
||||
if [ $? != 0 ]; then
|
||||
echo "Docker image $IMAGE not found. Have you run yarn run docker:setup?"
|
||||
echo "Docker image element-desktop-dockerbuild not found. Have you run yarn run docker:setup?"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Taken from https://www.electron.build/multi-platform-build#docker
|
||||
# Pass through any vars prefixed with INDOCKER_, removing the prefix
|
||||
docker run --rm -ti \
|
||||
--env-file <(env | grep -E '^INDOCKER_' | sed -e 's/^INDOCKER_//') \
|
||||
--env-file <(env | grep -iE '^BUILDKITE_API_KEY=') \
|
||||
--env ELECTRON_CACHE="/root/.cache/electron" \
|
||||
--env ELECTRON_BUILDER_CACHE="/root/.cache/electron-builder" \
|
||||
-v ${PWD}:/project \
|
||||
@@ -20,4 +17,4 @@ docker run --rm -ti \
|
||||
-v ${PWD}/docker/.gnupg:/root/.gnupg \
|
||||
-v ~/.cache/electron:/root/.cache/electron \
|
||||
-v ~/.cache/electron-builder:/root/.cache/electron-builder \
|
||||
"$IMAGE" "$@"
|
||||
element-desktop-dockerbuild "$@"
|
||||
|
||||
@@ -52,4 +52,4 @@ if (require.main === module) {
|
||||
main(process.argv.slice(2)).then((ret) => process.exit(ret));
|
||||
}
|
||||
|
||||
module.exports = { versionFromAsar, setPackageVersion };
|
||||
module.exports = {versionFromAsar, setPackageVersion};
|
||||
|
||||
46
src/@types/global.d.ts
vendored
46
src/@types/global.d.ts
vendored
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
Copyright 2021 - 2022 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";
|
||||
import Store from "electron-store";
|
||||
import AutoLaunch from "auto-launch";
|
||||
|
||||
import { AppLocalization } from "../language-helper";
|
||||
|
||||
declare global {
|
||||
namespace NodeJS {
|
||||
interface Global {
|
||||
mainWindow: BrowserWindow;
|
||||
appQuitting: boolean;
|
||||
appLocalization: AppLocalization;
|
||||
launcher: AutoLaunch;
|
||||
vectorConfig: Record<string, any>;
|
||||
trayConfig: {
|
||||
// eslint-disable-next-line camelcase
|
||||
icon_path: string;
|
||||
brand: string;
|
||||
};
|
||||
store: Store<{
|
||||
warnBeforeExit?: boolean;
|
||||
minimizeToTray?: boolean;
|
||||
spellCheckerEnabled?: boolean;
|
||||
autoHideMenuBar?: boolean;
|
||||
locale?: string | string[];
|
||||
disableHardwareAcceleration?: boolean;
|
||||
}>;
|
||||
}
|
||||
}
|
||||
}
|
||||
54
src/@types/keytar.d.ts
vendored
54
src/@types/keytar.d.ts
vendored
@@ -1,54 +0,0 @@
|
||||
// Based on https://github.com/atom/node-keytar/blob/master/keytar.d.ts because keytar is a hak-dependency and not a normal one
|
||||
// Definitions by: Milan Burda <https://github.com/miniak>, Brendan Forster <https://github.com/shiftkey>, Hari Juturu <https://github.com/juturu>
|
||||
// Adapted from DefinitelyTyped: https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/keytar/index.d.ts
|
||||
|
||||
declare module "keytar" {
|
||||
/**
|
||||
* Get the stored password for the service and account.
|
||||
*
|
||||
* @param service The string service name.
|
||||
* @param account The string account name.
|
||||
*
|
||||
* @returns A promise for the password string.
|
||||
*/
|
||||
export function getPassword(service: string, account: string): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Add the password for the service and account to the keychain.
|
||||
*
|
||||
* @param service The string service name.
|
||||
* @param account The string account name.
|
||||
* @param password The string password.
|
||||
*
|
||||
* @returns A promise for the set password completion.
|
||||
*/
|
||||
export function setPassword(service: string, account: string, password: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Delete the stored password for the service and account.
|
||||
*
|
||||
* @param service The string service name.
|
||||
* @param account The string account name.
|
||||
*
|
||||
* @returns A promise for the deletion status. True on success.
|
||||
*/
|
||||
export function deletePassword(service: string, account: string): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Find a password for the service in the keychain.
|
||||
*
|
||||
* @param service The string service name.
|
||||
*
|
||||
* @returns A promise for the password string.
|
||||
*/
|
||||
export function findPassword(service: string): Promise<string | null>;
|
||||
|
||||
/**
|
||||
* Find all accounts and passwords for `service` in the keychain.
|
||||
*
|
||||
* @param service The string service name.
|
||||
*
|
||||
* @returns A promise for the array of found credentials.
|
||||
*/
|
||||
export function findCredentials(service: string): Promise<Array<{ account: string, password: string}>>;
|
||||
}
|
||||
145
src/@types/matrix-seshat.d.ts
vendored
145
src/@types/matrix-seshat.d.ts
vendored
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
Copyright 2022 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.
|
||||
*/
|
||||
|
||||
declare module "matrix-seshat" {
|
||||
interface IConfig {
|
||||
language?: string;
|
||||
passphrase?: string;
|
||||
}
|
||||
|
||||
/* eslint-disable camelcase */
|
||||
interface IMatrixEvent {
|
||||
event_id: string;
|
||||
sender: string;
|
||||
room_id: string;
|
||||
origin_server_ts: number;
|
||||
content: Record<string, any>;
|
||||
}
|
||||
|
||||
interface IMatrixProfile {
|
||||
displayname?: string;
|
||||
avatar_url?: string;
|
||||
}
|
||||
|
||||
interface ISearchArgs {
|
||||
searchTerm: number;
|
||||
limit: number;
|
||||
before_limit: number;
|
||||
after_limit: number;
|
||||
order_by_recency: boolean;
|
||||
next_batch?: string;
|
||||
}
|
||||
|
||||
interface ISearchContext {
|
||||
events_before: IMatrixEvent[];
|
||||
events_after: IMatrixEvent[];
|
||||
profile_info: { [userId: string]: IMatrixProfile };
|
||||
}
|
||||
|
||||
interface ISearchResult {
|
||||
next_batch: string;
|
||||
count: number;
|
||||
results: Array<{
|
||||
rank: number;
|
||||
result: IMatrixEvent;
|
||||
context: ISearchContext;
|
||||
}>;
|
||||
}
|
||||
/* eslint-enable camelcase */
|
||||
|
||||
interface ICheckpoint {
|
||||
roomId: string;
|
||||
token: string;
|
||||
fullCrawl: boolean;
|
||||
direction: "b" | "f";
|
||||
}
|
||||
|
||||
interface IDatabaseStats {
|
||||
size: number;
|
||||
eventCount: number;
|
||||
roomCount: number;
|
||||
}
|
||||
|
||||
interface ILoadArgs {
|
||||
roomId: string;
|
||||
limit: number;
|
||||
fromEvent: string;
|
||||
direction: "b" | "f";
|
||||
}
|
||||
|
||||
interface ILoadResult {
|
||||
event: IMatrixEvent;
|
||||
matrixProfile: IMatrixProfile;
|
||||
}
|
||||
|
||||
export class Seshat {
|
||||
constructor(path: string, config?: IConfig);
|
||||
public addEvent(matrixEvent: IMatrixEvent, profile?: IMatrixProfile): void;
|
||||
public deleteEvent(eventId: string): Promise<boolean>;
|
||||
public commit(force?: boolean): Promise<number>;
|
||||
public commitSync(wait?: boolean, force?: boolean): number;
|
||||
public reload(): void;
|
||||
public search(args: ISearchArgs): Promise<ISearchResult>;
|
||||
public searchSync(
|
||||
term: string,
|
||||
limit?: number,
|
||||
beforeLimit?: number,
|
||||
afterLimit?: number,
|
||||
orderByRecency?: boolean,
|
||||
): ISearchResult;
|
||||
public addHistoricEventsSync(
|
||||
events: IMatrixEvent[],
|
||||
newCheckpoint?: ICheckpoint,
|
||||
oldCheckpoint?: ICheckpoint,
|
||||
): boolean;
|
||||
public addHistoricEvents(
|
||||
events: IMatrixEvent[],
|
||||
newCheckpoint?: ICheckpoint,
|
||||
oldCheckpoint?: ICheckpoint,
|
||||
): Promise<boolean>;
|
||||
public addCrawlerCheckpoint(checkpoint: ICheckpoint): Promise<void>;
|
||||
public removeCrawlerCheckpoint(checkpoint: ICheckpoint): Promise<void>;
|
||||
public loadCheckpoints(): Promise<ICheckpoint[]>;
|
||||
public getSize(): Promise<number>;
|
||||
public getStats(): Promise<IDatabaseStats>;
|
||||
public delete(): Promise<void>;
|
||||
public shutdown(): Promise<void>;
|
||||
public changePassphrase(newPassphrase: string): Promise<void>;
|
||||
public isEmpty(): Promise<boolean>;
|
||||
public isRoomIndexed(roomId: string): Promise<boolean>;
|
||||
public getUserVersion(): Promise<number>;
|
||||
public setUserVersion(version: number): Promise<void>;
|
||||
public loadFileEvents(args: ILoadArgs): Promise<ILoadResult[]>;
|
||||
}
|
||||
|
||||
interface IRecoveryInfo {
|
||||
totalEvents: number;
|
||||
reindexedEvents: number;
|
||||
done: number;
|
||||
}
|
||||
|
||||
export class SeshatRecovery {
|
||||
constructor(path: string, config?: IConfig);
|
||||
public info(): IRecoveryInfo;
|
||||
public getUserVersion(): Promise<number>;
|
||||
public shutdown(): Promise<void>;
|
||||
public reindex(): Promise<void>;
|
||||
}
|
||||
|
||||
export class ReindexError extends Error {
|
||||
constructor(message?: string);
|
||||
}
|
||||
}
|
||||
1028
src/electron-main.js
Normal file
1028
src/electron-main.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,536 +0,0 @@
|
||||
/*
|
||||
Copyright 2016 Aviral Dasgupta
|
||||
Copyright 2016 OpenMarket Ltd
|
||||
Copyright 2017, 2019 Michael Telatynski <7t3chguy@gmail.com>
|
||||
Copyright 2018 - 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.
|
||||
*/
|
||||
|
||||
// Squirrel on windows starts the app with various flags as hooks to tell us when we've been installed/uninstalled etc.
|
||||
import "./squirrelhooks";
|
||||
import {
|
||||
app,
|
||||
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 { URL } from "url";
|
||||
import minimist from "minimist";
|
||||
|
||||
import "./ipc";
|
||||
import "./keytar";
|
||||
import "./seshat";
|
||||
import "./settings";
|
||||
import * as tray from "./tray";
|
||||
import { buildMenuTemplate } from './vectormenu';
|
||||
import webContentsHandler from './webcontents-handler';
|
||||
import * as updater from './updater';
|
||||
import { getProfileFromDeeplink, protocolInit } from './protocol';
|
||||
import { _t, AppLocalization } from './language-helper';
|
||||
import Input = Electron.Input;
|
||||
|
||||
const argv = minimist(process.argv, {
|
||||
alias: { help: "h" },
|
||||
});
|
||||
|
||||
// Things we need throughout the file but need to be created
|
||||
// async to are initialised in setupGlobals()
|
||||
let asarPath: string;
|
||||
let resPath: string;
|
||||
let iconPath: string;
|
||||
|
||||
if (argv["help"]) {
|
||||
console.log("Options:");
|
||||
console.log(" --profile-dir {path}: Path to where to store the profile.");
|
||||
console.log(" --profile {name}: Name of alternate profile to use, allows for running multiple accounts.");
|
||||
console.log(" --devtools: Install and use react-devtools and react-perf.");
|
||||
console.log(" --no-update: Disable automatic updating.");
|
||||
console.log(" --hidden: Start the application hidden in the system tray.");
|
||||
console.log(" --help: Displays this help message.");
|
||||
console.log("And more such as --proxy, see:" +
|
||||
"https://electronjs.org/docs/api/command-line-switches");
|
||||
app.exit();
|
||||
}
|
||||
|
||||
// Electron creates the user data directory (with just an empty 'Dictionaries' directory...)
|
||||
// as soon as the app path is set, so pick a random path in it that must exist if it's a
|
||||
// real user data directory.
|
||||
function isRealUserDataDir(d: string): boolean {
|
||||
return fs.existsSync(path.join(d, 'IndexedDB'));
|
||||
}
|
||||
|
||||
// check if we are passed a profile in the SSO callback url
|
||||
let userDataPath: string;
|
||||
|
||||
const userDataPathInProtocol = getProfileFromDeeplink(argv["_"]);
|
||||
if (userDataPathInProtocol) {
|
||||
userDataPath = userDataPathInProtocol;
|
||||
} else if (argv['profile-dir']) {
|
||||
userDataPath = argv['profile-dir'];
|
||||
} else {
|
||||
let newUserDataPath = app.getPath('userData');
|
||||
if (argv['profile']) {
|
||||
newUserDataPath += '-' + argv['profile'];
|
||||
}
|
||||
const newUserDataPathExists = isRealUserDataDir(newUserDataPath);
|
||||
let oldUserDataPath = path.join(app.getPath('appData'), app.getName().replace('Element', 'Riot'));
|
||||
if (argv['profile']) {
|
||||
oldUserDataPath += '-' + argv['profile'];
|
||||
}
|
||||
|
||||
const oldUserDataPathExists = isRealUserDataDir(oldUserDataPath);
|
||||
console.log(newUserDataPath + " exists: " + (newUserDataPathExists ? 'yes' : 'no'));
|
||||
console.log(oldUserDataPath + " exists: " + (oldUserDataPathExists ? 'yes' : 'no'));
|
||||
if (!newUserDataPathExists && oldUserDataPathExists) {
|
||||
console.log("Using legacy user data path: " + oldUserDataPath);
|
||||
userDataPath = oldUserDataPath;
|
||||
} else {
|
||||
userDataPath = newUserDataPath;
|
||||
}
|
||||
}
|
||||
app.setPath('userData', userDataPath);
|
||||
|
||||
async function tryPaths(name: string, root: string, rawPaths: string[]): Promise<string> {
|
||||
// Make everything relative to root
|
||||
const paths = rawPaths.map(p => path.join(root, p));
|
||||
|
||||
for (const p of paths) {
|
||||
try {
|
||||
await afs.stat(p);
|
||||
return p + '/';
|
||||
} catch (e) {
|
||||
}
|
||||
}
|
||||
console.log(`Couldn't find ${name} files in any of: `);
|
||||
for (const p of paths) {
|
||||
console.log("\t"+path.resolve(p));
|
||||
}
|
||||
throw new Error(`Failed to find ${name} files`);
|
||||
}
|
||||
|
||||
// Find the webapp resources and set up things that require them
|
||||
async function setupGlobals(): Promise<void> {
|
||||
// find the webapp asar.
|
||||
asarPath = await tryPaths("webapp", __dirname, [
|
||||
// If run from the source checkout, this will be in the directory above
|
||||
'../webapp.asar',
|
||||
// but if run from a packaged application, electron-main.js will be in
|
||||
// a different asar file so it will be two levels above
|
||||
'../../webapp.asar',
|
||||
// also try without the 'asar' suffix to allow symlinking in a directory
|
||||
'../webapp',
|
||||
// from a packaged application
|
||||
'../../webapp',
|
||||
]);
|
||||
|
||||
// we assume the resources path is in the same place as the asar
|
||||
resPath = await tryPaths("res", path.dirname(asarPath), [
|
||||
// If run from the source checkout
|
||||
'res',
|
||||
// if run from packaged application
|
||||
'',
|
||||
]);
|
||||
|
||||
try {
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
global.vectorConfig = require(asarPath + 'config.json');
|
||||
} catch (e) {
|
||||
// it would be nice to check the error code here and bail if the config
|
||||
// is unparsable, but we get MODULE_NOT_FOUND in the case of a missing
|
||||
// file or invalid json, so node is just very unhelpful.
|
||||
// Continue with the defaults (ie. an empty config)
|
||||
global.vectorConfig = {};
|
||||
}
|
||||
|
||||
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
|
||||
// config. This is to avoid a problem where Riot thinks there are multiple homeservers
|
||||
// defined, and panics as a result.
|
||||
const homeserverProps = ['default_is_url', 'default_hs_url', 'default_server_name', 'default_server_config'];
|
||||
if (Object.keys(localConfig).find(k => homeserverProps.includes(k))) {
|
||||
// Rip out all the homeserver options from the vector config
|
||||
global.vectorConfig = Object.keys(global.vectorConfig)
|
||||
.filter(k => !homeserverProps.includes(k))
|
||||
.reduce((obj, key) => {obj[key] = global.vectorConfig[key]; return obj;}, {});
|
||||
}
|
||||
|
||||
global.vectorConfig = Object.assign(global.vectorConfig, localConfig);
|
||||
} catch (e) {
|
||||
if (e instanceof SyntaxError) {
|
||||
dialog.showMessageBox({
|
||||
type: "error",
|
||||
title: `Your ${global.vectorConfig.brand || 'Element'} is misconfigured`,
|
||||
message: `Your custom ${global.vectorConfig.brand || 'Element'} configuration contains invalid JSON. ` +
|
||||
`Please correct the problem and reopen ${global.vectorConfig.brand || 'Element'}.`,
|
||||
detail: e.message || "",
|
||||
});
|
||||
}
|
||||
|
||||
// Could not load local config, this is expected in most cases.
|
||||
}
|
||||
|
||||
// The tray icon
|
||||
// It's important to call `path.join` so we don't end up with the packaged asar in the final path.
|
||||
const iconFile = `element.${process.platform === 'win32' ? 'ico' : 'png'}`;
|
||||
iconPath = path.join(resPath, "img", iconFile);
|
||||
global.trayConfig = {
|
||||
icon_path: iconPath,
|
||||
brand: global.vectorConfig.brand || 'Element',
|
||||
};
|
||||
|
||||
// launcher
|
||||
global.launcher = new AutoLaunch({
|
||||
name: global.vectorConfig.brand || 'Element',
|
||||
isHidden: true,
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
async function moveAutoLauncher(): Promise<void> {
|
||||
// Look for an auto-launcher under 'Riot' and if we find one, port it's
|
||||
// enabled/disabled-ness over to the new 'Element' launcher
|
||||
if (!global.vectorConfig.brand || global.vectorConfig.brand === 'Element') {
|
||||
const oldLauncher = new AutoLaunch({
|
||||
name: 'Riot',
|
||||
isHidden: true,
|
||||
mac: {
|
||||
useLaunchAgent: true,
|
||||
},
|
||||
});
|
||||
const wasEnabled = await oldLauncher.isEnabled();
|
||||
if (wasEnabled) {
|
||||
await oldLauncher.disable();
|
||||
await global.launcher.enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
global.store = new Store({ name: "electron-config" });
|
||||
|
||||
global.appQuitting = false;
|
||||
|
||||
const exitShortcuts: Array<(input: Input, platform: string) => boolean> = [
|
||||
(input, platform) => platform !== 'darwin' && input.alt && input.key.toUpperCase() === 'F4',
|
||||
(input, platform) => platform !== 'darwin' && input.control && input.key.toUpperCase() === 'Q',
|
||||
(input, platform) => platform === 'darwin' && input.meta && input.key.toUpperCase() === 'Q',
|
||||
];
|
||||
|
||||
const warnBeforeExit = (event: Event, input: Input): void => {
|
||||
const shouldWarnBeforeExit = global.store.get('warnBeforeExit', true);
|
||||
const exitShortcutPressed =
|
||||
input.type === 'keyDown' && exitShortcuts.some(shortcutFn => shortcutFn(input, process.platform));
|
||||
|
||||
if (shouldWarnBeforeExit && exitShortcutPressed) {
|
||||
const shouldCancelCloseRequest = dialog.showMessageBoxSync(global.mainWindow, {
|
||||
type: "question",
|
||||
buttons: [_t("Cancel"), _t("Close Element")],
|
||||
message: _t("Are you sure you want to quit?"),
|
||||
defaultId: 1,
|
||||
cancelId: 0,
|
||||
}) === 0;
|
||||
|
||||
if (shouldCancelCloseRequest) {
|
||||
event.preventDefault();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// handle uncaught errors otherwise it displays
|
||||
// stack traces in popup dialogs, which is terrible (which
|
||||
// it will do any time the auto update poke fails, and there's
|
||||
// no other way to catch this error).
|
||||
// Assuming we generally run from the console when developing,
|
||||
// this is far preferable.
|
||||
process.on('uncaughtException', function(error: Error): void {
|
||||
console.log('Unhandled exception', error);
|
||||
});
|
||||
|
||||
app.commandLine.appendSwitch('--enable-usermedia-screen-capturing');
|
||||
if (!app.commandLine.hasSwitch('enable-features')) {
|
||||
app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer');
|
||||
}
|
||||
|
||||
const gotLock = app.requestSingleInstanceLock();
|
||||
if (!gotLock) {
|
||||
console.log('Other instance detected: exiting');
|
||||
app.exit();
|
||||
}
|
||||
|
||||
// do this after we know we are the primary instance of the app
|
||||
protocolInit();
|
||||
|
||||
// Register the scheme the app is served from as 'standard'
|
||||
// which allows things like relative URLs and IndexedDB to
|
||||
// work.
|
||||
// Also mark it as secure (ie. accessing resources from this
|
||||
// protocol and HTTPS won't trigger mixed content warnings).
|
||||
protocol.registerSchemesAsPrivileged([{
|
||||
scheme: 'vector',
|
||||
privileges: {
|
||||
standard: true,
|
||||
secure: true,
|
||||
supportFetchAPI: true,
|
||||
},
|
||||
}]);
|
||||
|
||||
// Turn the sandbox on for *all* windows we might generate. Doing this means we don't
|
||||
// have to specify a `sandbox: true` to each BrowserWindow.
|
||||
//
|
||||
// This also fixes an issue with window.open where if we only specified the sandbox
|
||||
// on the main window we'd run into cryptic "ipc_renderer be broke" errors. Turns out
|
||||
// it's trying to jump the sandbox and make some calls into electron, which it can't
|
||||
// do when half of it is sandboxed. By turning on the sandbox for everything, the new
|
||||
// window (no matter how temporary it may be) is also sandboxed, allowing for a clean
|
||||
// transition into the user's browser.
|
||||
app.enableSandbox();
|
||||
|
||||
// We disable media controls here. We do this because calls use audio and video elements and they sometimes capture the media keys. See https://github.com/vector-im/element-web/issues/15704
|
||||
app.commandLine.appendSwitch('disable-features', 'HardwareMediaKeyHandling,MediaSessionService');
|
||||
|
||||
// Disable hardware acceleration if the setting has been set.
|
||||
if (global.store.get('disableHardwareAcceleration', false) === true) {
|
||||
console.log("Disabling hardware acceleration.");
|
||||
app.disableHardwareAcceleration();
|
||||
}
|
||||
|
||||
app.on('ready', async () => {
|
||||
try {
|
||||
await setupGlobals();
|
||||
await moveAutoLauncher();
|
||||
} catch (e) {
|
||||
console.log("App setup failed: exiting", e);
|
||||
process.exit(1);
|
||||
// process.exit doesn't cause node to stop running code immediately,
|
||||
// so return (we could let the exception propagate but then we end up
|
||||
// with node printing all sorts of stuff about unhandled exceptions
|
||||
// when we want the actual error to be as obvious as possible).
|
||||
return;
|
||||
}
|
||||
|
||||
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}`))
|
||||
.catch((err) => console.log('An error occurred: ', err));
|
||||
installExt(REACT_PERF)
|
||||
.then((name) => console.log(`Added Extension: ${name}`))
|
||||
.catch((err) => console.log('An error occurred: ', err));
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
protocol.registerFileProtocol('vector', (request, callback) => {
|
||||
if (request.method !== 'GET') {
|
||||
callback({ error: -322 }); // METHOD_NOT_SUPPORTED from chromium/src/net/base/net_error_list.h
|
||||
return null;
|
||||
}
|
||||
|
||||
const parsedUrl = new URL(request.url);
|
||||
if (parsedUrl.protocol !== 'vector:') {
|
||||
callback({ error: -302 }); // UNKNOWN_URL_SCHEME
|
||||
return;
|
||||
}
|
||||
if (parsedUrl.host !== 'vector') {
|
||||
callback({ error: -105 }); // NAME_NOT_RESOLVED
|
||||
return;
|
||||
}
|
||||
|
||||
const target = parsedUrl.pathname.split('/');
|
||||
|
||||
// path starts with a '/'
|
||||
if (target[0] !== '') {
|
||||
callback({ error: -6 }); // FILE_NOT_FOUND
|
||||
return;
|
||||
}
|
||||
|
||||
if (target[target.length - 1] == '') {
|
||||
target[target.length - 1] = 'index.html';
|
||||
}
|
||||
|
||||
let baseDir: string;
|
||||
if (target[1] === 'webapp') {
|
||||
baseDir = asarPath;
|
||||
} else {
|
||||
callback({ error: -6 }); // FILE_NOT_FOUND
|
||||
return;
|
||||
}
|
||||
|
||||
// Normalise the base dir and the target path separately, then make sure
|
||||
// the target path isn't trying to back out beyond its root
|
||||
baseDir = path.normalize(baseDir);
|
||||
|
||||
const relTarget = path.normalize(path.join(...target.slice(2)));
|
||||
if (relTarget.startsWith('..')) {
|
||||
callback({ error: -6 }); // FILE_NOT_FOUND
|
||||
return;
|
||||
}
|
||||
const absTarget = path.join(baseDir, relTarget);
|
||||
|
||||
callback({
|
||||
path: absTarget,
|
||||
});
|
||||
});
|
||||
|
||||
if (argv['no-update']) {
|
||||
console.log('Auto update disabled via command line flag "--no-update"');
|
||||
} else if (global.vectorConfig['update_base_url']) {
|
||||
console.log(`Starting auto update with base URL: ${global.vectorConfig['update_base_url']}`);
|
||||
updater.start(global.vectorConfig['update_base_url']);
|
||||
} else {
|
||||
console.log('No update_base_url is defined: auto update is disabled');
|
||||
}
|
||||
|
||||
// Load the previous window state with fallback to defaults
|
||||
const mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 1024,
|
||||
defaultHeight: 768,
|
||||
});
|
||||
|
||||
const preloadScript = path.normalize(`${__dirname}/preload.js`);
|
||||
global.mainWindow = new BrowserWindow({
|
||||
// https://www.electronjs.org/docs/faq#the-font-looks-blurry-what-is-this-and-what-can-i-do
|
||||
backgroundColor: '#fff',
|
||||
|
||||
icon: iconPath,
|
||||
show: false,
|
||||
autoHideMenuBar: global.store.get('autoHideMenuBar', true),
|
||||
|
||||
x: mainWindowState.x,
|
||||
y: mainWindowState.y,
|
||||
width: mainWindowState.width,
|
||||
height: mainWindowState.height,
|
||||
webPreferences: {
|
||||
preload: preloadScript,
|
||||
nodeIntegration: false,
|
||||
//sandbox: true, // We enable sandboxing from app.enableSandbox() above
|
||||
contextIsolation: true,
|
||||
webgl: true,
|
||||
},
|
||||
});
|
||||
global.mainWindow.loadURL('vector://vector/webapp/');
|
||||
|
||||
// Handle spellchecker
|
||||
// For some reason spellCheckerEnabled isn't persisted, so we have to use the store here
|
||||
global.mainWindow.webContents.session.setSpellCheckerEnabled(global.store.get("spellCheckerEnabled", true));
|
||||
|
||||
// Create trayIcon icon
|
||||
if (global.store.get('minimizeToTray', true)) tray.create(global.trayConfig);
|
||||
|
||||
global.mainWindow.once('ready-to-show', () => {
|
||||
mainWindowState.manage(global.mainWindow);
|
||||
|
||||
if (!argv['hidden']) {
|
||||
global.mainWindow.show();
|
||||
} else {
|
||||
// hide here explicitly because window manage above sometimes shows it
|
||||
global.mainWindow.hide();
|
||||
}
|
||||
});
|
||||
|
||||
global.mainWindow.webContents.on('before-input-event', warnBeforeExit);
|
||||
|
||||
global.mainWindow.on('closed', () => {
|
||||
global.mainWindow = null;
|
||||
});
|
||||
global.mainWindow.on('close', async (e) => {
|
||||
// If we are not quitting and have a tray icon then minimize to tray
|
||||
if (!global.appQuitting && (tray.hasTray() || process.platform === 'darwin')) {
|
||||
// On Mac, closing the window just hides it
|
||||
// (this is generally how single-window Mac apps
|
||||
// behave, eg. Mail.app)
|
||||
e.preventDefault();
|
||||
|
||||
if (global.mainWindow.isFullScreen()) {
|
||||
global.mainWindow.once('leave-full-screen', () => global.mainWindow.hide());
|
||||
|
||||
global.mainWindow.setFullScreen(false);
|
||||
} else {
|
||||
global.mainWindow.hide();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Handle forward/backward mouse buttons in Windows
|
||||
global.mainWindow.on('app-command', (e, cmd) => {
|
||||
if (cmd === 'browser-backward' && global.mainWindow.webContents.canGoBack()) {
|
||||
global.mainWindow.webContents.goBack();
|
||||
} else if (cmd === 'browser-forward' && global.mainWindow.webContents.canGoForward()) {
|
||||
global.mainWindow.webContents.goForward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
webContentsHandler(global.mainWindow.webContents);
|
||||
|
||||
global.appLocalization = new AppLocalization({
|
||||
store: global.store,
|
||||
components: [
|
||||
() => tray.initApplicationMenu(),
|
||||
() => Menu.setApplicationMenu(buildMenuTemplate()),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
global.mainWindow.show();
|
||||
});
|
||||
|
||||
function beforeQuit(): void {
|
||||
global.appQuitting = true;
|
||||
global.mainWindow?.webContents.send('before-quit');
|
||||
}
|
||||
|
||||
app.on('before-quit', 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
|
||||
if (commandLine.includes('--hidden')) return;
|
||||
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (global.mainWindow) {
|
||||
if (!global.mainWindow.isVisible()) global.mainWindow.show();
|
||||
if (global.mainWindow.isMinimized()) global.mainWindow.restore();
|
||||
global.mainWindow.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// Set the App User Model ID to match what the squirrel
|
||||
// installer uses for the shortcut icon.
|
||||
// This makes notifications work on windows 8.1 (and is
|
||||
// a noop on other platforms).
|
||||
app.setAppUserModelId('com.squirrel.element-desktop.Element');
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"File": "ملف",
|
||||
"Close": "أغلِق",
|
||||
"Actual Size": "المقاس الفعلي",
|
||||
"View": "منظور",
|
||||
"Select All": "حدّد الكل",
|
||||
"Delete": "احذف",
|
||||
"Copy": "انسخ",
|
||||
"Edit": "تحرير",
|
||||
"Close Element": "أغلِق Element",
|
||||
"Cancel": "ألغِ",
|
||||
"Bring All to Front": "ضَع الكل في المقدّمة",
|
||||
"Speech": "نطق",
|
||||
"Add to dictionary": "أضِف إلى القاموس",
|
||||
"The image failed to save": "فشل حفظ الصورة",
|
||||
"Failed to save image": "فشل حفظ الصورة",
|
||||
"Save image as...": "احفظ الصورة كَ...",
|
||||
"Copy link address": "انسخ عنوان الرابط",
|
||||
"Copy email address": "انسخ عنوان البريد الإلكتروني",
|
||||
"Copy image": "انسخ الصورة",
|
||||
"Zoom": "تقريب",
|
||||
"Stop Speaking": "أوقِف النطق",
|
||||
"Start Speaking": "ابدأ النطق",
|
||||
"Unhide": "اعرض",
|
||||
"Hide Others": "أخفِ البقية",
|
||||
"Hide": "أخفِ",
|
||||
"Services": "الخدمات",
|
||||
"About": "عن",
|
||||
"Element Help": "مساعدة Element",
|
||||
"Help": "مساعدة",
|
||||
"Minimize": "صغّر",
|
||||
"Window": "نافذة",
|
||||
"Toggle Developer Tools": "فعّل/عطّل أدوات المطوّرين",
|
||||
"Toggle Full Screen": "فعّل/عطّل ملء الشاشة",
|
||||
"Preferences": "التفضيلات",
|
||||
"Zoom In": "قرّب",
|
||||
"Zoom Out": "بعّد",
|
||||
"Paste and Match Style": "ألصِق وطابِق النمط",
|
||||
"Paste": "ألصِق",
|
||||
"Cut": "قصّ",
|
||||
"Redo": "أعِد",
|
||||
"Undo": "تراجَع",
|
||||
"Quit": "غادِر",
|
||||
"Show/Hide": "اعرض/أخفِ",
|
||||
"Are you sure you want to quit?": "أمتأكّد من الإغلاق؟"
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"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": "Вынесці ўсё наперад",
|
||||
"Zoom": "Маштаб",
|
||||
"Stop Speaking": "Перастаць гаварыць",
|
||||
"Start Speaking": "Гаварыць",
|
||||
"Speech": "Голас",
|
||||
"Unhide": "Паказаць",
|
||||
"Hide Others": "Схаваць іншыя",
|
||||
"Hide": "Схаваць",
|
||||
"Services": "Сервісы",
|
||||
"About": "Аб праграме",
|
||||
"Element Help": "Даведка Element",
|
||||
"Help": "Даведка",
|
||||
"Close": "Зачыніць",
|
||||
"Minimize": "Згарнуць",
|
||||
"Window": "Акно",
|
||||
"Toggle Developer Tools": "Пераключэнне інструментаў распрацоўніка",
|
||||
"Toggle Full Screen": "Пераключэнне на ўвесь экран",
|
||||
"Preferences": "Параметры",
|
||||
"Zoom Out": "Паменшыць",
|
||||
"Zoom In": "Павялічыць",
|
||||
"Actual Size": "Фактычны Памер",
|
||||
"View": "Прагляд",
|
||||
"Select All": "Выбраць усё",
|
||||
"Delete": "Выдаліць",
|
||||
"Paste and Match Style": "Уставіць і супаставіць стыль",
|
||||
"Paste": "Уставіць",
|
||||
"Copy": "Капіяваць",
|
||||
"Cut": "Выразаць",
|
||||
"Redo": "Паўтарыць",
|
||||
"Undo": "Адмяніць",
|
||||
"Edit": "Змяніць",
|
||||
"Quit": "Выйсці",
|
||||
"Show/Hide": "Паказаць / схаваць",
|
||||
"Are you sure you want to quit?": "Вы ўпэўненыя, што хочаце выйсці?",
|
||||
"Close Element": "Зачыніць Element",
|
||||
"Cancel": "Адмена"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Добави към речника",
|
||||
"The image failed to save": "Изображението не успя да се запази",
|
||||
"Failed to save image": "Неуспешно запазване на изображението",
|
||||
"Save image as...": "Запази изображението като...",
|
||||
"Copy link address": "Копирай линка",
|
||||
"Copy image address": "Копирай адреса на изображението",
|
||||
"Copy email address": "Копирай имейл адрес",
|
||||
"Copy image": "Копирай изображение",
|
||||
"File": "Файл",
|
||||
"Bring All to Front": "Покажи всички най-отгоре",
|
||||
"Zoom": "Мащабирай",
|
||||
"Stop Speaking": "Спри да говориш",
|
||||
"Start Speaking": "Започни да говориш",
|
||||
"Speech": "Говор",
|
||||
"Unhide": "Покажи",
|
||||
"Hide Others": "Скрий Останалите",
|
||||
"Hide": "Скрий",
|
||||
"Services": "Услуги",
|
||||
"About": "Относно",
|
||||
"Element Help": "Помощ за Елемент",
|
||||
"Help": "Помощ",
|
||||
"Close": "Затвори",
|
||||
"Minimize": "Минимизирай",
|
||||
"Window": "Прозорец",
|
||||
"Toggle Developer Tools": "Превключи инструментите за разработчици",
|
||||
"Toggle Full Screen": "Превключи на Цял екран",
|
||||
"Preferences": "Предпочитания",
|
||||
"Zoom Out": "Намали",
|
||||
"Zoom In": "Увеличи",
|
||||
"Actual Size": "Действителен Размер",
|
||||
"View": "Преглед",
|
||||
"Select All": "Избери Всичко",
|
||||
"Delete": "Изтрий",
|
||||
"Paste and Match Style": "Постави и Използвай текущия стил",
|
||||
"Paste": "Постави",
|
||||
"Copy": "Копирай",
|
||||
"Cut": "Изрежи",
|
||||
"Redo": "Върни",
|
||||
"Undo": "Отмени",
|
||||
"Edit": "Редактирай",
|
||||
"Quit": "Напусни",
|
||||
"Show/Hide": "Покажи/Скрий",
|
||||
"Are you sure you want to quit?": "Сигурен ли си че искаш да напуснеш?",
|
||||
"Close Element": "Затвори Елемент",
|
||||
"Cancel": "Отказ"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Are you sure you want to quit?": "তুমি কি আসলেই বের হতে চাও?",
|
||||
"Close Element": "এলিমেন্ট বন্ধ করো",
|
||||
"Cancel": "বাতিল",
|
||||
"Save image as...": "ছবি সংরক্ষণের ধরন...",
|
||||
"Failed to save image": "ছবি সংরক্ষণ ব্যর্থ",
|
||||
"The image failed to save": "ছবি সংরক্ষণ ব্যর্থ",
|
||||
"Add to dictionary": "অভিধানে যোগ করি",
|
||||
"Copy link address": "সংযোগের ঠিকানা অনুলিপি করো",
|
||||
"Copy image address": "ছবির ঠিকানা অনুলিপি করো",
|
||||
"Copy email address": "ইমেইল ঠিকানা অনুলিপি করো",
|
||||
"Copy image": "ছবি অনুলিপি করো",
|
||||
"File": "নথি",
|
||||
"Bring All to Front": "সবকিছু সামনে আনো",
|
||||
"Zoom": "বড় করা",
|
||||
"Stop Speaking": "কথা বন্ধ করো",
|
||||
"Start Speaking": "কথা শুরু করো",
|
||||
"Speech": "বাচন",
|
||||
"Unhide": "দেখাও",
|
||||
"Hide Others": "অন্যগুলো লুকাও",
|
||||
"Hide": "লুকাও",
|
||||
"Services": "সেবা",
|
||||
"About": "আমাদের সম্পর্কে",
|
||||
"Element Help": "এলিমেন্ট সাহায্য",
|
||||
"Help": "সাহায্য",
|
||||
"Close": "বন্ধ",
|
||||
"Minimize": "সংকোচন",
|
||||
"Window": "জানালা",
|
||||
"Toggle Developer Tools": "ডেভেলপার সরঞ্জামাদি",
|
||||
"Toggle Full Screen": "পূর্ণ পর্দা করো/বের হও",
|
||||
"Preferences": "পছন্দসমূহ",
|
||||
"Zoom Out": "ছোট করো",
|
||||
"Zoom In": "বড়ো করো",
|
||||
"Actual Size": "আসল আকার",
|
||||
"View": "দেখো",
|
||||
"Select All": "সব নির্বাচন",
|
||||
"Delete": "অপসারণ",
|
||||
"Paste and Match Style": "লেপন ও একই ধরনে",
|
||||
"Paste": "লেপন",
|
||||
"Copy": "অনুলিপি",
|
||||
"Cut": "কাটো",
|
||||
"Redo": "পুন",
|
||||
"Undo": "ফিরত",
|
||||
"Edit": "সম্পাদনা",
|
||||
"Quit": "প্রস্থান",
|
||||
"Show/Hide": "দেখাও/লুকাও"
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Afegeix al diccionari",
|
||||
"The image failed to save": "S'ha fallat en desar la imatge",
|
||||
"Failed to save image": "S'ha fallat en desar la imatge",
|
||||
"Save image as...": "Anomena i desa la imatge...",
|
||||
"Copy link address": "Copia l'adreça de l'enllaç",
|
||||
"Copy email address": "Copia l'adreça de correu electrònic",
|
||||
"Copy image": "Copia la imatge",
|
||||
"File": "Fitxer",
|
||||
"Bring All to Front": "Porta-ho tot al davant",
|
||||
"Zoom": "Escala",
|
||||
"Stop Speaking": "Para la veu",
|
||||
"Start Speaking": "Comença la veu",
|
||||
"Speech": "Veu",
|
||||
"Unhide": "Deixa d'amagar",
|
||||
"Hide Others": "Amaga les altres",
|
||||
"Hide": "Amaga",
|
||||
"Services": "Serveis",
|
||||
"About": "Quant a",
|
||||
"Element Help": "Ajuda sobre l'Element",
|
||||
"Help": "Ajuda",
|
||||
"Close": "Tanca",
|
||||
"Minimize": "Minimitza",
|
||||
"Window": "Finestra",
|
||||
"Toggle Developer Tools": "Commuta les eines per a desenvolupadors",
|
||||
"Toggle Full Screen": "Commuta la pantalla completa",
|
||||
"Preferences": "Preferències",
|
||||
"Zoom Out": "Allunya",
|
||||
"Zoom In": "Apropia",
|
||||
"Actual Size": "Mida real",
|
||||
"View": "Visualitza",
|
||||
"Select All": "Selecciona-ho tot",
|
||||
"Delete": "Suprimeix",
|
||||
"Paste and Match Style": "Enganxa i fes coincidir l'estil",
|
||||
"Paste": "Enganxa",
|
||||
"Copy": "Copia",
|
||||
"Cut": "Retalla",
|
||||
"Redo": "Refés",
|
||||
"Undo": "Desfés",
|
||||
"Edit": "Edita",
|
||||
"Quit": "Surt",
|
||||
"Show/Hide": "Mostra/Amaga",
|
||||
"Are you sure you want to quit?": "Esteu segur que voleu sortir?",
|
||||
"Close Element": "Tanca l'Element",
|
||||
"Cancel": "Cancel·la"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Speech": "Sprache",
|
||||
"Paste and Match Style": "Einfügen und Formatierung beibehalten",
|
||||
"Stop Speaking": "Aufnahme beenden",
|
||||
"Start Speaking": "Aufnahme starten",
|
||||
"Services": "Dienste",
|
||||
"Are you sure you want to quit?": "Wirklich beenden?",
|
||||
"Add to dictionary": "Wörterbuch hinzufügen",
|
||||
"The image failed to save": "Das Bild konnte nicht gespeichert werden",
|
||||
"Failed to save image": "Bild kann nicht gespeichert werden",
|
||||
"Save image as...": "Bild speichern unter...",
|
||||
"Copy link address": "Link-Adresse kopieren",
|
||||
"Copy email address": "Email-Adresse kopieren",
|
||||
"Copy image": "Bild kopieren",
|
||||
"File": "Datei",
|
||||
"Bring All to Front": "Alles in den Vordergrund",
|
||||
"Zoom": "Zoom",
|
||||
"Unhide": "Wieder anzeigen",
|
||||
"Hide Others": "Andere verstecken",
|
||||
"Hide": "Verstecken",
|
||||
"About": "Über",
|
||||
"Element Help": "Hilfe zu Element",
|
||||
"Help": "Hilfe",
|
||||
"Close": "Schließen",
|
||||
"Minimize": "Minimieren",
|
||||
"Window": "Fenster",
|
||||
"Toggle Developer Tools": "Developer-Tools an/aus",
|
||||
"Toggle Full Screen": "Vollbildschirm an/aus",
|
||||
"Preferences": "Einstellungen",
|
||||
"Zoom Out": "Verkleinern",
|
||||
"Zoom In": "Vergrößern",
|
||||
"Actual Size": "Tatsächliche Größe",
|
||||
"View": "Ansicht",
|
||||
"Select All": "Alles auswählen",
|
||||
"Delete": "Löschen",
|
||||
"Paste": "Einfügen",
|
||||
"Copy": "Kopieren",
|
||||
"Cut": "Ausschneiden",
|
||||
"Redo": "Wiederherstellen",
|
||||
"Undo": "Rückgängig",
|
||||
"Edit": "Bearbeiten",
|
||||
"Quit": "Beenden",
|
||||
"Show/Hide": "Anzeigen/Ausblenden",
|
||||
"Close Element": "Element schließen",
|
||||
"Cancel": "Abbrechen",
|
||||
"Copy image address": "Bild-Adresse kopieren"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Are you sure you want to quit?": "Είστε βέβαιος ότι θέλετε να εγκαταλείψετε;",
|
||||
"Zoom": "Ζουμ",
|
||||
"Unhide": "Εμφάνιση",
|
||||
"Window": "Παράθυρο",
|
||||
"Toggle Developer Tools": "Άνοιγμα Εργαλείων Προγραμματιστή",
|
||||
"Toggle Full Screen": "Εναλλαγή σε Πλήρη Οθόνη",
|
||||
"Copy email address": "Αντιγραφή διεύθυνσης email",
|
||||
"File": "Αρχείο",
|
||||
"Bring All to Front": "Μεταφορά Όλων στο Προσκήνιο",
|
||||
"Stop Speaking": "Τερματίστε να μιλάτε",
|
||||
"Start Speaking": "Ξεκινήστε να μιλάτε",
|
||||
"Speech": "Ομιλία",
|
||||
"Hide Others": "Απόκρυψη Άλλων",
|
||||
"Hide": "Απόκρυψη",
|
||||
"Services": "Υπηρεσίες",
|
||||
"About": "Σχετικά με",
|
||||
"Element Help": "Βοήθεια για το Element",
|
||||
"Help": "Βοήθεια",
|
||||
"Close": "Κλείσιμο",
|
||||
"Minimize": "Ελαχιστοποίηση",
|
||||
"Preferences": "Προτιμήσεις",
|
||||
"Zoom Out": "Σμίκρυνση",
|
||||
"Zoom In": "Μεγέθυνση",
|
||||
"Actual Size": "Πραγματικό Μέγεθος",
|
||||
"View": "Προβολή",
|
||||
"Select All": "Επιλογή Όλων",
|
||||
"Delete": "Διαγραφή",
|
||||
"Paste and Match Style": "Επικόλληση και Ταίριασμα Στυλ",
|
||||
"Paste": "Επικόλληση",
|
||||
"Copy": "Αντιγραφή",
|
||||
"Cut": "Αποκοπή",
|
||||
"Redo": "Επανάληψη",
|
||||
"Undo": "Αναίρεση",
|
||||
"Edit": "Επεξεργασία",
|
||||
"Quit": "Κλείσιμο",
|
||||
"Show/Hide": "Eμφάνιση/Απόκρυψη",
|
||||
"Close Element": "Κλείστε το Element",
|
||||
"Cancel": "Ακύρωση",
|
||||
"Add to dictionary": "Προσθήκη στο λεξικό",
|
||||
"The image failed to save": "Η αποθήκευση της εικόνας απέτυχε",
|
||||
"Failed to save image": "Αποτυχία αποθήκευσης εικόνας",
|
||||
"Save image as...": "Αποθήκευση εικόνας ως...",
|
||||
"Copy link address": "Αντιγραφή διεύθυνσης συνδέσμου",
|
||||
"Copy image address": "Αντιγραφή διεύθυνσης εικόνας",
|
||||
"Copy image": "Αντιγραφή εικόνας"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Cancel": "Cancel",
|
||||
"Close Element": "Close Element",
|
||||
"Are you sure you want to quit?": "Are you sure you want to quit?",
|
||||
"Show/Hide": "Show/Hide",
|
||||
"Quit": "Quit",
|
||||
"Edit": "Edit",
|
||||
"Undo": "Undo",
|
||||
"Redo": "Redo",
|
||||
"Cut": "Cut",
|
||||
"Copy": "Copy",
|
||||
"Paste": "Paste",
|
||||
"Paste and Match Style": "Paste and Match Style",
|
||||
"Delete": "Delete",
|
||||
"Select All": "Select All",
|
||||
"View": "View",
|
||||
"Actual Size": "Actual Size",
|
||||
"Zoom In": "Zoom In",
|
||||
"Zoom Out": "Zoom Out",
|
||||
"Preferences": "Preferences",
|
||||
"Toggle Full Screen": "Toggle Full Screen",
|
||||
"Toggle Developer Tools": "Toggle Developer Tools",
|
||||
"Window": "Window",
|
||||
"Minimize": "Minimize",
|
||||
"Close": "Close",
|
||||
"Help": "Help",
|
||||
"Element Help": "Element Help",
|
||||
"About": "About",
|
||||
"Services": "Services",
|
||||
"Hide": "Hide",
|
||||
"Hide Others": "Hide Others",
|
||||
"Unhide": "Unhide",
|
||||
"Speech": "Speech",
|
||||
"Start Speaking": "Start Speaking",
|
||||
"Stop Speaking": "Stop Speaking",
|
||||
"Zoom": "Zoom",
|
||||
"Bring All to Front": "Bring All to Front",
|
||||
"File": "File",
|
||||
"Copy image": "Copy image",
|
||||
"Copy email address": "Copy email address",
|
||||
"Copy image address": "Copy image address",
|
||||
"Copy link address": "Copy link address",
|
||||
"Save image as...": "Save image as...",
|
||||
"Failed to save image": "Failed to save image",
|
||||
"The image failed to save": "The image failed to save",
|
||||
"Add to dictionary": "Add to dictionary"
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"Close": "Close",
|
||||
"Add to dictionary": "Add to dictionary",
|
||||
"The image failed to save": "The image failed to save",
|
||||
"Failed to save image": "Failed to save image",
|
||||
"Save image as...": "Save image as...",
|
||||
"Copy link address": "Copy link address",
|
||||
"Copy email address": "Copy email address",
|
||||
"Copy image": "Copy image",
|
||||
"File": "File",
|
||||
"Bring All to Front": "Bring All to Front",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Stop Speaking",
|
||||
"Start Speaking": "Start Speaking",
|
||||
"Speech": "Speech",
|
||||
"Unhide": "Unhide",
|
||||
"Hide Others": "Hide Others",
|
||||
"Hide": "Hide",
|
||||
"Services": "Services",
|
||||
"About": "About",
|
||||
"Element Help": "Element Help",
|
||||
"Help": "Help",
|
||||
"Minimize": "Minimize",
|
||||
"Window": "Window",
|
||||
"Toggle Developer Tools": "Toggle Developer Tools",
|
||||
"Toggle Full Screen": "Toggle Full Screen",
|
||||
"Preferences": "Preferences",
|
||||
"Zoom Out": "Zoom Out",
|
||||
"Zoom In": "Zoom In",
|
||||
"Actual Size": "Actual Size",
|
||||
"View": "View",
|
||||
"Select All": "Select All",
|
||||
"Delete": "Delete",
|
||||
"Paste and Match Style": "Paste and Match Style",
|
||||
"Paste": "Paste",
|
||||
"Copy": "Copy",
|
||||
"Cut": "Cut",
|
||||
"Redo": "Redo",
|
||||
"Undo": "Undo",
|
||||
"Edit": "Edit",
|
||||
"Quit": "Quit",
|
||||
"Show/Hide": "Show/Hide",
|
||||
"Are you sure you want to quit?": "Are you sure you want to quit?",
|
||||
"Close Element": "Close Element",
|
||||
"Cancel": "Cancel"
|
||||
}
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"The image failed to save": "La bildo malsukcesis elŝutiĝi",
|
||||
"Failed to save image": "Malsukcesis elŝuti bildon",
|
||||
"Stop Speaking": "Ĉesi Paroli",
|
||||
"Start Speaking": "Ekparoli",
|
||||
"Unhide": "Malkaŝi",
|
||||
"Hide Others": "Kaŝi Aliajn",
|
||||
"Hide": "Kaŝi",
|
||||
"About": "Informilo",
|
||||
"Element Help": "Element-a Helpo",
|
||||
"Help": "Helpo",
|
||||
"Toggle Developer Tools": "Baskuligi Programistajn Ilojn",
|
||||
"Preferences": "Preferoj",
|
||||
"View": "Vidi",
|
||||
"Delete": "Forigi",
|
||||
"Redo": "Refari",
|
||||
"Undo": "Malfari",
|
||||
"Edit": "Redakti",
|
||||
"Show/Hide": "Montri/Kaŝi",
|
||||
"Cancel": "Nuligi"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Añadir al diccionario",
|
||||
"The image failed to save": "La imagen no se ha podido guardar",
|
||||
"Failed to save image": "No se ha podido guardar la imagen",
|
||||
"Save image as...": "Guardar imagen como...",
|
||||
"Copy link address": "Copiar dirección de enlace",
|
||||
"Copy email address": "Copiar dirección de correo",
|
||||
"Copy image": "Copiar imagen",
|
||||
"File": "Archivo",
|
||||
"Bring All to Front": "Traer todas al primer plano",
|
||||
"Zoom": "Zoom",
|
||||
"Start Speaking": "Empezar a hablar",
|
||||
"Stop Speaking": "Parar de hablar",
|
||||
"Speech": "Dictado",
|
||||
"Unhide": "Mostrar",
|
||||
"Hide Others": "Ocultar otros",
|
||||
"Hide": "Ocultar",
|
||||
"Services": "Servicios",
|
||||
"About": "Acerca de",
|
||||
"Element Help": "Ayuda de Element",
|
||||
"Help": "Ayuda",
|
||||
"Close": "Cerrar",
|
||||
"Minimize": "Minimizar",
|
||||
"Window": "Ventana",
|
||||
"Toggle Developer Tools": "Abrir/cerrar herramientas de desarrollo",
|
||||
"Toggle Full Screen": "Entrar/salir de pantalla completa",
|
||||
"Preferences": "Preferencias",
|
||||
"Zoom Out": "Alejar",
|
||||
"Zoom In": "Acercar",
|
||||
"Actual Size": "Tamaño real",
|
||||
"View": "Ver",
|
||||
"Select All": "Seleccionar todo",
|
||||
"Delete": "Eliminar",
|
||||
"Paste and Match Style": "Pegar manteniendo estilo",
|
||||
"Paste": "Pegar",
|
||||
"Copy": "Copiar",
|
||||
"Cut": "Cortar",
|
||||
"Redo": "Rehacer",
|
||||
"Undo": "Deshacer",
|
||||
"Edit": "Editar",
|
||||
"Quit": "Salir",
|
||||
"Show/Hide": "Ver/Ocultar",
|
||||
"Are you sure you want to quit?": "¿Quieres salir?",
|
||||
"Close Element": "Cerrar Element",
|
||||
"Cancel": "Cancelar",
|
||||
"Copy image address": "Copiar dirección de la imagen"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Element Help": "Rakenduse Element abiteave",
|
||||
"About": "Rakenduse teave",
|
||||
"The image failed to save": "Seda pilti ei õnnestunud salvestada",
|
||||
"Add to dictionary": "Lisa sõnastikku",
|
||||
"Failed to save image": "Pildi salvestamine ei õnnestunud",
|
||||
"Save image as...": "Salvesta pilt kui...",
|
||||
"Copy link address": "Kopeeri lingi aadress",
|
||||
"Copy email address": "Kopeeri e-posti aadress",
|
||||
"Copy image": "Kopeeri pilt",
|
||||
"File": "Fail",
|
||||
"Bring All to Front": "Too kõik esiplaanile",
|
||||
"Zoom": "Suumi",
|
||||
"Stop Speaking": "Lõpeta rääkimine",
|
||||
"Start Speaking": "Alusta rääkimist",
|
||||
"Speech": "Kõne",
|
||||
"Unhide": "Näita uuesti",
|
||||
"Hide Others": "Peida muud",
|
||||
"Hide": "Peida",
|
||||
"Services": "Teenused",
|
||||
"Help": "Abiteave",
|
||||
"Close": "Sulge",
|
||||
"Minimize": "Vähenda",
|
||||
"Window": "Aken",
|
||||
"Toggle Developer Tools": "Arendaja töövahendid sisse/välja",
|
||||
"Toggle Full Screen": "Täisekraanivaade sisse/välja",
|
||||
"Preferences": "Seadistused",
|
||||
"Zoom Out": "Vähenda",
|
||||
"Zoom In": "Suurenda",
|
||||
"Actual Size": "Näita tavasuuruses",
|
||||
"View": "Vaata",
|
||||
"Select All": "Vali kõik",
|
||||
"Delete": "Kustuta",
|
||||
"Paste and Match Style": "Aseta kasutades sama stiili",
|
||||
"Paste": "Aseta",
|
||||
"Copy": "Kopeeri",
|
||||
"Cut": "Lõika",
|
||||
"Redo": "Tee uuesti",
|
||||
"Undo": "Võta tagasi",
|
||||
"Edit": "Muuda",
|
||||
"Quit": "Välju",
|
||||
"Show/Hide": "Näita/peida",
|
||||
"Are you sure you want to quit?": "Kas sa kindlasti soovid rakendusest väljuda?",
|
||||
"Close Element": "Sulge Element",
|
||||
"Cancel": "Tühista",
|
||||
"Copy image address": "Kopeeri pildi aadress"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Paste and Match Style": "جایگذاری و تطبیق سَبک",
|
||||
"Add to dictionary": "افزودن به لغتنامه",
|
||||
"The image failed to save": "تصویر ذخیره نشد",
|
||||
"Failed to save image": "ذخیرهٔ تصویر شکست خورد",
|
||||
"Save image as...": "ذخیرهٔ تصویر به عنوان...",
|
||||
"Copy link address": "رونوشت نشانی پیوند",
|
||||
"Copy image address": "رونوشت نشانی تصویر",
|
||||
"Copy email address": "رونوشت نشانی رایانامه",
|
||||
"Copy image": "رونوشت تصویر",
|
||||
"File": "پرونده",
|
||||
"Bring All to Front": "همه را به جلو بیاورید",
|
||||
"Zoom": "بزرگنمایی",
|
||||
"Speech": "صحبت کردن",
|
||||
"Stop Speaking": "صحبت کردن را تمام کنید",
|
||||
"Start Speaking": "صحبت کردن را شروع کنید",
|
||||
"Unhide": "آشکار",
|
||||
"Hide Others": "پنهان کردن دیگران",
|
||||
"Hide": "پنهان",
|
||||
"Services": "خدمات",
|
||||
"About": "درباره",
|
||||
"Element Help": "راهنمای المنت",
|
||||
"Help": "راهنما",
|
||||
"Close": "بستن",
|
||||
"Minimize": "کمینه",
|
||||
"Window": "پنجره",
|
||||
"Toggle Developer Tools": "تغییر وضعیت ابزارهای توسعهدهنده",
|
||||
"Toggle Full Screen": "تغییر وضعیت تمامصفحه",
|
||||
"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": "لغو"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Paste": "Liitä",
|
||||
"Paste and Match Style": "Liitä ja sovita tyyli",
|
||||
"Add to dictionary": "Lisää sanakirjaan",
|
||||
"The image failed to save": "Kuvan tallennus epäonnistui",
|
||||
"Failed to save image": "Kuvan tallennus epäonnistui",
|
||||
"Save image as...": "Tallenna kuva nimellä...",
|
||||
"Copy link address": "Kopioi linkin osoite",
|
||||
"Copy email address": "Kopioi sähköpostiosoite",
|
||||
"Copy image": "Kopioi kuva",
|
||||
"File": "Tiedosto",
|
||||
"Bring All to Front": "Tuo kaikki eteen",
|
||||
"Zoom": "Suurennus",
|
||||
"Stop Speaking": "Lopeta puhe",
|
||||
"Start Speaking": "Aloita puhe",
|
||||
"Speech": "Puhe",
|
||||
"Unhide": "Palauta näkyviin",
|
||||
"Hide Others": "Piilota muut",
|
||||
"Hide": "Piilota",
|
||||
"Services": "Palvelut",
|
||||
"About": "Tietoja",
|
||||
"Element Help": "Elementin ohjeet",
|
||||
"Help": "Apua",
|
||||
"Close": "Sulje",
|
||||
"Minimize": "Pienennä",
|
||||
"Window": "Ikkuna",
|
||||
"Toggle Developer Tools": "Näytä tai piilota kehittäjätyökalut",
|
||||
"Toggle Full Screen": "Vaihda koko näytön tilaa",
|
||||
"Preferences": "Asetukset",
|
||||
"Zoom Out": "Pienennä",
|
||||
"Zoom In": "Suurenna",
|
||||
"Actual Size": "Alkuperäinen koko",
|
||||
"View": "Näytä",
|
||||
"Select All": "Valitse kaikki",
|
||||
"Delete": "Poista",
|
||||
"Copy": "Kopioi",
|
||||
"Cut": "Leikkaa",
|
||||
"Redo": "Tee uudestaan",
|
||||
"Undo": "Peru",
|
||||
"Edit": "Muokkaa",
|
||||
"Quit": "Lopeta",
|
||||
"Show/Hide": "Näytä/piilota",
|
||||
"Are you sure you want to quit?": "Haluatko varmasti poistua?",
|
||||
"Close Element": "Sulje Element",
|
||||
"Cancel": "Peruuta",
|
||||
"Copy image address": "Kopioi kuvan osoite"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Undo": "Annuler",
|
||||
"Edit": "Modifier",
|
||||
"Quit": "Quitter",
|
||||
"Show/Hide": "Afficher/Masquer",
|
||||
"Are you sure you want to quit?": "Êtes-vous sûr de vouloir quitter ?",
|
||||
"Close Element": "Fermer Element",
|
||||
"Cancel": "Annuler",
|
||||
"Unhide": "Dé-masquer",
|
||||
"Hide Others": "Masquer les autres",
|
||||
"Hide": "Masquer",
|
||||
"Services": "Services",
|
||||
"About": "À propos",
|
||||
"Element Help": "Aide d’Element",
|
||||
"Help": "Aide",
|
||||
"Close": "Fermer",
|
||||
"Minimize": "Minimiser",
|
||||
"Window": "Fenêtre",
|
||||
"Toggle Developer Tools": "Basculer les outils de développement",
|
||||
"Toggle Full Screen": "Basculer le plein écran",
|
||||
"Preferences": "Préférences",
|
||||
"Zoom Out": "Dé-zoomer",
|
||||
"Zoom In": "Zoomer",
|
||||
"Actual Size": "Taille réelle",
|
||||
"View": "Afficher",
|
||||
"Select All": "Tout sélectionner",
|
||||
"Delete": "Supprimer",
|
||||
"Paste and Match Style": "Copier avec le style de destination",
|
||||
"Paste": "Coller",
|
||||
"Copy": "Copier",
|
||||
"Cut": "Couper",
|
||||
"Speech": "Dictée",
|
||||
"Add to dictionary": "Ajouter au dictionnaire",
|
||||
"The image failed to save": "L’image n’a pas pu être sauvegardée",
|
||||
"Failed to save image": "Échec de la sauvegarde de l’image",
|
||||
"Save image as...": "Enregistrer l’image sous…",
|
||||
"Copy link address": "Copier l’adresse du lien",
|
||||
"Copy email address": "Copier l’adresse e-mail",
|
||||
"Copy image": "Copier l’image",
|
||||
"File": "Fichier",
|
||||
"Bring All to Front": "Tout amener au premier plan",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Arrêter la dictée",
|
||||
"Start Speaking": "Commencer la dictée",
|
||||
"Copy image address": "Copier l'adresse de l'image",
|
||||
"Redo": "Refaire"
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"Copy image": "Ofbylding kopiearje",
|
||||
"Speech": "Spraak",
|
||||
"View": "Byld",
|
||||
"Paste and Match Style": "Plakke en lit stilen oerienkomme",
|
||||
"Add to dictionary": "Oan wurdlist tafoegje",
|
||||
"The image failed to save": "It is net slagge de ôfbylding te bewarjen",
|
||||
"Failed to save image": "Ofbylding bewarjen mislearre",
|
||||
"Save image as...": "Ofbylding bewarje as…",
|
||||
"Copy link address": "Keppeling kopiearje",
|
||||
"Copy email address": "E-mailadres kopiearje",
|
||||
"File": "Bestân",
|
||||
"Bring All to Front": "Alles nei foaren bringe",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Stopje mei praten",
|
||||
"Start Speaking": "Begjin mei praten",
|
||||
"Unhide": "Wer toane",
|
||||
"Hide Others": "Oare ferbergje",
|
||||
"Hide": "Ferbergje",
|
||||
"Services": "Tsjinsten",
|
||||
"About": "Oer",
|
||||
"Element Help": "Element help",
|
||||
"Help": "Help",
|
||||
"Close": "Slute",
|
||||
"Minimize": "Minimalisearje",
|
||||
"Window": "Finster",
|
||||
"Toggle Developer Tools": "Untwikkelersark yn-/útskeakelje",
|
||||
"Toggle Full Screen": "Folslein skerm yn-/útskeakelje",
|
||||
"Preferences": "Foarkarren",
|
||||
"Zoom Out": "Utzoome",
|
||||
"Zoom In": "Ynzoome",
|
||||
"Actual Size": "Werklike grutte",
|
||||
"Select All": "Alles selektearje",
|
||||
"Delete": "Fuortsmite",
|
||||
"Paste": "Plakke",
|
||||
"Copy": "Kopiearje",
|
||||
"Cut": "Knippe",
|
||||
"Redo": "Opnij útfiere",
|
||||
"Undo": "Ungedien meitsje",
|
||||
"Edit": "Bewurkje",
|
||||
"Quit": "Ofslute",
|
||||
"Show/Hide": "Toane/Ferbergje",
|
||||
"Are you sure you want to quit?": "Binne jo der wis fan dat jo ôfslute wolle?",
|
||||
"Close Element": "Element ôfslute",
|
||||
"Cancel": "Annulearje"
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Engadir ao dicionario",
|
||||
"The image failed to save": "Non se gardou a imaxe",
|
||||
"Failed to save image": "Fallou o gardado da imaxe",
|
||||
"Save image as...": "Gardar imaxe como...",
|
||||
"Copy link address": "Copiar enderezo da ligazón",
|
||||
"Copy email address": "Copiar enderezo de email",
|
||||
"Copy image": "Copiar imaxe",
|
||||
"File": "Ficheiro",
|
||||
"Bring All to Front": "Traer todo á fronte",
|
||||
"Zoom": "Aumento",
|
||||
"Stop Speaking": "Deixa de falar",
|
||||
"Start Speaking": "Comeza a falar",
|
||||
"Speech": "Falar",
|
||||
"Unhide": "Desagochar",
|
||||
"Hide Others": "Agochar outras",
|
||||
"Hide": "Agochar",
|
||||
"Services": "Servizos",
|
||||
"About": "Acerca de",
|
||||
"Element Help": "Axuda de Element",
|
||||
"Help": "Axuda",
|
||||
"Close": "Pechar",
|
||||
"Minimize": "Minimizar",
|
||||
"Window": "Ventá",
|
||||
"Toggle Developer Tools": "Activar ferramentas de desenvolvemento",
|
||||
"Toggle Full Screen": "Activar pantalla completa",
|
||||
"Preferences": "Preferencias",
|
||||
"Zoom Out": "Diminuir",
|
||||
"Zoom In": "Aumentar",
|
||||
"Actual Size": "Tamaño real",
|
||||
"View": "Ver",
|
||||
"Select All": "Elexir todo",
|
||||
"Delete": "Eliminar",
|
||||
"Paste and Match Style": "Pegar e imitar estilo",
|
||||
"Paste": "Pegar",
|
||||
"Copy": "Copiar",
|
||||
"Cut": "Cortar",
|
||||
"Redo": "Refacer",
|
||||
"Undo": "Desfacer",
|
||||
"Edit": "Editar",
|
||||
"Quit": "Saír",
|
||||
"Show/Hide": "Mostrar/Agochar",
|
||||
"Are you sure you want to quit?": "Tes a certeza de que queres saír?",
|
||||
"Close Element": "Pechar Element",
|
||||
"Cancel": "Cancelar"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Actual Size": "גודל ממשי",
|
||||
"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": "הבא הכל לחזית",
|
||||
"Zoom": "גודל תצוגה",
|
||||
"Stop Speaking": "הפסק לדבר",
|
||||
"Start Speaking": "התחל לדבר",
|
||||
"Speech": "דיבור",
|
||||
"Unhide": "בטל הסתרה",
|
||||
"Hide Others": "הסתר אחרים",
|
||||
"Hide": "הסתר",
|
||||
"Services": "שרותים",
|
||||
"About": "אודות",
|
||||
"Element Help": "עזרה של אלמנט",
|
||||
"Help": "עזרה",
|
||||
"Close": "סגור",
|
||||
"Minimize": "מזער",
|
||||
"Window": "חלון",
|
||||
"Toggle Developer Tools": "הפעל כלי מפתחים",
|
||||
"Toggle Full Screen": "הפעל מצב מסך מלא",
|
||||
"Preferences": "העדפות",
|
||||
"Zoom Out": "התרחק",
|
||||
"Zoom In": "התקרב",
|
||||
"View": "צפה",
|
||||
"Select All": "בחר הכל",
|
||||
"Delete": "מחק",
|
||||
"Paste": "הדבק",
|
||||
"Copy": "העתק",
|
||||
"Cut": "גזור",
|
||||
"Undo": "בטל ביצוע",
|
||||
"Redo": "בצע שוב",
|
||||
"Edit": "עריכה",
|
||||
"Quit": "יציאה",
|
||||
"Show/Hide": "הצג\\הסתר",
|
||||
"Are you sure you want to quit?": "האם אתה בטוח שברצונך לצאת?",
|
||||
"Close Element": "סגור את אלמנט",
|
||||
"Cancel": "ביטול",
|
||||
"Paste and Match Style": "הדבק והתאם סגנון",
|
||||
"Copy image address": "העתקת כתובת התמונה"
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Hozzáadás a szótárhoz",
|
||||
"The image failed to save": "A kép mentése sikertelen",
|
||||
"Failed to save image": "Kép mentése sikertelen",
|
||||
"Save image as...": "Kép mentése másként...",
|
||||
"Copy link address": "Hivatkozás másolása",
|
||||
"Copy email address": "E-mail cím másolása",
|
||||
"Copy image": "Kép másolása",
|
||||
"File": "Fájl",
|
||||
"Bring All to Front": "Mindent előtérbe hoz",
|
||||
"Zoom": "Nagyítás",
|
||||
"Stop Speaking": "Fejezze be a beszédet",
|
||||
"Start Speaking": "Kezdjen beszélni",
|
||||
"Speech": "Beszéd",
|
||||
"Unhide": "Felfed",
|
||||
"Hide Others": "Minden mást eltakar",
|
||||
"Hide": "Eltakar",
|
||||
"Services": "Szolgáltatás",
|
||||
"About": "Névjegy",
|
||||
"Element Help": "Element segítség",
|
||||
"Help": "Segítség",
|
||||
"Close": "Bezár",
|
||||
"Minimize": "Lecsukás",
|
||||
"Window": "Ablak",
|
||||
"Toggle Developer Tools": "Fejlesztői eszközök",
|
||||
"Toggle Full Screen": "Teljes képernyő",
|
||||
"Preferences": "Beállítások",
|
||||
"Zoom Out": "Kicsinyít",
|
||||
"Zoom In": "Nagyít",
|
||||
"Actual Size": "Jelenlegi méret",
|
||||
"View": "Nézet",
|
||||
"Select All": "Összes kijelölése",
|
||||
"Delete": "Töröl",
|
||||
"Paste and Match Style": "Beillesztés formázással",
|
||||
"Paste": "Beillesztés",
|
||||
"Copy": "Másol",
|
||||
"Cut": "Kivág",
|
||||
"Redo": "Újra",
|
||||
"Undo": "Visszavon",
|
||||
"Edit": "Szerkeszt",
|
||||
"Quit": "Kilép",
|
||||
"Show/Hide": "Megmutat/Elrejt",
|
||||
"Are you sure you want to quit?": "Biztos, hogy kilép?",
|
||||
"Close Element": "Element bezárása",
|
||||
"Cancel": "Mégsem",
|
||||
"Copy image address": "Kép címének másolása"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Tambah ke kamus",
|
||||
"The image failed to save": "Gambar gagal disimpan",
|
||||
"Failed to save image": "Gagal menyimpan gambar",
|
||||
"Save image as...": "Simpan gambar sebagai...",
|
||||
"Copy link address": "Salin alamat tautan",
|
||||
"Copy email address": "Salin surel",
|
||||
"Copy image": "Salin gambar",
|
||||
"File": "File",
|
||||
"Hide Others": "Sembunyikan yang Lain",
|
||||
"Bring All to Front": "Bawa Semua ke Depan",
|
||||
"Zoom": "Perbesar",
|
||||
"Stop Speaking": "Berhenti Berbicara",
|
||||
"Start Speaking": "Mulai Berbicara",
|
||||
"Speech": "Dikte",
|
||||
"Unhide": "Tampilkan",
|
||||
"Hide": "Sembunyikan",
|
||||
"Services": "Layanan",
|
||||
"About": "Tentang",
|
||||
"Element Help": "Bantuan Element",
|
||||
"Help": "Bantuan",
|
||||
"Close": "Tutup",
|
||||
"Minimize": "Minimalkan",
|
||||
"Window": "Jendela",
|
||||
"Toggle Developer Tools": "Beralih Alat Pengembang",
|
||||
"Toggle Full Screen": "Beralih Layar Penuh",
|
||||
"Preferences": "Pengaturan",
|
||||
"Zoom Out": "Perkecil",
|
||||
"Zoom In": "Perbesar",
|
||||
"Cut": "Potong",
|
||||
"Redo": "Ulangi",
|
||||
"Undo": "Urungkan",
|
||||
"Actual Size": "Ukuran Sebenarnya",
|
||||
"View": "Tampilan",
|
||||
"Select All": "Pilih Semua",
|
||||
"Delete": "Hapus",
|
||||
"Paste and Match Style": "Tempel dan Cocokkan Gaya",
|
||||
"Paste": "Tempel",
|
||||
"Copy": "Salin",
|
||||
"Edit": "Edit",
|
||||
"Quit": "Keluar",
|
||||
"Show/Hide": "Tampilkan/Sembunyikan",
|
||||
"Are you sure you want to quit?": "Apakah Anda yakin ingin keluar?",
|
||||
"Close Element": "Tutup Element",
|
||||
"Cancel": "Batal",
|
||||
"Copy image address": "Salin alamat gambar"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Bæta við orðasafn",
|
||||
"The image failed to save": "Myndina var ekki hægt að vista",
|
||||
"Failed to save image": "Mistókst að vista mynd",
|
||||
"Save image as...": "Vista mynd sem...",
|
||||
"Copy link address": "Afrita vistfang tengils",
|
||||
"Copy email address": "Afrita tölvupóstfang",
|
||||
"Copy image": "Afrita mynd",
|
||||
"File": "Skrá",
|
||||
"Bring All to Front": "Setja allt fremst",
|
||||
"Zoom": "Stærð",
|
||||
"Stop Speaking": "Hætta tali",
|
||||
"Start Speaking": "Byrja tal",
|
||||
"Speech": "Tal",
|
||||
"Unhide": "Birta",
|
||||
"Hide Others": "Fela aðra",
|
||||
"Hide": "Fela",
|
||||
"Services": "Þjónustur",
|
||||
"About": "Um hugbúnaðinn",
|
||||
"Element Help": "Hjálp við Element",
|
||||
"Help": "Hjálp",
|
||||
"Close": "Loka",
|
||||
"Minimize": "Lágmarka",
|
||||
"Window": "Gluggi",
|
||||
"Toggle Developer Tools": "Víxla forritunarverkfærum af/á",
|
||||
"Toggle Full Screen": "Víxla fullum skjá af/á",
|
||||
"Preferences": "Stillingar",
|
||||
"Zoom Out": "Minnka",
|
||||
"Zoom In": "Stækka",
|
||||
"Actual Size": "Raunstærð",
|
||||
"View": "Skoða",
|
||||
"Select All": "Velja allt",
|
||||
"Delete": "Eyða",
|
||||
"Paste and Match Style": "Líma og samsvara stíl",
|
||||
"Paste": "Líma",
|
||||
"Copy": "Afrita",
|
||||
"Cut": "Klippa",
|
||||
"Redo": "Endurgera",
|
||||
"Undo": "Afturkalla",
|
||||
"Edit": "Breyta",
|
||||
"Quit": "Hætta",
|
||||
"Show/Hide": "Sýna/Fela",
|
||||
"Are you sure you want to quit?": "Ertu viss um að þú viljir hætta?",
|
||||
"Close Element": "Loka Element",
|
||||
"Cancel": "Hætta við",
|
||||
"Copy image address": "Afrita slóð myndar"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Aggiungi al dizionario",
|
||||
"The image failed to save": "Non è stato possibile salvare l'immagine",
|
||||
"Failed to save image": "Salvataggio immagine fallito",
|
||||
"Save image as...": "Salva immagine come...",
|
||||
"Copy link address": "Copia indirizzo collegamento",
|
||||
"Copy email address": "Copia indirizzo email",
|
||||
"Copy image": "Copia immagine",
|
||||
"File": "File",
|
||||
"Bring All to Front": "Porta tutto in primo piano",
|
||||
"Zoom": "Zoom",
|
||||
"Start Speaking": "Inizia a parlare",
|
||||
"Unhide": "Mostra",
|
||||
"Hide Others": "Nascondi gli altri",
|
||||
"Hide": "Nascondi",
|
||||
"Services": "Servizi",
|
||||
"About": "Al riguardo",
|
||||
"Element Help": "Aiuto di Element",
|
||||
"Help": "Aiuto",
|
||||
"Close": "Chiudi",
|
||||
"Minimize": "Riduci",
|
||||
"Window": "Finestra",
|
||||
"Toggle Developer Tools": "Attiva strumenti per sviluppatori",
|
||||
"Toggle Full Screen": "Passa a schermo intero",
|
||||
"Preferences": "Preferenze",
|
||||
"Zoom Out": "Rimpicciolisci",
|
||||
"Zoom In": "Ingrandisci",
|
||||
"Actual Size": "Dimensione effettiva",
|
||||
"View": "Vedi",
|
||||
"Select All": "Seleziona tutto",
|
||||
"Delete": "Elimina",
|
||||
"Paste and Match Style": "Incolla e abbina lo stile",
|
||||
"Paste": "Incolla",
|
||||
"Copy": "Copia",
|
||||
"Cut": "Taglia",
|
||||
"Redo": "Ripeti",
|
||||
"Undo": "Annulla",
|
||||
"Edit": "Modifica",
|
||||
"Quit": "Esci",
|
||||
"Show/Hide": "Mostra/Nascondi",
|
||||
"Are you sure you want to quit?": "Vuoi veramente uscire?",
|
||||
"Close Element": "Chiudi Element",
|
||||
"Cancel": "Annulla",
|
||||
"Stop Speaking": "Smetti di parlare",
|
||||
"Speech": "Dettatura",
|
||||
"Copy image address": "Copia indirizzo immagine"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Toggle Developer Tools": "ສະຫຼັບໄປໜ້າເຄື່ອງມືພັດທະນາ",
|
||||
"Add to dictionary": "ເພີ່ມເຂົ້າໄປວັດຈະນານຸກົມ",
|
||||
"The image failed to save": "ຮູບພາບບໍ່ສາມາດບັດທຶກໄດ້",
|
||||
"Failed to save image": "ການບັນທຶກຮູບພາບບໍ່ສຳເລັດ",
|
||||
"Save image as...": "ບັນທຶກຮູບພາບເປັນ...",
|
||||
"Copy link address": "ສຳເນົາທີ່ຢູ່ລິ້ງ",
|
||||
"Copy image address": "ສຳເນົາທີ່ຢູ່ຮູບພາບ",
|
||||
"Copy email address": "ສຳເນົາທີ່ຢູ່ເມວ",
|
||||
"Copy image": "ສຳເນົາຮູບ",
|
||||
"File": "ຟາຍ",
|
||||
"Bring All to Front": "ເອົາທັງໝົດມາທາງໜ້າ",
|
||||
"Zoom": "ຊູມ",
|
||||
"Stop Speaking": "ເຊົາສົນທະນາ",
|
||||
"Start Speaking": "ເລີ່ມສົນທະນາ",
|
||||
"Speech": "ຄຳກ່າວ",
|
||||
"Unhide": "ໂຊຄືນ",
|
||||
"Hide Others": "ເຊື່ອງອັນອື່ນ",
|
||||
"Hide": "ເຊື່ອງ",
|
||||
"Services": "ບໍລິການ",
|
||||
"About": "ກ່ຽວກັບ",
|
||||
"Element Help": "ລະບົບຊ່ວຍເຫຼືອ",
|
||||
"Help": "ຊ່ວຍເຫຼືອ",
|
||||
"Close": "ປິດ",
|
||||
"Minimize": "ຫຍໍ້ນ້ອຍ",
|
||||
"Window": "ປ່ອງຢ້ຽມ",
|
||||
"Toggle Full Screen": "ສະຫຼັບເຕັມຈໍ",
|
||||
"Preferences": "ການຕັ້ງຄ່າ",
|
||||
"Zoom Out": "ຊູມອອກ",
|
||||
"Zoom In": "ຊູມເຂົ້າ",
|
||||
"Actual Size": "ຂະໜາດຕົວຈິງ",
|
||||
"View": "ເບິ່ງ",
|
||||
"Select All": "ເລືອກທັງໝົດ",
|
||||
"Delete": "ລຶບ",
|
||||
"Paste and Match Style": "ກັອບມາໃສ່ ແລະໃຫ້ສະຕາຍຕົງກັນ",
|
||||
"Paste": "ກັອບມາໃສ່",
|
||||
"Copy": "ສຳເນົາ",
|
||||
"Cut": "ຕັດ",
|
||||
"Redo": "ລຶ້ມຄືນ",
|
||||
"Undo": "ຮື້ຄືນ",
|
||||
"Edit": "ແກ້ໄຂ",
|
||||
"Quit": "ຍົກເລີກ",
|
||||
"Show/Hide": "ສະແດງ/ເຊື່ອງ",
|
||||
"Are you sure you want to quit?": "ທ່ານຕ້ອງການປິດແທ້ບໍ່?",
|
||||
"Close Element": "ປິດລະບົບ",
|
||||
"Cancel": "ຍົກເລີກ"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Failed to save image": "Nepavyko įrašyti paveikslėlio",
|
||||
"Save image as...": "Įrašyti paveikslėlį kaip...",
|
||||
"Copy image address": "Kopijuoti paveikslėlio adresą",
|
||||
"Copy image": "Kopijuoti paveikslėlį",
|
||||
"The image failed to save": "Paveikslėlio nepavyko išsaugoti",
|
||||
"Bring All to Front": "Viską iškelti į priekį",
|
||||
"Speech": "Kalba",
|
||||
"Actual Size": "Tikrasis dydis",
|
||||
"Toggle Developer Tools": "Perjungti kūrėjo įrankius",
|
||||
"Toggle Full Screen": "Perjungti viso ekrano režimą",
|
||||
"Paste and Match Style": "Įklijuoti ir suderinti stilių",
|
||||
"Redo": "Sugrąžinti veiksmą",
|
||||
"Undo": "Atšaukti veiksmą",
|
||||
"Select All": "Pasirinkti visus",
|
||||
"Delete": "Ištrinti",
|
||||
"Paste": "Įklijuoti",
|
||||
"Copy": "Kopijuoti",
|
||||
"Cut": "Iškirpti",
|
||||
"Add to dictionary": "Pridėti prie žodyno",
|
||||
"Copy link address": "Kopijuoti nuorodos adresą",
|
||||
"Copy email address": "Kopijuoti el. pašto adresą",
|
||||
"File": "Failas",
|
||||
"Zoom": "Priartinti",
|
||||
"Stop Speaking": "Nustoti kalbėti",
|
||||
"Start Speaking": "Pradėti kalbėti",
|
||||
"Unhide": "Nebeslėpti",
|
||||
"Hide Others": "Slėpti kitus",
|
||||
"Hide": "Slėpti",
|
||||
"Services": "Paslaugos",
|
||||
"About": "Apie",
|
||||
"Element Help": "Element Pagalba",
|
||||
"Help": "Pagalba",
|
||||
"Close": "Uždaryti",
|
||||
"Minimize": "Sumažinti",
|
||||
"Window": "Langas",
|
||||
"Preferences": "Nuostatos",
|
||||
"Zoom Out": "Atitolinti",
|
||||
"Zoom In": "Priartinti",
|
||||
"View": "Peržiūrėti",
|
||||
"Edit": "Redaguoti",
|
||||
"Quit": "Išeiti",
|
||||
"Show/Hide": "Rodyti/Slėpti",
|
||||
"Are you sure you want to quit?": "Ar tikrai norite išeiti?",
|
||||
"Close Element": "Uždaryti Element",
|
||||
"Cancel": "Atšaukti"
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"Start Speaking": "Runājiet...",
|
||||
"Add to dictionary": "Pievienot vārdnīcai",
|
||||
"The image failed to save": "Attēlu saglabāt neizdevās",
|
||||
"Failed to save image": "Neizdevās saglabāt attēlu",
|
||||
"Save image as...": "Saglabāt attēlu kā...",
|
||||
"Copy link address": "Kopēt saiti",
|
||||
"Copy email address": "Kopēt e-pastu",
|
||||
"Copy image": "Kopēt attēlu",
|
||||
"File": "Fails",
|
||||
"Bring All to Front": "Iznest visu priekšplānā",
|
||||
"Zoom": "Mērogošana",
|
||||
"Stop Speaking": "Beidziet runāt",
|
||||
"Speech": "Runa",
|
||||
"Unhide": "Rādīt",
|
||||
"Hide Others": "Slēpt citus",
|
||||
"Hide": "Slēpt",
|
||||
"Services": "Servisi/pakalpojumi",
|
||||
"About": "Par programmu",
|
||||
"Element Help": "Element palīdzība",
|
||||
"Help": "Palīdzība",
|
||||
"Close": "Aizvērt",
|
||||
"Minimize": "Minimizēt",
|
||||
"Window": "Logs",
|
||||
"Toggle Developer Tools": "Pārslēgt uz Izstrādātāja rīkiem",
|
||||
"Toggle Full Screen": "Pārslēgt uz pilnekrānu",
|
||||
"Preferences": "Parametri/iestatījumi",
|
||||
"Zoom Out": "Samazināt",
|
||||
"Zoom In": "Palielināt",
|
||||
"Actual Size": "Faktiskais izmērs",
|
||||
"View": "Skats",
|
||||
"Select All": "Atzīmēt visus",
|
||||
"Delete": "Dzēst",
|
||||
"Paste and Match Style": "Ievietot, saglabājot stilu",
|
||||
"Paste": "Ievietot",
|
||||
"Copy": "Kopēt",
|
||||
"Cut": "Izgriezt",
|
||||
"Redo": "Atatdarīt/atatgriezt (redo)",
|
||||
"Undo": "Atgreizt/atdarīt (undo)",
|
||||
"Edit": "Rediģēt",
|
||||
"Quit": "Iziet",
|
||||
"Show/Hide": "Rādīt/nerādīt",
|
||||
"Are you sure you want to quit?": "Tiešām vēlaties iziet?",
|
||||
"Close Element": "Aizvērt Elementu",
|
||||
"Cancel": "Atcelt"
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"Toggle Developer Tools": "Veksle Utvikleralternativer",
|
||||
"Add to dictionary": "Legg til i ordbok",
|
||||
"The image failed to save": "Bildet kunne ikke lagres",
|
||||
"Failed to save image": "Kunne ikke lagre bildet",
|
||||
"Save image as...": "Lagre bildet som...",
|
||||
"Copy email address": "Kopier e-postadressen",
|
||||
"Copy image": "Kopier bildet",
|
||||
"File": "Fil",
|
||||
"Stop Speaking": "Slutt å snakke",
|
||||
"Start Speaking": "Begynn å snakke",
|
||||
"Speech": "Tale",
|
||||
"Hide": "Skjul",
|
||||
"About": "Om",
|
||||
"Element Help": "Element Hjelp",
|
||||
"Help": "Hjelp",
|
||||
"Close": "Lukk",
|
||||
"Minimize": "Minimere",
|
||||
"Window": "Vindu",
|
||||
"Zoom Out": "Zoom ut",
|
||||
"Zoom In": "Zoom inn",
|
||||
"Actual Size": "Faktisk størrelse",
|
||||
"View": "Se",
|
||||
"Select All": "Velg alle",
|
||||
"Delete": "Slett",
|
||||
"Paste": "Lim inn",
|
||||
"Copy": "Kopier",
|
||||
"Undo": "Angre",
|
||||
"Edit": "Rediger",
|
||||
"Quit": "Avslutt",
|
||||
"Show/Hide": "Vis/Skjul",
|
||||
"Are you sure you want to quit?": "Er du sikker på at du vil slutte?",
|
||||
"Close Element": "Lukk Element",
|
||||
"Cancel": "Avbryt",
|
||||
"Services": "Tjenester",
|
||||
"Hide Others": "Skjul Andre"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Aan woordenboek toevoegen",
|
||||
"The image failed to save": "De afbeelding opslaan is mislukt",
|
||||
"Failed to save image": "Afbeelding opslaan is mislukt",
|
||||
"Save image as...": "Afbeelding opslaan als...",
|
||||
"Copy link address": "Link kopiëren",
|
||||
"Copy email address": "E-mailadres kopiëren",
|
||||
"Copy image": "Afbeelding kopiëren",
|
||||
"File": "Bestand",
|
||||
"Bring All to Front": "Alles naar voren brengen",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Stop met praten",
|
||||
"Start Speaking": "Begin met praten",
|
||||
"Speech": "Spraak",
|
||||
"Unhide": "Weer laten zien",
|
||||
"Hide Others": "Anderen verbergen",
|
||||
"Hide": "Verbergen",
|
||||
"Services": "Diensten",
|
||||
"About": "Over",
|
||||
"Element Help": "Element-hulp",
|
||||
"Help": "Help",
|
||||
"Close": "Sluiten",
|
||||
"Minimize": "Minimaliseren",
|
||||
"Window": "Venster",
|
||||
"Toggle Developer Tools": "Developer Tools wisselen",
|
||||
"Toggle Full Screen": "Volledig scherm wisselen",
|
||||
"Preferences": "Voorkeuren",
|
||||
"Zoom Out": "Uitzoomen",
|
||||
"Zoom In": "Inzoomen",
|
||||
"Actual Size": "Werkelijke grootte",
|
||||
"View": "Bekijken",
|
||||
"Select All": "Alles selecteren",
|
||||
"Delete": "Verwijderen",
|
||||
"Paste and Match Style": "Plakken zonder stijl",
|
||||
"Paste": "Plakken",
|
||||
"Copy": "Kopiëren",
|
||||
"Cut": "Knippen",
|
||||
"Redo": "Opnieuw doen",
|
||||
"Undo": "Ongedaan maken",
|
||||
"Edit": "Bewerken",
|
||||
"Quit": "Sluiten",
|
||||
"Show/Hide": "Tonen/Verbergen",
|
||||
"Are you sure you want to quit?": "Weet u zeker dat u wilt stoppen?",
|
||||
"Close Element": "Element sluiten",
|
||||
"Cancel": "Annuleren",
|
||||
"Copy image address": "Kopieer afbeeldingsadres"
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
{
|
||||
"The image failed to save": "Biletet vart ikkje lagra",
|
||||
"Paste and Match Style": "Lim inn og tilpass stil",
|
||||
"Redo": "Gjer om",
|
||||
"Undo": "Angre",
|
||||
"Are you sure you want to quit?": "Er du sikker på at du vil avslutta?",
|
||||
"Add to dictionary": "Legg til i ordlista",
|
||||
"Failed to save image": "Klarte ikkje å lagra biletet",
|
||||
"Save image as...": "Lagre bilete som…",
|
||||
"Copy link address": "Kopier lenkjeadresse",
|
||||
"Copy email address": "Kopier e-postadresse",
|
||||
"Copy image": "Kopier bilete",
|
||||
"File": "Fil",
|
||||
"Bring All to Front": "Plasser lengst fram",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Stopp snakka",
|
||||
"Start Speaking": "Byrja snakka",
|
||||
"Speech": "Tale",
|
||||
"Unhide": "Ikkje gøym",
|
||||
"Hide Others": "Gøym andre",
|
||||
"Hide": "Gøym",
|
||||
"Services": "Tenester",
|
||||
"About": "Om",
|
||||
"Element Help": "Hjelp med Element",
|
||||
"Help": "Hjelp",
|
||||
"Close": "Lat att",
|
||||
"Minimize": "Minimer",
|
||||
"Window": "Vindauga",
|
||||
"Toggle Developer Tools": "Developer Tools av/på",
|
||||
"Toggle Full Screen": "Fullskjerm av/på",
|
||||
"Preferences": "Innstillingar",
|
||||
"Zoom Out": "Zoom ut",
|
||||
"Zoom In": "Zoom inn",
|
||||
"Actual Size": "Faktisk storleik",
|
||||
"View": "Vis",
|
||||
"Select All": "Marker alt",
|
||||
"Delete": "Slett",
|
||||
"Paste": "Lim inn",
|
||||
"Copy": "Lim inn",
|
||||
"Cut": "Klipp ut",
|
||||
"Edit": "Rediger",
|
||||
"Quit": "Avslutt",
|
||||
"Show/Hide": "Vis/Gøym",
|
||||
"Close Element": "Lat att Element",
|
||||
"Cancel": "Avbryt"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Bring All to Front": "Wyciągnij wszystko do przodu",
|
||||
"Add to dictionary": "Dodaj do słownika",
|
||||
"The image failed to save": "Obraz nie został zapisany",
|
||||
"Failed to save image": "Nie udało się zapisać obrazu",
|
||||
"Save image as...": "Zapisz obraz jako...",
|
||||
"Copy link address": "Skopiuj adres łącza",
|
||||
"Copy email address": "Skopiuj adres email",
|
||||
"Copy image": "Skopiuj obraz",
|
||||
"File": "Plik",
|
||||
"Zoom": "Powiększenie",
|
||||
"Stop Speaking": "Przestań mówić",
|
||||
"Start Speaking": "Zacznij mówić",
|
||||
"Speech": "Mowa",
|
||||
"Unhide": "Odkryj",
|
||||
"Hide Others": "Ukryj inne",
|
||||
"Hide": "Ukryj",
|
||||
"Services": "Usługi",
|
||||
"About": "O nas",
|
||||
"Element Help": "Pomoc Element",
|
||||
"Help": "Pomoc",
|
||||
"Close": "Zamknij",
|
||||
"Minimize": "Zminimalizuj",
|
||||
"Window": "Okno",
|
||||
"Toggle Developer Tools": "Przełącz na narzędzia deweloperskie",
|
||||
"Toggle Full Screen": "Przełącz na pełny ekran",
|
||||
"Preferences": "Preferencje",
|
||||
"Zoom Out": "Pomniejsz",
|
||||
"Zoom In": "Powiększ",
|
||||
"Actual Size": "Rzeczywisty rozmiar",
|
||||
"View": "Pokaż",
|
||||
"Select All": "Zaznacz wszystko",
|
||||
"Delete": "Usuń",
|
||||
"Paste and Match Style": "Wklej i dopasuj styl",
|
||||
"Paste": "Wklej",
|
||||
"Copy": "Skopiuj",
|
||||
"Cut": "Wytnij",
|
||||
"Redo": "Powtórz",
|
||||
"Undo": "Cofnij",
|
||||
"Edit": "Edytuj",
|
||||
"Quit": "Zamknij",
|
||||
"Show/Hide": "Pokaż/Ukryj",
|
||||
"Are you sure you want to quit?": "Czy na pewno chcesz zamknąć?",
|
||||
"Close Element": "Zamknij Elementa",
|
||||
"Cancel": "Anuluj",
|
||||
"Copy image address": "Skopiuj adres obrazu"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Adicionar a dicionário",
|
||||
"The image failed to save": "A imagem falhou para salvar",
|
||||
"Failed to save image": "Falha para salvar imagem",
|
||||
"Save image as...": "Salvar imagem como...",
|
||||
"Copy link address": "Copiar endereço de link",
|
||||
"Copy email address": "Copiar endereço de email",
|
||||
"Copy image": "Copiar imagem",
|
||||
"File": "Arquivo",
|
||||
"Zoom": "Zoom",
|
||||
"Stop Speaking": "Parar de Falar",
|
||||
"Start Speaking": "Começar a Falar",
|
||||
"Speech": "Fala",
|
||||
"Unhide": "Desesconder",
|
||||
"Hide": "Esconder",
|
||||
"Services": "Serviços",
|
||||
"About": "Sobre",
|
||||
"Element Help": "Ajuda de Element",
|
||||
"Help": "Ajuda",
|
||||
"Close": "Fechar",
|
||||
"Minimize": "Minimizar",
|
||||
"Window": "Janela",
|
||||
"Toggle Developer Tools": "Ativar/Desativar Ferramentas de Desenvolvimento",
|
||||
"Toggle Full Screen": "Pôr em/Tirar de Tela Cheia",
|
||||
"Preferences": "Preferências",
|
||||
"Zoom Out": "Dar Zoom Out",
|
||||
"Zoom In": "Dar Zoom In",
|
||||
"Actual Size": "Tamanho de Verdade",
|
||||
"View": "Visualizar",
|
||||
"Select All": "Selecionar Todas",
|
||||
"Delete": "Deletar",
|
||||
"Paste and Match Style": "Colar e Adequar Estilo",
|
||||
"Paste": "Colar",
|
||||
"Copy": "Copiar",
|
||||
"Cut": "Cortar",
|
||||
"Redo": "Refazer",
|
||||
"Undo": "Desfazer",
|
||||
"Edit": "Editar",
|
||||
"Quit": "Sair",
|
||||
"Show/Hide": "Mostrar/Esconder",
|
||||
"Are you sure you want to quit?": "Você tem certeza que você quer sair?",
|
||||
"Close Element": "Fechar Element",
|
||||
"Cancel": "Cancelar",
|
||||
"Bring All to Front": "Trazer Todas Para Frente",
|
||||
"Hide Others": "Esconder Outras(os)",
|
||||
"Copy image address": "Copiar endereço de imagem"
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Adăugați la dicționar",
|
||||
"Failed to save image": "Eroare in salvarea imaginii",
|
||||
"Save image as...": "Salvează imagine ca ...",
|
||||
"Copy link address": "Copiază link",
|
||||
"Copy email address": "Copiază adresă de email",
|
||||
"Copy image": "Copiază imagine",
|
||||
"File": "Fișier",
|
||||
"Bring All to Front": "Aduce-ți totul in față",
|
||||
"Zoom": "zoom",
|
||||
"Stop Speaking": "Oprire Voce",
|
||||
"Start Speaking": "Pornire Voce",
|
||||
"Speech": "Voce",
|
||||
"Hide Others": "Ascunde restul",
|
||||
"Hide": "Ascunde",
|
||||
"Services": "Servicii",
|
||||
"About": "Despre",
|
||||
"Element Help": "Ajutor Element",
|
||||
"Help": "Ajutor",
|
||||
"Close": "Inchide",
|
||||
"Minimize": "Minimizare",
|
||||
"Window": "Fereastră",
|
||||
"Toggle Developer Tools": "Comutare unelte dezvoltator",
|
||||
"Toggle Full Screen": "Comutare pe tot ecranul",
|
||||
"Preferences": "Preferințe",
|
||||
"Zoom Out": "Micșorează",
|
||||
"Zoom In": "Mărește",
|
||||
"Actual Size": "Mărime reală",
|
||||
"View": "Vizualizează",
|
||||
"Select All": "Selectează tot",
|
||||
"Delete": "Șterge",
|
||||
"Paste and Match Style": "Lipește si potrivește stilul",
|
||||
"Paste": "Lipește",
|
||||
"Copy": "Copiere",
|
||||
"Redo": "Refă",
|
||||
"Undo": "Anulare",
|
||||
"Edit": "Editare",
|
||||
"Quit": "Închide",
|
||||
"Show/Hide": "Arată/Ascunde",
|
||||
"Are you sure you want to quit?": "Sunte-ți sigur ca doriți sa inchideți aplicația ?",
|
||||
"Close Element": "închide aplicația",
|
||||
"Cancel": "Anulare"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Edit": "Изменить",
|
||||
"Quit": "Выйти",
|
||||
"Close Element": "Закрыть Element",
|
||||
"Cancel": "Отмена",
|
||||
"Show/Hide": "Показать/скрыть",
|
||||
"Are you sure you want to quit?": "Вы уверены, что хотите выйти?",
|
||||
"Copy email address": "Копировать адрес почты",
|
||||
"Copy image": "Копировать изображение",
|
||||
"File": "Файл",
|
||||
"Zoom": "Масштаб",
|
||||
"Unhide": "Показать",
|
||||
"Hide": "Скрыть",
|
||||
"Services": "Сервисы",
|
||||
"About": "О программе",
|
||||
"Element Help": "Справка Element",
|
||||
"Help": "Справка",
|
||||
"Close": "Закрыть",
|
||||
"Minimize": "Свернуть",
|
||||
"Window": "Окно",
|
||||
"Toggle Developer Tools": "Переключить инструменты разработчика",
|
||||
"Toggle Full Screen": "Переключить полноэкранный режим",
|
||||
"Preferences": "Параметры",
|
||||
"Zoom Out": "Уменьшить",
|
||||
"Zoom In": "Увеличить",
|
||||
"Actual Size": "Фактический размер",
|
||||
"View": "Просмотр",
|
||||
"Select All": "Выбрать все",
|
||||
"Delete": "Удалить",
|
||||
"Paste": "Вставить",
|
||||
"Copy": "Копировать",
|
||||
"Cut": "Вырезать",
|
||||
"Redo": "Повторить",
|
||||
"Undo": "Отменить",
|
||||
"Save image as...": "Сохранить изображение как...",
|
||||
"Copy link address": "Копировать ссылку",
|
||||
"Add to dictionary": "Добавить в словарь",
|
||||
"The image failed to save": "Не удалось сохранить изображение",
|
||||
"Failed to save image": "Не удалось сохранить изображение",
|
||||
"Bring All to Front": "Вынести всё вперёд",
|
||||
"Stop Speaking": "Перестаньте говорить",
|
||||
"Start Speaking": "Говорите",
|
||||
"Speech": "Голос",
|
||||
"Hide Others": "Скрыть прочие",
|
||||
"Paste and Match Style": "Вставить с тем же стилем",
|
||||
"Copy image address": "Копировать адрес изображения"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Show/Hide": "පෙන්වන්න/සඟවන්න",
|
||||
"Are you sure you want to quit?": "ඔබට ඉවත් වීමට අවශ්ය බව විශ්වාස ද?",
|
||||
"Close Element": "ඉලමෙන්ට් වසන්න",
|
||||
"Cancel": "අවලංගු කරන්න",
|
||||
"Add to dictionary": "ශබ්ද කෝෂයට එකතු කරන්න",
|
||||
"Copy link address": "සබැඳියේ ලිපිනය පිටපත් කරන්න",
|
||||
"Copy email address": "වි-තැපෑල පිටපත් කරන්න",
|
||||
"File": "ගොනුව",
|
||||
"Zoom": "විශාල කරන්න",
|
||||
"Hide Others": "වෙනත් දෑ සඟවන්න",
|
||||
"Hide": "සඟවන්න",
|
||||
"Services": "සේවා",
|
||||
"About": "පිළිබඳව",
|
||||
"Element Help": "ඉලමෙන්ට් උපකාර",
|
||||
"Help": "උපකාර",
|
||||
"Close": "වසන්න",
|
||||
"Minimize": "හකුලන්න",
|
||||
"Window": "කවුළුව",
|
||||
"Zoom Out": "කුඩාලනය කරන්න",
|
||||
"Zoom In": "විශාලනය කරන්න",
|
||||
"Actual Size": "සැබෑ ප්රමාණය",
|
||||
"Select All": "සියල්ල තෝරන්න",
|
||||
"Paste": "අලවන්න",
|
||||
"Copy": "පිටපත්",
|
||||
"Cut": "කපන්න",
|
||||
"Redo": "පසුසේ",
|
||||
"Undo": "පෙරසේ",
|
||||
"Edit": "සංස්කරණය",
|
||||
"Quit": "ඉවත් වන්න",
|
||||
"Paste and Match Style": "අලවා ශෛලිය ගැළපුම",
|
||||
"Delete": "මකන්න",
|
||||
"The image failed to save": "රූපය සුරැකීමට අසමත්",
|
||||
"Failed to save image": "රූපය සුරැකීමට අසමත්",
|
||||
"Save image as...": "...ලෙස රූපය සුරකින්න",
|
||||
"Copy image address": "රූපයේ ලිපිනයේ පිටපතක්",
|
||||
"Copy image": "රූපයෙහි පිටපතක්",
|
||||
"Bring All to Front": "සියල්ල ඉදිරිපසට",
|
||||
"Stop Speaking": "කථාව නිමාව",
|
||||
"Start Speaking": "කථාව ආරම්භය",
|
||||
"Speech": "කථාව",
|
||||
"Unhide": "නොසඟවන්න",
|
||||
"Toggle Developer Tools": "සංවර්ධක මෙවලම්",
|
||||
"Toggle Full Screen": "පූර්ණ තිරයට",
|
||||
"Preferences": "පෙනුම",
|
||||
"View": "දකින්න"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Unhide": "Odkryť",
|
||||
"Stop Speaking": "Zastaviť nahrávanie hlasu",
|
||||
"Start Speaking": "Spustiť nahrávanie hlasu",
|
||||
"Speech": "Reč",
|
||||
"Element Help": "Pomocník pre aplikáciu Element",
|
||||
"Paste and Match Style": "Vložiť a prispôsobiť štýl",
|
||||
"Close Element": "Zavrieť aplikáciu Element",
|
||||
"Add to dictionary": "Pridať do slovníka",
|
||||
"The image failed to save": "Obrázok sa nepodarilo uložiť",
|
||||
"Failed to save image": "Chyba pri ukladaní obrázka",
|
||||
"Save image as...": "Uložiť obrázok ako...",
|
||||
"Copy link address": "Kopírovať adresu odkazu",
|
||||
"Copy email address": "Kopírovať e-mailovú adresu",
|
||||
"Copy image": "Kopírovať obrázok",
|
||||
"File": "Súbor",
|
||||
"Bring All to Front": "Preniesť všetky do popredia",
|
||||
"Zoom": "Lupa",
|
||||
"Hide Others": "Skryť ostatné",
|
||||
"Hide": "Skryť",
|
||||
"Services": "Služby",
|
||||
"About": "O aplikácii",
|
||||
"Help": "Pomocník",
|
||||
"Close": "Zavrieť",
|
||||
"Minimize": "Minimalizovať",
|
||||
"Window": "Okno",
|
||||
"Toggle Developer Tools": "Nástroje pre vývojárov",
|
||||
"Toggle Full Screen": "Celá obrazovka",
|
||||
"Preferences": "Vlastnosti",
|
||||
"Zoom Out": "Oddialiť",
|
||||
"Zoom In": "Priblížiť",
|
||||
"Actual Size": "Aktuálna veľkosť",
|
||||
"View": "Zobraziť",
|
||||
"Select All": "Vybrať všetko",
|
||||
"Delete": "Odstrániť",
|
||||
"Paste": "Vložiť",
|
||||
"Copy": "Kopírovať",
|
||||
"Cut": "Vystrihnúť",
|
||||
"Redo": "Opakovať",
|
||||
"Undo": "Späť",
|
||||
"Edit": "Úpravy",
|
||||
"Quit": "Ukončiť",
|
||||
"Show/Hide": "Zobraziť/Skryť",
|
||||
"Are you sure you want to quit?": "Naozaj chcete zavrieť aplikáciu?",
|
||||
"Cancel": "Zrušiť",
|
||||
"Copy image address": "Kopírovať adresu obrázka"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Save image as...": "Spara bild som…",
|
||||
"Copy link address": "Kopiera länkadress",
|
||||
"Copy email address": "Kopiera e-postadress",
|
||||
"Copy image": "Kopiera bild",
|
||||
"File": "Arkiv",
|
||||
"Bring All to Front": "Lägg alla överst",
|
||||
"Stop Speaking": "Sluta tala",
|
||||
"Start Speaking": "Börja tala",
|
||||
"Speech": "Tal",
|
||||
"Hide Others": "Göm övriga",
|
||||
"Hide": "Göm",
|
||||
"Services": "Tjänster",
|
||||
"About": "Om",
|
||||
"Element Help": "Element-Hjälp",
|
||||
"Help": "Hjälp",
|
||||
"Close": "Stäng",
|
||||
"Minimize": "Minimera",
|
||||
"Window": "Fönster",
|
||||
"Preferences": "Inställningar",
|
||||
"Actual Size": "Verklig storlek",
|
||||
"View": "Visa",
|
||||
"Select All": "Markera allt",
|
||||
"Delete": "Radera",
|
||||
"Paste and Match Style": "Klistra in och matcha stilen",
|
||||
"Paste": "Klistra in",
|
||||
"Copy": "Kopiera",
|
||||
"Cut": "Klipp ut",
|
||||
"Redo": "Gör om",
|
||||
"Undo": "Ångra",
|
||||
"Edit": "Redigera",
|
||||
"Quit": "Avsluta",
|
||||
"Cancel": "Avbryt",
|
||||
"Zoom": "Zooma",
|
||||
"Toggle Developer Tools": "Växla utvecklarverktyg",
|
||||
"Toggle Full Screen": "Växla helskärm",
|
||||
"Unhide": "Sluta gömma",
|
||||
"Zoom Out": "Zooma ut",
|
||||
"Zoom In": "Zooma in",
|
||||
"Close Element": "Stäng Element",
|
||||
"Show/Hide": "Visa/dölj",
|
||||
"Add to dictionary": "Lägg till i ordlistan",
|
||||
"The image failed to save": "Bilden sparades inte",
|
||||
"Failed to save image": "Misslyckades med att spara bilden",
|
||||
"Are you sure you want to quit?": "Är du säker att du vill avsluta?",
|
||||
"Copy image address": "Kopiera bildadress"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"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": "விலக்கிக்கொள்",
|
||||
"Copy image address": "பட முகவரியை நகலெடு"
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"About": "గ్రెంచ్",
|
||||
"Paste and Match Style": "మునుపటి వంటి అతికించండి",
|
||||
"Paste": "పేస్ట్",
|
||||
"Cut": "కట్",
|
||||
"Copy": "కాపీ",
|
||||
"Are you sure you want to quit?": "మీరు వెళ్ళిపోవాలని అనుకుంటున్నారా?",
|
||||
"Close Element": "మూసివేత element",
|
||||
"Cancel": "ఆపు"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Sözlüğe ekle",
|
||||
"The image failed to save": "Bu resim kaydedilemedi",
|
||||
"Failed to save image": "Resim kaydedilemedi",
|
||||
"Save image as...": "Resmi ... olarak farklı kaydet",
|
||||
"Copy link address": "Bağlantılı adresi kopyala",
|
||||
"Copy email address": "E-posta adresini kopyala",
|
||||
"Copy image": "Resmi kopyala",
|
||||
"File": "Dosya",
|
||||
"Bring All to Front": "Hepsini öne getir",
|
||||
"Zoom": "Yaklaştır",
|
||||
"Stop Speaking": "Konuşmayı durdur",
|
||||
"Start Speaking": "Konuşmaya başla",
|
||||
"Speech": "Konuşma",
|
||||
"Unhide": "Gizlemeyi bırak",
|
||||
"Hide Others": "Diğerlerini gizle",
|
||||
"Hide": "Gizle",
|
||||
"Services": "Hizmetler",
|
||||
"About": "Hakkında",
|
||||
"Element Help": "Element yardımı",
|
||||
"Help": "Yardım",
|
||||
"Close": "Kapat",
|
||||
"Minimize": "Küçült",
|
||||
"Window": "Pencere",
|
||||
"Toggle Developer Tools": "Geliştirici araçları",
|
||||
"Toggle Full Screen": "Tam ekran",
|
||||
"Preferences": "Tercihler",
|
||||
"Zoom Out": "Uzaklaştır",
|
||||
"Zoom In": "Yaklaştır",
|
||||
"Actual Size": "Gerçek boyut",
|
||||
"View": "Görünüm",
|
||||
"Select All": "Tümünü seç",
|
||||
"Delete": "Sil",
|
||||
"Paste and Match Style": "Biçimiyle bir yapıştır",
|
||||
"Paste": "Yapıştır",
|
||||
"Copy": "Kopyala",
|
||||
"Cut": "Kes",
|
||||
"Redo": "Yinele",
|
||||
"Undo": "Geri al",
|
||||
"Edit": "Düzenle",
|
||||
"Quit": "Çık",
|
||||
"Show/Hide": "Göster/Gizle",
|
||||
"Are you sure you want to quit?": "Çıkmak istediğinize emin misiniz?",
|
||||
"Close Element": "Element'i kapat",
|
||||
"Cancel": "İptal",
|
||||
"Copy image address": "Görsel adresini kopyala"
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
{
|
||||
"Add to dictionary": "Thêm vào từ điển",
|
||||
"The image failed to save": "Ảnh không lưu được",
|
||||
"Failed to save image": "Lưu ảnh thất bại",
|
||||
"Save image as...": "Lưu ảnh…",
|
||||
"Copy link address": "Sao chép địa chỉ liên kết",
|
||||
"Copy email address": "Sao chép địa chỉ email",
|
||||
"Copy image": "Sao chép ảnh",
|
||||
"File": "Tệp",
|
||||
"Bring All to Front": "Đưa tất cả lên trước",
|
||||
"Zoom": "Thu phóng",
|
||||
"Stop Speaking": "Dừng nói",
|
||||
"Start Speaking": "Bắt đầu nói",
|
||||
"Speech": "Đọc màn hình",
|
||||
"Unhide": "Bỏ ẩn",
|
||||
"Hide Others": "Ẩn cái khác",
|
||||
"Hide": "Ẩn",
|
||||
"Services": "Dịch vụ",
|
||||
"About": "Giới thiệu",
|
||||
"Element Help": "Trợ giúp Element",
|
||||
"Help": "Hỗ trợ",
|
||||
"Close": "Đóng",
|
||||
"Minimize": "Thu nhỏ",
|
||||
"Window": "Cửa sổ",
|
||||
"Toggle Developer Tools": "Công cụ cho Nhà phát triển",
|
||||
"Toggle Full Screen": "Toàn màn hình",
|
||||
"Preferences": "Tùy chọn",
|
||||
"Zoom Out": "Thu nhỏ",
|
||||
"Zoom In": "Phóng to",
|
||||
"Actual Size": "Kích thước thực",
|
||||
"View": "Xem",
|
||||
"Select All": "Chọn tất cả",
|
||||
"Delete": "Xóa",
|
||||
"Paste and Match Style": "Dán và khớp kiểu",
|
||||
"Paste": "Dán",
|
||||
"Copy": "Sao chép",
|
||||
"Cut": "Cắt",
|
||||
"Redo": "Làm lại",
|
||||
"Undo": "Hoàn tác",
|
||||
"Edit": "Chỉnh sửa",
|
||||
"Quit": "Thoát",
|
||||
"Show/Hide": "Hiển thị/Ẩn",
|
||||
"Are you sure you want to quit?": "Bạn có chắc chắn muốn thoát?",
|
||||
"Close Element": "Đóng Element",
|
||||
"Cancel": "Hủy bỏ",
|
||||
"Copy image address": "Sao chép địa chỉ ảnh"
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user